_captureWorklogDetailDisclosureState stamps state._sid; _restoreWorklogDetailDisclosureState
skips restore on sid mismatch. Cross-session isolation no longer depends solely on the
sessions.js #msgInner wipe invariant — a future change to that loading-placeholder
behavior can't silently replay one session's collapse state onto another.
panels.js _autosaveAppearanceSettings (6309) + _applySavedSettingsUi (7837) read
auto_scroll_follow from a settings/body object — use !==false (default True) for
consistency with boot.js + the checkbox render, so a future partial-body refactor
can't silently default the True setting OFF. The .checked reads (6223/8170) keep !!
(a real bool).
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.
1. _settleMessageScrollToBottom(force, explicit): when explicit (End-button /
scrollToBottom), late-layout settling runs even with Auto-follow OFF — the
setting only suppresses AUTOMATIC follow, not a deliberate jump. The RO + 2s
fallback guards now bail on (!_autoScrollFollow && !explicit). scrollToBottom()
passes explicit=true; the automatic scrollIfPinned() path stays guarded.
2. renderMessages now captures the pre-wipe scrollSnapshot when (Auto-follow off
&& user unpinned) too, so the non-preserve restore lands the reader where they
were instead of receiving a null snapshot (no-op).
The keys shipped only in en+zh; added it/ja/ru/es/de/zh-Hant/pt/ko/fr/tr/pl so the
Settings → Appearance auto-follow toggle isn't English-only outside those two.
Codex found that with Auto-follow OFF the #3920 ResizeObserver settle + 2s fallback
+ the non-preserve render path still force-scrolled to bottom (the setting was
incomplete vs the scroll machinery shipped in #3920). Fixes:
1. RO settle guard (ui.js:3153): bail when !_autoScrollFollow.
2. 2s static fallback guard (ui.js:3186): bail when !_autoScrollFollow.
3. non-preserve render fall-through (ui.js:8653): when Auto-follow off AND the user
has unpinned, restore the scroll snapshot instead of scrollToBottom() — also
closes the send() race (renderMessages runs before S.activeStreamId is set).
A fresh session load (not unpinned) still lands at the bottom. Explicit
user-initiated scrollToBottom() (End button) is untouched.
Per maintainer decision, auto_scroll_follow defaults TRUE (sticky-bottom: follow
new output while streaming, scroll-up unpins and is respected) — matching Codex
CLI / Claude Code. The PR shipped it default-False.
Default-mismatch bug fixed (would have shown the True default as OFF for existing
users with no saved value):
- boot.js settings path: !!s.auto_scroll_follow -> s.auto_scroll_follow!==false
- boot.js no-settings fallback: false -> true
- panels.js checkbox render: !!settings.auto_scroll_follow -> !==false
Resolved the config.py + panels.js merge conflicts (kept both the Transparent
Stream setting and auto_scroll_follow). Added test_issue4006_auto_scroll_follow_default.py
pinning the True default across config/boot/panels + the follow-gate references.
The 2 failing assertions pinned the removed implementation (const passes=[0,16,80,180]
setTimeout fan-out + _settleMessageScrollToBottom(true) in scrollToBottom). Updated to
assert the new behavior contract: ResizeObserver on #msgInner + 2s static fallback +
force=false so the observer runs. Behavioral intent (settle survives late markdown
growth; sync write before settle scheduling) preserved.
Codex SHIP-ONLY-WITH-FIXES (3 real issues; Codex+Opus disagreed on #1, stricter wins):
1. CORE — .messages lost overflow-anchor:none, reopening the #1360 (d21c9720) streaming-scroll-anchor regression. Restored it (native anchoring stays OFF; the RO does the settle). Opus had judged this removal acceptable; took Codex's stricter call.
2. SILENT — the ResizeObserver observed #messages (the scroll container, fixed by flex layout, never resizes) instead of #msgInner (.messages-inner, the growing transcript node) → callback would never fire. Now observes #msgInner.
3. SILENT — stale RO callbacks mutated the global _settleRO, which could disconnect a newer active observer after settle re-entry. Now instance-owned: close over const ro, only clear the global when _settleRO===ro.
3 maintainer-review items the contributor hadn't addressed:
1. (blocking) restored .session-list overflow-anchor:none — the PR accidentally reverted the deliberate Firefox sidebar fix (2dfe765b); only the on-topic .messages removal is kept.
2/3. _settleFinalScroll was dead code + the promised 2s static-content safety timeout was missing — wired _settleFinalScroll(token) via a single 2s top-level fallback after _settleRO.observe(el), declared _settleFinalTimer + cleared it in _cancelBottomSettle and at settle re-entry.
Opus final gate caught that the staged re-implementation placed the
window._showThinking===false gate in _worklogReasonNodeFromText, which has
ZERO callers — so live/settled reasoning rows (built by _syncWorklogReasonFromAnchor
and _appendWorklogReason) were never actually hidden, and removeThinking swept
data-worklog-reason-source=reasoning which the real rows (data-worklog-anchor-reason=1)
don't carry. The string-match tests passed against the dead function = green-suite blind spot.
Fix:
- Gate _syncWorklogReasonFromAnchor (live + settled): when _showThinking===false, remove any
existing reasoning row and bail before building.
- Gate _appendWorklogReason (settled rebuild): return null when _showThinking===false.
- removeThinking + the settled-rebuild cleanup now sweep BOTH data-worklog-anchor-reason=1
(the real attribute) and the legacy data-worklog-reason-source=reasoning.
- Re-anchor the tests to assert against the real render functions + the correct attribute.
These 5 PNGs (722K) are the agent trifecta review-pass screenshots copied into
the worktree so the Opus gate could read them — review scratch, not durable UX
evidence. The intended evidence lives in docs/ui-ux/evidence/pr4024-*.png and is
kept. Mirrors the .review-crops/ cleanup; gitignore the dir so review scratch
can't leak into a release diff. No code or test change.
The release diff committed 7 PNG review-crop screenshots under a root-level
.review-crops/ directory (652K) that nothing references — not the CHANGELOG,
docs HTML, or any source file. These are deep-review before/after scratch
shots, not durable UX evidence (the intended evidence lives in
docs/ui-ux/evidence/pr4024-*.png and is kept). Shipping them in v0.51.375
would bloat the repo history permanently for every user.
Remove the directory and add .review-crops/ to .gitignore (next to the
.local-review/ harness entry) so review scratch can't leak into a release
diff again — mirroring the stray-uv.lock drop in #3963.
No code or test change; the Transparent Stream feature is untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- _applyTransparentRowFading was only called from the settled loop (which the fn skips), so live-turn recency fade never applied — now called from _syncTransparentEventControls (internally live-gated, no-ops on settled).
- _attachProgressBar isCompleted now includes Interrupted so an interrupted row's bar fills 100% instead of rendering empty.
- C1-r2 (both Codex+Opus): _rehydrateTransparentStreamDom now also rewires the root element when it IS the assistant turn (querySelectorAll only matched descendants), so turn-toggle + event-controls re-bind after a live-turn restore.
- O-Bug2-r2: preserve the Full/Output detail tab regardless of open state.
- r2 #4: drop the 'Running: ' name prefix — the legible status badge already carries it (V6 redundancy class).
Functional (Codex+Opus trifecta findings):
- C1: restoreLiveTurnHtmlForSession now calls _rehydrateTransparentStreamDom (controls were dead after active-session live-turn restore).
- O-Bug1: per-turn echoed-thinking dedup in the transparent settled path (mirrors the compact seenReasons), fixes duplicate out-of-order Thinking rows.
- O-Bug2: tool completion preserves the row's open state + Full/Output tab.
- O-Edge: settled/reloaded done===false tools show 'Interrupted' (not a permanent Running shimmer); progress bar clears for Interrupted too.
Visual (trifecta CSS):
- V1: reset .thinking-card to flat/transparent (was the heaviest object in the stream).
- V2: failed=error color + left border, running/interrupted legible (was an invisible muted badge).
- V3: scoped !important reset beats the 6 per-skin .tool-card re-card rules.
- V4: inline key:value args (was a tall stacked form).
- V5/V7: output pre flattened to a quiet left-rail, max-height:none (one expanded zone, no nested scroll).
- V6: drop redundant tool/status pseudo-args from the detail body.
- V8: row fading gated to the live turn only + floor raised for WCAG; settled history stays full-opacity.
- V9: entrance animation scoped to #liveAssistantTurn (was replaying on every renderMessages).
- V10: larger mobile header tap targets + active detail-tab underline.
O-Bug3 (live thinking overwrite when no seq) intentionally deferred — documented follow-up; settled render is already correct and a live heuristic risks a streaming regression.
Opus advisor found a key-space asymmetry: _pruneLineageReportCacheToVisibleSessions
built visibleKeys from RAW rows via _sidebarLineageKeyForRow, but the render loop
keys the lineage-report cache by _sidebarLineageKeyForRow on the COLLAPSED row,
which can differ when collapse merges segments. On a malformed/edge chain the
expanded row's cache could be evicted every payload and re-fetched ~every 5s
(partial regression of the bug #4020 fixes). Fold the collapsed rows' cache keys
into the visible set too, mirroring the _resolveSessionIdFromSidebarLineage
precedent, behind a defensive try/catch.
docs(changelog): stamp #4020+#4055 as v0.51.373 (Release ML)
_active_skill_search_dirs filters to existing dirs, so on a host with no local
skills dir but configured external dirs the local root is dropped from the list
and the position-based skills_dirs[0]==local assumption misclassified the first
external root as local (its flat skills silently lost their category label).
Pass the local dir explicitly (backward-compatible optional param defaulting to
the old skills_dirs[0] behavior) + regression test for the absent-local-dir case.