Files
buzz/desktop/tests
npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57ccandTyler Longwell 78ac6caf19 fix(desktop): restore anchor on in-viewport reflow when scrolled up
The ResizeObserver in useAnchoredScroll only re-pinned the scroller when
the user was at-bottom — when scrolled up reading older history and a row
above the reading row reflowed (link-card decode, async embed expand, late
font load, markdown that expands), the anchor row shifted on them and
nothing restored it. The PR description claimed image and embed loads all
flow through the anchor, but on a careful read only NIP-92 imeta images
are actually covered (their dim is reserved before decode, so no resize
fires). Every other in-viewport content growth fell into the gap.

Extract the anchor-restoration primitive — find the row (with the
nearest-newer fallback already used for prepends), measure its current
top, scrollBy the delta — into a shared restoreAnchorToMessage helper,
and call it from both the layout effect and the ResizeObserver. One
primitive serves the React-driven path (post-commit, on messages /
spinner change) and the non-React-driven path (image decode, embed
expand, font load), preserving the single-owner invariant. A messagesRef
is mirrored from the layout effect so the observer reads the same list
the DOM was last rendered from without resubscribing on every commit.

E2E coverage: scroll-history e2e adds an in-viewport reflow case that
seeds a scrollable channel, scrolls to a mid position, captures the
top-crossing row's offset, programmatically grows a row above the anchor
via style.minHeight, and asserts the anchor's offset is unchanged within
2px after the observer fires. Confirmed the assertion catches the
pre-fix behavior (80px drift) by reverting the implementation and
re-running.

tsc, biome (764 files), 998/998 unit, scroll-history e2e 7/7 green.

Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
2026-06-18 16:39:26 -04:00
..