Release v0.51.334 — Release KX (new-message cue when scrolled up, #3631)#3849
Conversation
…3545) # Conflicts: # static/ui.js # tests/test_issue1690_scroll_completion.py # tests/test_tars_scroll_reset_regressions.py
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>
…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>
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>
|
| Filename | Overview |
|---|---|
| static/ui.js | Core scroll-state machine change: adds _newMessageCueVisible flag, _syncScrollToBottomCue/show/clear helpers, and _maybeShowNewMessageScrollCue decision function; hooks them into renderMessages preserve-scroll path, scrollToBottom, and both reset functions. Codex regression (scrollIfPinned vs forced follow) is correctly fixed. |
| static/i18n.js | Adds session_new_message and session_new_message_label keys to all 13 locales; Polish (pl) added by maintainer to complete the set. Translations look accurate. |
| static/style.css | Adds pill styling for the --new-message button variant and makes .session-jump-btn__text visible inside it; the .session-jump-btn__text{display:none} rule that follows is intentionally unrelated to the new modifier class. |
| tests/test_issue3545_streaming_scroll_cue.py | New test file covering cue show/clear lifecycle, growth threshold logic, i18n key presence (asserts >=8 locales when 13 are present — weak lower bound but green), and CSS pill-style existence. |
| tests/test_issue1690_scroll_completion.py | Updated assertion now matches the exact multi-line code shape of the preserve-scroll branch including the comment text; structurally correct but brittle against whitespace/comment rewording. |
| tests/test_issue677.py | Widens scroll-listener search window from 1200 to 1400 chars and updates assertion from raw btn.style.display to _syncScrollToBottomCue call site; correct. |
| tests/test_session_jump_buttons.py | Single assertion updated to match the new _syncScrollToBottomCue call; no behavioral change to what is tested. |
| tests/test_tars_scroll_reset_regressions.py | Updated to assert that _maybeShowNewMessageScrollCue immediately follows _restoreMessageScrollSnapshot on the same source line (literal newline match); correct but brittle. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[renderMessages called\npreserveScroll=true] --> B[_captureMessageScrollSnapshot\nrecords scrollTop + scrollHeight]
B --> C[DOM rebuilt]
C --> D[_scrollAfterMessageRender]
D --> E{_followMessagesAfterDomReplace?\npinned / near-bottom}
E -- yes --> F[scrollToBottom forced\n_clearNewMessageScrollCue]
E -- no scrolled-up user --> G[_restoreMessageScrollSnapshot\nrestores old scrollTop]
G --> H[_maybeShowNewMessageScrollCue]
H --> I{scrollHeight grew\n>24px AND distance>80?}
I -- yes --> J[_showNewMessageScrollCue\nbtn shows New message pill]
I -- no --> K[_syncScrollToBottomCue\npreserve current cue state]
L[User scrolls naturally] --> M{nearBottom?\ndistance<250}
M -- yes --> N[_clearNewMessageScrollCue\nbtn reverts to normal / hides]
M -- no --> O[_syncScrollToBottomCue\nmaintain current cue state]
P[scrollToBottom called\nor session/stream reset] --> Q[_clearNewMessageScrollCue\n_newMessageCueVisible=false]
Reviews (1): Last reviewed commit: "Release v0.51.334 — Release KX (new-mess..." | Re-trigger Greptile
| assert "_scrollAfterMessageRender(preserveScroll, scrollSnapshot);" in render_body | ||
| assert "const scrollSnapshot=preserveScroll?_captureMessageScrollSnapshot():null" in render_body |
There was a problem hiding this comment.
Brittle literal-whitespace assertions on comment text
Both new assertions match exact whitespace and the inline comment "// Keep master's follow heuristic". If the block is ever reformatted or the comment is updated (e.g. issue number changes), this test will fail despite the code being functionally correct. The behavioral invariant being tested — that _followMessagesAfterDomReplace() comes before _restoreMessageScrollSnapshot and _maybeShowNewMessageScrollCue — could be checked with three independent in checks (already done in test_issue3545_streaming_scroll_cue.py) rather than a single multi-line string that encodes the exact comment wording.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
|
||
| def test_new_message_cue_has_stable_pill_styling(): | ||
| assert ".scroll-to-bottom-btn.scroll-to-bottom-btn--new-message" in STYLE_CSS |
There was a problem hiding this comment.
i18n locale count assertion is a weak lower bound
I18N_JS.count("session_new_message:") >= 8 passes even if only 8 of the 13 locales are present. The PR explicitly fixed a missing Polish locale to reach 13/13, so it's worth locking in that count — == 13 would catch any future locale being accidentally dropped.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| assert I18N_JS.count("session_new_message:") >= 8 | ||
| assert I18N_JS.count("session_new_message_label:") >= 8 |
There was a problem hiding this comment.
Pinning the count to
== 13 matches the 13-locale parity the maintainer explicitly achieved and ensures future locale additions don't silently regress.
| assert I18N_JS.count("session_new_message:") >= 8 | |
| assert I18N_JS.count("session_new_message_label:") >= 8 | |
| assert I18N_JS.count("session_new_message:") == 13 | |
| assert I18N_JS.count("session_new_message_label:") == 13 |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Release v0.51.334 — Release KX (new-message cue when scrolled up)
Absorbs #3631 (@rodboev) — closes #3545. Deep-reviewed (Opus + Codex), rebased, fixed, and shipped.
What ships
If you scroll up to read while a turn is still arriving, a new message previously either landed silently off-screen or yanked you to the bottom. Now the jump-to-bottom button shows a "New message" cue you can click to catch up. Pinned / at-bottom readers still auto-follow to the latest response exactly as before.
Deep review (Opus + Codex + independent trace)
if(_scrollPinned) scrollIfPinned(), butscrollIfPinned()skips the synchronous bottom write unlessdistance > 500and its delayed settles can be cancelled by the DOM-rebuild scroll event — so a pinned reader could be left a few lines above the settled final response.if(_followMessagesAfterDomReplace()) return;(which does a forcedscrollToBottom()) 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 shape while preserving their behavioral intent._captureMessageScrollSnapshot()producer, which now always includesscrollHeight; the only other path passesnull, handled by an early-return). Cue-state (_newMessageCueVisible) is reset on every boundary — session switch, stream start,scrollToBottom(), and natural scroll-near-bottom.session_new_message*keys to 12 of 13 locales — Polish (pl) was missing both. Added the Polish translations → all 13/13, locale-parity test green.Greptile flags (all adjudicated)
_syncScrollToBottomCuedispatch on near-bottom tick" → FOLD-cosmetic (idempotent synchronous style writes, no flicker)_syncScrollToBottomCueafter_clearNewMessageScrollCueinscrollToBottom" → FOLD-cosmetic (same as Portability #1)Gate
node -c✓ · ESLint runtime CLEAN · scope-undef CLEAN · ruff forward CLEANCo-authored-by: rodboev rodboev@users.noreply.github.com