Repository navigation
fix: align heading anchors after client-side navigation - #1133
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| observer.observe(document.body, { childList: true, subtree: true }) | ||
| const resizeObserver = new ResizeObserver(align) | ||
| resizeObserver.observe(document.body) | ||
| const userEvents = ['wheel', 'touchstart', 'keydown', 'mousedown'] as const |
There was a problem hiding this comment.
🟣 pre-existing, not blocking: On slow connections, readers following a cross-page heading link still land at the top whenever the page takes longer than 4 s to stream in. The SETTLE_MS timer starts at the route commit, not when the heading mounts. That commit happens before the content streams. If streaming takes longer than 4000 ms, stop disconnects both observers before the target exists, so nothing ever scrolls to it. Fix: keep waiting until the target first mounts, with a separate, longer cap; start the 4 s keep-aligned window from that point. The wheel/touch/key/mouse stop already protects users who scroll while waiting.
A small fix can ride a push you are already making; otherwise a short reply is enough.
Why this was flagged
A reader on a slow mobile network clicks a link from one docs page to a heading on another. The effect in src/components/HashScrollOnNavigate.tsx:41-87 runs at the route commit, before the content has streamed. It starts setTimeout(stop, SETTLE_MS) right away at line 81, with SETTLE_MS = 4000 (line 6). align returns early while targetFor(currentHash) is null (line 59). If the heading arrives after 4000 ms, stop has already disconnected the MutationObserver and ResizeObserver and set done. Nothing re-runs, because path and hash do not change again. The router's scrollTopForMissingHash scroll-to-top stays in place, exactly as on the base branch. So the fix works only when streaming beats a fixed 4 s. Bounding the wait by user input is not the problem: the userEvents listeners at lines 79-80 already stop it as soon as the reader scrolls.
Verification: Pre-existing (partial fix). This happens when the target page's content streams in more than 4 s after the route commit. In src/components/HashScrollOnNavigate.tsx, const timer = setTimeout(stop, SETTLE_MS) (line 81) starts the timer at that moment, with SETTLE_MS = 4000 (line 6). So if the heading mounts after the 4 s timer has fired, no code scrolls to it.
…ient-side navigation
Links to a heading on another docs page land at the top of the page instead of the heading, for example the "create a configurable account" link on /docs/accounts, which goes to /docs/accounts/create#create-a-configurable-account. A full page load of the same URL works.
Cause: on a client-side navigation, Waku's router and Vocs' scroll restoration try to scroll to the hash in the commit that changes the route. The page content streams in after that commit, so the heading doesn't exist yet. Waku then scrolls to the top (
scrollToHashwithscrollTopForMissingHash), and nothing retries once the heading mounts.Fix:
HashScrollOnNavigate, mounted in the docs layout. After a path change with a hash, it watches the DOM until the heading mounts and aligns it below the sticky header (honouringscroll-padding-topandscroll-margin-top). It keeps the heading aligned while demos above it render, and stops at the first wheel, touch, key or mouse input, after 4 s, or on back/forward navigation, which Vocs' scroll restoration still handles. Full page loads and same-page hash links are unchanged.Tests:
e2e/outline-anchors.test.ts: clicks that link on /docs/accounts and checks the heading lines up. Passes against a production build (pnpm build+dist/preview.js), along with the existing outline test.scrollTo(0, 0)still fires while the heading is missing, and this component then scrolls the heading into place about 0.5 s later (scrollY 0 → 1515).biome check,pnpm check:typesandpnpm test(1688 tests) pass.