Conversation
…snap-back Increase MESSAGE_UPWARD_INTENT_MS from 450ms to 2000ms to fix a race condition where the user scrolls up during streaming, pauses to read for >450ms, and then gets snapped back to the bottom. The root cause: after the 450ms upward-intent window expires, DOM layout changes from the streaming markdown parser (smd), tool card insertions, or code re-highlighting can trigger scroll events that the handler no longer recognizes as user-initiated. When the resulting position lands inside the 250px near-bottom zone for two consecutive samples, the hysteresis counter re-pins (_scrollPinned=true) and the next streaming token's scrollIfPinned() call forces scrollTop to the bottom. With a 2-second window, the user's upward intent persists through typical streaming DOM churn. Downward scrolling and the scroll-to-bottom button are unaffected — movedUp requires top < _lastScrollTop-2 which is false for downward movement regardless of the intent timeout. Refs: nesquena#1360 (macOS momentum protection), nesquena#1731 (direction-aware unpin)
This was referenced May 31, 2026
Collaborator
|
Absorbed and shipped in v0.51.187 (Release FG) via #3271. 🎉 Thanks @emanon312 — clear diagnosis of the streaming snap-back, and the 450ms→2000ms intent window is the right lever (the direction check keeps downward re-pin and the scroll-to-bottom button unaffected). I added a regression test ( |
AJV20
pushed a commit
to AJV20/hermes-webui
that referenced
this pull request
May 31, 2026
…File anchor for nesquena#3262 - Pin MESSAGE_UPWARD_INTENT_MS>=2000ms + intent-helper-uses-constant + downward-repin-independence (nesquena#3250, co-authored emanon312). - Update the nesquena#2823 large-markdown-preview test's openFile() signature anchor for nesquena#3262's openFile(path, opts={}) extension. Co-authored-by: emanon312 <emanon312@users.noreply.github.com>
3 tasks
eleboucher
pushed a commit
to eleboucher/homelab
that referenced
this pull request
Jun 1, 2026
…➔ 0.51.197) (#749)
This PR contains the following updates:
| Package | Update | Change |
|---|---|---|
| [ghcr.io/nesquena/hermes-webui](https://github.com/nesquena/hermes-webui) | patch | `0.51.157` → `0.51.197` |
---
### Release Notes
<details>
<summary>nesquena/hermes-webui (ghcr.io/nesquena/hermes-webui)</summary>
### [`v0.51.197`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051197--2026-06-01--Release-FQ-stage-batch9--stop-agent-replaying-editedundone-messages)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.196...v0.51.197)
##### Fixed
- Editing or undoing a message no longer lets the agent replay the original pre-edit content from `state.db`: the truncation-watermark filter now also skips replaced/stale rows whose timestamp sorts *below* the watermark, and `POST /api/session/truncate` truncates `context_messages` in sync with `messages` so the agent's context matches the visible transcript after Edit/Regenerate. The earlier `_clamp_context_to_watermark()` approach (which turned the watermark into a permanent ceiling that dropped every new turn) is removed. Closes [#​2914](https://github.com/nesquena/hermes-webui/issues/2914) ([#​3102](https://github.com/nesquena/hermes-webui/issues/3102), [@​AlexeyDsov](https://github.com/AlexeyDsov)).
### [`v0.51.196`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051196--2026-06-01--Release-FP-stage-batch8--file-manager-external-sessions--artifacts-tool-metadata--edge-toggle-icon--type-hints)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.195...v0.51.196)
##### Fixed
- File manager (folder download, raw file fetch, and related handlers) now falls back to a `state.db` lookup for sessions created by Telegram/CLI rather than the WebUI, resolving them against the active WebUI workspace instead of returning a 404. Closes [#​3280](https://github.com/nesquena/hermes-webui/issues/3280) ([#​3314](https://github.com/nesquena/hermes-webui/issues/3314), [@​Sanjays2402](https://github.com/Sanjays2402)).
- Artifacts tab now detects files from structured `tool_calls` (OpenAI format) and `tool_use` content blocks (Anthropic format) on messages, not just text-mined diff fences, so artifacts surface even when `S.toolCalls` is cleared after a reload; display paths are trimmed of the workspace prefix ([#​3329](https://github.com/nesquena/hermes-webui/issues/3329), [@​mysoul12138](https://github.com/mysoul12138)).
- Workspace panel edge-toggle chevron now points left (toward the panel it reveals) instead of right ([#​3318](https://github.com/nesquena/hermes-webui/issues/3318), [@​xz-dev](https://github.com/xz-dev)).
##### Internal
- `api/state_sync.py` now uses `Optional[T]` annotations for parameters defaulting to `None` instead of the implicit `T = None` form ([#​3323](https://github.com/nesquena/hermes-webui/issues/3323), [@​kuishou68](https://github.com/kuishou68)).
### [`v0.51.195`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051195--2026-06-01--Release-FO-stage-batch7--hide-attachment-path-markers-in-chat-UI)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.194...v0.51.195)
##### Fixed
- Uploaded image attachment path context (`[Attached files: …]`) remains available to the agent in the stored message, but the chat transcript, sidebar display title, and server-derived provisional titles no longer show the raw path suffix to the user ([#​3296](https://github.com/nesquena/hermes-webui/issues/3296), [@​AJV20](https://github.com/AJV20)).
### [`v0.51.194`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051194--2026-06-01--Release-FN-stage-batch6--profiles-config-import-cycle-fix)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.193...v0.51.194)
##### Fixed
- Profile startup now shares platform-default Hermes home resolution through a small `api/paths.py` helper instead of importing the full `api.config` module from `api.profiles`, so importing profiles before config no longer hits a latent circular-load that silently skipped active-profile initialization. Closes [#​3283](https://github.com/nesquena/hermes-webui/issues/3283) ([#​3303](https://github.com/nesquena/hermes-webui/issues/3303), [@​AJV20](https://github.com/AJV20)).
### [`v0.51.193`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051193--2026-06-01--Release-FM-stage-batch5--ctl-dotenv-opt-out--workspace-inline-open--gateway-reply-polish)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.192...v0.51.193)
##### Fixed
- `ctl.sh` now honors `HERMES_WEBUI_NO_DOTENV=1`, letting tests and scripted launches opt out of repo-local `.env` loading so host-specific `HERMES_WEBUI_STATE_DIR` values do not make the ctl test suite flaky. Closes [#​3246](https://github.com/nesquena/hermes-webui/issues/3246) ([#​3304](https://github.com/nesquena/hermes-webui/issues/3304), [@​AJV20](https://github.com/AJV20)).
- Workspace **Open in browser** now opens HTML files inline (with the same `inline=1` + CSP sandbox isolation as the file preview) instead of forcing a download, and uses `noopener` for the new tab ([#​3305](https://github.com/nesquena/hermes-webui/issues/3305), [@​xz-dev](https://github.com/xz-dev)).
- Gateway-backed chat now carries the same WebUI final-answer polish guidance as the in-process chat paths, so terse scratchpad fragments such as "Need script" are not encouraged as visible assistant replies ([#​3301](https://github.com/nesquena/hermes-webui/issues/3301), [@​AJV20](https://github.com/AJV20)).
### [`v0.51.192`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051192--2026-05-31--Release-FL-stage-batch4--per-model-contextlength-default-only-guard)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.191...v0.51.192)
##### Fixed
- A global `model.context_length` cap (set in config for the default model, e.g. 232000) no longer silently shrinks **non-default** models' real context windows. The cap is now applied only when the session model equals `model.default`; other models (e.g. a 1M-context variant) keep their real metadata window. The guard is applied consistently across the session context-length resolver (`api/routes.py`), the per-turn persistence path, and the live SSE usage payload, and the auto-compress `threshold_tokens` is rescaled to the real cap so the context-window indicator and compression trigger reflect the actual window. The live-usage perf path caches the resolved per-model window once per stream (it runs \~10×/sec during streaming) so non-default-model streams don't take a config/metadata lookup on every metering tick. Backend-only; default-model sessions are unaffected. Closes [#​3256](https://github.com/nesquena/hermes-webui/issues/3256) ([#​3263](https://github.com/nesquena/hermes-webui/issues/3263), [@​allenliang2022](https://github.com/allenliang2022)).
### [`v0.51.191`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051191--2026-05-31--Release-FK-stage-batch3--skills-detail-markdown-styling--launchd-duplicate-start-guard)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.190...v0.51.191)
##### Fixed
- Skills detail view now renders `SKILL.md` markdown with the same `.preview-md` typography used by Memory and Notes, instead of unstyled `renderMd()` output. Linked markdown skill files opened from the detail view use the same wrapper plus scoped post-render `highlightCode` / KaTeX enhancement ([#​3284](https://github.com/nesquena/hermes-webui/issues/3284), [@​pamnard](https://github.com/pamnard)).
- `ctl.sh start` now refuses to launch a second WebUI instance when a launchd-managed job already owns it (macOS), instead of racing the launchd instance into repeated `Address already in use` churn on port 8787. The guard is macOS/launchd-only, no-ops on every non-launchd path, and can be overridden with `HERMES_WEBUI_CTL_ALLOW_LAUNCHD_CONFLICT=1`; `docs/supervisor.md` documents launchd as the single source of truth. Closes [#​3289](https://github.com/nesquena/hermes-webui/issues/3289) ([#​3291](https://github.com/nesquena/hermes-webui/issues/3291), [@​andrewkangkr](https://github.com/andrewkangkr)).
### [`v0.51.190`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051190--2026-05-31--Release-FJ-stage-batch2--Windows-upgrade-state-stranding-hotfix--gateway-banner--quiet-tool-previews)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.189...v0.51.190)
##### Fixed
- **Windows upgrade no longer strands WebUI state (data-loss-class, priority).** v0.51.134 moved the Windows default Hermes home from `%USERPROFILE%\.hermes` to `%LOCALAPPDATA%\hermes` without a migration, so upgrading users opened an empty app (sessions, pins, UI settings appeared lost — actually just at the address the new build no longer read). `_platform_default_hermes_home()` now prefers the legacy `%USERPROFILE%\.hermes` **only** on the exact post-upgrade fingerprint (legacy holds real `webui/` state AND the new location is not yet established), and `api/profiles.py` delegates to the same resolver so the two can never drift. Non-destructive and self-healing — no files are moved; affected users recover on next launch with no action. Markers key on WebUI-owned `webui/` state only (not agent `auth.json`/`config.yaml`) so a long-time agent user installing WebUI fresh isn't wrongly diverted. Closes [#​2905](https://github.com/nesquena/hermes-webui/issues/2905) ([#​3279](https://github.com/nesquena/hermes-webui/issues/3279)).
- **"Gateway not configured" banner on two-container Docker first deploy.** `GET /api/gateway/status` treated all `alive is None` health payloads as unconfigured-unless-`identity_map`, so a freshly-started gateway that hadn't ticked `updated_at` yet (and had no conversations) reported "not configured." A stale-but-**running** gateway (reason `gateway_stale_running_state`, or a `gateway_state == "running"` detail) now reports `configured = True`; a stale-**stopped** gateway deliberately still falls through to the `identity_map` signal so a stopped root gateway reads as "not configured" per [#​1944](https://github.com/nesquena/hermes-webui/issues/1944). Closes [#​3194](https://github.com/nesquena/hermes-webui/issues/3194) ([#​3279](https://github.com/nesquena/hermes-webui/issues/3279)).
- Collapsed tool-call previews stay quiet: instead of falling back to raw result JSON (which made tool-heavy turns look like debug logs), a settled collapsed tool card now shows a compact argument summary (with verbose/secret-bearing keys like `content`/`patch`/`api_key`/`token` excluded) or a short status, keeping the full result inside the expandable detail body ([#​3267](https://github.com/nesquena/hermes-webui/issues/3267), [@​ai-ag2026](https://github.com/ai-ag2026)).
### [`v0.51.189`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051189--2026-05-31--Release-FI-stage-batch1--ruff-lint-gate--SSE-refresh-dedupe--tooltip-i18n)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.188...v0.51.189)
##### Added
- **Forward-looking Python lint gate (ruff).** A curated `[tool.ruff]` ruleset (E9 syntax/runtime + F pyflakes + B bugbear — high-signal correctness rules, no style/formatting families) now gates **new and changed Python code** in CI (`tests.yml` `lint` job) and as part of the maintainer pre-release pre-gate. It is the Python twin of the existing ESLint runtime guard for `static/*.js`. Crucially it is **line-scoped** (`scripts/ruff_lint.py --diff`): it flags violations only on lines a change adds or modifies, so it keeps incoming code clean **without** reformatting the existing tree's cosmetic backlog (a separate, deferred, maintainer-run decision). `tests/test_ruff_forward_lint.py` additionally holds the whole tree free of E9 errors and skips cleanly when ruff isn't installed. See TESTING.md > "Python lint gate (ruff)". Closes [#​3273](https://github.com/nesquena/hermes-webui/issues/3273) ([#​3275](https://github.com/nesquena/hermes-webui/issues/3275)).
##### Fixed
- Gateway SSE reconnect no longer triggers a phantom "new dialog created" sidebar refresh: the initial sessions snapshot pushed on every reconnect is now compared against the current gateway-session set and `renderSessionList()` is skipped when nothing changed ([#​3270](https://github.com/nesquena/hermes-webui/issues/3270), [@​PINKIIILQWQ](https://github.com/PINKIIILQWQ)).
- Raw-audio recording mic tooltip now uses a recording-specific i18n key instead of the dictation "Stop" label; sidebar lineage/child tooltip suffixes are localized across the locale catalog; and the localized read-only title hover hint for imported sessions is restored. Closes [#​3242](https://github.com/nesquena/hermes-webui/issues/3242), [#​3214](https://github.com/nesquena/hermes-webui/issues/3214) ([#​3272](https://github.com/nesquena/hermes-webui/issues/3272), [@​ai-ag2026](https://github.com/ai-ag2026)).
### [`v0.51.188`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051188--2026-05-31--Release-FH-stage-batchH--configured-runner-client-boundary-default-off)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.187...v0.51.188)
##### Added
- **Configured runner-client boundary** for the `runner-local` runtime adapter (RFC `hermes-run-adapter-contract`, tracking [#​1925](https://github.com/nesquena/hermes-webui/issues/1925), Slice 4c/4d). When — and only when — an operator sets `HERMES_WEBUI_RUNNER_BASE_URL`, WebUI delegates the agent run to that external/supervised runner over a small JSON HTTP client (start / observe / status / cancel / approval / clarify / queue / goal) and bridges the runner's events through the existing SSE stream route, instead of owning the run in the main WebUI process. **Default-off and fully reversible:** with no endpoint configured, the factory preserves the existing bounded "runner-local not configured" path and the live in-process streaming path is unchanged — no behavior change for existing users. New `api/runner_client.py` (`HttpRunnerClient` + `runner_client_configured()`) plus additive `_runner_*` SSE-bridge helpers in `api/routes.py`; the legacy `_run_agent_streaming` control flow is untouched ([#​3073](https://github.com/nesquena/hermes-webui/issues/3073), [@​AJV20](https://github.com/AJV20)).
### [`v0.51.187`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051187--2026-05-31--Release-FG-stage-batchG--workspace-preview-persistence--scroll-intent-window)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.186...v0.51.187)
##### Fixed
- Workspace file preview no longer closes when a chat response finishes and the UI refreshes the workspace file tree. Background `loadDir('.')` on stream `done` now preserves an open preview instead of always calling `clearPreview()`, and reloads the open file when a write/edit tool touched that path during the turn (skipping reload while the preview has unsaved local edits) ([#​3262](https://github.com/nesquena/hermes-webui/issues/3262), [@​pamnard](https://github.com/pamnard)).
- During streaming, scrolling up to read earlier content no longer snaps back to the bottom after a brief pause: the upward-scroll intent window was widened from 450ms to 2000ms so DOM-layout changes from the markdown parser / tool-card insertions are still recognized as co-occurring with user intent and don't re-pin the view. Downward scroll, the scroll-to-bottom button, and trackpad-momentum protection are unaffected ([#​3250](https://github.com/nesquena/hermes-webui/issues/3250), [@​emanon312](https://github.com/emanon312)).
### [`v0.51.186`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051186--2026-05-31--Release-FF-stage-batchF--update-checker-ff-reachability-fall-through--utf-8-git-output-test-coverage)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.185...v0.51.186)
##### Fixed
- Agent self-update no longer advertises or applies unreachable release tags when the checkout tracks `main` past an older tag but the newest published tag lives on a divergent side branch (for example `v2026.5.29` → `v2026.5.29.2`). The update checker and apply path now fall through to the configured upstream branch when `git pull --ff-only <latest-tag>` cannot fast-forward, matching the existing [#​2653](https://github.com/nesquena/hermes-webui/issues/2653)/[#​3140](https://github.com/nesquena/hermes-webui/issues/3140) release-vs-branch routing ([#​3257](https://github.com/nesquena/hermes-webui/issues/3257), [@​pamnard](https://github.com/pamnard)).
- Added regression coverage pinning `_run_git()`'s UTF-8 decoding (`encoding='utf-8'`, `errors='replace'`) and its defensive `None`-stdout guard, so version detection cannot crash on non-UTF-8 Windows console output ([#​3254](https://github.com/nesquena/hermes-webui/issues/3254), [@​zapabob](https://github.com/zapabob)).
### [`v0.51.185`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051185--2026-05-31--Release-FE-stage-batchE--clarify-card-bug-fix-batch-identical-prompt-dedup--autofill-guard--GBK-startup-crash)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.184...v0.51.185)
##### Fixed
- The clarify popup could remain visible with stale input state after sending a response when the next queued clarification prompt was text-identical to the previous one. The dedupe signature now includes the prompt's `clarify_id`, so a newly queued identical prompt is treated as new instead of being mistaken for the one already answered ([#​3245](https://github.com/nesquena/hermes-webui/issues/3245), closes [#​3241](https://github.com/nesquena/hermes-webui/issues/3241)).
- Chrome's password manager could autofill the clarify-card input with saved credentials (typically a provider base URL), causing a phantom "Clarification closed" toast on every session completion and injecting the saved URL into the main composer. The clarify input now carries `autocomplete="off"` plus a `readonly` guard that is lifted only when an actual clarification prompt is shown (or on focus), so the browser's heuristic autofill skips it ([#​3247](https://github.com/nesquena/hermes-webui/issues/3247)).
- Prevent a server startup crash on non-UTF-8 Windows locales (e.g. Chinese GBK codepage): `_run_git()` now decodes git subprocess output as UTF-8 with `errors='replace'` and defensively guards against `None` streams, instead of letting a `UnicodeDecodeError` on binary `git diff --binary` output leave `stdout=None` and take down `import api.updates` with an `AttributeError` ([#​3249](https://github.com/nesquena/hermes-webui/issues/3249)).
### [`v0.51.184`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051184--2026-05-31--Release-FD-stage-batchD--raw-audio-upload-mode--scroll-preserve--non-POSIX-test-skip)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.183...v0.51.184)
##### Added
- Optional **raw audio upload mode** (Settings → Sound, off by default): when enabled, the composer mic button sends the recorded audio as a file attachment instead of transcribing it locally, so you can use external STT, raw-audio/emotion analysis, or multimodal models. Classic push-to-talk dictation is unchanged when the toggle is off. The mic tooltip reflects the active mode; localized across all 12 locales ([#​3169](https://github.com/nesquena/hermes-webui/issues/3169)).
##### Fixed
- Transcript scroll position is now preserved during same-session CLI/gateway import SSE refreshes (and the active session's metadata is synced from the refreshed transcript), instead of jumping to the bottom on each refresh ([#​3237](https://github.com/nesquena/hermes-webui/issues/3237)).
##### Changed
- `tests/test_terminal_process_cleanup.py` (POSIX terminal coverage that imports `fcntl` at module load) is now skipped at collection time on non-POSIX hosts instead of erroring ([#​3235](https://github.com/nesquena/hermes-webui/issues/3235)).
### [`v0.51.183`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051183--2026-05-31--Release-FC-stage-batchC--inline-file-media-artifacts--apimedia-state-file-confinement)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.182...v0.51.183)
##### Fixed
- Render assistant-emitted `file://` image artifact links inline through the authenticated `/api/media` route, so browser clients can view generated local images instead of seeing unusable server-local file paths. Only bare (line-start or whitespace-delimited) `file://` URLs are rewritten — `[label](file://...)` markdown anchors keep the normal link path ([#​3219](https://github.com/nesquena/hermes-webui/issues/3219)).
##### Security
- `/api/media` now hard-denies WebUI state and secret/config files even when they fall under an allowed root (the WebUI state dir, `settings.json`, `state.db`, `auth.json`, `auth.lock`, `config.yaml`, `.env`, signing/PBKDF2 keys, the `sessions`/`memories`/`profiles` state subdirs). Previously the whole Hermes home was an allowed root, so an authenticated session viewing attacker-influenced agent output that emitted a `file://`/`MEDIA:` link to such a file could fetch it. Hardened the boundary at the route for every entry path (bare `file://`, markdown anchors, and `MEDIA:` tokens), closing [#​3234](https://github.com/nesquena/hermes-webui/issues/3234).
### [`v0.51.182`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051182--2026-05-31--Release-FB-stage-batchB2--headless-browser-smoke-gate--consolidated-client-disconnect-handling)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.181...v0.51.182)
##### Added
- CI now runs a headless browser smoke test (`tests/browser_smoke.py`, `.github/workflows/browser-smoke.yml`) on every PR and push: it boots the real server agent-free and loads the key pages in Chromium, failing on any console error or uncaught JS exception. This catches the runtime-JS brick class (e.g. a `const` reassigned at runtime as in [#​3162](https://github.com/nesquena/hermes-webui/issues/3162), or a `function`/`window` name collision as in [#​2715](https://github.com/nesquena/hermes-webui/issues/2715)/[#​2771](https://github.com/nesquena/hermes-webui/issues/2771)) that `node --check`, ESLint, and the mocked test suite cannot see because they only manifest when a real browser executes the page. The smoke is credential-free — it strips `*_API_KEY` from the environment and drives no real model ([#​3231](https://github.com/nesquena/hermes-webui/issues/3231)).
##### Fixed
- Client disconnects during response writes (browser tab close, SSE reconnect races, mobile network switches, half-closed sockets) are now handled through a single `_CLIENT_DISCONNECT_ERRORS` set and a `_safe_write` helper instead of ad-hoc per-call-site `try/except`, so an expected disconnect no longer surfaces as a misleading server 500 in the logs. The error-response path is itself wrapped so a disconnect while sending a 500 is swallowed quietly rather than cascading. A bare `TimeoutError` from the Joplin notes integration's `urlopen` is now converted to a clean "not reachable" error at the route rather than escaping to the dispatch-level disconnect handler ([#​3210](https://github.com/nesquena/hermes-webui/issues/3210)).
### [`v0.51.181`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051181--2026-05-30--Release-FA-stage-batchA--agent-cache-eviction-teardown--streaming-finalize-race)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.180...v0.51.181)
##### Fixed
- Cached agents evicted after session-identity mismatches, unsafe runtime refresh, credential self-heal, or skipped compression migration now go through the normal session-boundary teardown path, committing pending memory and closing provider/session resources instead of silently dropping the cache entry ([#​3218](https://github.com/nesquena/hermes-webui/issues/3218), closes [#​3215](https://github.com/nesquena/hermes-webui/issues/3215)).
- Assistant streaming text is no longer lost when a stream completes while you have switched to a different session tab: the SSE `done` handler now sets the stream-finalized flag immediately (before the fade window), so a `stream_end` event arriving mid-fade can no longer trigger `_restoreSettledSession()` and overwrite the live messages with a stale server snapshot ([#​3201](https://github.com/nesquena/hermes-webui/issues/3201), closes [#​3195](https://github.com/nesquena/hermes-webui/issues/3195)).
### [`v0.51.180`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051180--2026-05-30--Release-EZ-stage-batch62--sessionagent-cache-ownership-hardening)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.179...v0.51.180)
##### Fixed
- Guard session and agent caches against compression/continuation id drift: `GET /api/session` now evicts a cached `Session` whose own `session_id` no longer matches the requested key (instead of trusting the LRU), the background title-update/refresh paths only adopt a cached session when its identity matches, and the compression checkpoint migration no longer re-stores a stale object under the old lineage id. Prevents a stale cached object from making `/api/session?session_id=<tip>` return an older transcript segment, which looked like a disappeared session ([#​3191](https://github.com/nesquena/hermes-webui/issues/3191)).
- Evicted cached agents are now torn down cleanly at the WebUI session boundary: pending session memory is committed first, and only if the lifecycle entry is clean afterward does the agent get unregistered and its memory provider shut down via `shutdown_memory_provider(messages)` (closing provider-owned clients such as Hindsight's aiohttp session) before the session DB is closed — instead of leaking those resources until garbage collection ([#​3166](https://github.com/nesquena/hermes-webui/issues/3166)).
### [`v0.51.179`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051179--2026-05-30--Release-EY-stage-batch61--custom-provider-reasoning-efforts--clearer-sidebar-tooltips)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.178...v0.51.179)
##### Fixed
- Reasoning effort selector now appears for thinking-capable models served through custom API aggregators (New API, One API, etc.) that use non-standard model naming — bare names like `deepseek-v4-flash` or dot-separated `moonshotai.kimi-k2.5` rather than the OpenRouter-style `vendor/model` slash format. The heuristic now also strips a dot-vendor prefix and recognizes a `thinking`/`reasoning` token anywhere in the model name; plain non-reasoning models stay hidden as before ([#​3202](https://github.com/nesquena/hermes-webui/issues/3202)).
- Sidebar session row tooltips now explain the fork, prior-turn, child-session, and running/unread status badges, and hovering a truncated chat title shows the full title instead of the old "Double-click to rename" hint ([#​3203](https://github.com/nesquena/hermes-webui/issues/3203)). The localized pending-approval/clarify attention tooltip retains precedence over the generic running/unread state tooltip on the status dot, and the fork tooltip keeps its localized "Forked from" base.
### [`v0.51.178`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051178--2026-05-30--Release-EX-stage-batch60--parallel-sharded-CI-test-runs)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.177...v0.51.178)
##### Changed
- CI: the test suite now runs in 3 parallel shards per Python version (9 jobs total) via `pytest-shard`, cutting wall-clock test time roughly in half (slowest shard \~70s vs \~180s sequential). To make sharding safe, several tests that asserted a pristine default while a sibling test mutated shared process/server state were fixed to establish their own preconditions: onboarding-completed flag reset (`test_onboarding_mvp`), password-hash cache invalidation (`test_issue693_system_health_panel`), authoritative sessions-file path (`test_auth_session_persistence`), and — the root cause of the worst leak — `test_profile_env_isolation` no longer deletes + re-imports `api.profiles` (which poisoned the module's cached base-home global for every later test); it now points the cached path via `monkeypatch.setattr`. A conftest fixture also restores `HERMES_HOME`/`HERMES_BASE_HOME` after each test as defense-in-depth. Completes the test-sharding half of [#​3197](https://github.com/nesquena/hermes-webui/issues/3197) (the Docker-cache half shipped in v0.51.177).
### [`v0.51.177`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051177--2026-05-30--Release-EW-stage-batch59--Docker-smoke-test-layer-caching)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.176...v0.51.177)
##### Changed
- CI: the Docker smoke-test workflow now builds the image once and caches its layers via the GitHub Actions cache (`type=gha`), then each compose variant restores from that cache instead of rebuilding from scratch — saving \~1-3 minutes per variant. The image is still built from the PR's local Dockerfile (`load: true`), so PR changes are tested, not the released image. (Partial adoption of [#​3197](https://github.com/nesquena/hermes-webui/issues/3197) — the Docker half; the test-sharding half is deferred pending test-suite shard-safety work.)
### [`v0.51.176`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051176--2026-05-30--Release-EV-stage-batch58--sidebar-attention-indicators)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.175...v0.51.176)
##### Added
- Sidebar session rows now surface pending approval and clarify work with a color-coded status dot (red for approvals, amber for clarifies) plus a matching left rail and tinted background, so inactive conversations that need a permission decision or an answer are easy to spot at a glance. A distinct two-tone attention sound also plays for approval/clarify prompts, separate from the completion sound ([#​3190](https://github.com/nesquena/hermes-webui/issues/3190)).
### [`v0.51.175`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051175--2026-05-30--Release-EU-stage-batch57--internal-conversation-links)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.174...v0.51.175)
##### Added
- Internal conversation links: a "Copy conversation link" action copies a Markdown reference (`session://<id>`) for any conversation, and `session://` references render as same-origin in-app links that open the target conversation without a full page reload ([#​3179](https://github.com/nesquena/hermes-webui/issues/3179)). Links are sanitized through the existing safe-URL allowlist (rewritten to `/session/<id>`, label escaped, sid URL-encoded — verified against quote-breakout and script-injection payloads).
- Conversation filtering now recognizes pasted session references directly: raw session IDs, `session://...` references, `/session/...` URLs, and Markdown session links surface the target conversation while preserving normal content-search hits ([#​3179](https://github.com/nesquena/hermes-webui/issues/3179)).
### [`v0.51.174`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051174--2026-05-30--Release-ET-stage-batch56--CLIgateway-session-usage-in-Insights)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.173...v0.51.174)
##### Added
- The Insights page now includes CLI and gateway sessions (Telegram, Discord, cron, TUI) from the Hermes agent's `state.db` in usage totals, model breakdown, and daily activity — not just WebUI-native sessions ([#​3189](https://github.com/nesquena/hermes-webui/issues/3189)). WebUI sessions are de-duplicated so they are counted once, not double-counted against their `state.db` row.
### [`v0.51.173`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051173--2026-05-30--Release-ES-stage-batch55--Windows-pathjournal-safety--pin-quota-snapshot-fix--tool-card-paging-anchor--sidebar-dedupe--quieter-tool-cards)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.172...v0.51.173)
##### Fixed
- Windows native runs now skip the POSIX-only turn-journal directory fsync instead of raising `AttributeError` for missing `os.O_DIRECTORY` on every submitted turn ([#​3170](https://github.com/nesquena/hermes-webui/issues/3170)).
- `/api/media` now treats Windows cross-drive `commonpath()` comparisons as non-matches instead of 500ing when media paths and allowed roots live on different drives ([#​3171](https://github.com/nesquena/hermes-webui/issues/3171)).
- Hidden pre-compression snapshots no longer keep stale pin state or count toward the visible pinned-session quota ([#​3181](https://github.com/nesquena/hermes-webui/issues/3181)).
- Tool-call cards stay anchored when scrolling back through paginated history; legacy session-level tool-call indices are rebased to the returned message window and the browser refreshes tool-call anchors whenever a larger history window is loaded ([#​3120](https://github.com/nesquena/hermes-webui/issues/3120)).
- Avoid duplicate sidebar rows when a compressed session completes after both the preserved snapshot id and continuation id are already present in the session list.
##### Changed
- Restored the legacy compact tool-call card chrome by removing the persistent "Tool output" badge and returning the left rail to the muted border treatment. This keeps tool activity visually quieter while preserving the existing collapsible tool details.
### [`v0.51.172`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051172--2026-05-30--Release-ER-stage-batch54--model-label-fallback--dev-cache-bust-hash--tilde-workspace-completion--cron-project-chip-sessions)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.171...v0.51.172)
##### Fixed
- Session model labels now fall back to the friendly `getModelLabel()` form instead of the raw model id when gateway routing info is unavailable ([#​3174](https://github.com/nesquena/hermes-webui/issues/3174)).
- Dev-build cache-busting now includes a short hash of the tracked dirty diff, so local asset URLs change on each edit instead of staying at a constant `-dirty` suffix ([#​3159](https://github.com/nesquena/hermes-webui/issues/3159)).
- Workspace path autocomplete now preserves `~/` suggestions while browsing under the user's home directory ([#​3173](https://github.com/nesquena/hermes-webui/issues/3173)).
- CLI-sourced cron sessions that were squeezed past the default sidebar window now stay addressable under their project chip via a dedicated cron-only lookup pass ([#​3172](https://github.com/nesquena/hermes-webui/issues/3172)).
### [`v0.51.171`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051171--2026-05-30--Release-EQ-stage-batch53--tool-output-card-badge--Neon-opt-in-skin)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.170...v0.51.171)
##### Added
- Tool-call output cards now carry a persistent "Tool output" badge and accent rail so tool output stays visually distinct from final assistant responses without requiring hover ([#​2867](https://github.com/nesquena/hermes-webui/issues/2867)).
- New opt-in "Neon" cyberpunk skin (dark-first, purple/cyan accents). Default-off; select it from the skin list like Catppuccin or Nous.
### [`v0.51.170`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051170--2026-05-30--Release-EP-stage-batch52--run-aware-SSE-replay-cursors)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.169...v0.51.170)
##### Fixed
- SSE run-journal replay cursors are now run-aware: a stale `after_seq` from an interrupted prior stream can no longer suppress replay events in a newer stream whose sequence numbers reset from 1. The reconnect cursor now carries a run-scoped `after_event_id` (`run_id:seq`) and the server ignores it when the run id differs, falling back to same-run `after_seq` dedupe ([#​3124](https://github.com/nesquena/hermes-webui/issues/3124)).
### [`v0.51.169`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051169--2026-05-30--Release-EO-stage-batch51--skill-toggle-profile-scoping--update-tag-filter--Docker-docs)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.168...v0.51.169)
##### Fixed
- Docker docs now explain host-localhost URLs (`host.docker.internal` / `host.containers.internal`) and the `sudo docker compose` `$HOME=/root` bind-mount pitfall for users whose WebUI cannot reach host APIs or see `~/.hermes` ([#​3012](https://github.com/nesquena/hermes-webui/issues/3012), [#​3006](https://github.com/nesquena/hermes-webui/issues/3006)).
- Skills panel disabled/enabled state and toggle writes now resolve `config.yaml` from the active WebUI profile instead of the process default Hermes home or startup config override ([#​3066](https://github.com/nesquena/hermes-webui/issues/3066)).
- Update checks no longer advertise a newer release tag when a main-tracking checkout already contains that tag; the banner now falls through to the branch comparison path instead of offering an update that cannot fast-forward ([#​3140](https://github.com/nesquena/hermes-webui/issues/3140)).
### [`v0.51.168`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051168--2026-05-30--Release-EN-stage-batch50--hotfix-mobile-Failed-to-load-conversation-messages)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.167...v0.51.168)
##### Fixed
- Fixed a `TypeError` in `_ensureMessagesLoaded` that surfaced as a "Failed to load conversation messages" toast on mobile after most messages: a `const msgs` binding was reassigned by the [#​3018](https://github.com/nesquena/hermes-webui/issues/3018) ephemeral-field carry-forward (introduced v0.51.161), which throws at runtime. Changed to `let`. Mobile triggered it most because SSE/visibility events fire the session-reload path more aggressively ([#​3162](https://github.com/nesquena/hermes-webui/issues/3162)).
##### Added
- Static JS runtime-error lint guard (`eslint.runtime-guard.config.mjs` + `tests/test_static_js_runtime_lint.py`): a curated, zero-false-positive ESLint check (`no-const-assign`, `no-import-assign`) over `static/**/*.js` that catches the brick-class of runtime errors `node --check` and source-presence tests miss. Runs in the test suite when ESLint is present and skips gracefully otherwise. See `TESTING.md` > "Static JS runtime lint".
### [`v0.51.167`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051167--2026-05-30--Release-EM-stage-batch49--iOS-style-swipe-actions-for-touch-devices--session-list-FLIP-reflow)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.166...v0.51.167)
##### Added
- Touch devices now support iOS-mail-style swipe actions on session rows: swipe left to reveal a Delete action, swipe right to reveal Archive (Restore for already-archived sessions). Swipe is gated to touch/coarse-pointer input, so desktop click, context-menu, and drag behavior are unchanged. Delete still routes through the existing confirmation dialog. The session list also gains FLIP-based reflow animation when rows are archived, deleted, or reordered, honoring `prefers-reduced-motion`.
### [`v0.51.166`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051166--2026-05-30--Release-EL-stage-batch48--shared-OpenCode-runtime-key--cron-project-chip-sessions)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.165...v0.51.166)
##### Fixed
- OpenCode provider key lookup now honors the shared `OPENCODE_API_KEY` fallback for both Zen and Go runtime paths, matching model-picker detection when provider-specific keys are absent ([#​3145](https://github.com/nesquena/hermes-webui/issues/3145)).
- Agent-side cron sessions imported from state.db now remain available to their assigned project chip as `default_hidden` rows instead of being filtered out before project reveal logic can see them ([#​3134](https://github.com/nesquena/hermes-webui/issues/3134)).
### [`v0.51.165`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051165--2026-05-30--Release-EK-stage-batch47--stop-EventSource-reconnect-storm-on-long-lived-SSE-streams)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.164...v0.51.165)
##### Fixed
- The session-events and gateway SSE streams no longer emit `Connection: close`, which browsers interpreted as a signal that the EventSource lifecycle had ended — triggering an instant reconnect loop that thrashed the session list roughly once per second and forced repeated re-renders / scroll-to-bottom ([#​3103](https://github.com/nesquena/hermes-webui/issues/3103), regression from the HTTP/1.1 keep-alive change in [`598fd4f`](https://github.com/nesquena/hermes-webui/commit/598fd4ff)). Finite responses that lack a `Content-Length` (e.g. the on-the-fly workspace ZIP download) keep `Connection: close` for unambiguous HTTP/1.1 message framing.
### [`v0.51.164`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051164--2026-05-30--Release-EJ-stage-batch46--passive-performance-hardening-refresh-coalescing--draft-save-dedup--activity-placeholders--bounded-restart-safety-wait)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.163...v0.51.164)
##### Fixed
- Coalesced duplicate in-flight sidebar/project refreshes and suppressed overlapping approval/clarify fallback polls, reducing repeated passive requests while preserving latest-refresh-wins sidebar state.
- Skipped duplicate composer-draft writes when the normalized autosave payload is unchanged, avoiding redundant full session JSON rewrites during debounced input/focus churn.
- Refined empty Activity waiting placeholders to distinguish stream creation, first-token wait, post-tool model wait, and running-tool wait states.
- Self-update restart safety now checks active agent runs as well as open SSE streams and waits for in-flight work before re-exec, avoiding update-triggered interruption when a run outlives its browser stream. The wait is bounded (300s) with a logged fallback to re-exec so a long-running or stuck agent run cannot soft-jam the self-update indefinitely.
- Documented the WebUI prefill context budget in README and architecture notes so operators can keep new-browser-turn startup context compact.
### [`v0.51.163`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051163--2026-05-30--Release-EI-stage-batch45--session-duplicatebranch-field-propagation)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.162...v0.51.163)
##### Fixed
- Duplicating or branching a session now carries over the fields that were previously dropped, so the copy behaves identically to the original until further edits: `truncation_watermark`, model-facing `context_messages` (deep-copied for independence), gateway routing + routing history, context-engine state, cache-token counters, composer draft, LLM-title flag, and per-session settings (model provider, project, personality, toolsets, context length, threshold). Compression anchors and last-prompt-token counts are intentionally not carried so the copy re-derives them. Prevents data-loss scenarios where, e.g., editing a message in a duplicated session would drop messages during state.db merge.
### [`v0.51.162`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051162--2026-05-30--Release-EH-stage-batch44--conversation-filter-clear-button--code-only-title-language-regression-coverage)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.161...v0.51.162)
##### Added
- Conversation filtering now shows a clear button inside the search field whenever text is present, letting users clear the filter with one click.
### [`v0.51.161`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051161--2026-05-29--Release-EG-stage-batch43--3-PR-live-display-fixes-jump-to-question-on-intermediate-assistant-messages--per-turn-usage-badge-persistence--stale-unreadcompression-timertool-card-dedup)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.160...v0.51.161)
##### Fixed
- The jump-to-question button now appears on every assistant message that has a resolvable question target, not only the turn-final one. Multi-step turns (tool call → assistant → tool call → assistant) previously stripped the navigation affordance from intermediate assistant bubbles.
- Per-turn ephemeral fields (`_turnUsage`, `_turnDuration`, `_turnTps`, `_gatewayRouting`) are now carried forward when a session refresh replaces the in-memory message list with fresh server data, so the usage badge / duration / gateway-routing pill no longer flash and disappear after a compaction restore, external active-session poll, or SSE error recovery ([#​3018](https://github.com/nesquena/hermes-webui/issues/3018)).
- The sidebar unread dot no longer sticks on a session after it has been viewed: syncing the viewed count now clears any stale completion-unread marker, and an actively-viewed session syncs its count instead of being flagged unread on tab switch ([#​3020](https://github.com/nesquena/hermes-webui/issues/3020)).
- The auto-compression card's elapsed timer is now cleared on completion/error, so a replaced card is no longer treated as a still-running compression; a background-session completion no longer kills the active session's compression timer ([#​2973](https://github.com/nesquena/hermes-webui/issues/2973)).
- Tool cards no longer duplicate the result text in both the header and the detail row: the completed tool result is routed to the detail snippet (falling back to the header only when no progress text was streamed), and the detail row is suppressed when the snippet equals the header preview.
### [`v0.51.160`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051160--2026-05-29--Release-EF-stage-batch42--3-PR-low-risk-cleanup-OpenCode-shared-key-detection--skills-panel-profile-aware-disabled-read--session-index-metadata-refresh-perf)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.159...v0.51.160)
##### Fixed
- Detect a shared `OPENCODE_API_KEY` as enabling both OpenCode Zen and OpenCode Go provider groups, matching Hermes Agent bridge environments that expose one OpenCode credential.
- The skills panel now reads each skill's disabled state from the active WebUI profile's `config.yaml` (checking `skills.platform_disabled.webui` then falling back to `skills.disabled`) instead of the process-global `HERMES_HOME`, so non-default profiles show the correct enabled/disabled state and stay consistent with the skill-toggle write path.
##### Changed
- WebUI session-sidebar metadata refresh is faster on large session directories: the persisted session-id listing is cached by directory mtime instead of re-globbing under the sessions lock on every `/api/sessions` poll, metadata-only loads skip the per-row session-index read when the sidecar already carries an authoritative `message_count`, and only runtime/lineage-shaped rows are overlaid with fuller sidecar metadata (historical transcripts are no longer scanned on every refresh).
### [`v0.51.159`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051159--2026-05-29--Release-EE-stage-batch41--5-PR-low-risk-cleanup-Gateway-tool-progress-forwarding--shutdown-diagnostics--CLI-snippet-limit-parity--numpad-Enter-submit--sync-chat-notes-guardrail)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.158...v0.51.159)
##### Changed
- WebUI's durable-notes guardrail now also applies to sync chat and explicitly asks agents to leave external notes and durable memory unchanged unless a turn contains an explicit capture or reusable durable signal; durable note writes should be summarized back to the user.
##### Fixed
- Gateway-backed browser chat now forwards Hermes Gateway `hermes.tool.progress` SSE events into WebUI's live tool/activity stream, so Gateway runs no longer appear idle while server-side tools are running.
- WebUI now logs structured shutdown diagnostics when the server exits or `/api/shutdown` is called, including active stream IDs to help diagnose interrupted turns after restarts.
- The chat composer now treats the numeric keypad Enter key as a submit shortcut even when the send-key preference is set to Ctrl/Cmd+Enter, while preserving regular Enter-as-newline behavior in that mode.
- The CLI tool-result snippet limit in the browser now matches the backend (`_TOOL_RESULT_SNIPPET_MAX = 4000`), so longer non-diff CLI tool output is no longer truncated to 200 characters before reaching the tool card.
### [`v0.51.158`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051158--2026-05-29--Release-ED-stage-batch40--5-PR-low-risk-cleanup-numpadkeyboard-composer-fixes--Joplin-search-auth--provider-qualified-model-preservation--SSE-fallback-poll-throttle--assistant-reply-polish)
[Compare Source](https://github.com/nesquena/hermes-webui/compare/v0.51.157...v0.51.158)
##### Changed
- WebUI chat instructions now explicitly prevent terse scratchpad/planning fragments from appearing in visible assistant replies, while still allowing clear user-facing progress updates during tool-heavy work.
##### Fixed
- The chat composer no longer forces mobile newline-on-Enter behavior on touch-primary devices that also have a fine pointer present (tablet plus Bluetooth keyboard, detachable Surface, iPad plus Magic Keyboard), so Enter submits with desktop semantics when a real keyboard is in the picture.
- The active-session external-refresh fallback poll now fires every 30 s instead of every 5 s. The SSE session-events stream already pushes invalidations in real time, so the poll is only a fallback; the slower interval removes visible scroll jitter and a network/CPU floor on long sessions.
- The model picker no longer fuzzy-matches a provider-qualified model id (`@provider:model` or slash-qualified `vendor/model`) to a nearby curated sibling once exact lookup fails, preserving the raw typed value so uncatalogued models stay routable instead of silently snapping to a different model.
- Joplin notes search now keeps the `Authorization` header and adds a query-token compatibility shim only for Web Clipper `/search` calls, covering clipper builds that return HTTP 403 for header-only search auth while keeping other Joplin API URLs token-free.
</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/749
viraatdas
pushed a commit
to viraatdas/hermes-webui
that referenced
this pull request
Jun 1, 2026
Supersedes the v0.51.199 proximity-re-pin (nesquena#3330) and the nesquena#3250 upward-intent timeout with a sticky-unpin model (ChatGPT/Claude/Codex behavior): scroll up = stay put until you return to the bottom or click the scroll-to-bottom control. Reconciled against the shipped nesquena#3330 code: removed the now-dead _recentMessageUpwardIntent reference from the nesquena#3319 rAF retry, kept the load-time -Infinity intent-init fix, kept the pinned-only >500 catch-up. Co-authored-by: pamnard <pamnard@users.noreply.github.com>
eleboucher
pushed a commit
to eleboucher/homelab
that referenced
this pull request
Jun 2, 2026
…➔ 0.51.210) (#782) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/nesquena/hermes-webui](https://github.com/nesquena/hermes-webui) | patch | `0.51.197` → `0.51.210` | --- ### Release Notes <details> <summary>nesquena/hermes-webui (ghcr.io/nesquena/hermes-webui)</summary> ### [`v0.51.210`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051210--2026-06-02--Release-GD-stage-batch1--model-picker-multi-slash-fix--extensionless-preview-highlighting) [Compare Source](nesquena/hermes-webui@v0.51.209...v0.51.210) ##### Fixed - Model picker no longer snaps to the wrong model when multiple multi-slash model IDs from the same proxy provider share the same base name. Exact-match priority in `_findModelInDropdown` and first-segment-only stripping in `_normalizeConfiguredModelKey` / `_norm_model_id` prevent collisions in selection, badge assignment, and configured-entry dedup ([#​3360](nesquena/hermes-webui#3360), [@​b3nw](https://github.com/b3nw)). - Workspace file previews now syntax-highlight common code/config filenames without useful extensions, including `Dockerfile`, `Dockerfile.*`, `Makefile`, `GNUmakefile`, `CMakeLists.txt`, `.gitignore`, and `.dockerignore` ([#​3365](nesquena/hermes-webui#3365), [@​AJV20](https://github.com/AJV20)). ### [`v0.51.209`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051209--2026-06-02--Release-GC-WebUI-dashboard-plugin-system-with-iframe-isolation) [Compare Source](nesquena/hermes-webui@v0.51.208...v0.51.209) ##### Added - WebUI dashboard plugins: plugins that ship a UI under `~/.hermes/plugins/<name>/dashboard/` (with a `manifest.json`) now appear as opt-in cards in Settings → Plugins (default off). Once enabled, an **Open** button renders the plugin page inside a sandboxed iframe (`sandbox="allow-scripts allow-forms allow-popups"` — no `allow-same-origin`, so plugin JS/CSS/modals stay fully isolated from the parent app). New `/plugins/` (shared assets) and `/dashboard-plugins/<name>/` (per-plugin assets) static routes serve only built `dist/`/`static/` files with path-traversal, dotfile, and extension-allowlist protection (plugin source/config such as `plugin_api.py`/`manifest.json`/`.env` is never served), and both the page and asset routes are gated server-side on the enable state + an HTTP `sandbox` CSP + `nosniff`. Plugin `name` and `tab.path` are validated at load. Display-only — no plugin backend/subprocess execution ([#​2622](nesquena/hermes-webui#2622), [@​pix0127](https://github.com/pix0127)). ### [`v0.51.208`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051208--2026-06-02--Release-GB-workspace-upload-hardening-hotfix) [Compare Source](nesquena/hermes-webui@v0.51.207...v0.51.208) ##### Fixed - Hardened the workspace file-upload surface ([#​3104](nesquena/hermes-webui#3104) follow-up): (1) a negative `Content-Length` no longer bypasses the size cap and triggers an unbounded `rfile.read(-1)` — the length is now validated `[0, MAX_UPLOAD_BYTES]` centrally in `parse_multipart` for every upload handler; (2) `.tar`, `.tbz2`, and `.txz` archives now auto-extract (the upload handler's archive-suffix set was narrower than `extract_archive`'s, so those silently landed as raw files); (3) a rejected archive (zip-slip / zip-bomb / corrupt / too-many-members) now surfaces an error toast in the workspace panel instead of a misleading "Uploaded" success; (4) an in-workspace symlink subpath can no longer make the upload target `mkdir`/write outside the workspace root. Regression tests added. ### [`v0.51.207`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051207--2026-06-02--Release-GA-Edge-TTS-as-an-alternative-speech-engine) [Compare Source](nesquena/hermes-webui@v0.51.206...v0.51.207) ##### Added - Added an optional server-side **Edge TTS** speech engine (Microsoft neural voices) selectable in Settings → Preferences → TTS Engine, alongside the existing browser speech synthesis. The voice list switches to the Edge neural voices when selected. A new `POST /api/tts` endpoint streams the audio, gated by the same-origin CSRF check + session auth, a per-client rate limit, a 5000-character cap, and a voice allowlist. `edge-tts` is an optional dependency — the endpoint returns a clear install hint (503) when it isn't present, so existing installs are unaffected ([#​2931](nesquena/hermes-webui#2931), [@​liuqiangweb-svg](https://github.com/liuqiangweb-svg)). ### [`v0.51.206`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051206--2026-06-02--Release-FZ-workspace-file-upload--drag-and-drop-with-archive-extraction) [Compare Source](nesquena/hermes-webui@v0.51.205...v0.51.206) ##### Added - Workspace file panel: an **Upload** button and drag-and-drop that POST to a new `/api/workspace/upload` endpoint. Files land in the session workspace (resolved via the trusted-workspace guard), are de-duplicated with `-1`/`-2` suffixes, and archives (`.zip`/`.tar.*`) are auto-extracted into the target subdirectory with zip-bomb (size-cap + member-count-cap) and zip-slip (path-containment) protections. The extraction size cap is tunable via `HERMES_WEBUI_MAX_EXTRACTED_MB` (defaults to 10× the upload cap). Extraction errors are surfaced to the frontend instead of being silently swallowed, and the archive is removed on failure ([#​3104](nesquena/hermes-webui#3104), [@​antoniocarlos97ss](https://github.com/antoniocarlos97ss)). ### [`v0.51.205`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051205--2026-06-01--Release-FY-stage-hi1--workspace-syntax-highlighting--generated-image-cards--manual-title-regeneration) [Compare Source](nesquena/hermes-webui@v0.51.204...v0.51.205) ##### Added - Workspace file previews now render with syntax highlighting via Prism.js (already loaded for chat code blocks), covering common languages (Python, JS/TS, CSS, JSON, SQL, shell, and more) and degrading gracefully to plain text for unknown/plain files and when offline. The preview code surface uses a single uniform background across light and dark themes ([#​3337](nesquena/hermes-webui#3337), [@​mysoul12138](https://github.com/mysoul12138)). - Generated local image artifacts now render as a clean inline image (with click-to-zoom lightbox) plus a hover/focus-revealed **Download** action overlaid on the image, served through authenticated `/api/media` URLs — matching the common AI-chat pattern of letting the image be the hero rather than wrapping it in a permanent card ([#​3220](nesquena/hermes-webui#3220), [@​AJV20](https://github.com/AJV20)). - The session action menu can regenerate conversation titles on demand from the saved transcript, updating the sidebar without touching conversation chronology and syncing the new title through to state.db when Insights sync is enabled. The menu was also streamlined to a compact icon + label layout (descriptions move to hover tooltips). Closes [#​3106](nesquena/hermes-webui#3106) ([#​3223](nesquena/hermes-webui#3223), [@​AJV20](https://github.com/AJV20)). ### [`v0.51.204`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051204--2026-06-01--Release-FX-stage-batch17--projectsession-operations-honor-the-sessions-own-profile) [Compare Source](nesquena/hermes-webui@v0.51.203...v0.51.204) ##### Fixed - Project and session operations (project create/rename/recolor/delete/unassign, session move, and the profile chip label) now key on the session's own profile (`S.session.profile`) instead of the global active profile, so switching between sessions from different profiles no longer causes silent 404s, misleading chip labels, or project-picker entries from the wrong profile. The project picker also filters to the session's profile and surfaces an error toast on failure instead of a silent no-op ([#​3331](nesquena/hermes-webui#3331), [@​PINKIIILQWQ](https://github.com/PINKIIILQWQ)). ### [`v0.51.203`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051203--2026-06-01--Release-FW-stage-batch15--sticky-manual-unpin-for-streaming-chat-scroll) [Compare Source](nesquena/hermes-webui@v0.51.202...v0.51.203) ##### Changed - Streaming chat scroll now uses a sticky manual-unpin model: once you scroll up to read earlier content during a streaming response, the view stays put and no longer auto-follows the live tail until you scroll back to the bottom (near-bottom hysteresis on downward motion) or click the scroll-to-bottom control. Tool cards, token updates, and layout growth no longer re-pin the viewport after a reading pause. This replaces the [#​3250](nesquena/hermes-webui#3250) upward-intent timeout and supersedes the v0.51.199 proximity-re-pin ([#​3330](nesquena/hermes-webui#3330)), matching the streaming-scroll behavior of ChatGPT/Claude/Codex. Fresh streams reset the follow state on attach ([#​3343](nesquena/hermes-webui#3343), [@​pamnard](https://github.com/pamnard)). ### [`v0.51.202`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051202--2026-06-01--Release-FV-stage-batch14--filter-interrupted-recovery-control-text-from-visible-transcript) [Compare Source](nesquena/hermes-webui@v0.51.201...v0.51.202) ##### Fixed - Interrupted SSE-recovery control text (the synthetic `stale_interrupted_event` run-journal payload) is now kept out of the visible chat transcript instead of being replayed as a message: it's marked `recovery_control` on the backend and filtered across the `msgContent()` render path, the SSE settle/error handlers, and final transcript filtering, so platform-only control state no longer leaks into the conversation ([#​3321](nesquena/hermes-webui#3321), [@​franksong2702](https://github.com/franksong2702)). ### [`v0.51.201`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051201--2026-06-01--Release-FU-stage-batch13--colored-diff-lines-in-tool-card-snippets) [Compare Source](nesquena/hermes-webui@v0.51.200...v0.51.201) ##### Added - Tool-card result snippets that contain a unified diff now render with the same green/red/cyan diff coloring already used for diffs in chat messages (reusing the existing `.diff-block` styles), with an expand/collapse toggle that preserves the coloring. Non-diff snippets are unchanged ([#​3336](nesquena/hermes-webui#3336), [@​mysoul12138](https://github.com/mysoul12138)). ### [`v0.51.200`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051200--2026-06-01--Release-FT-stage-batch12--remote-gateway-health-probe--ephemeral-turn-field-preservation) [Compare Source](nesquena/hermes-webui@v0.51.199...v0.51.200) ##### Fixed - The Tasks/Cron panel no longer shows a spurious "Gateway not configured" banner in multi-container Docker deployments where the WebUI image doesn't ship the `gateway` Python package: agent-health now probes the remote gateway via `HERMES_API_URL` before falling back to the local `gateway.status` import. Closes [#​3281](nesquena/hermes-webui#3281) ([#​3312](nesquena/hermes-webui#3312), [@​Sanjays2402](https://github.com/Sanjays2402)). - Force-reloading the active session (`loadSession(sid, {forceReload:true})`) no longer drops ephemeral turn fields (`_turnUsage`, `_turnDuration`, `_turnTps`, `_gatewayRouting`, `_statusCard`): the ephemeral-field carry-forward now reads the prior `S.messages` before it's reset, so the token-usage badge and status cards survive an external refresh. Closes [#​3306](nesquena/hermes-webui#3306) ([#​3313](nesquena/hermes-webui#3313), [@​Sanjays2402](https://github.com/Sanjays2402)). ### [`v0.51.199`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051199--2026-06-01--Release-FS-stage-batch11--pinned-scroll-recovery--inline-math-currency-false-positive) [Compare Source](nesquena/hermes-webui@v0.51.198...v0.51.199) ##### Fixed - Pinned chat now recovers its scroll position after a DOM rebuild: `_setMessageScrollToBottom` retries on the next layout frame, and `scrollIfPinned` re-pins when the pane has drifted more than 500px from the bottom, so a message-list rebuild no longer leaves a pinned conversation stranded mid-scroll. Closes [#​3319](nesquena/hermes-webui#3319) ([#​3330](nesquena/hermes-webui#3330), [@​jianongHe](https://github.com/jianongHe)). - The `$...$` inline-math renderer no longer treats currency like `$1,000 xuống ~$95` as math: the opening `$` followed by a digit is now rejected (aligning with smd's `se()` guard), so dollar amounts render as plain text. Digit-leading inline math (e.g. `$2x = 4$`) should now use the LaTeX-style `\(2x = 4\)` or display `$$2x = 4$$` delimiters ([#​3311](nesquena/hermes-webui#3311), [@​toanalien](https://github.com/toanalien)). ### [`v0.51.198`](https://github.com/nesquena/hermes-webui/blob/HEAD/CHANGELOG.md#v051198--2026-06-01--Release-FR-stage-batch10--custom-provider-reasoning-model-id-normalize--profile-skill-counts--run-adapter-RFC-slice) [Compare Source](nesquena/hermes-webui@v0.51.197...v0.51.198) ##### Fixed - Reasoning-effort detection for named `custom:*` providers now normalizes non-slash model ids before applying its fallback family heuristics, so separator variants such as `deepseek.v3.2`, `deepseek_v4_flash`, and vendor-namespaced ids like `vendor.deepseek.v3.2` resolve the same way as `deepseek-v4-flash`. The keyword fallback is now token-aware rather than substring-based, preserving names like `model-thinking-preview` without falsely enabling reasoning for unrelated prefixes such as `thinkinghub.llama-3.1-70b` ([#​3327](nesquena/hermes-webui#3327), [@​Carry00](https://github.com/Carry00)). - Profile cards now show enabled vs compatible skill counts (computed with an 8s TTL cache that clears on profile switch) instead of a single ambiguous count. Closes [#​3339](nesquena/hermes-webui#3339) ([#​3341](nesquena/hermes-webui#3341), [@​b3nw](https://github.com/b3nw)). ##### Changed - The [#​1925](nesquena/hermes-webui#1925) runtime-adapter RFC now marks the configured runner-client boundary as shipped in v0.51.188 ([#​3073](nesquena/hermes-webui#3073) / [#​3274](nesquena/hermes-webui#3274)) and defines the next Slice 4g gate for a supervised local runner process harness: real runner-owned `AIAgent` execution, restart/reattach proof, bounded runner health diagnostics, and no new WebUI runtime-surrogate globals ([#​3334](nesquena/hermes-webui#3334), [@​Michaelyklam](https://github.com/Michaelyklam)). </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/782
SysAdminDoc
pushed a commit
to SysAdminDoc/hermes-webui
that referenced
this pull request
Jun 26, 2026
…File anchor for nesquena#3262 - Pin MESSAGE_UPWARD_INTENT_MS>=2000ms + intent-helper-uses-constant + downward-repin-independence (nesquena#3250, co-authored emanon312). - Update the nesquena#2823 large-markdown-preview test's openFile() signature anchor for nesquena#3262's openFile(path, opts={}) extension. Co-authored-by: emanon312 <emanon312@users.noreply.github.com>
SysAdminDoc
pushed a commit
to SysAdminDoc/hermes-webui
that referenced
this pull request
Jun 26, 2026
Supersedes the v0.51.199 proximity-re-pin (nesquena#3330) and the nesquena#3250 upward-intent timeout with a sticky-unpin model (ChatGPT/Claude/Codex behavior): scroll up = stay put until you return to the bottom or click the scroll-to-bottom control. Reconciled against the shipped nesquena#3330 code: removed the now-dead _recentMessageUpwardIntent reference from the nesquena#3319 rAF retry, kept the load-time -Infinity intent-init fix, kept the pinned-only >500 catch-up. Co-authored-by: pamnard <pamnard@users.noreply.github.com>
This was referenced Jul 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
During streaming, users who scroll up to read earlier content get snapped back to the bottom — especially when they pause scrolling for a moment before the message finishes generating.
Root Cause
The
MESSAGE_UPWARD_INTENT_MStimeout (_recentMessageUpwardIntent()) was only 450ms. The race condition:_scrollPinned = false✓movedUp = false_scrollPinned = truescrollIfPinned()→ forcesscrollTop = scrollHeight→ snap!This is particularly noticeable on long responses where the user scrolls up a small amount (still within 250px of bottom) and pauses to read.
Fix
Increase
MESSAGE_UPWARD_INTENT_MSfrom 450ms → 2000ms.This gives the user a 2-second protection window after their last upward scroll, during which DOM-triggered scroll events are correctly identified as co-occurring with user intent, preventing re-pinning.
What is NOT affected
movedUprequirestop < _lastScrollTop - 2which is false for downward movement regardless of intent timeout)scrollToBottom()directly sets_scrollPinned = true)Testing
No existing tests hardcode the 450ms value — the test suite validates the structural behavior (direction detection, unpin semantics, hysteresis thresholds) without depending on the specific timeout.
Refs: #1360, #1731