Skip to content

fix(website): keep the heading fragment on a hard-loaded docs deep link - #1079

Merged
blove merged 1 commit into
mainfrom
blove/clever-sammet-6ce520
Sep 9, 2026
Merged

fix(website): keep the heading fragment on a hard-loaded docs deep link#1079
blove merged 1 commit into
mainfrom
blove/clever-sammet-6ce520

Conversation

@blove

@blove blove commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

The bug

Hard-loading /docs/<library>/<section>/<slug>#heading lands at the top of the article instead of at the heading. This is not a dev-server artifact — measured against a production build (nx build website + nx serve --configuration=production), /docs/langgraph/guides/streaming#whats-next left the heading 9760px below the reading pane, with .docs-workspace-article at scrollTop: 0.

Shared links, search-engine results, and the heading-granular deep links the docs search emits are all exactly this shape, and the failure is silent: the page renders perfectly, just in the wrong place.

Root cause

Traced with instrumented hooks on scrollTo / scrollIntoView / scrollTop / focus:

t event
0–140ms the browser starts its native (smooth) fragment scroll on the document — scrollY climbs 2 → 25
~187ms hydration: useLayoutEffect registers the workspace and WebsiteWorkspaceSurface mounts
~187ms html:has([data-website-workspace-host]) starts matching overflow: hidden (apps/website/src/styles/docs.css) — the page scroller ceases to exist and the scroll clamps to 0
after .docs-workspace-article, mounted with the shell, becomes the only scroller and starts at 0. Nothing re-applies the fragment.

No script resets anything — CSS discards the scroll. That is why none of the scroll-mutation hooks fired.

Non-workspace docs pages (/docs/choosing-an-adapter) were never affected: they keep the document scroller and html's scroll-padding-top.

The fix

One useEffect(…, []) in WebsiteWorkspaceSurface — at the exact mount that causes the loss — re-applying location.hash with scrollIntoView({ block: 'start' }). The resulting position is byte-identical to clicking the same rail link, including the shell's pre-existing 45px section offset.

Coverage

apps/website/e2e/docs-deep-link.spec.ts, one test per page kind. Two deliberate details:

  • about:blank between gotos. page.goto() to a URL differing only by #hash is a same-document navigation and never reloads — the first draft of this spec passed against the broken build because of it.
  • Heading ids read off the page's own rail, filtered to ones below the fold and inside the scroll range. A hardcoded id goes stale on a retitle, and the last heading on a page usually cannot reach the top of its scroller.

Mutation-tested: with the fix removed the workspace test fails (9760), with it restored it passes.

About the original report

The symptom was first seen on /docs/choosing-an-adapter#ag-ui-adapter under nx serve website. That page is fine — on a route's first next dev request Turbopack compiles it, load fires early, and Chrome drops the pending fragment scroll (zero scroll events, ever). Warm dev and production both land it at nav-bottom + 16px, exactly the scroll-padding-top. The spec covers that page too, so the distinction stays recorded rather than being re-investigated.

Verification

  • New spec green against the production build and against dev; 6/6 on --repeat-each=3.
  • docs.spec.ts + docs-shell.spec.ts + the new spec: 33 passed, twice.
  • Full website suite vs. the production build: 97 passed, 1 failed — workspace shell › …without replacing the runtime frame, which needs a live cockpit runtime on :4300. A/B'd with the fix on and off in the same environment: identical failure both ways, so it is environmental (a concurrent local session held that port).
  • nx build website clean, nx lint website 0 errors, nx test website green.
  • Deep links confirmed in view at 390px and 820px as well as 1280px.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Sep 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
threadplane Ready Ready Preview Sep 9, 2026 3:13am UTC

Request Review

@blove
blove enabled auto-merge (squash) September 9, 2026 02:09

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

Hard-loading `/docs/<library>/<section>/<slug>#heading` landed at the top of
the article instead of the heading. Not a dev-server artifact: measured
against a production build, the heading sat ~9760px below the reading pane.

The article is server-rendered straight into the document, so the browser
performs its native scroll to the fragment on the page scroller. Hydration
then mounts `WebsiteWorkspaceSurface`, `html:has([data-website-workspace-host])`
starts matching `overflow: hidden`, the page scroller ceases to exist, and the
real scroller — `.docs-workspace-article`, mounted with the shell — starts at
zero. No script resets anything; CSS simply discards the scroll, and nothing
puts the reader back. Shared links, search results and the heading-granular
deep links the docs search emits are all exactly this shape, and the failure
is silent: the page renders perfectly, just in the wrong place.

Re-apply the fragment once, on the mount that takes the scrolling over. The
resulting position is identical to clicking the same rail link, including the
shell's pre-existing 45px section offset.

Non-workspace docs pages were never affected — they keep the document scroller
and `html`'s `scroll-padding-top`. The one report against
`/docs/choosing-an-adapter` was a cold `next dev` compile dropping the pending
fragment scroll, which a production build does not do; the new spec covers
that page anyway so the difference stays recorded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@blove
blove force-pushed the blove/clever-sammet-6ce520 branch from 8fce0cf to 4317ba5 Compare September 9, 2026 03:02
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit 0efddee into main Sep 9, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant