11 Commits
Author SHA1 Message Date
allenliang2022 d31a345b17 fix(webui): show new-message cue after unpinned mid-stream scroll restore
Address greptile review: the new S.activeStreamId restore branch omitted the
_maybeShowNewMessageScrollCue call that the preserveScroll and idle-unpin restore
branches both make. An unpinned reader whose position is restored mid-stream should
still see the new-message cue, so add it for consistency. Update the behavioral test
to assert the cue fires and the test_issue1690 source-lock for the new branch body.
2026-07-06 20:42:58 +08:00
allenliang2022 f7dd4f41ba fix(webui): restore unpinned reader after mid-stream wipe-and-rebuild scroll clamp
renderMessages() rebuilds the transcript via inner.innerHTML='' then re-appends
rows. That wipe collapses #msgInner scrollHeight toward the empty-table height, so
the browser is forced to clamp #messages.scrollTop down to the new near-zero max --
a browser primitive (no JS writes scrollTop; overflow-anchor does not govern it).

When this rebuild happens mid-stream while a reader has scrolled up into history
(_messageUserUnpinned), _scrollAfterMessageRender took the S.activeStreamId branch
and called scrollIfPinned(), which is a no-op for an unpinned reader -- so the
clamped-to-0 scrollTop was never restored and the reader was stranded at the top.

Fix: in that branch, when the reader is unpinned and a pre-wipe scroll snapshot
exists (renderMessages already captures one whenever _messageUserUnpinned), restore
the snapshot instead of the no-op. Pinned / tail-following readers keep
scrollIfPinned() so live-follow is unchanged. Reuses the existing
_restoreMessageScrollSnapshot helper (same one the preserveScroll and idle-unpin
branches use), so no new core-timing risk.

