mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
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>