Commit graph

2392 commits

Author SHA1 Message Date
nesquena-hermes
819f1a1e7b Merge PR #5466 from ai-ag2026: reduce O(n^2) streaming render to O(n) (#5455) 2026-07-03 21:04:16 +00:00
nesquena-hermes
8fe2d63ec0 Merge remote-tracking branch 'origin/master' into release/stage-p1c
# Conflicts:
#	CHANGELOG.md
2026-07-03 20:44:31 +00:00
nesquena-hermes
6edcf14bbc Merge PR #5465 from ai-ag2026: pause background session polling while tab hidden (#5455) 2026-07-03 20:16:35 +00:00
nesquena-hermes
8120e69e1d Merge PR #5442 from rodboev: persist TTS and voice preferences server-side (#5435) 2026-07-03 20:04:55 +00:00
nesquena-hermes
00e7203ec8 Merge PR #5449 from rodboev: hide read-only cron fork affordances (#5439) 2026-07-03 19:44:33 +00:00
TARS
3f4ebf18a8 fix(sidebar): refresh timestamp labels on tab return
Add a dedicated visibilitychange hook for the 60s relative-time refresh poll so skipped hidden-tab ticks repaint immediately when the tab becomes visible, even when no streaming poll is active.
2026-07-03 20:13:24 +02:00
TARS
eedbf8b05f fix: clear stream perf cleanup state 2026-07-03 20:10:40 +02:00
Rod Boev
af3eb88225 Drop redundant live-row suppression after removing the animation source 2026-07-03 13:48:20 -04:00
TARS
fcf3e23643 perf(sidebar): pause background session polling while the tab is hidden (#5455)
The 30s streaming poll (renderSessionList -> GET /api/sessions + full sidebar
rebuild) and the 60s relative-time refresh (renderSessionListFromCache -> full
DOM rebuild) ran unconditionally, including in hidden background tabs, so a
backgrounded window kept fetching and rebuilding the sidebar it could not show.

Guard both interval callbacks with document.hidden and refresh the list on
visibilitychange when the tab is shown again, mirroring the existing gateway
poll. Visually neutral while the tab is visible; a hidden tab simply stops
burning network and DOM churn and re-syncs the moment it is shown.

Verified: node --check + ESLint runtime guard clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:41:49 +02:00
TARS
ed922f4f18 perf(stream): throttle live-turn snapshot and skip redundant per-token parse (#5455)
Two O(n^2) costs on the per-token/per-frame streaming path, both over the
growing answer:

WS2.2 — snapshotLiveTurn() serialized the whole live turn via turn.outerHTML on
every render frame. That snapshot only backs mid-stream session-switch restore,
and the switch path (sessions.js) plus tool/done event boundaries already
capture synchronously, so the per-frame call is throttled to a coarse trailing
snapshot instead of serializing the entire growing DOM each frame.

WS2.3 — the token handler ran _parseStreamState() (strip tool-call XML + extract
inline thinking over the FULL accumulated text) on every token, but _doRender
re-parses once per throttled frame anyway and the only per-token use is the
assistant-row creation gate. Skip the parse once the row exists (the common
case); keep it only until the row is created. Also drop the full-string
toLowerCase() allocation in _stripXmlToolCalls for a non-allocating regex test.

Output-neutral: same rendered DOM; the row-creation gate and snapshot semantics
are preserved (row appears within one 66ms frame; switch/settle snapshots stay
synchronous). node --check + ESLint runtime guard clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:41:49 +02:00
TARS
304a82d9b4 perf(stream): render anchor-scene live prose incrementally via smd (#5455)
In compact-worklog mode the anchor activity scene re-renders every frame, and
its live prose row was rebuilt with renderMd(fullText) each time — re-parsing
the entire growing answer per frame, which is O(n^2) over a long streamed
response (the incremental smd parser that the main body already uses was
bypassed and its segment hidden).

Keep a persistent per-segment smd parser + DOM node (keyed by the prose row's
local_id, using the same safe renderer as the live body) and feed it only the
delta each frame; hand that persistent node back to the scene builder instead
of re-parsing. Turns the per-frame prose cost from O(n) into O(delta), i.e. the
whole answer from O(n^2) to O(n).

Safe by construction: settled rows and any failure (no smd, no stable key,
parser error, text divergence) fall through to the existing renderMd path, so
worst case is identical to today. Cache is bounded (<=32 segments) and self-heals
on non-append text changes, mirroring _smdWrite.

Verified: node --check + ESLint runtime guard clean. Streaming render profiled
in-browser via a per-frame timing probe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:41:49 +02:00
Rod Boev
8c82ed8f11 fix(#5435): read voice silence timeout at use time 2026-07-03 08:57:44 -04:00
Rod Boev
7a21825722 fix(#5435): keep speech settings payload sparse 2026-07-03 00:22:39 -04:00
Rod Boev
904d741ed2 fix(#5367): suppress transparent stream live row animation 2026-07-02 23:31:57 -04:00
nesquena-hermes
0735a1b853 fix(#5439): hide read-only cron fork affordances
Clean rebase of rodboev's #5449 (rebase-first).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-03 00:58:30 +00:00
Rod Boev
3b344b1d9a fix(#5435): preserve local speech prefs until settings.json owns them 2026-07-02 19:33:13 -04:00
nesquena-hermes
a6504a8c58 fix(#5434): scope new Mermaid sizing to inline only — restore master lightbox behavior + inline Fit button (Codex gate findings)
Codex re-gate found 2 SILENT regressions the certifier missed:
- inline lost the 'Fit to screen' toolbar button (PR gated Fit to lightbox-only)
- the new readable-scale/envelope path leaked into lightbox mode (changed zoom-out floor + fit init for wide diagrams)
Fix: toolbar restored to master shape (Fit for all modes, Fullscreen inline-only);
_minScale() returns the flat _MERMAID_VIEWER_MIN_SCALE for lightbox; lightbox init
restored to master's _fitScale()/box*initialFit; the new readable-height sizing is
now scoped strictly to inline mode. Tests updated to the master-consistent contract.
2026-07-02 22:37:12 +00:00
nesquena-hermes
a4ad03bd68 fix(#5413): restore readable Mermaid sizing (rebased onto current master w/ #5437)
Clean rebase of rodboev's #5434 (rebase-first, re-based post-#5437).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-02 21:56:29 +00:00
nesquena-hermes
dc7e9e34d2 Merge #5441: cap generated update summary cache (#5435) [rodboev] 2026-07-02 21:14:03 +00:00
Rod Boev
ca31bf969b fix(#5435): keep saved default speech prefs authoritative 2026-07-02 16:49:45 -04:00
nesquena-hermes
e65013676a fix(#5435): cap generated update summary cache
Clean rebase of rodboev's #5441 (rebase-first).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-02 20:46:33 +00:00
nesquena-hermes
01487009d6 fix(#5408): add missing German providers_status_model locale key; correct CHANGELOG to note Test-connection reuses the authenticated onboarding probe (Codex gate findings) 2026-07-02 20:37:15 +00:00
nesquena-hermes
8689c40995 Merge #5408: self-hosted provider setup (Ollama/LM Studio) in Settings (#3260) [rodboev] 2026-07-02 20:20:12 +00:00
Rod Boev
7ee2757afe fix(#5435): preserve zero TTS pitch in settings 2026-07-02 16:09:15 -04:00
nesquena-hermes
517419f18a Merge #5404: hide inactive prompt flyouts from screen readers [sheldon-im] 2026-07-02 19:52:19 +00:00
Rod Boev
91ae1dede9 fix(#5435): persist TTS and voice preferences server-side 2026-07-02 15:43:24 -04:00
nesquena-hermes
2839c16ab7 Merge #5392: defer mobile overflow-anchor suppression across height-churn window (#4856) [allenliang2022] 2026-07-02 19:35:40 +00:00
nesquena-hermes
76f31a5478 Merge #5402: keep sidebar action menu open during chat scroll (#5347) [nankingjing] 2026-07-02 19:10:24 +00:00
nesquena-hermes
3763f60646 Merge #5400: preserve transparent stream live rows on rerender (#5367) [rodboev] 2026-07-02 18:44:30 +00:00
nesquena-hermes
176720b55a fix(profiles): guard non-string cached model in dropdown render + purge from cache predicate (Codex gate finding on #5412)
A poisoned cache row {name:'x', model:{}} passed the name-only cache predicate
but threw 'p.model.split is not a function' synchronously on profile-chip click,
bricking profile switching until localStorage was cleared. Guard both render
sites (typeof p.model==='string') and reject unsafe model types in
_profileDropdownDataCacheUsable; regression covers {model:{}} and {model:123}.
2026-07-02 18:27:27 +00:00
nesquena-hermes
4e74de45f8 fix(#5367): preserve transparent stream live rows on rerender (rehydrate controls)
Clean rebase of rodboev's #5400 (rebase-first).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-02 14:49:05 +00:00
nesquena-hermes
2032ac079e fix: speed up profile dropdown opening (preserve shared single-profile cache)
Clean rebase of ruizanthony's #5412 (rebase-first).

Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-07-02 13:38:43 +00:00
nesquena-hermes
791523dffa fix(ux): keep sidebar action menu open during chat scroll + close on anchor detach (#5347)
Clean rebase of nankingjing's #5402 (rebase-first).

Co-authored-by: nankingjing <nankingjing@users.noreply.github.com>
2026-07-02 10:09:55 +00:00
nesquena-hermes
7220747a1d feat(#3260): self-hosted provider setup (Ollama/LM Studio) in Settings (i18n complete)
Clean rebase of rodboev's #5408 (rebase-first).

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-02 08:17:44 +00:00
nesquena-hermes
012c66d12c Hide inactive flyouts from screen readers (+ visual-hide CSS)
Clean rebase of sheldon-im's #5404 (rebase-first).

Co-authored-by: sheldon-im <sheldon-im@users.noreply.github.com>
2026-07-02 07:15:53 +00:00
nesquena-hermes
b931ae2f9b fix(settings): reconcile #5145 rename+steer-flip onto master's #5170 mirror
Rebase PR #5162 (rename busy_input_mode -> default_message_mode; flip the
default from 'queue' to 'steer') onto current origin/master WITHOUT dropping
the shipped #5170 localStorage persistence mirror.

Rename the mirror machinery to the new setting name for consistency:
  _BUSY_INPUT_MODES        -> _DEFAULT_MESSAGE_MODES        (values unchanged)
  _normalizeBusyInputMode  -> _normalizeDefaultMessageMode  (fallback now 'steer')
  _persistBusyInputMode    -> _persistDefaultMessageMode
  _readPersistedBusyInputMode -> _readPersistedDefaultMessageMode
  window._busyInputMode    -> window._defaultMessageMode (+ renamed exports)

localStorage: write the new 'hermes-default-message-mode' key; read it with a
fallback to the legacy 'hermes-busy-input-mode' key so an existing user's
persisted preference survives the rename.

Preserve #5170 behavior at every mirror site under the new names:
  - boot success  -> window._defaultMessageMode=_persistDefaultMessageMode(...)
  - boot FAILURE  -> window._defaultMessageMode=_readPersistedDefaultMessageMode()
    (NOT a hardcoded 'steer' — a saved 'interrupt'/'queue' must still apply when
    the server is unreachable; do not regress #5167/#5132)
  - preferences autosave, settings-panel load, and _applySavedSettingsUi all
    persist through _persistDefaultMessageMode(...)

Tests updated for the rename while keeping the persistence-behavior assertions
(test_1062, test_5145, test_5167); test_5167 gains explicit guards that the
load-failure path reads the persisted pref and never hardcodes a literal mode,
plus autosave/panel-load mirror-write coverage.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-07-02 05:44:48 +00:00
nankingjing
cabbb9a841 fix(ux): add expand control for update summary panel (#4705)
Move scrolling to an inner container and add an Expand/Collapse toggle
so long generated summaries are readable on narrow viewports.

Fixes #4705
2026-07-02 05:14:00 +00:00
nesquena-hermes
3fde698232 fix(sessions): always retry sidebar session-list GET on 502/503/504 (#5394)
The sidebar session-list GET had 502/503/504 retry logic, but it was gated
to cold boot only. Once `_sessionListHasLoadedOnce` flipped true, every later
refresh (profile switch, focus/visible/reconnect) shipped no retryStatuses, so
a transient 502 during an nginx->backend restart window failed on the first
attempt and left the sidebar stale until a hard reload (Ctrl+F5).

The session-list GET is idempotent, so retrying it is safe unconditionally.
This moves `retries:1` + `retryStatuses:[502,503,504]` into the base request
options so they apply to every refresh, while keeping the larger boot timeout
(`_SESSION_LIST_BOOT_TIMEOUT_MS`) and `retryTimeouts` boot-only. The api()
wrapper in static/workspace.js already retries when the error status is in
retryStatuses, so no other change is needed.

Extends the existing source-string regression test to assert the retry
options are now always present (declared before the boot-only gate) while the
boot path still carries the timeout + timeout retry.

Reported and root-caused by @weidzhou, who traced the boot-only retry gate.

Co-authored-by: weidzhou <weidzhou@users.noreply.github.com>
2026-07-02 05:13:59 +00:00
nesquena-hermes
9fd349898f fix(chat): defer mobile overflow-anchor suppression (re-arm reachable + independent hard cap) #4856
Clean rebase of allenliang2022's #5392 (rebase-first).

Co-authored-by: allenliang2022 <allenliang2022@users.noreply.github.com>
2026-07-02 04:01:38 +00:00
nesquena-hermes
f50b1d84e1 feat(sessions): move Export-to-HTML into the sidebar conversation menu
Follow-up to v0.51.819 which removed the export button from the composer footer
(it tripped the footer overflow-collapse, hiding model/workspace labels). Per
design consult (Fable) + ChatGPT/Open-WebUI convention, export now lives in the
per-conversation sidebar three-dot action menu, right after Duplicate:
- exportSessionHTML(session) parameterized (was active-session-only); Settings
  button now wired ()=>exportSessionHTML() and still exports the active session
- new _appendSessionExportHtmlAction() added after Duplicate + in the read-only
  early-return branch (export is non-mutating; imported sessions re-exportable)
- exports THAT row's conversation, not just the active one
- download icon added to ICONS; session_export_html[_desc] added to 14 locales
- Settings HTML button retained as secondary data-management entry
2026-07-02 03:21:18 +00:00
nesquena-hermes
219615b5a7 fix(composer): remove Export-to-HTML button from composer footer (keep settings-panel export)
The #4968 export button was hard-inserted into the composer footer .composer-left
row, bypassing the configurable composer-control framework. On desktop it pushed the
footer over its overflow threshold, tripping _fitComposerFooter into cf-icons mode
which HIDES the model/workspace/profile text labels. Removing it restores label
visibility. Export stays available via the settings-panel HTML button (#btnExportHTML).
2026-07-02 02:48:45 +00:00
nesquena-hermes
b31f3fab3b #4968: export chat to self-contained themed HTML, rebased on master 2026-07-02 02:36:09 +00:00
nesquena-hermes
da03745b79 #5390: don't preserve dead empty live-turn shell across DOM wipe (blank assistant turn), rebased on master 2026-07-02 02:24:55 +00:00
nesquena-hermes
4bbb64bcc1 #5213: Claude Code sidebar visibility toggle (#4714), rebased on master 2026-07-02 01:40:19 +00:00
nesquena-hermes
76d6cc7f74 #5142: office-doc preview + safe docx editing (#540), rebased on master; optional deps 2026-07-02 01:15:57 +00:00
nesquena-hermes
728caf5ec6 #4682: surface read-only other-profile cron jobs in Tasks panel (#3947), rebased on master 2026-07-02 00:44:24 +00:00
nesquena-hermes
3d99763f3a #5228: opt-in extension loopback proxy (#4747), rebased on master; union urllib imports 2026-07-01 23:49:25 +00:00
nesquena-hermes
9fb2731848 #5310: push-to-talk hold gesture (#3700) 2026-07-01 23:26:15 +00:00
nesquena-hermes
ffad5e4acd stage #5374 2026-07-01 22:57:37 +00:00
Stacey2911
6044eaa0cc fix: respect auxiliary title timeout for manual regenerate 2026-07-02 08:48:47 +10:00