Repository navigation
fix(app): one title and description per page - #12442
Merged
Merged
Conversation
Fixes duplicate metadata elements (title, description, and OG/Twitter properties) that are present in the shell `index.html` by default and redundantly appended when React routes boot up and hydrate. We now mark the 6 shell tags with `data-shell-default` and aggressively strip them immediately before creating the React root. Additionally, `DebugPage` now consistently sets its own explicit title. Tests added for both the DOM utility and integration into the specific pages. Verification steps run successfully: ``` yarn lint yarn fm:check yarn type-check yarn test src/pages src/utils yarn build ``` Refs #9629 --- *PR created automatically by Jules for task [16201668932719198013](https://jules.google.com/task/16201668932719198013) started by @MarkusNeusinger* --------- Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gemini Flash pre-PR check: removing the shell's title and description before the first render left the tab without a title while a lazy route chunk loads, on the route error boundary, and on a spec page that is loading or failed. watchShellDefaults() now detaches a shell default only while a page renders a tag of the same kind and re-attaches it when none is left (MutationObserver on <head>). The page tests run that sync and require exactly one title and one description instead of "at most one". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Browser-runtime confirmation is still needed because the fix depends on React 19 metadata hoisting and MutationObserver ordering during lazy, error, loading, and client-side route transitions, while the local servers were unavailable.
No open issues 🎉
What changed in this PR
This pull request deduplicates SPA page metadata while preserving shell defaults for non-JavaScript, loading, and error states. It also adds titles for all /debug states and expands metadata synchronization tests.
Changes:
- Marks six shell metadata tags as fallback defaults.
- Adds
MutationObserver-based synchronization for route metadata. - Adds
/debugtitles across all render states. - Adds unit, integration, and changelog coverage.
| File | Summary |
|---|---|
changelog.d/head-metadata-dedup.md |
Documents the metadata deduplication and fallback behavior. |
app/src/utils/shellMetadata.ts |
Implements shell metadata detection, detachment, restoration, and observation. |
app/src/utils/shellMetadata.test.ts |
Tests synchronization, restoration, idempotency, and observer behavior. |
app/src/pages/headMetadata.test.tsx |
Tests page-level metadata deduplication; coverage should also include all supported Open Graph and Twitter tags and shell-only fallback behavior. Nit. |
app/src/pages/DebugPage.tsx |
Adds titles across authenticated, loading, error, and success states. |
app/src/main.tsx |
Starts shell metadata observation before rendering the React application. |
app/index.html |
Marks six shell metadata tags as defaults; og:url and og:image remain unmarked and can duplicate SpecPage metadata. Moderate. |
This pull request requires changes.
Review effort: Lite
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Summary
<title>, description, and Open Graph and Twitter tags stayed in<head>next to the ones every page renders since refactor(app): replace react-helmet-async with React 19 native metadata #12424, so each route had two of each and the shell's description came first. The six defaults are now markeddata-shell-default, andwatchShellDefaults()(aMutationObserveron<head>, started inapp/src/main.tsx) detaches a default only while a page renders a tag of the same kind and re-attaches it when none is left./debuggets its own title.feature/head-metadata-dedup: variant A deleted the shell defaults (fix(app): drop the shell's per-route head defaults #12439, closed: clients without JavaScript lost title and description, and/debughad none), variant B removed them once at boot (fix(app): strip the shell's head defaults when the app boots #12440, merged into the feature branch). The Gemini Flash pre-PR check then found that B left the tab without a title while a lazy route chunk loads, on the error boundary, and on a loading or failed spec page, and that its tests accepted zero descriptions; Claude replaced the one-time removal with the fallback-keeping sync and tightened the tests.Plan
Refs #9629 (the head-duplication half of audit Medium#6).
Test plan
yarn lint,yarn fm:check,yarn type-check,yarn test(110 files, 1192 tests),yarn build<title>, one description and oneog:titleon/,/plots,/plots?lib=matplotlib,/about,/legal,/debug,/scatter-basicand a 404;/debugand the 404 keep the shell description as their fallback; a client-side navigation from/to/aboutleaves one title