Tests: test_wipe_rows0_streaming_unpinned_restore.py executes the real extracted
_scrollAfterMessageRender against a fake #messages + instrumented restore stubs and
asserts the unpinned+activeStreamId+snapshot case restores (scrollTop returns to the
pre-clamp value) while pinned / no-snapshot cases fall back to scrollIfPinned();
mutation-checked (reverting to the bare scrollIfPinned() fails the behavioral asserts).
Updates the test_issue1690 source-lock for the new branch structure.
2026-07-06 20:32:52 +08:00
3edbec7c97 stage #4613 (dso2ng): preserve reader scroll position across re-render/reload (#4675)
Sticky-unpin across idle renderMessages(), 3-tier anchor restore (data-message-anchor-key
-> data-session-msg-idx -> data-msg-idx) for tail-window->full-session reloads, deferred
external refresh while unpinned (flushed on explicit scrollToBottom), iOS/touch momentum
protection. CONFLICT RESOLVED with tonight's shipped #4584: kept #4584's deltaY<-30
jitter-resistant unpin threshold AND #4613's _markMessageTouchScrollIntent(true) on touchmove
(both present, 34 scroll-family tests pass). Authoritative head 03343e04b3f2.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: dso2ng <dso2ng@users.noreply.github.com>
2026-06-21 23:57:24 -07:00
nesquena-hermes 8db88c882d fix(#4123): preserve reader position on PWA same-session refresh + re-anchor 2 scroll tests
#4124: treat preserved snapshot bottom>250px as active reader (restore before follow-to-bottom),
re-mark restored mid-answer as unpinned, scope _resetScrollDirectionTracker to real session
switches only. Re-anchored test_preserve_scroll_restores_unpinned_viewport + test_render_messages_
preserve_scroll to the new readerAwayFromBottom-guarded early-return (was pinning the old exact line).
CHANGELOG stamped v0.51.390 (NC).
2026-06-13 17:53:53 +00:00
nesquena-hermes 9f6ed5a61b test(#4006): re-anchor scroll tests to the explicit-settle signature + new snapshot-capture condition
scrollToBottom now calls _settleMessageScrollToBottom(false, true); renderMessages
captures scrollSnapshot when (preserveScroll || (!_autoScrollFollow && _messageUserUnpinned)).
Updated the 4 change-detector assertions in test_tars_scroll_reset_regressions.py +
test_issue1690_scroll_completion.py to the new strings; behavioral intent unchanged.
2026-06-13 04:20:25 +00:00
Ming e05b99b7fd fix(scroll): guard _scrollAfterMessageRender and _finishDone against pin hysteresis snap-back
Two scroll-state bugs cause the viewport to snap to the bottom after
streaming completes, even when the user has scrolled up to read:

1. _scrollAfterMessageRender (ui.js): when preserveScroll=true and
   _scrollPinned=true (re-set by the 250px near-bottom hysteresis),
   _followMessagesAfterDomReplace() calls scrollToBottom(), overriding
   the user's position. Added !_messageUserUnpinned guard so explicitly
   scrolled-up users get their snapshot restored instead.

2. _finishDone (messages.js): the explicit scrollToBottom() at L3302
   fires when shouldFollowOnDone is true, but that flag only checks a
   120px near-bottom threshold. Added _isMessagePaneNearBottom(250)
   gate so users reading 250px+ above bottom keep their position.

Also adds a new 'auto_scroll_follow' setting (default: off) that gates
scrollIfPinned() and _shouldFollowMessagesOnDomReplace(). When disabled,
the viewport never auto-scrolls during streaming — the user controls
scroll position manually and uses the ↓ button to jump to bottom.

7-file setting-add pattern applied:
- api/config.py: _SETTINGS_DEFAULTS + _SETTINGS_BOOL_KEYS
- static/boot.js: both init paths
- static/ui.js: scrollIfPinned() + _shouldFollowMessagesOnDomReplace()
- static/index.html: checkbox in appearance settings
- static/i18n.js: en + zh-CN translations
- static/panels.js: payload, autosave, populate, full save
- messages.js: no additional changes (gated via _shouldFollowMessagesOnDomReplace)

Tests updated: test_tars_scroll_reset_regressions, test_issue1690,
test_issue3545. All 116 scroll-related tests pass.
2026-06-11 21:48:14 +02:00
a71dbcd6bb Release v0.51.334 — Release KX (new-message cue when scrolled up, #3631) (#3849)
* fix(streaming): show new-message cue when preserving scroll position (#3545)

# Conflicts:
#	static/ui.js
#	tests/test_issue1690_scroll_completion.py
#	tests/test_tars_scroll_reset_regressions.py

* i18n: add missing Polish (pl) translation for session_new_message keys

The PR added session_new_message / session_new_message_label to 12 of 13
locales; Polish was missing both, which fails the per-locale parity test.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

* fix(streaming): keep forced follow path for pinned users in preserve-scroll branch

Codex CORE catch: the PR's preserve-scroll branch used
'if(_scrollPinned) scrollIfPinned()' which skips the synchronous bottom
write unless distance>500 and can have its delayed settles cancelled by the
DOM-rebuild scroll event — leaving a pinned reader a few lines above the
settled final response. Restore master's _followMessagesAfterDomReplace()
forced-scrollToBottom() path for pinned/near-bottom users; only genuinely
scrolled-up (unpinned, not near bottom) users restore their viewport and
get the new-message cue. Updated the 3 structure-pinning tests to assert the
corrected (safer) shape while preserving their behavioral intent.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

* Release v0.51.334 — Release KX (new-message cue when scrolled up, #3631)

New-message cue on the jump-to-bottom button when the user has scrolled up
during a live turn (#3545/#3631, @rodboev). Deep-reviewed (Opus+Codex);
maintainer fixes during re-gate: (1) restored master's forced follow path
for pinned/near-bottom users (Codex CORE: scrollIfPinned could leave a pinned
reader short of the settled response) + updated 3 structure-pinning tests to
the corrected shape; (2) added missing Polish (pl) i18n keys (PR had 12/13).
Full suite 8308, ESLint/scope-undef CLEAN, Opus SHIP-safe, Codex SAFE-TO-SHIP.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: Hermes Agent <hermes-agent@nesquena-hermes.local>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-08 15:58:13 -07:00
e3a7c93dc6 [HELD — independent review pending] Release v0.51.294 — stage-3401 (live-to-final redesign #3401 + 4 deep-review fixes) (#3741)
* Harden interrupted recovery control filtering

* Redesign live-to-final assistant replies

* Fix live activity anchor test fixture

* Fix CI lint issues for live reply tests

* Strengthen live progress prompt contract

* Recover PR #3401 refresh on origin/master

* Repair live-to-final refresh regressions

* Fix live worklog refresh regressions

* Show live footer timer on initial stream start

* Restore live stream shell after reload

* Preserve per-frame live SSE replay cursors

* Preserve reasoning as Worklog Thinking cards

* Quiet Worklog Thinking card styling

* Align Worklog Thinking card styling

* Scope live Worklog Thinking cards by segment

* Suppress exact duplicate settled Thinking

* Close #3401 merge review test gaps

* fix(#3401): resolve 4 deep-review regressions (inline-think, reconnect-dup, neon skin, busy-gate worklog)

Deep review (Codex diff-vs-master + live-browser drive) of the live-to-final refactor
surfaced 4 regressions vs master that the rewritten suite no longer guarded:

1. Inline <think>…</think>answer reasoning vanished — _assistantReasoningPayloadText
   used $-anchored regexes so a leading think block + visible answer extracted nothing
   and the Thinking card never rendered. Removed the 3 $ anchors to match the
   (non-anchored) display stripper. Live: inline-think thinking-only turn now renders.
2. (CORE) reconnect/reload duplicated the live reply — _rememberRunJournalCursor advanced
   a closure-local seq but never wrote INFLIGHT[activeSid].lastRunJournalSeq, so a reload
   replayed the journal from after_seq=0 over restored lastAssistantText. Now mirrors the
   cursor onto INFLIGHT + schedules a throttled persist.
3. Neon skin silently broke — PR deleted the :root[data-skin="neon"] CSS but left Neon in
   the picker. Restored the neon CSS block from master.
4. Settled tool-worklog rebuild gated purely on !S.busy — dropped every prior settled
   turn's worklog when renderMessages re-ran during an active stream (switch-back to an
   in-progress session). Restored master's !S.busy || (S.toolCalls && S.toolCalls.length).
   Live: busy re-render now preserves tool cards (4→4, was 4→0).

Live-verified all 4 + confirmed #3709/#3592 invariants still hold (1 thinking card, none
below footer; distinct siblings preserved). + tests/test_issue3401_deep_review_fixes.py (7).

* test(#3401): realign 3 stale source-shape assertions to the deep-review fixes

Fix commit changed two source literals that existing stage tests scanned for:
- test_live_activity_timeline.py (x2): split anchor 'if(!S.busy){' → the restored
  'if(!S.busy || (S.toolCalls&&S.toolCalls.length)){' guard (fix 4).
- test_run_journal_frontend_static.py: 'after_seq=0' not in source — fix 2's comment
  contained that literal; rephrased the comment to 'the zero floor (after_seq of 0)'.
Intent of all three assertions unchanged; only the matched string updated. No code
behavior change.

* docs(changelog): v0.51.294 — Release JJ (stage-3401, #3401 live-to-final redesign)

---------

Co-authored-by: Frank Song <franksong2702@gmail.com>
Co-authored-by: Nathan-Hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 12:12:37 -07:00
nesquena-hermes 3603027b74 test: update loadSession source-assertion tests for #3326 conditional renderMessages arg
#3326 changed loadSession's INFLIGHT + idle render calls from bare
renderMessages() to renderMessages(sameSessionForceReload?{preserveScroll:true}:undefined),
and added reload-width-hint handling inside _ensureMessagesLoaded. Updated 5
source-pinning tests to match the new (behaviorally-equivalent) call form:
- test_regressions / test_issue2341: match 'renderMessages(' call form
- test_parallel_session_switch: match call form + widen 600->850 block window
- test_issue1690: assert the CONDITIONAL preserveScroll (cross-session still bottom-pins)
- test_issue3162: widen _ensureMessagesLoaded slice 2000->2600
2026-06-02 19:11:15 +00:00
ai-ag2026 1559c70a41 fix: preserve chat scroll across final render 2026-05-09 02:15:35 +02:00
Michael Lam 311e69b0ba fix: preserve scroll on stream completion 2026-05-05 09:23:29 -07:00