feat(#4006): default auto-follow ON (Codex/Claude-Code parity) + fix default-hydration mismatch + CHANGELOG v0.51.378

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.
This commit is contained in:
nesquena-hermes
2026-06-13 03:55:28 +00:00
parent c02c31c837
commit 00ea7766db
2 changed files with 84 additions and 0 deletions
+10
View File
@@ -3,6 +3,16 @@
## [Unreleased]
## [v0.51.378] — 2026-06-13 — Release MQ (don't yank the viewport while reading; auto-follow toggle, #4006)
### Fixed
- **Streaming no longer snaps you to the bottom when you've scrolled up to read (#4006).** Two pin-hysteresis bugs are fixed: `_scrollAfterMessageRender` now honors `_messageUserUnpinned` (so a forced follow can't override a deliberate scroll-up after the 250px near-bottom counter re-pins), and `_finishDone`'s completion scroll is gated on an actual near-bottom check instead of firing unconditionally. (#4006)
### Added
- **Auto-follow toggle (Settings → Appearance).** A new "Auto-follow new content" setting controls whether the transcript follows new output to the bottom while streaming. It defaults **on** — a Codex/Claude-Code-style sticky bottom: you stay pinned to the latest output until you scroll up, at which point you're left where you are. Turn it off to always manage the scroll position yourself. (#4006)
## [v0.51.377] — 2026-06-13 — Release MP (Firefox post-stream scroll jitter, #3920)
### Fixed
@@ -0,0 +1,74 @@
"""Pin the auto_scroll_follow setting's default + hydration consistency (#4006).
The viewport-follow default is `True` (Codex/Claude-Code-style sticky bottom:
follow new output to the bottom while streaming, but a deliberate scroll-up
unpins and is respected). This pins the default in every place it is read so a
future edit can't silently flip it or, worse, default it ON in config.py while
hydrating it OFF in the browser (the classic default-mismatch bug, where an
existing user with no saved value sees the feature as disabled).
"""
import pathlib
import re
ROOT = pathlib.Path(__file__).resolve().parent.parent
def _read(rel):
return (ROOT / rel).read_text(encoding="utf-8")
def test_auto_scroll_follow_default_is_true_in_config():
src = _read("api/config.py")
assert re.search(r'["\']auto_scroll_follow["\']\s*:\s*True', src), (
"auto_scroll_follow must default to True in _SETTINGS_DEFAULTS "
"(sticky-bottom follow; scroll-up unpins)"
)
def test_auto_scroll_follow_in_bool_keys():
src = _read("api/config.py")
m = re.search(r"_SETTINGS_BOOL_KEYS\s*=\s*\{([^}]+)\}", src, re.DOTALL)
assert m, "_SETTINGS_BOOL_KEYS not found"
assert "auto_scroll_follow" in m.group(1), (
"auto_scroll_follow must be in _SETTINGS_BOOL_KEYS so it round-trips as a bool"
)
def test_boot_hydration_defaults_true_when_setting_absent():
"""boot.js must hydrate _autoScrollFollow as True when the saved settings
omit the key — `!!s.auto_scroll_follow` would wrongly default it OFF for
every existing user, contradicting the config.py default."""
src = _read("static/boot.js")
# Settings path: default-true read (=== false), not the truthy-coerce form.
assert "window._autoScrollFollow=s.auto_scroll_follow!==false" in src, (
"boot.js settings path must default _autoScrollFollow True when absent"
)
assert "window._autoScrollFollow=!!s.auto_scroll_follow" not in src, (
"boot.js must not use !!s.auto_scroll_follow — that defaults the True "
"setting OFF for users with no saved value"
)
# Fallback (no-settings) path must also default true.
assert "window._autoScrollFollow=true" in src, (
"boot.js fallback path must default _autoScrollFollow to true"
)
def test_settings_checkbox_renders_checked_by_default():
"""The Appearance checkbox must render checked when the setting is absent,
matching the True default (panels.js settings-load)."""
src = _read("static/panels.js")
assert "autoScrollFollowCb.checked=settings.auto_scroll_follow!==false" in src, (
"the auto-follow checkbox must default checked (=== false), not "
"!!settings.auto_scroll_follow which would render it unchecked by default"
)
def test_follow_gate_references_auto_scroll_follow_and_unpin():
"""The DOM-replace follow gate must consult both _autoScrollFollow (the
setting) and _messageUserUnpinned (the user's scroll-up), so the opt-out and
the read-while-streaming behaviors both hold."""
src = _read("static/ui.js")
assert "_shouldFollowMessagesOnDomReplace" in src
assert "_autoScrollFollow" in src and "_messageUserUnpinned" in src, (
"the follow gate must reference both _autoScrollFollow and _messageUserUnpinned"
)