Skip to content

feat: clarify profiles and workspaces - #2343

Closed
Michaelyklam wants to merge 2 commits into
nesquena:masterfrom
Michaelyklam:fix/issue-2147-profile-workspace-copy
Closed

Michaelyklam wants to merge 2 commits into
nesquena:masterfrom
Michaelyklam:fix/issue-2147-profile-workspace-copy

Conversation

@Michaelyklam

@Michaelyklam Michaelyklam commented May 16, 2026

Copy link
Copy Markdown
Contributor

Thinking Path

  • Issue Feature request: Improve/clarify profiles #2147 shows that profiles and workspaces are easy to confuse, especially for users coming from OpenClaw-style role agents.
  • Michael clarified in the issue that profiles are first-class agent/persona peers, while workspaces are WebUI project/file scopes.
  • The smallest useful product slice is not a new profile-template system; it is inline guidance at the moment users manage profiles.
  • The Profiles panel already has room for a lightweight explanatory card, so this PR adds that copy without changing profile/workspace behavior.
  • Review feedback correctly pointed out that the Profiles panel is localized, so the explainer now follows the same i18n path.

What Changed

  • Adds a clickable localized "Profiles vs workspaces" help card at the top of the Profiles panel.
  • Adds a detail view that explains:
    • profiles = identity, memory, skills, model/provider config, and tools;
    • workspaces = project/product folders and file context;
    • profiles answer "who is working?" while workspaces answer "where are they working?".
  • Wires the new copy through static/i18n.js instead of hardcoded English strings.
  • Gives the help card a subtle distinct visual treatment so it reads as guidance, not a profile entry.
  • Keeps the help card visible even when no profiles are returned.
  • Adds source-level regression tests for the i18n wiring, copy, card styling, and empty-state behavior.
  • Updates CHANGELOG.md for the user-facing clarification.

Why It Matters

Users should not have to read a GitHub comment to understand whether to create profiles for roles like researcher/marketer/developer or workspaces for products/repos. This puts the explanation directly in the profile-management surface and preserves localization parity for the panel.

Refs #2147.

Verification

  • node --check static/panels.js
  • node --check static/i18n.js
  • env -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_issue2147_profile_workspace_copy.py -q — 5 passed
  • git diff --check

Risks / Follow-ups

  • This is a narrow copy/UI clarification, not a profile-template system or a redesign of scheduled-task/profile scoping.
  • No browser screenshot is attached; the change is static copy in an existing card/detail layout and is covered by source-level tests.
  • A future PR can add richer profile templates if maintainers choose that direction.

Model Used

AI-assisted change with repository inspection, targeted editing, and shell-based test verification.

@Michaelyklam
Michaelyklam force-pushed the fix/issue-2147-profile-workspace-copy branch from 838f400 to 56e6114 Compare May 16, 2026 02:41
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Diagnosis

Read static/panels.js:4465-4548 on the PR branch (the new help card + _renderProfileConceptHelp detail view) and the surrounding loadProfilesPanel() to confirm the insertion point, plus tests/test_issue2147_profile_workspace_copy.py for the assertions. Cross-referenced #2147's comment thread where the maintainer wrote the canonical explanation ("Profiles act sort of like first class subagents... Workspaces are a WebUI extension..."). The card copy matches that framing well — the dichotomy of "who is working" vs "where they are working" is concise and accurate.

One blocker: i18n bypass

The Profiles panel is fully internationalized. Every neighboring string in loadProfilesPanel() already uses t(...):

// static/panels.js (existing master, near where the help card is inserted)
panel.innerHTML = `<div ...>${esc(t('profiles_no_profiles'))}</div>`;
// ...
if (p.gateway_running) /* uses t('profile_gateway_running') etc */
if (p.skill_count) meta.push(t('profile_skill_count', p.skill_count));
const activeBadge = isActive ? `... ${esc(t('profile_active'))} ...` : '';

But the new help card and _renderProfileConceptHelp() ship 6 user-facing English strings hardcoded:

// static/panels.js (this PR)
<div class="profile-card-name">Profiles vs workspaces</div>
<div class="profile-card-meta">Use profiles for how the agent works; use workspaces for what files it works on.</div>
// ...
title.textContent = 'Profiles vs workspaces';
<div class="detail-card-title">Use profiles for how; workspaces for what</div>
<div class="detail-row-value">Agent identity, memory, skills, model/provider config...</div>
<div class="detail-row-value">Project or product folders on disk...</div>

