Commit Graph

6530 Commits

Author SHA1 Message Date
Rod Boev 2f815f040c fix(#5619): clear stale cfg overrides on profile switch 2026-07-05 16:33:01 -04:00
Rod Boev 4c3a4e011e fix(#5619): reload config cache on profile switch 2026-07-05 16:22:35 -04:00
nesquena-hermes 9b18ea9f99 Merge pull request #5643 from nesquena/release/stage-i18n2
Release: complete 664 UI translations across 8 locales (#5535)
v0.51.888
2026-07-05 10:48:54 -07:00
nesquena-hermes 665b7b47df release: complete 664 remaining UI translations across 8 locales (#5535) + CHANGELOG 2026-07-05 17:43:45 +00:00
nesquena-hermes 79e485a038 stage: merge #5535 (c1aa45fceb) 2026-07-05 17:43:11 +00:00
nesquena-hermes bb0d33d343 Merge pull request #5642 from nesquena/release/stage-mobile2
Release: fix live mobile scroll jump-back regression (#5638, #5635)
v0.51.887
2026-07-05 10:36:12 -07:00
nesquena-hermes adce95d0e3 release: fix live mobile scroll jump-back regression (#5637) — scope content-visibility to user rows + recover recycled virtual-scroll anchor (#5638, #5635) + CHANGELOG 2026-07-05 17:32:53 +00:00
nesquena-hermes 74d807a07c stage: merge #5638 (f657014aa3) 2026-07-05 17:32:22 +00:00
nesquena-hermes c0b0321081 stage: merge #5635 (7811b13f49) 2026-07-05 17:32:22 +00:00
nesquena-hermes 866a25b253 Merge pull request #5640 from nesquena/release/stage-lr1
Release: low-risk docs+tests batch (#5536, #5549, #5537, #5589)
v0.51.886
2026-07-05 10:24:13 -07:00
nesquena-hermes c6d957904a release: low-risk docs+tests batch — env var docs (#5536), gateway approval docs (#5549), utf-8 test reads (#5537), messaging-clear watermark test (#5589) + CHANGELOG 2026-07-05 17:19:27 +00:00
nesquena-hermes 2fe1976eac stage: merge #5589 (cc7896cfab) 2026-07-05 17:10:06 +00:00
nesquena-hermes dc4c31ee1b stage: merge #5549 (0ec29b14ec) 2026-07-05 17:10:06 +00:00
nesquena-hermes 278e506468 stage: merge #5537 (0fd954fe78) 2026-07-05 17:10:06 +00:00
nesquena-hermes 806c15b417 stage: merge #5536 (4bfc9c8aad) 2026-07-05 17:10:05 +00:00
allenliang2022 f657014aa3 fix(webui): stop mobile scroll jump-back — scope content-visibility + hold unpinned position (#5637)
Two linked causes of the mobile scroll jump-back, both confirmed by the
maintainer against origin/master in #5637.

Primary (CSS): the @media (pointer: coarse) block applied a flat
`content-visibility: auto; contain-intrinsic-size: auto 1px` to every .msg-row.
Off-screen assistant rows can be multi-thousand px (tool-result / long answers),
so a 1px reservation collapses their off-screen height; scrollHeight lurches down
by tens of thousands of px on a long transcript, the browser force-clamps
scrollTop, and the viewport is thrown to the top. A flat intrinsic-size cannot
both reserve tall rows and keep scrollHeight stable for iOS flick momentum (why
1px was chosen), so content-visibility is scoped to the short, size-predictable
user rows; assistant rows keep real rendered height. Mirrors the existing
#liveAssistantTurn opt-out pattern.

Secondary (JS): _restoreMessageScrollSnapshotSameFrame fell back to an absolute
snapshot.top when the semantic anchor restore failed. During streaming the live
activity-scene refresh fires this every tick; for a reader scrolled up into
history (unpinned), snapping to a stale absolute top nudges the viewport backward
by an amount that grows with scrollHeight. Now: hold position (no scrollTop write)
for the unpinned/anchor-failed case and let the browser's own scroll anchoring
keep the reader put. Pinned/near-bottom readers keep the tail-relative restore.

Adds tests/test_issue5637_mobile_scroll_clamp_jumpback.py (mutation-checked:
reverting either fix fails the matching assertion).
2026-07-05 22:52:59 +08:00
allenliang2022 7811b13f49 test(webui): harden extractFunc to skip braces in string/regex/comment literals
Address greptile P2 on the vscroll recycled-anchor test harness: the
brace-counting function extractor desynced on a bare { or } inside a string,
template, regex, or comment within the extracted function body, silently
truncating the extract. Skip those spans so brace matching runs on real code
structure only, and add a regression test with a function whose string/regex/
comment literals contain unbalanced-looking braces.

Verified: the naive extractor fails all four tests (the compensation function
now contains querySelector attribute-selector strings that desync it); the
hardened extractor passes.
2026-07-05 20:14:38 +08:00
allenliang2022 ad1843f31b fix(webui): recover virtual-scroll compensation when the anchor row is recycled
_compensateScrollForMeasurementDelta re-found its viewport anchor only by
rawIdx and bailed with a bare 'if(!row) return' when that row was recycled
out of the render window. On long sessions containing multi-thousand-px
turns, a large scroll delta recycles the anchor row, so the estimated->measured
topPad swap (a scrollHeight change of tens of thousands of px) hit scrollTop
uncompensated and threw the viewport to the top -- a mobile scroll jump-back
that did not reproduce on desktop (where the anchor row stays rendered).

Fall back when the rawIdx row is gone: (1) re-find the same row by its stable
data-session-msg-idx and compensate by its measured offset delta; (2) if that
row is also unrendered, compensate by the top-spacer (topPad) height delta
captured before the re-render. _captureMessageViewportAnchor now records
topPadBefore so the fallback has a reference.

Adds tests/test_issue4346_vscroll_recycled_anchor_jumpback.py (Node-harness
behavioral tests). All fail on the pre-fix bare-return and pass on the fix;
existing vscroll suites stay green.

Relates to #4346.
2026-07-05 19:53:47 +08:00
nesquena-hermes 8dc47b83c0 Merge pull request #5629 from nesquena/release/stage-5626
release #5626: ignore inactive compacted state-db rows in context replay
v0.51.885
2026-07-05 02:18:28 -07:00
nesquena-hermes 8230785097 release #5626: exclude inactive compacted state-db rows from context replay + CHANGELOG 2026-07-05 09:14:40 +00:00
nesquena-hermes 1165bd9ca2 fix(#5626): ignore inactive compacted state-db rows in context replay
get_state_db_session_messages() returned archived (active=0) rows that in-place
compaction had marked inactive. WebUI reconciliation feeds this reader straight
into the next model-facing context (reconciled_state_db_messages_for_session,
prefer_context=True), so those archived rows got pulled back in — resurrecting
pre-compaction history and making every later turn re-trigger compression. Now,
WHEN the messages table exposes an `active` column, inactive rows are excluded by
default (`AND (active IS NULL OR active != 0)` — NULL/legacy rows preserved), with
an `include_inactive=True` escape hatch for explicit recovery/audit callers.
Schemas without the column are unaffected (guarded by `'active' in available`).

Contributor stage; ungated on arrival. Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-05 08:59:21 +00:00
nesquena-hermes 35464be634 Merge pull request #5627 from nesquena/fix/5588-auth-cache-isolation
fix(#5588): reset memoized password-hash cache around every test (isolation flake)
v0.51.884
2026-07-05 01:53:12 -07:00
nesquena-hermes 3051dc4dda release #5588: reset memoized password-hash cache around every test (isolation flake) + CHANGELOG 2026-07-05 08:44:41 +00:00
nesquena-hermes 4c9690bf59 fix(#5588): reset memoized password-hash cache around every test (isolation flake)
tests/test_issue803.py::TestProfileCookieHelpers failed when run after
tests/test_session_static_assets.py (passed in isolation). Root cause:
api.auth.get_password_hash() memoizes the resolved hash process-wide
(_AUTH_HASH_CACHE / _AUTH_HASH_COMPUTED) and is NOT keyed on the
HERMES_WEBUI_PASSWORD env var. test_session_static_auth_exemption sets that env
var + populates the cache; monkeypatch pops the env on teardown but the cache
stays populated, so is_auth_enabled() reads stale True and build_profile_cookie
raises "requires a request handler when auth is enabled".

Fix: an autouse conftest fixture (_reset_password_hash_cache) that invalidates the
cache before AND after every test — matching the existing _isolate_hermes_config_path
/ _restore_profile_home_globals autouse-reset convention. Fixes the whole class
regardless of which test sets the password; no-op when auth is off. Adds a
direct regression test (cache a password, pop env, assert auth reads disabled +
build_profile_cookie doesn't raise). Paired-run repro now passes.
2026-07-05 08:35:14 +00:00
nesquena-hermes c922f179f7 Merge pull request #5625 from nesquena/release/stage-5582
release #5582: keep fork children out of compression stitch
v0.51.883
2026-07-05 01:20:03 -07:00
nesquena-hermes 4f0b6576b5 release #5582: keep fork children out of compression stitch + CHANGELOG 2026-07-05 08:16:30 +00:00
nesquena-hermes dbc2345fea fix(#5571): keep fork children out of compression stitch
_webui_sidecar_lineage_messages_for_display stitches a session's pre-compression
snapshot parents for full-transcript display. A cleared fork child could resurrect
its ORIGINAL (non-fork) parent's transcript when that parent was later compressed.
Now, when the root session is itself a fork (session_source=='fork'), the stitch
walk allows fork-sourced snapshot parents but stops at the first non-fork
ancestor — so a compressed fork continuation still shows its own pre-compression
turns, while an ordinary fork child stays isolated from the original parent.
Additive guard (one extra break condition); non-fork lineage display unchanged.

Contributor stage; gate-pass. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 08:07:38 +00:00
nesquena-hermes f1f4514799 Merge pull request #5623 from nesquena/release/stage-5459
release #5459: include attachments in steer submissions
v0.51.882
2026-07-05 00:24:21 -07:00
nesquena-hermes 1e4d237cc2 release #5459: include attachments in steer submissions (+ retry-dedup + identity chip clear) + CHANGELOG 2026-07-05 07:20:24 +00:00
nesquena-hermes c78d5083ad fix(#5459): dedup steer upload on retry + remove only delivered files by identity (gate)
Codex gate found two SILENT defects, fixed:
1) Failed-steer RETRY re-uploaded the same File objects (uploadPendingFiles ran
   again with clearPending:false). Added _steerUploadCache keyed by ownerSid +
   file signature so a retry with the same staged set reuses the uploaded paths;
   invalidated when the staged set changes (signature miss) or on accepted steer.
2) Accepted steer cleared ALL S.pendingFiles, dropping files staged during the
   upload/API await. Now removes ONLY the delivered snapshot by object identity,
   preserving newly staged files.

(The owner-session draft is still cleared on accepted steer — clearing the
delivered session's draft is correct even after a mid-upload switch; the existing
test asserts this.) Tests updated for the identity-based removal + new
cache/dedup guard. 69 steer/composer tests pass, node --check clean.
2026-07-05 07:10:23 +00:00
nesquena-hermes ceea2438eb fix: include attachments in steer submissions
Steering (submitting mid-stream) dropped any pending composer attachments. Now the
pending files are uploaded before the /steer payload is sent, the uploaded file
paths are appended to the steer text so the active agent can inspect them with
tools, file chips stay staged if the steer fails (so nothing is lost), and
file-only busy submissions are handled.

Contributor stage; gate-pass. Co-authored-by: ruizanthony <ruizanthony@users.noreply.github.com>
2026-07-05 06:50:34 +00:00
nesquena-hermes abdf6c7ae3 Merge pull request #5620 from nesquena/fix/mobile-sidebar-header-icon-align
fix(ui): align mobile sidebar close/new-conversation header icons
v0.51.881
2026-07-04 23:49:05 -07:00
nesquena-hermes 7ad9707c4b fix(ui): align mobile sidebar close (X) with the new-conversation (+) button
On the mobile sidebar header the close (X) button rendered an 18px glyph — the
only 18px .panel-head-btn icon — sitting ~6px lower than the adjacent 14px
new-conversation (+) button, an inconsistent pair. Match the X glyph to 14px and
vertically center it on the ~41px panel-head row so both icons share one size and
baseline (top:calc(safe-top - 2px) keeps the existing safe-area offset so the X
still clears the notch, with no extra drawer spacing). 44x44 tap target kept for
mobile touch; mobile @media(max-width:640px) only, and the X is display:none on
desktop, so desktop is unaffected. + CHANGELOG + source-guard tests.
2026-07-05 06:40:26 +00:00
nesquena-hermes 58e857ec95 Merge pull request #5617 from nesquena/release/stage-5602
release #5602: iOS PWA session-switch instant sidebar close + race guard
v0.51.880
2026-07-04 22:46:39 -07:00
nesquena-hermes 9d85c23c5f release #5602: iOS PWA session-switch instant sidebar close + race guard + CHANGELOG 2026-07-05 05:43:45 +00:00
nesquena-hermes 4559ca85f0 fix(#5409): iOS PWA session-switch — close sidebar instantly + prevent _loadingSessionId race
Two iOS-PWA bugs when tapping an old/large session:
1) closeMobileSidebar() ran AFTER the slow `await _openSidebarSession()` (3-15s
   for large sessions on WKWebView), so the sidebar stayed open with only a tiny
   spinner. Moved closeMobileSidebar() to run synchronously BEFORE the await in
   the tap handler + lineage/child open paths for instant feedback.
2) _loadingSessionId race: an SSE-triggered idle reload
   (_scheduleActiveSessionIdleReload / refreshActiveSessionIfExternallyUpdated,
   fed by idle-reconcile/poll/visibility/focus) could overwrite _loadingSessionId
   and silently cancel an in-flight session switch. Both paths now skip while a
   different session's loadSession() is in flight.

Contributor stage; gate-pass. Co-authored-by: luperrypf <luperrypf@users.noreply.github.com>
2026-07-05 05:42:51 +00:00
nesquena-hermes b0340a7b3e Merge pull request #5616 from nesquena/release/stage-5611
release #5611: opencode-go uses curated static model list (no probe 404s)
v0.51.879
2026-07-04 22:18:12 -07:00
nesquena-hermes f864c276ff release #5611: opencode-go uses curated static model list (no probe 404s) + CHANGELOG 2026-07-05 05:14:44 +00:00
nesquena-hermes 14ad45bd1f fix(#5311): skip live /v1/models probe for opencode-go — use curated static list
OpenCode Go's /v1/models endpoint returns models from the full public catalog that
are NOT enabled on the Go tier; selecting any such probe-only model 404s
('model not found') when the chat request is sent. get_available_models now skips
the live probe for opencode-go (bare branch) so the next `if not raw_models` falls
through to the curated static _PROVIDER_MODELS['opencode-go'] list — the actual
Go-tier models. Narrow, provider-scoped; no other provider affected.

Contributor stage; gate-pass. Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-05 05:05:30 +00:00
nesquena-hermes 0bbf7c3d37 Merge pull request #5615 from nesquena/release/stage-5505
release #5505: improve session-list retry feedback
v0.51.878
2026-07-04 22:01:28 -07:00
nesquena-hermes fbd3c8c26b release #5505: session-list retry feedback + a11y + stuck-retry guard + CHANGELOG 2026-07-05 04:58:04 +00:00
nesquena-hermes f6cc48b834 fix(#5501): clear pending retry state on render invalidation (Codex gate)
Codex re-gate SILENT: a retry whose fetch is invalidated mid-flight (e.g. a
profile switch) left the button stuck as an inert 'Retrying…' with no request in
flight — the stale catch returns before _showSessionListLoadError and the
.finally() bails when the skeleton removed the old button. _invalidateSessionListRenders()
now clears the pending retry markers (retrying, _retryFailedFocus) so the next
repaint shows an actionable idle Retry. Regression test added (fails without the
fix); test harness extracts _invalidateSessionListRenders + declares its render-gen
globals.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 04:48:50 +00:00
nesquena-hermes 9be4048c72 fix(#5501): fold in Fable a11y hardening — live region, aria-disabled, focus restore, ellipsis
Fable UX gate SHIP-WITH-UX-FIXES (non-blocking). Applied all: (1) role=status +
aria-live=polite on the error note so retry failure/restore is announced to
screen readers; (2) pending state uses aria-disabled (not the disabled property)
so the button retains keyboard focus, with click/keydown guards keeping it inert;
(3) on a failed retry, the fresh rebuilt Retry button reclaims keyboard focus
(_retryFailedFocus flag, rAF-guarded) so keyboard users aren't dropped to <body>;
(4) label uses a true ellipsis 'Retrying…' matching the app's other progress
string. CSS styles [aria-disabled=true] alongside [aria-busy]. Tests updated +
new a11y assertions; 8/8 pass, node --check clean.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 04:39:14 +00:00
nesquena-hermes 7349a9434f fix(#5501): improve session-list retry feedback
The sidebar's load-error Retry button behaved like a raw idle browser button while
the next slow fetch was pending, so it looked broken/unresponsive. Retry now paints
an immediate pending state (label 'Retrying...', disabled, aria-busy), keeps the
retry state local to the error block via _renderSessionListLoadErrorNote(), and
lets the existing success/failure paths replace the error object when the request
settles (restoring 'Retry' on failure). Also adds proper styling.

Contributor stage; gate-pass. Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-05 04:39:14 +00:00
nesquena-hermes bbcd6d19cd Merge pull request #5613 from nesquena/release/stage-5539
release #5539: increase approval card height cap on mobile
v0.51.877
2026-07-04 21:25:57 -07:00
nesquena-hermes 2346f4b7f8 release #5539: raise mobile approval-card height cap + CHANGELOG 2026-07-05 04:18:09 +00:00
nesquena-hermes 39a4d67e5c fix(#5385): increase approval card height cap on mobile
The mobile command/tool approval popup rendered too small — options didn't all fit
and required scrolling within the cramped popup to select (Android WebUI wrapper).
Raises the mobile @supports(dvh) .approval-inner max-height cap from
min(52dvh,360px) to min(60dvh,420px) so the options fit without in-popup scrolling;
still overflow-y:auto so very tall content scrolls rather than clipping. Scoped to
the mobile dvh rule; desktop unaffected. Adds a source-level guard test.

Contributor stage; gate-pass. Co-authored-by: nankingjing <nankingjing@users.noreply.github.com>
2026-07-05 04:04:02 +00:00
nesquena-hermes 035e84ae00 Merge pull request #5612 from nesquena/release/stage-5534
release #5534: CORS preflight echoes only same-origin or allowlisted origins
v0.51.876
2026-07-04 19:48:27 -07:00
nesquena-hermes c21a7629f4 release #5534: CORS preflight echoes only same-origin/allowlisted origins + CHANGELOG 2026-07-05 02:45:07 +00:00
nesquena-hermes 8da96a84d3 refactor(#5534): move CORS preflight header logic into api/routes (keep server.py thin)
The initial fix added ~16 lines to server.py, tripping test_server_py_under_750_lines
(server.py must stay a thin dispatcher; logic belongs in api/). Moved the whole
preflight-response into apply_cors_preflight_headers(handler) in api/routes.py;
server.py's do_OPTIONS is now a 4-line dispatcher. server.py 764 -> 746 lines.
Same security behavior (same-origin/allowlist echo, Vary: Origin, no wildcard,
header-less denial); test rewritten to the header-emitting API + added Vary and
no-header-on-denial assertions and a routes.py wildcard regression guard.
2026-07-05 02:35:10 +00:00