fix(pages): consistency fixes — titles, structured data, dark mode, copy, email capture#34
Merged
Merged
Conversation
…ture Five small fixes for documented R10/R11 consistency findings; the other four findings (WebApplication/BreadcrumbList JSON-LD, LQ-EQ page layout, ADR-detail email capture) verified already-fixed by intervening PRs. Changes: - app/layout.tsx: root title template `'%s | FreightUtils.com'` → `'%s | FreightUtils'`. Fixes 5 of the 6 documented double-branded pages in one place. - app/changelog/page.tsx: switched to `title.absolute` so the template doesn't duplicate "FreightUtils" in a title that already contains it. - app/components/ThemeToggle.tsx: added `storage` event listener so a theme flip in one tab mirrors into other open tabs. - app/api-docs/page.tsx: normalised the lone "25 calls/day" mention to "25 requests per day" so all three rate-limit references match. - app/hs/code/[subheadingCode]/page.tsx + app/hs/heading/[headingCode]/page.tsx: added <NewsletterCapture /> below the "Found an error?" footer to match the ADR-detail / LQ-EQ-checker pattern. - lib/seo/page-metadata.ts: docstring updated to reflect new template. - CHANGELOG.md + lib/changelog-data.ts: 2026-05-15 Bug Fix entry. Verified via `next build` static HTML: /about → "About — Free Freight Tools & API Platform | FreightUtils" /changelog → "Changelog — FreightUtils Updates & Releases" (absolute) /consignment-calculator → "Multi-Item Consignment Calculator | FreightUtils" /duty → "UK Import Duty & VAT Calculator | FreightUtils" /unlocode → "UN/LOCODE Lookup | FreightUtils" /vehicles → "Road Freight Vehicle & Trailer Types | FreightUtils" / → "FreightUtils — Free Freight Calculators & APIs" (default title) /hs → unchanged (uses title.absolute via lib/seo builders) /changelog renders the new May 15 entry. `tsc --noEmit` clean. Lint on touched files: only pre-existing react-hooks/set-state-in-effect on ThemeToggle (unrelated to this change).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
36 tasks
SoapyRED
added a commit
that referenced
this pull request
May 16, 2026
…LT 15) (#44) * audit(seo): diagnose title-template state (Phase 1) Diagnosis-first per sprint hard rule. Findings: 1. The literal 'FreightUtils | FreightUtils.com' double-branding is NOT currently in prod. PR #34 fixed it at the root template (changed '%s | FreightUtils.com' to '%s | FreightUtils' in app/layout.tsx). Curl of 11 representative URLs shows single brand or no brand. Sprint context (R8/R9/R10 rounds) was written against pre-PR-#34 prod. 2. Two real issues remain: - ADR detail titles lost 'ADR 2025' context segment. Builder doesn't emit it; year/edition signal lives only in meta description. - Prevention is by convention. lint-seo-titles.mjs checks length / keyword / description rules but does not assert 'FreightUtils appears at most once per rendered title'. Three rounds of regression returned because the discipline isn't enforced. 3. Fix direction (Phase 2): - Extend lint-seo-titles.mjs with Rule A (no double-brand) covering ALL static metadata exports under app/, and Rule B (ADR titles contain 'ADR 2025') run against ADR builder fixtures. - Update buildAdrUnMetadata to emit 'ADR 2025' in titles using the same SITE_YEAR -> 2025 mapping already used for descriptions. 4. Contract test fails on pre-fix main (Rule B fires on every ADR fixture); passes after builder edit. PR body documents both states. Companion follow-ups noted but out of scope: - /changelog and /containers use title.absolute without brand — brand recall lost on those, debatable tradeoff. - /pallet title is 78 chars — SERPs cut the brand. Separate length sprint. Doc: docs/audit/title-template-2026-05-16.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * audit(seo): amend Phase 1 — wider grep finds 12+ double-branded pages Initial spot-check covered only the 8 pages flagged by R8/R9/R10 consensus rounds and concluded the literal double-brand was gone. Wider grep over app/**/page.tsx for title:.*FreightUtils finds 12+ pages where title is hardcoded as 'X — FreightUtils' and the root template appends '| FreightUtils' — rendered as 'X — FreightUtils | FreightUtils'. Prod curl confirms: /account → Account — FreightUtils | FreightUtils /for-it → For IT Departments — FreightUtils | FreightUtils /roadmap → Roadmap — FreightUtils | FreightUtils /contact → Contact — FreightUtils | FreightUtils /refund-policy → Refund Policy — FreightUtils | FreightUtils /dpa → Data Processing Agreement — FreightUtils | FreightUtils /docs/deprecation → Deprecation Policy — FreightUtils | FreightUtils /docs/versioning → Versioning Policy — FreightUtils | FreightUtils /guides → Guides — FreightUtils | FreightUtils /status → Status — FreightUtils | FreightUtils /signin → Sign in — FreightUtils | FreightUtils /guides/[slug] → LHR Shed Codes: ... — FreightUtils Guide | FreightUtils The symptom mutated from the pre-PR-#34 form ('| FreightUtils.com | FreightUtils.com') to the new form ('— FreightUtils | FreightUtils'). Same root cause: no enforced contract test for 'brand appears at most once per rendered title'. Same architecture-level fix from Phase 2. Updated Phase 1 'TL;DR' + 'Per-page static metadata exports' table in docs/audit/title-template-2026-05-16.md to reflect the broader scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(seo): single-brand titles + restore ADR 2025 (Phase 2) Root cause (per docs/audit/title-template-2026-05-16.md): no enforced contract test for 'FreightUtils appears at most once per rendered title'. 12 pages drifted to 'X — FreightUtils' as the per-page title, which then received '| FreightUtils' from the root template at app/layout.tsx:18 — rendered double-branded on prod across /account, /for-it, /roadmap, /contact, /refund-policy, /dpa, /docs/deprecation, /docs/versioning, /guides, /guides/[slug], /status, /signin. Three changes, all component-level: 1. scripts/lint-seo-titles.mjs — extended with two new rules: - Rule A: walks every app/**/page.tsx, extracts title literals from static metadata exports and generateMetadata bodies, composes the rendered title with the root template suffix where applicable, asserts 'FreightUtils' appears at most once. Catches the regression class going forward. - Rule B: asserts ADR builder fixtures produce titles containing 'ADR 2025' (the dataset edition signal). Restores the year/edition context that was lost in a prior fix attempt. - Pre-fix run failed with 17 violations (12 Rule A + 5 Rule B). - Post-fix run: 103 title literals scanned across 54 page files, no double-brand; all 5 ADR fixtures contain 'ADR 2025'. 2. lib/seo/page-metadata.ts — buildAdrUnMetadata now emits 'ADR 2025' in the title suffix. Example: 'UN 1203 Petrol — ADR 2025 Class 3 PG II'. Adds 5 chars to the suffix; truncation logic for properShippingName absorbs the difference. Pinned via SITE_YEAR mapping ('2026' → '2025'), consistent with the same pattern already used in the meta description. 3. 12 page.tsx files — removed '— FreightUtils' from the per-page title string. Root template adds '| FreightUtils' once. /guides/[slug] changed from '${title} — FreightUtils Guide' to '${title} — Guide'; template adds '| FreightUtils' → 'Title — Guide | FreightUtils' (single brand, retains Guide content signal). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(seo): FAULT 15 + STATE baseline + CI gate + changelog (Phase 3-4) Phase 3: - STATE.md gains a 'Search performance baseline' subsection capturing the Bing Webmaster baseline (60 clicks / 4.7K imp / 1.29% CTR overall, 8 top zero-click queries with positions). Documents the Bing 3-7d / Google 7-21d re-crawl windows so the CTR uplift can be assessed against this baseline at 2026-05-23–05-30 (Bing) and 2026-05-30–06-06 (Google). GSC pull noted as Soap-manual pending (no GSC connector configured). - STATE.md Sprint cadence count bumped 18 -> 19 with PR #44 entry. - /changelog roadmap 'GSC CTR improvement' card: not present in app/roadmap/page.tsx — N/A. Phase 4: - FAULT 15 added to docs/FAULT-HISTORY-AND-PREVENTION.md as a new category. Includes root cause, symptom, detection mechanism (lint Rule A + Rule B), and prevention via CI gate. Plus a 2026-05-16 fault-log row referencing PR #44. - package.json: new 'prebuild' script runs lint:seo-titles before 'next build'. Vercel build now blocks any PR that introduces a double-brand title or drops the ADR edition signal. Release hygiene: - CHANGELOG.md gets a 2026-05-16 'SEO' entry at the top. - lib/changelog-data.ts gets a 2026-05-16 'Bug Fix' entry; verified Rule A still passes after the edit (new entry doesn't contain 'FreightUtils', the only safe form for a plain-string title). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: SoapyRED <soapyred@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Sprint
page-consistency-r1— five small fixes for documented R10/R11 consistency findings. The other four findings verified already-resolved by intervening PRs (#28, #30, #33) and required no action.Phase 0 verification table (pre-fix, against working tree of
origin/main@6f3d1b0)... | FreightUtils.com)app/layout.tsx:18template was'%s | FreightUtils.com'; 6 affected pages use plain-stringtitle:WebApplicationJSON-LD missing on/adr/lq-eq-checkerapp/adr/lq-eq-checker/page.tsx:29BreadcrumbListJSON-LD missing on ADR detailapp/adr/un/[unNumber]/page.tsx:356BreadcrumbListJSON-LD missing on HS detail (code + heading)app/hs/code/[subheadingCode]/page.tsx:44,app/hs/heading/[headingCode]/page.tsx:47#fffon/api-docsapp/api-docs/page.tsx:1816— intentional white-on-red error-code badge; left aloneapp/components/ThemeToggle.tsxhad nostoragelistener/api-docs25 requests per day per IP(×2),25 calls/day(×1)/adr/lq-eq-checkerlayout: email after API CTA, source badge, FAQ, 200-word minApiCtaBanner→DataTimestamp→NewsletterCaptureorder,FAQPageJSON-LD with 3 questions, ~500 words of educational contentFixes applied
app/layout.tsx: root title template'%s | FreightUtils.com'→'%s | FreightUtils'. Fixes 5 of the 6 documented pages in one component-level change.app/changelog/page.tsx: switched totitle.absoluteso the template doesn't duplicate "FreightUtils" in a title that already contains it.app/components/ThemeToggle.tsx: added astorageevent listener so a theme flip in one tab mirrors into other open tabs.app/api-docs/page.tsx: normalised the lone "25 calls/day" mention to "25 requests per day"; all three rate-limit references now match.app/hs/code/[subheadingCode]/page.tsx+app/hs/heading/[headingCode]/page.tsx: added<NewsletterCapture />below the "Found an error?" footer to match the ADR-detail / LQ-EQ pattern.lib/seo/page-metadata.ts: docstring updated to reflect new template.CHANGELOG.md+lib/changelog-data.ts: 2026-05-15 Bug Fix entry.Verification
Static-HTML output from
next build:tsc --noEmitclean. ESLint on touched files: only the pre-existingreact-hooks/set-state-in-effectwarning onThemeToggle.tsx:12(the originalsetDark(isDark)) — unrelated to this change.FAULT 5 checklist
next buildoutput)withAuditReston new API routes — N/AgenerateMetadataon new public pages — N/ATest plan
npx tsc --noEmitcleannpx next buildsucceeds; static HTML titles verified🤖 Generated with Claude Code
Generated by Claude Code