- Remove dead _scrollAnchorRestore variable (returns undefined)
- Remove redundant _fixMobileScrollJank() call from _doRender()
(CSS already gives overflow-anchor:auto on mobile via media query;
desktop exits guard early — call was a no-op on every tick)
- Update JSDoc on _fixMobileScrollJank to reflect actual usage
Root cause: .messages had overflow-anchor:none unconditionally. On mobile
touch devices (iOS Safari, Android Chrome), the compositor can paint a frame
with scrollTop=0 between innerHTML='' and snapshot restore, scroll-janking
the user to the top of the transcript.
Three-layer fix:
1. CSS (style.css): Use overflow-anchor:auto on touch devices (mobile),
keep overflow-anchor:none on desktop (mouse-driven) via
@media (hover:hover) and (pointer:fine) — preserves the existing
behavior where tool/card inserts don't yank a reading user.
2. JS guard (ui.js): Add window._fixMobileScrollJank() that temporarily
sets overflowAnchor='auto' on #messages before innerHTML='' and
clears it one rAF later — prevents the compositor frame with scrollTop=0.
3. Streaming protection (messages.js): Call _fixMobileScrollJank() at the
top of each _doRender() tick so streaming DOM growth doesn't jank the
viewport position.
Tests:
- Updated test_issue1360_streaming_scroll_hardening.py: split the
overflow-anchor test into desktop (none) and mobile (auto) variants.
- Added test_mobile_scroll_jank.html: interactive visual test that
simulates the scroll jank on a mobile-sized viewport.
The JS module default was false (consolidated) while the server defaults
lane_by_profile=True (lanes). If the board painted before _kanbanApplyConfigDefaults
ran, there'd be a one-frame consolidated flicker before snapping to lanes. Matching
the client default to the server default removes the flicker. Co-authored-by: stefan-wisbric
Took master's i18n.js as base (it has all 13 locale blocks + had reorganized the
kanban keys, so the stale-branch conflict block would have duplicated them in
zh-Hant), then inserted #2814's 5 kanban_view_* keys into every locale block after
kanban_new_task — real translations for it/ja/zh/zh-Hant/fr (from the PR), English
fallback elsewhere. All 13 locales now have exactly 1 of each key; locale-parity +
kanban tests green (291 passed).
#4800 changed the token SSE handler's call from _scheduleRender() to
_scheduleRender(parsed) (passes the pre-computed parse into the cache — the perf
optimization). The slice6 anchor-registry test asserted the literal no-arg call;
relaxed to _scheduleRender( so it matches the call regardless of args. Behavior
(token handler schedules a render) preserved + improved. Co-authored-by: wlknight
The parse-result cache keyed only on assistantText, but _parseStreamState() is a
function of BOTH assistantText AND liveReasoningText. A 'reasoning' SSE event (which
mutates liveReasoningText without calling _scheduleRender) landing between a token's
_scheduleRender(parsed) and the rAF firing made _doRender serve a parsed computed from
the older reasoning -> _renderLiveThinking could show stale thinking text. Keying the
cache on liveReasoningText too forces a fresh _parseStreamState when reasoning changed,
and also makes a canceled-then-rescheduled render miss the stale entry. Co-authored-by: wlknight
#4800 changed _scheduleRender() -> _scheduleRender(parsed) (parse-result cache).
test_streaming_race_fix's regex hardcoded the empty-arg signature so it couldn't
find the function ('_scheduleRender not found') even though the _streamFinalized
guard it checks is intact (line 3759). Regex now matches any args. Co-authored-by: wlknight
Introduce _safeSmdRenderer — a renderer wrapper that hooks smd's set_attr
to validate URL schemes inline as each DOM node is created, eliminating
the per-token _sanitizeSmdLinks(assistantBody) full-DOM scan that caused
progressive freezing on answers of 30,000+ chars (5,000+ DOM nodes).
Also add parse-result caching to _scheduleRender to avoid redundant
_extractInlineThinkingFromContent scans between the token event handler
and the rAF render callback.
Changes:
- New _safeSmdRenderer(el): URL scheme safety inline via set_attr hook
(identical algorithm to proven _streamFadeRenderer.set_attr, no animation)
- _smdNewParser: non-fade path uses _safeSmdRenderer instead of bare
default_renderer
- _smdWrite: removed _sanitizeSmdLinks(assistantBody) call — safety now
handled by renderer's set_attr at node-creation time
- _scheduleRender(parsed): accepts optional cached parse result from
token handler to avoid duplicate O(n) text scans
- Token handler: passes _parseStreamState() result to _scheduleRender
_sanitizeSmdLinks is preserved at _smdEndParser (stream-end safety net)
and in fallback paths where smd is not available.
All 166 existing tests pass.