Skip to content

docs: list the page heading in the table of contents - #2988

Open
Lisa18289 wants to merge 8 commits into
mainfrom
claude/flow-issue-2876-3d0d84
Open

docs: list the page heading in the table of contents#2988
Lisa18289 wants to merge 8 commits into
mainfrom
claude/flow-issue-2876-3d0d84

Conversation

@Lisa18289

@Lisa18289 Lisa18289 commented Aug 28, 2026

Copy link
Copy Markdown
Member

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 the table of contents offered no way back to the top.

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 with it. As a bonus, #top is 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: 100vh rather than the 6rem the 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:

  • Its position scales with the viewport. On a 1400px window it spans 280–420px and lands on the first section while the page is still at the top. Previously that section was also the first entry, so the wrong answer looked right.
  • It only reports when a heading crosses it. On /releases both anchors sit in the top 274px, so the last callback fires around scrollY 34 and 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-deps warning.

Release headings had no scroll-margin-top at all, so jumping to one put it under the sticky header. They now carry the same 6rem as the MDX headings.

Verified in the browser

  • All five pages with a table of contents (0104 and /releases) show the title as the first, non-indented entry, linking to an id that exists.
  • The heading sits 121px down the document at every width from 820px to 1920px.
  • Clicking the top entry from scrollY: 4000 lands at 0, and deep-linking …/button#top does too. Clicking a release entry puts its heading 96px down, clear of the 72px header.
  • At scrollY: 0 the page heading is active at 900px and 1400px viewport height; on /releases the entry switches to the release at scrollY: 200 and stays there to the bottom of the page.

titleFrom was extracted from getTitle so the factory can build the entry before constructing the MdxFile; its fallback chain is covered by a unit test.

03-patterns/02-codesnippets/multi-upload has 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

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>
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for ./packages/components/

Status Category Percentage Covered / Total
🔵 Lines 76% 627 / 825
🔵 Statements 75.91% 643 / 847
🔵 Functions 77.95% 145 / 186
🔵 Branches 66.66% 298 / 447
File CoverageNo changed files found.
Generated in workflow #6407 for commit 7e252fa by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

Preview environments are ready:

Type URL
docs pr-2988.docs.review.flow-components.de
storybook pr-2988.storybook.review.flow-components.de

Images:

  • docs: ghcr.io/mittwald/flow/docs:pr-2988
  • storybook: ghcr.io/mittwald/flow/storybook:pr-2988

Lisa18289 and others added 3 commits August 28, 2026 11:36
…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>
@Lisa18289 Lisa18289 self-assigned this Aug 28, 2026
Lisa18289 and others added 4 commits August 28, 2026 12:56
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
Lisa18289 marked this pull request as ready for review August 31, 2026 06:35
@Lisa18289
Lisa18289 requested a review from a team August 31, 2026 06:35
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.

Table of Contents: cannot navigate back to the top of a component page

1 participant