docs: list the page heading in the table of contents - #2988
Open
Lisa18289 wants to merge 8 commits into
Open
Conversation
The page heading is rendered from the frontmatter, not from the MDX, so `getAnchors` never saw it. The topmost section of every page — intro, status callout, live example — had no anchor at all, and there was no way to get back to the top from the table of contents. `MdxFileFactory.fromFile` now prepends an entry for the page title, and the heading carries the matching `id`. `/releases` builds its anchors itself and prepends the same entry. The slug is the constant `top` rather than the slugified title: `# Color` and `# Releases` already exist as MDX headings and would collide. Closes #2876 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Coverage Report for ./packages/components/
File CoverageNo changed files found. |
Contributor
🚀 Preview DeploymentPreview environments are ready:
Images:
|
…6-3d0d84 # Conflicts: # apps/docs/src/app/_components/layout/TopContent/TopContent.tsx
`titleFrom` is what the table-of-contents entry for the page heading renders, so its three-level fallback — frontmatter title, component name, humanized slug — is worth pinning down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`scroll-margin-top: 6rem` matched the MDX headings, but the page heading sits 121px down the document, so the jump stopped 25px short and scrolled the page padding away. A full-viewport offset makes it clamp to 0 without a fixed length that needs re-tuning when the header or padding changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The observation band sits 20–30% down the viewport, so on tall windows it falls onto the first section while the page is still scrolled to the top: at 1400px the band spans 280–420px and the first heading sits at 354px, so it was marked active instead of the page heading. The band is a poor answer to "which section am I in" at the top of a page, where none has been reached yet. While the page heading is still on screen it now wins outright. This was already wrong before the heading became an entry — the first section was both the wrong answer and the first entry, so it looked right. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The active entry was decided by an IntersectionObserver watching a band 20–30% down the viewport. That band only reports when a heading crosses it, so on /releases — where both anchors sit in the top 274px — the last callback fires around scrollY 34 and none ever follows. Whatever was decided there stuck for the rest of the page, so "Releases" stayed marked all the way to the bottom. The band was also a poor answer to "which section am I in": its position scales with the viewport, so on a 1400px window it landed on the first section while the page was still at the top. Compute it from the scroll position instead: the active entry is the last heading that has crossed the line an anchor jump lands on, and the page heading before any has. This recomputes on every scroll and resize, so it cannot freeze, and it drops the observer along with its fallback branch. Release headings get the same `scroll-margin-top` as the MDX ones — they had none, so jumping to one put it under the sticky header. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…6-3d0d84 # Conflicts: # apps/docs/src/app/_components/layout/ComponentContent/ComponentContent.tsx # apps/docs/src/app/_components/layout/TopContent/TopContent.tsx
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lisa18289
marked this pull request as ready for review
August 31, 2026 06:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The page heading is rendered from the frontmatter, not from the MDX, so
getAnchorsnever saw it. The topmost section of every page — intro, status callout, live example — had no anchor at all, and the table of contents offered no way back to the top.MdxFileFactory.fromFilenow prepends an entry for the page title, and the heading carries the matchingid./releasesbuilds its anchors itself and prepends the same entry.The slug is the constant
toprather than the slugified title:# Colorand# Releasesalready exist as MDX headings and would collide with it. As a bonus,#topis a special case in HTML — if the element is ever missing, the browser still jumps to the top of the page.The heading gets
scroll-margin-top: 100vhrather than the6remthe MDX headings use. It sits 121px down the document, so a matching offset stopped 25px short and scrolled the page padding away; a full viewport clamps the jump to 0 without a fixed length that needs re-tuning when the header height or page padding changes.The active entry
Adding an entry above the first section exposed two faults in how the active entry was picked, both pre-existing. It came from an IntersectionObserver watching a band 20–30% down the viewport, and that band answers "which section am I in" badly:
/releasesboth anchors sit in the top 274px, so the last callback fires aroundscrollY 34and none ever follows — whatever was decided there stuck for the rest of the page.The active entry is now computed from the scroll position: the last heading that has crossed the line an anchor jump lands on, and the page heading before any has. It recomputes on scroll and resize, so it cannot freeze, and the observer and its fallback branch are gone. This also clears a standing
react-hooks/exhaustive-depswarning.Release headings had no
scroll-margin-topat all, so jumping to one put it under the sticky header. They now carry the same6remas the MDX headings.Verified in the browser
01–04and/releases) show the title as the first, non-indented entry, linking to anidthat exists.scrollY: 4000lands at0, and deep-linking…/button#topdoes too. Clicking a release entry puts its heading 96px down, clear of the 72px header.scrollY: 0the page heading is active at 900px and 1400px viewport height; on/releasesthe entry switches to the release atscrollY: 200and stays there to the bottom of the page.titleFromwas extracted fromgetTitleso the factory can build the entry before constructing theMdxFile; its fallback chain is covered by a unit test.03-patterns/02-codesnippets/multi-uploadhas no#/##heading at all and now shows a table of contents with a single entry. Accepted as-is; giving that page a heading is content work, not part of this fix.No release:
apps/is publish-irrelevant per.github/scripts/release-relevance-lib.mjs.Closes #2876
🤖 Generated with Claude Code