fix(seo): lowercase language in plot page title#7206
Merged
Conversation
The browser tab title for plot detail pages was rendering
`{spec-id} · Python · {library}` with Python/R capitalized while spec-id
and library remained lowercase, which looked inconsistent. Use the raw
URL language value so the title now reads `{spec-id} · python · {library}`,
matching the lowercase library tokens. Breadcrumb display (which still
uses LANG_DISPLAY) keeps the capitalized form where Title Case is
appropriate.
Contributor
There was a problem hiding this comment.
Pull request overview
Updates the plot detail page <title> formatting to use the lowercase language token from the URL (e.g. python) instead of the display-mapped value (e.g. Python), improving visual consistency with lowercase library IDs.
Changes:
- Use
urlLanguage/languageFilterdirectly (noLANG_DISPLAYmapping) when building the page title suffix. - Update the inline comment to reflect the lowercase language convention for the browser tab title.
Comments suppressed due to low confidence (1)
app/src/pages/SpecPage.tsx:366
- This change updates the document title language token to be lowercase (e.g.
python), but existing frontend unit tests still assertPythonin the title (seeapp/src/pages/SpecPage.test.tsxexpectations). Update those tests (and any other title assertions) to match the new lowercase behavior soyarn testpasses.
// Page title surfaces language alongside library so the browser tab matches
// the rendered image-title format `{spec-id} · {language} · {library}`. Hub
// mode under a `?language=` filter also surfaces the language so users see
// what's been narrowed down. Language is kept lowercase to match the
// lowercase library name (e.g. `matplotlib`, `ggplot2`).
const detailLanguage = mode === 'detail' && urlLanguage ? urlLanguage : null;
const hubFilterLanguage = languageFilter ? languageFilter : null;
const titleSuffix =
mode === 'detail' && detailLanguage && selectedLibrary
? ` · ${detailLanguage} · ${selectedLibrary}`
: hubFilterLanguage
Match the lowercased `python` token introduced in the previous commit so frontend tests reflect the new title format.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Mirror the frontend change: rendered image titles now use
`{spec-id} · {language} · {library} · anyplot.ai` with `python`/`r`
lowercase to match the lowercase `{spec-id}` and `{library}` tokens.
Updates the canonical title definition, all examples in plot-generator.md,
the R example in library/ggplot2.md, and the SC-04 rubric line in
quality-criteria.md / quality-evaluator.md / ai-quality-review.md so
generation and review stay in sync.
Existing plot files generated since PR #7141 still carry the capitalized
form; they'll be normalised the next time daily-regen.yml picks them up.
Comment on lines
+356
to
+362
| // Page title surfaces language alongside library so the browser tab matches | ||
| // the rendered image-title format `{spec-id} · {Language} · {library}`. Hub | ||
| // the rendered image-title format `{spec-id} · {language} · {library}`. Hub | ||
| // mode under a `?language=` filter also surfaces the language so users see | ||
| // what's been narrowed down. | ||
| const detailLanguage = mode === 'detail' && urlLanguage ? (LANG_DISPLAY[urlLanguage] || urlLanguage) : null; | ||
| const hubFilterLanguage = languageFilter ? (LANG_DISPLAY[languageFilter] || languageFilter) : null; | ||
| // what's been narrowed down. Language is kept lowercase to match the | ||
| // lowercase library name (e.g. `matplotlib`, `ggplot2`). | ||
| const detailLanguage = mode === 'detail' && urlLanguage ? urlLanguage : null; | ||
| const hubFilterLanguage = languageFilter ? languageFilter : null; |
Per Copilot review: the previous fix relied on URL/query params already being lowercase, but a mixed-case route like /scatter-basic/Python/matplotlib or ?language=Python would still surface "Python" in the title. Force the token to lowercase when building titleSuffix so the canonical form (python/r) is always emitted regardless of URL casing. Scoped to the title only; broader URL normalization (filtering, canonical redirects) is out of scope for this PR.
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
app/src/pages/SpecPage.test.tsx:209
- This block is labeled "language in title + breadcrumb", but it only asserts the document title. Since this PR explicitly aims to keep breadcrumbs Title-Cased, adding an assertion for the breadcrumb language label (or renaming the describe block to only mention title) would prevent regressions and align the test intent with what’s being verified.
describe('language in title + breadcrumb', () => {
it('includes ` · python · matplotlib` in the document title in detail mode', async () => {
mockParams = { specId: 'scatter-basic', language: 'python', library: 'matplotlib' };
mockSearchParams.delete('language');
mockFetchSuccess();
render(<SpecPage />);
await waitFor(() => {
expect(document.title).toContain('Basic Scatter Plot · python · matplotlib');
});
});
it('includes ` · python` in the document title when ?language= is set in hub mode', async () => {
mockParams = { specId: 'scatter-basic' };
mockSearchParams.set('language', 'python');
mockFetchSuccess();
render(<SpecPage />);
await waitFor(() => {
expect(document.title).toContain('Basic Scatter Plot · python');
});
mockSearchParams.delete('language');
});
});
Comment on lines
356
to
365
| // Page title surfaces language alongside library so the browser tab matches | ||
| // the rendered image-title format `{spec-id} · {Language} · {library}`. Hub | ||
| // the rendered image-title format `{spec-id} · {language} · {library}`. Hub | ||
| // mode under a `?language=` filter also surfaces the language so users see | ||
| // what's been narrowed down. | ||
| const detailLanguage = mode === 'detail' && urlLanguage ? (LANG_DISPLAY[urlLanguage] || urlLanguage) : null; | ||
| const hubFilterLanguage = languageFilter ? (LANG_DISPLAY[languageFilter] || languageFilter) : null; | ||
| // what's been narrowed down. Force-lowercase the language token so a | ||
| // mixed-case URL (`/Python/...` or `?language=Python`) still renders the | ||
| // canonical lowercase form (`python`/`r`) that matches `{spec-id}` and | ||
| // `{library}`. | ||
| const detailLanguage = mode === 'detail' && urlLanguage ? urlLanguage.toLowerCase() : null; | ||
| const hubFilterLanguage = languageFilter ? languageFilter.toLowerCase() : null; | ||
| const titleSuffix = |
Per Copilot's broader review: only lowercasing in the title left the rest of the page (impl matching, gridImpls filter, canonical URL, analytics pageview, navigate() back-writes) using the unnormalised value. A mixed-case route like /Python/matplotlib or ?language=Python would desync — title shows `python` while filtering finds no matching impls and the canonical URL keeps the wrong case. Force-lowercase `urlLanguage` and the `?language` query param once where they're read, so every downstream comparison, URL, and tracker uses the canonical form. The earlier .toLowerCase() calls in the title block are now redundant and removed. Breadcrumb keeps Title Case via LANG_DISPLAY['python'] -> 'Python'. Also rename the test describe block to 'language in document title' — the previous name promised a breadcrumb assertion that doesn't exist (breadcrumbs render only as JSON-LD inside Helmet, which is mocked).
Comment on lines
+46
to
+49
| // Language tokens are canonical lowercase (`python`/`r`) everywhere they're | ||
| // compared against impl data, written back to URLs, or rendered. Force-lowercase | ||
| // at the read site so a mixed-case route like /Python/matplotlib or | ||
| // ?language=Python doesn't desync title, filtering, canonical URL, and analytics. |
| expect(document.title).toContain('Basic Scatter Plot · python'); | ||
| }); | ||
| mockSearchParams.delete('language'); | ||
| }); |
Per Copilot review: - Reword the read-site comment so it no longer implies every UI surface must be lowercase — breadcrumbs intentionally render Title Case via LANG_DISPLAY. - Add two SpecPage tests that pass mixed-case input (`/Python/matplotlib` path and `?language=Python` query) and assert the document title is emitted in canonical lowercase, locking in the new read-site normalisation.
Comment on lines
64
to
71
| const mode: Mode = urlLibrary ? 'detail' : 'hub'; | ||
| const selectedLibrary = urlLibrary || null; | ||
| // Carousel scope. The `?language=` query param expresses user intent — | ||
| // "I'm browsing R impls" — independent of which impl is currently rendered. | ||
| // It's set in both hub and detail mode. Without it, the carousel walks | ||
| // through ALL impls (cross-language). With it, the carousel stays scoped to | ||
| // that language. This is also what lets a future python.anyplot.ai-style | ||
| // subdomain pin the scope without users having to manually re-filter. |
Comment on lines
+48
to
+52
| // Force-lowercase at the read site so a mixed-case route like | ||
| // /Python/matplotlib or ?language=Python doesn't desync title, filtering, | ||
| // canonical URL, and analytics. Title-cased display labels for UI surfaces | ||
| // (e.g. breadcrumbs) still go through LANG_DISPLAY. | ||
| const urlLanguage = urlLanguageRaw?.toLowerCase(); |
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
{spec-id} · Python · {library} | anyplot.ai— language was Title-Cased while spec-id and library stayed lowercase, which looked inconsistent.SpecPage.tsxso the title now reads{spec-id} · python · {library} | anyplot.ai, matching the lowercase library tokens (e.g.matplotlib,ggplot2).LANG_DISPLAY(Title Case) — that's the correct convention for breadcrumb labels and was left untouched.Test plan
yarn type-checkpassesyarn lintpasses (only pre-existing warnings)<title>on a detail page like/bar-grouped/python/matplotlibreadsGrouped Bar Chart · python · matplotlib | anyplot.aiPython(unchanged)https://claude.ai/code/session_01EovzGTufzre1isXFPwA8k2
Generated by Claude Code