You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We built dsh-think-ux (https://github.com/el16z3c/dsh-think-ux) — a client plugin that makes think rows stream as a smooth capped preview and makes the main conversation view glide to the bottom instead of snapping. While chasing an intermittent "stiff scroll" report we instrumented the scroller and found three findings that may be useful for anyone working on Web-UI scroll behavior (and for the bundle itself):
1. Browser scroll anchoring fights smooth follow on large insertions. When a tool row / code block lands while the reader is bottom-pinned, the browser's overflow-anchor adjustment shifts scrollTop by the whole inserted chunk in a single frame (we measured 20–200 px downward shifts landing exactly at the floor — 295 of 333 uncaught scroller motions in one capture). A smooth chaser stepping ~16 px/frame cannot catch a whole-chunk snap, so the result is a visible hard jump. Fix: overflow-anchor: none on the scroller while following (restore the previous inline value on unload — an empty original must still clear the inline override).
2. Think-box settle unmounts ~490 px in one frame. When a think row settles, the body unmounts; a bottom-pinned reader is clamped down by the whole box in one frame (the turn-boundary "stiff snap"). Fix: play a short height animation (180 ms) before the unmounting commit so the clamp happens frame by frame. The animation should end via transitionend (a fixed timer racing the animation desyncs under hidden tabs / main-thread stalls), with the timer started at the animation's real start as a safety net; the finish should keep the inline height: 0 until the element unmounts (clearing it first flashes the natural full height for a frame under load).
3. Reader intent vs bundle re-pin is a structural problem, not a timing one. The bundle's follow re-pin is a plain JS el.scrollTop = el.scrollHeight write; native reader scrolling never passes through the JS property setter. A defineProperty trap on the scroller's scrollTop therefore identifies re-pins by construction: writes landing at the floor while the reader sits above it are re-pins — allow them (so the bundle's own bookkeeping stays consistent) and restore the reader's position in the same tick. The real change fires a scroll event the bundle's handler reads as "reader moved", which flips its follow off — self-healing. Every reader return is native, passes through untouched, and re-engages follow. (One Chrome fact we verified: scroll events carry isTrusted=true even for JS scrollTop writes — trust the trap, not the flag.)
Happy to share the trace methodology (per-instance rAF echo ring + a local HTTP trace sink) if useful.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
We built dsh-think-ux (https://github.com/el16z3c/dsh-think-ux) — a client plugin that makes think rows stream as a smooth capped preview and makes the main conversation view glide to the bottom instead of snapping. While chasing an intermittent "stiff scroll" report we instrumented the scroller and found three findings that may be useful for anyone working on Web-UI scroll behavior (and for the bundle itself):
1. Browser scroll anchoring fights smooth follow on large insertions. When a tool row / code block lands while the reader is bottom-pinned, the browser's overflow-anchor adjustment shifts
scrollTopby the whole inserted chunk in a single frame (we measured 20–200 px downward shifts landing exactly at the floor — 295 of 333 uncaught scroller motions in one capture). A smooth chaser stepping ~16 px/frame cannot catch a whole-chunk snap, so the result is a visible hard jump. Fix:overflow-anchor: noneon the scroller while following (restore the previous inline value on unload — an empty original must still clear the inline override).2. Think-box settle unmounts ~490 px in one frame. When a think row settles, the body unmounts; a bottom-pinned reader is clamped down by the whole box in one frame (the turn-boundary "stiff snap"). Fix: play a short height animation (180 ms) before the unmounting commit so the clamp happens frame by frame. The animation should end via
transitionend(a fixed timer racing the animation desyncs under hidden tabs / main-thread stalls), with the timer started at the animation's real start as a safety net; the finish should keep the inlineheight: 0until the element unmounts (clearing it first flashes the natural full height for a frame under load).3. Reader intent vs bundle re-pin is a structural problem, not a timing one. The bundle's follow re-pin is a plain JS
el.scrollTop = el.scrollHeightwrite; native reader scrolling never passes through the JS property setter. AdefinePropertytrap on the scroller'sscrollToptherefore identifies re-pins by construction: writes landing at the floor while the reader sits above it are re-pins — allow them (so the bundle's own bookkeeping stays consistent) and restore the reader's position in the same tick. The real change fires a scroll event the bundle's handler reads as "reader moved", which flips its follow off — self-healing. Every reader return is native, passes through untouched, and re-engages follow. (One Chrome fact we verified: scroll events carryisTrusted=trueeven for JSscrollTopwrites — trust the trap, not the flag.)Happy to share the trace methodology (per-instance rAF echo ring + a local HTTP trace sink) if useful.
All reactions