static/i18n.js already has 10 locales (each with a profiles_no_profiles key around lines 1064, 2245, 3431, 4369, 5507, 6795, 7730, 8852, 11226, 12328). Shipping the help card as English-only is a regression for the 7+ non-English locales — they'll get a panel where every line is translated except the new "Profiles vs workspaces" introduction.

Suggested minimum: add a small i18n bundle entry per locale, then t('profiles_vs_workspaces_title'), t('profiles_vs_workspaces_subtitle'), t('profiles_vs_workspaces_profile_body'), etc. The detail view's three detail-row-value paragraphs can stay as separate keys to make translation chunks manageable.

Smaller follow-ups

  1. Source-level test fragility. tests/test_issue2147_profile_workspace_copy.py:13-32 asserts on raw English substrings in static/panels.js. Once you wire i18n, those assertions will need to switch to checking either the t(...) call sites or the English entry in static/i18n.js. The current shape locks in the bypass.
  2. Missing CSS for .profile-help-card. static/style.css:1743-1750 defines .profile-card, but profile-help-card has no rule. As written the card inherits the regular cursor:pointer from .profile-card (good — it's clickable) but it'll look identical to a profile entry, which could be confusing in the empty-state where it's the only item. A small visual differentiator (subtle border or muted background) would help users understand it's a help card, not a profile.
  3. data.active || 'default' thread-through. explainer.onclick = () => _renderProfileConceptHelp(data.active || 'default'); passes activeName to _renderProfileConceptHelp(activeName), but the helper body never reads the argument. Either remove the parameter or use it (e.g. add "Active: foo" in the detail card). Right now it's dead.

Verdict

The product direction is right, the copy matches the maintainer's own explanation, and the scope (lightweight inline guidance, not a profile-template system) is the correct first slice. The i18n gap is the only thing I'd actually require before merge — everything else is polish. Once i18n is wired and the test assertions point at i18n keys instead of raw English, this should be straightforward to land.

@Michaelyklam

Copy link
Copy Markdown
Contributor Author

Thanks — pushed a follow-up in cff6570 that addresses the review blocker and the cheap cleanup items:

  • moved the Profiles vs workspaces card/detail copy behind t(...) keys in static/i18n.js
  • updated the regression tests to assert i18n wiring instead of hardcoded English in static/panels.js
  • added a subtle .profile-help-card style so the guidance card is visually distinct from profile entries
  • removed the unused _renderProfileConceptHelp(activeName) parameter
  • refreshed the changelog/PR body to mention the localized explainer

Verification:

  • node --check static/panels.js
  • node --check static/i18n.js
  • env -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/test_issue2147_profile_workspace_copy.py -q — 5 passed
  • git diff --check

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Shipped in v0.51.71 ✅

Stage-364 release shipped at #2352. Merged commit 761cf55 into master. Tag v0.51.71 pushed.

Verified before release:

  • Full pytest: 5713 passed, 0 failed
  • QA harness: 20/20 pass
  • Opus advisor: independently verified your changes against source, APPROVE with one SHOULD-FIX caught for PR Add WebUI run event journal replay #2283 (now fixed inline)
  • Browser sanity: features confirmed live with no console errors
  • Agent self-verification (TWO-LAYER catch, first live use): PR Add WebUI run event journal replay #2283 producer→consumer chain verified end-to-end via unmocked production reader chain

Maintainer fix applied inline to #2283 (per CHANGELOG): Opus caught that live SSE frames carried no id: field, which would have caused replay-after-mid-stream-error to double-render every token. Fixed by adding STREAM_LAST_EVENT_ID side-channel dict in api/config.py; queue tuple shape preserved as (event, data) to avoid breaking existing tests. 6 regression tests added.

Thanks for the contribution! Closing now.

Michaelyklam pushed a commit to Michaelyklam/hermes-webui that referenced this pull request May 16, 2026
Michaelyklam pushed a commit to Michaelyklam/hermes-webui that referenced this pull request May 16, 2026
v0.51.71 — Release AU:
- PR nesquena#2349 (fixes nesquena#2345) — Stale-stream cleanup non-touching of updated_at
- PR nesquena#2343 (refs nesquena#2147) — Profiles vs workspaces help card
- PR nesquena#2283 (refs nesquena#1925) — WebUI run event journal replay (RFC slice 1)

Also relabeled nesquena#2283's CHANGELOG entry to add proper PR nesquena#2283 attribution
(it had been added without the PR number prefix during the contributor PR),
and nesquena#2349's 'PR TBD' placeholder filled in.
eleboucher pushed a commit to eleboucher/homelab that referenced this pull request May 16, 2026
… 0.51.74) (#501)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/nesquena/hermes-webui](https://github.com/nesquena/hermes-webui) | patch | `0.51.67` → `0.51.74` |

---

### Release Notes

<details>
<summary>nesquena/hermes-webui (ghcr.io/nesquena/hermes-webui)</summary>

### [`v0.51.74`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05174--2026-05-16--Release-AX-stage-367--4-PR-safe-lane-batch--2362-table-cell-spacing--2363-run-state-consistency-RFC--2365-customproviders-list-format--2367-settings-sidebar-i18n)

[Compare Source](nesquena/hermes-webui@v0.51.73...v0.51.74)

##### Added

- **PR [#&#8203;2363](nesquena/hermes-webui#2363 by [@&#8203;franksong2702](https://github.com/franksong2702) (refs [#&#8203;2361](nesquena/hermes-webui#2361), refs [#&#8203;1925](nesquena/hermes-webui#1925)) — Adds `docs/rfcs/webui-run-state-consistency-contract.md` as a documentation companion to the [#&#8203;1925](nesquena/hermes-webui#1925) runtime-boundary RFC. Documents the shared coherence contract across visible transcript, model context, pending turn metadata, live stream, run journal, compression handoff, browser timeline cache, and sidebar metadata. Complementary to [#&#8203;1925](nesquena/hermes-webui#1925): that RFC says where execution ownership should move, this one says what must stay coherent across the current and future state layers.

##### Fixed

- **PR [#&#8203;2362](nesquena/hermes-webui#2362 by [@&#8203;franksong2702](https://github.com/franksong2702) (fixes [#&#8203;2360](nesquena/hermes-webui#2360)) — Markdown table rows no longer become too tall when cell text is wrapped in paragraph tags by the renderer. Adds a table-specific CSS reset for `.msg-body td p` and `.msg-body th p` so the global `margin-bottom: 10px` rule on `.msg-body p` doesn't add unwanted vertical space inside table cells. Especially visible on narrow viewports such as iPad Safari/Chrome.

- **PR [#&#8203;2365](nesquena/hermes-webui#2365 by [@&#8203;mccxj](https://github.com/mccxj) (fixes [#&#8203;1106](nesquena/hermes-webui#1106)) — `get_available_models()` now handles YAML-list format `custom_providers.models` entries in addition to dict format. Pre-fix, declaring models as a list (`[m1, m2]`) or list-of-dicts (`[{id: m1, label: ...}]`) in `config.yaml` silently discarded every model from that provider in the picker dropdown because the code only recognized dict shape (`{model_id: {}}`). Now supports all three YAML shapes consistently with existing provider-config and live-models-fallback handlers.

- **PR [#&#8203;2367](nesquena/hermes-webui#2367 by [@&#8203;mccxj](https://github.com/mccxj) — Settings sidebar menu items (Conversation, Appearance, Preferences, Plugins, System) now respect locale selection. Pre-fix these were hardcoded English; only Providers had `data-i18n`. Adds `data-i18n` attributes plus the missing `settings_tab_plugins` key. **Stage-367 maintainer fix applied inline**: the PR only added the new key to English, breaking 5 locale-parity tests. Added `settings_tab_plugins` translations to all 10 non-English locales (it/ja/ru/es/de/zh/zh-TW/pt/ko/fr).

### [`v0.51.73`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05173--2026-05-16--Release-AW-stage-366--1-PR-safe-lane-batch--2357-compression-reference-card-anchoring-fix)

[Compare Source](nesquena/hermes-webui@v0.51.72...v0.51.73)

##### Fixed

- **PR [#&#8203;2357](nesquena/hermes-webui#2357 by [@&#8203;franksong2702](https://github.com/franksong2702) (fixes [#&#8203;2355](nesquena/hermes-webui#2355)) — Auto-compression reference cards no longer get mixed into the final answer turn after a session rotation. Pre-fix, `_insertCompressionLikeNodeByRawIdx()` appended the compression-reference node to the future assistant anchor turn's blocks, which projected the `[CONTEXT COMPACTION — REFERENCE ONLY]` card into the live tail. The fix inserts the node *before* the anchor segment so the reference card stays a sibling, not a child of the answer turn.

### [`v0.51.72`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05172--2026-05-16--Release-AV-stage-365--2-PR-safe-lane-batch--2354-recovered-pending-turn-context-fix--2348-Thinking-card-interim-text-echo-suppression)

[Compare Source](nesquena/hermes-webui@v0.51.71...v0.51.72)

##### Fixed

- **PR [#&#8203;2354](nesquena/hermes-webui#2354 by [@&#8203;franksong2702](https://github.com/franksong2702) (fixes [#&#8203;2353](nesquena/hermes-webui#2353)) — Stale stream recovery now keeps a recovered pending user turn in the model context (`context_messages`) as well as the visible transcript. Pre-fix, a server restart during an in-flight turn could restore the user's message in WebUI while omitting it from `context_messages`, so the next agent turn could forget a prompt that was visibly present just above it. The repair path now appends the recovered user turn to both surfaces with 8-message lookback dedup so already-checkpointed entries are not duplicated.

- **PR [#&#8203;2348](nesquena/hermes-webui#2348 by [@&#8203;franksong2702](https://github.com/franksong2702) (fixes [#&#8203;2346](nesquena/hermes-webui#2346)) — Thinking cards now suppress exact snippets that are already shown as user-visible interim assistant text, avoiding duplicated progress lines when an agent emits the same sentence through both reasoning and interim-assistant callbacks. Tracks `_liveThinkingText` during the live stream to strip the visible echo from the live Thinking card display; applies the same suppression in the settled-transcript path so reload/session-switch sees the cleaned-up view too.

### [`v0.51.71`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05171--2026-05-16--Release-AU-stage-364--3-PR-batch--2349-stale-stream-cleanup-non-touching--2343-profiles-vs-workspaces-help-card--2283-run-event-journal-replay-refs-1925-RFC-slice-1--with-Opus-caught-replay-double-render-fix)

[Compare Source](nesquena/hermes-webui@v0.51.70...v0.51.71)

##### Added

- **PR [#&#8203;2343](nesquena/hermes-webui#2343 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (refs [#&#8203;2147](nesquena/hermes-webui#2147)) — The Profiles panel now includes an inline "Profiles vs workspaces" explainer. The copy clarifies that profiles control how the agent works — identity, memory, skills, model/provider config, and tools — while workspaces control what project/files a session operates on, making the OpenClaw-style role/profile mental model easier to map onto Hermes WebUI.

- **PR [#&#8203;2283](nesquena/hermes-webui#2283 by [@&#8203;franksong2702](https://github.com/franksong2702) (refs [#&#8203;1925](nesquena/hermes-webui#1925)) — Adds an append-only WebUI run event journal for browser-originated chat streams (refs [#&#8203;1925](nesquena/hermes-webui#1925)). Every SSE event emitted by the legacy in-process runner is mirrored to a per-session JSONL file, `/api/chat/stream/status` reports when replay is available for a dead stream, `/api/chat/stream` can replay journaled events with SSE event IDs and a clear stale-restart diagnostic, and the frontend reattach path uses that replay before clearing local running state. Reconnect replay uses the last rendered SSE event id as its `after_seq` cursor so it does not replay already-rendered events, and journal fsync defaults to terminal events only (`HERMES_WEBUI_RUN_JOURNAL_FSYNC=eager` restores per-event fsync). This is the first compatibility slice only: it preserves the existing WebUI runner and does not make active execution survive a WebUI restart. **Stage-364 maintainer fix applied inline**: Opus advisor caught that live SSE frames emitted by `_sse()` in `api/streaming.py:2296` carry no `id:` field, so the frontend's `_lastRunJournalSeq` cursor stayed at 0 during live streaming and a mid-stream error→replay would arrive with `after_seq=0`, replaying every journaled event from seq 1 and double-rendering tokens. The fix adds `STREAM_LAST_EVENT_ID: dict = {}` as a per-stream side-channel in `api/config.py`; `put()` writes the journal's `event_id` to that dict on every event; `_handle_sse_stream` reads it at SSE emit time and uses `_sse_with_id(handler, event, data, event_id)` when present. The queue tuple shape is preserved as `(event, data)` so existing queue consumers (cancel sentinel, sprint42/51 tests, etc.) are not broken. Cleaned up in the worker's finally block alongside the other STREAM\_\* dicts. 6 regression tests added covering side-channel dict declaration, writer/reader paths, tuple shape preservation, and cleanup.

##### Fixed

- **PR [#&#8203;2349](nesquena/hermes-webui#2349 by [@&#8203;franksong2702](https://github.com/franksong2702) (fixes [#&#8203;2345](nesquena/hermes-webui#2345)) — Clearing stale stream runtime flags no longer refreshes a session's `updated_at`, so old compressed continuations should not jump back to the top of the sidebar just because WebUI repaired a dead `active_stream_id` during a read/list request.

### [`v0.51.70`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05170--2026-05-16--Release-AS-stage-363--4-PR-snapshotjournalUI-batch--2337-compression-snapshot-runtime-clear--2334-turn-journal-fcntl-lock--2342-INFLIGHT-reattach-pending-row--2339-workspace-panel-edge-toggle)

[Compare Source](nesquena/hermes-webui@v0.51.69...v0.51.70)

##### Added

- **PR [#&#8203;2339](nesquena/hermes-webui#2339 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (refs [#&#8203;2211](nesquena/hermes-webui#2211)) — The workspace panel now has a small desktop edge toggle that remains clickable after the right panel is hidden, making it possible to reopen the workspace browser without returning to Settings. The existing panel close button and composer workspace button remain unchanged; the new affordance only appears when the workspace panel is closed on desktop widths.

##### Fixed

- **PR [#&#8203;2337](nesquena/hermes-webui#2337 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2336](nesquena/hermes-webui#2336)) — Pre-compression snapshot preservation now also clears stale runtime stream fields when the existing on-disk snapshot is already as complete as the in-memory session. This keeps the load-and-mark branch aligned with the full-save branch and adds regression coverage so archived parent snapshots cannot retain stale `active_stream_id` / `pending_*` state.

- **PR [#&#8203;2342](nesquena/hermes-webui#2342 by [@&#8203;franksong2702](https://github.com/franksong2702) (fixes [#&#8203;2341](nesquena/hermes-webui#2341)) — Reattaching to an active streaming session now keeps the user prompt that started the running turn visible. Pre-fix, reload/session-switch restore could hydrate from the browser's INFLIGHT stream cache while the backend still held the initiating prompt only as `pending_user_message`, so the transcript showed assistant Thinking/Tool activity without the user's just-submitted message. The restore path now merges that pending user row into the live transcript before rendering and updates the INFLIGHT cache, while duplicate suppression checks the current message array so final session payloads do not show the prompt twice.

- **PR [#&#8203;2334](nesquena/hermes-webui#2334 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (refs [#&#8203;2097](nesquena/hermes-webui#2097)) — Turn journal appends now take an advisory `flock` around each JSONL event write and fsync when Unix file locks are available. This keeps oversized submitted-message events from interleaving at the byte level if a future deployment runs multiple WebUI worker processes against the same state directory, while preserving the previous best-effort append path on platforms without `fcntl`.

### [`v0.51.69`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05169--2026-05-15--Release-AT-stage-362--8-PR-follow-up-batch--Ollama-routing--legacy-toolset--cancel-copy--cleanup--custom-provider-mismatch--cron-metadata--dead-code-removal-2323-reverted-after-Opus-caught-silent-regression-refiled-as-2321-reopen)

[Compare Source](nesquena/hermes-webui@v0.51.68...v0.51.69)

##### Added

- **PR [#&#8203;2332](nesquena/hermes-webui#2332 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (refs [#&#8203;2290](nesquena/hermes-webui#2290)) — Cron run history/output cards now surface token/cost metadata when the underlying cron output markdown includes it. The backend parses optional model/token/cost/duration frontmatter from cron output files and returns it from `/api/crons/history` and `/api/crons/run`; the Tasks panel renders a compact usage strip beside run rows and below expanded output without affecting older outputs that lack usage metadata.

##### Fixed

- **PR [#&#8203;2322](nesquena/hermes-webui#2322 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (refs [#&#8203;2271](nesquena/hermes-webui#2271)) — LAN Ollama models selected from endpoint-discovered `custom:<host>-<port>` / `custom:<host>:<port>` picker entries now route through the configured `ollama` provider and base URL instead of surfacing a missing `CUSTOM_*_API_KEY` error. The picker still surfaces endpoint-discovered entries; the fix is to recognize them as UI routing hints matching the configured local-server base URL and resolve them via the actual `ollama` provider.

- **PR [#&#8203;2326](nesquena/hermes-webui#2326 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2232](nesquena/hermes-webui#2232)) — Legacy `hermes` CLI toolset alias is now normalized to `hermes-cli` + `hermes-api-server` when WebUI resolves CLI toolsets from shared Hermes config. Modern Hermes Agent exposes the composite under those two names; older configs that still contain the legacy `hermes` toolset name no longer surface as "unknown toolset" warnings.

- **PR [#&#8203;2327](nesquena/hermes-webui#2327 by [@&#8203;dotBeeps](https://github.com/dotBeeps) — Cancel-mid-stream messaging now uses the user's configured assistant name (e.g. "Hermes") instead of hardcoded "Skyly". Preferences allow defining an Assistant Name that persists throughout the UI; the cancel copy was the last place still showing the persona placeholder. Backend persisted-cancelled-turn text and frontend live-cancel toast both now read from the same `botName` setting.

- **PR [#&#8203;2328](nesquena/hermes-webui#2328 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2325](nesquena/hermes-webui#2325)) — Two cleanup follow-ups from v0.51.68 stage-361 review: (a) when a session is deleted via `/api/session/delete`, its `~/.hermes/webui/attachments/<sid>/` inbox is also removed (orphan accumulation prevention); (b) the deferred stream-recovery listener bound by `_deferStreamErrorIfPageHidden()` now bails out when the user switches sessions in the same tab — the recovery would otherwise fire `setComposerStatus('Reconnected')` for a stream the user has moved past. Both fixes are narrow cleanup with regression tests.

- **PR [#&#8203;2330](nesquena/hermes-webui#2330 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2329](nesquena/hermes-webui#2329)) — Provider mismatch warnings now skip named custom providers such as `custom:zenmux`. Custom aggregators can legitimately route vendor-prefixed models like `google/gemini-3.1-flash-lite`, so `_checkProviderMismatch()` now treats `custom:<name>` the same as bare `custom` and avoids false-positive "may not work with your configured provider" warnings.

- **PR [#&#8203;2331](nesquena/hermes-webui#2331 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) — Live activity row now shows a transient human-readable progress phrase derived from the current tool category (e.g. "Reading file…", "Searching files…", "Running command…") instead of only the elapsed-time counter `Working 1m 23s`. Compact transcript view unchanged.

- **PR [#&#8203;2333](nesquena/hermes-webui#2333 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2312](nesquena/hermes-webui#2312) follow-up [#&#8203;1](nesquena/hermes-webui#1)) — Removed dead production helper `_save_pre_compression_snapshot()` at `api/streaming.py:1945`. The production path now uses `_preserve_pre_compression_snapshot()` exclusively (which must index snapshots with `skip_index=False` for sidebar filtering). The dead helper was only called from `tests/test_compression_snapshot_runtime_clear.py`; the test is retargeted to exercise the actual production helper instead. Closes follow-up item [#&#8203;1](nesquena/hermes-webui#1) from the v0.51.66 review ([#&#8203;2312](nesquena/hermes-webui#2312)).

### [`v0.51.68`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v05168--2026-05-15--Release-AR-stage-361--4-PR-follow-up-batch--2315-profile-skill-seeding--2317-theme-fallback--2318-mobile-stream-defer--2319-chat-upload-relocation--with-Opus-caught-vision-model-regression-fix)

[Compare Source](nesquena/hermes-webui@v0.51.67...v0.51.68)

##### Added

- **PR [#&#8203;2319](nesquena/hermes-webui#2319 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) — Chat file uploads now land in a session-scoped attachment inbox instead of cluttering the active workspace root. By default uploads are stored under `~/.hermes/webui/attachments/<session_id>/`; operators can override the root with `HERMES_WEBUI_ATTACHMENT_DIR`, and the agent still receives the absolute uploaded file path for context. Archive extraction stays workspace-scoped (it's an explicit workspace operation). README updated to document the new default location. **Stage-361 maintainer fix applied inline**: Opus advisor caught that `_build_native_multimodal_message` at `api/streaming.py:787` required uploads to be under `workspace_root`, which would have silently dropped every image upload for vision-capable models once the inbox moved outside the workspace. The fix adds `_attachment_root()` (from `api/upload.py`) as a second allowed location, with 3 regression tests covering the new code path AND verifying the original workspace + cross-root rejection paths still work.

##### Fixed

- **PR [#&#8203;2315](nesquena/hermes-webui#2315 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2305](nesquena/hermes-webui#2305), refs [#&#8203;749](nesquena/hermes-webui#749)) — WebUI profile creation now seeds bundled profile skills for newly-created non-cloned profiles, matching the CLI's `hermes profile create` behaviour. Pre-fix, creating a profile via Settings → New Profile (without checking "Clone from active profile") left the profile's `skills/` directory empty, which was inconsistent with CLI-created profiles that get the full bundled-skills overlay. The fix calls `seed_profile_skills(profile_path, quiet=True)` after `profile_path.mkdir()` when `clone_from is None`. Cloned profiles still inherit skills from their source — they don't get a second bundled-skills overlay. Seed failures (e.g. `hermes_cli` unavailable in Docker fallback) are logged as warnings, not fatal — profile creation still succeeds.

- **PR [#&#8203;2317](nesquena/hermes-webui#2317 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (refs [#&#8203;2312](nesquena/hermes-webui#2312) follow-up [#&#8203;2](nesquena/hermes-webui#2)) — Appearance boot reconciliation now treats explicit `light`, `dark`, and `system` localStorage theme values as user selections when a prior Settings autosave failed. Pre-fix, the predicate `lsHasExplicitTheme = lsTheme === 'system'` only treated 'system' as explicit, so a user who picked `light` on a server defaulted to `dark` (or vice versa) with a failed autosave still reverted to the server default on refresh. Now broadened to `['system','light','dark'].includes(lsTheme)`. Skin handling was already correct (`lsSkin !== 'default'`). Closes follow-up item [#&#8203;2](nesquena/hermes-webui#2) from the v0.51.66 review ([#&#8203;2312](nesquena/hermes-webui#2312)).

- **PR [#&#8203;2318](nesquena/hermes-webui#2318 by [@&#8203;Michaelyklam](https://github.com/Michaelyklam) (closes [#&#8203;2307](nesquena/hermes-webui#2307)) — Mobile/Android backgrounded tabs no longer show a permanent `**Error:** Connection lost` banner when the backend stream is still alive and able to replay buffered events. Pre-fix, the SSE error finalization fired regardless of page visibility state, so any tab discarded by the mobile OS (battery saver, tab compression, brief switch to another app) showed a permanent error even though the stream could be re-attached on visibility return. The fix defers inline stream error rendering while `document.visibilityState === 'hidden'` or `document.wasDiscarded === true`, then on visibility return polls `/api/chat/stream/status?stream_id=...`. If the stream is still active, reattaches with a fresh `EventSource`. If not, falls back to the settled-session restore path. If both paths fail, falls back to the original error rendering. Behaviour on desktop and on tabs that ARE visible is unchanged.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19-->

Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/501
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
v0.51.71 — Release AU:
- PR nesquena#2349 (fixes nesquena#2345) — Stale-stream cleanup non-touching of updated_at
- PR nesquena#2343 (refs nesquena#2147) — Profiles vs workspaces help card
- PR nesquena#2283 (refs nesquena#1925) — WebUI run event journal replay (RFC slice 1)

Also relabeled nesquena#2283's CHANGELOG entry to add proper PR nesquena#2283 attribution
(it had been added without the PR number prefix during the contributor PR),
and nesquena#2349's 'PR TBD' placeholder filled in.
bernyforce pushed a commit to bernyforce/hermes-webui that referenced this pull request Jul 29, 2026
bernyforce pushed a commit to bernyforce/hermes-webui that referenced this pull request Jul 29, 2026
v0.51.71 — Release AU:
- PR nesquena#2349 (fixes nesquena#2345) — Stale-stream cleanup non-touching of updated_at
- PR nesquena#2343 (refs nesquena#2147) — Profiles vs workspaces help card
- PR nesquena#2283 (refs nesquena#1925) — WebUI run event journal replay (RFC slice 1)

Also relabeled nesquena#2283's CHANGELOG entry to add proper PR nesquena#2283 attribution
(it had been added without the PR number prefix during the contributor PR),
and nesquena#2349's 'PR TBD' placeholder filled in.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants