diff --git a/.lane/reports/fix-appraisal-page-clean-canonicals-lane1.md b/.lane/reports/fix-appraisal-page-clean-canonicals-lane1.md new file mode 100644 index 00000000..26432f0c --- /dev/null +++ b/.lane/reports/fix-appraisal-page-clean-canonicals-lane1.md @@ -0,0 +1,56 @@ +# Lane 1 report — appraisal-page canonicals and JSON-LD @ids at clean non-307 URLs + +Item: "Point appraisal-page canonicals and JSON-LD WebPage @ids at the clean +URLs that do not 307" (finding item f7a18209b7, `[unreviewed-by-opus]`). + +Branch: `fix/appraisal-page-clean-canonicals-lane1` +Commit: `df69628` + +## What was wrong + +PR #56 (commit 1cc7a4e) migrated only the audit page's canonical, `og:url`, +and JSON-LD `WebPage` `@id`/`url` from the `.html` form to the clean `/audit` +form. The three remaining appraisal pages still named the 307-redirecting +`.html` twins in all three places: + +- `public/agents.html` -> `https://tinystudio.io/agents.html` +- `public/pricing.html` -> `https://tinystudio.io/pricing.html` +- `public/specimen.html` -> `https://tinystudio.io/specimen.html` + +Live verification (2026-08-15, `curl -sI`): + +| Address | HTTP | Location | +|---|---|---| +| `https://tinystudio.io/agents.html` | 307 | `/agents` | +| `https://tinystudio.io/agents` | 200 | — | +| `https://tinystudio.io/pricing.html` | 307 | `/pricing` | +| `https://tinystudio.io/pricing` | 200 | — | +| `https://tinystudio.io/specimen.html` | 307 | `/specimen` | +| `https://tinystudio.io/specimen` | 200 | — | + +The sitemap already lists the clean forms (`/audit`, `/agents`, `/pricing`, +`/specimen`), and the internal-links guard already forbids `.html` hrefs — the +canonicals and JSON-LD were the remaining place naming the redirecting twins. + +## Change + +- `public/agents.html`: canonical, `og:url`, JSON-LD `@id`/`url` -> + `https://tinystudio.io/agents` (+`#webpage`) +- `public/pricing.html`: canonical, `og:url`, JSON-LD `@id`/`url` -> + `https://tinystudio.io/pricing` (+`#webpage`) +- `public/specimen.html`: canonical, `og:url`, JSON-LD `@id`/`url` -> + `https://tinystudio.io/specimen` (+`#webpage`) +- `scripts/check-site.mjs`: the canonical, social-share and structured-data + guard expectations for the three pages moved to the clean forms (the same + shape PR #56 used for the audit page), and the canonical-guard comment no + longer defers the migration. + +## Evidence + +- `npm run check` -> "TinyStudio.io checks passed." (canonical guard now + expects `/agents`, `/pricing`, `/specimen`) +- `npm test` -> all suites pass: check, headings 6/6, sitemap 7/7, worker + 80/80, ui 16/16, contract 8/8, viewport 4/4, narrow pages + narrow + viewports. +- Live 307/200 measurement above. +- Evidence doc: `docs/evidence/appraisal-canonicals-clean-urls-lane1-2026-08-15.md` diff --git a/docs/evidence/appraisal-canonicals-clean-urls-lane1-2026-08-15.md b/docs/evidence/appraisal-canonicals-clean-urls-lane1-2026-08-15.md new file mode 100644 index 00000000..26b71930 --- /dev/null +++ b/docs/evidence/appraisal-canonicals-clean-urls-lane1-2026-08-15.md @@ -0,0 +1,94 @@ +# Appraisal-page canonicals and JSON-LD @ids at the clean non-307 URLs — lane 1 (2026-08-15) + +Item: "Point appraisal-page canonicals and JSON-LD WebPage @ids at the clean +URLs that do not 307" (finding item f7a18209b7, `[unreviewed-by-opus]`). + +## What the item asks + +Every appraisal page must name, in its ``, `og:url`, +and JSON-LD `WebPage` `@id`/`url`, the address that serves 200 — the clean +extensionless form — never the `.html` twin that the deployed worker +307-redirects to it. + +## State before this change + +PR #56 (commit `1cc7a4e`, "point appraisal-page canonicals and JSON-LD @ids +at the clean /audit URL") migrated only `public/audit.html`. The +`check-site.mjs` canonical guard carried the deferral note: "the audit +page's canonical is the clean /audit, while the remaining pages keep their +.html form here until they are migrated the same way." The three remaining +pages still named the redirecting `.html` forms in canonical, `og:url`, and +JSON-LD `WebPage` `@id`/`url`: + +- `public/agents.html` -> `https://tinystudio.io/agents.html` +- `public/pricing.html` -> `https://tinystudio.io/pricing.html` +- `public/specimen.html` -> `https://tinystudio.io/specimen.html` + +## Live measurement (2026-08-15) + +`curl -sI` against the deployed site: + +| Address | HTTP | Location | +|---|---|---| +| `https://tinystudio.io/agents.html` | 307 | `/agents` | +| `https://tinystudio.io/agents` | 200 | — | +| `https://tinystudio.io/pricing.html` | 307 | `/pricing` | +| `https://tinystudio.io/pricing` | 200 | — | +| `https://tinystudio.io/specimen.html` | 307 | `/specimen` | +| `https://tinystudio.io/specimen` | 200 | — | +| `https://tinystudio.io/audit.html` | 307 | `/audit` | +| `https://tinystudio.io/audit` | 200 | — | + +The sitemap already lists the clean forms (`/audit`, `/agents`, `/pricing`, +`/specimen`), and the internal-links guard already forbids `.html` hrefs — +the canonicals, `og:url`s, and JSON-LD identities were the remaining places +naming the redirecting twins. + +## Fix (commit df69628) + +Four lines per page, the exact shape of PR #56's audit migration: + +- `public/agents.html`: canonical, `og:url`, JSON-LD `@id`/`url` -> + `https://tinystudio.io/agents` (`@id` keeps `#webpage`) +- `public/pricing.html`: canonical, `og:url`, JSON-LD `@id`/`url` -> + `https://tinystudio.io/pricing` (`@id` keeps `#webpage`) +- `public/specimen.html`: canonical, `og:url`, JSON-LD `@id`/`url` -> + `https://tinystudio.io/specimen` (`@id` keeps `#webpage`) +- `scripts/check-site.mjs`: the canonical, social-share (`og:url`) and + structured-data (`WebPage` `@id`/`url`) guard expectations for the three + pages moved to the clean forms; the canonical-guard comment no longer + defers the migration. + +No page's Organization/WebSite nodes, titles, descriptions, or internal +links changed. The `llms.txt` Answer Readiness section already maps to the +clean forms (`https://tinystudio.io/pricing`, `/audit`, `/`), and the +AI-search evidence fixture records verbatim historical citations, so neither +was touched. + +## Source checks + +`npm run check` -> "TinyStudio.io checks passed." The canonical guard now +expects exactly one non-commented `` in `` per +page pointing at `https://tinystudio.io/agents|pricing|specimen|audit|` +(home), all distinct. + +`npm test` -> all suites pass: + +| Suite | Result | +|---|---| +| check (site) | pass | +| headings | 6/6 | +| sitemap | 7/7 | +| worker | 80/80 | +| ui | 16/16 | +| contract | 8/8 | +| viewport | 4/4 | +| narrow pages / narrow | pass | + +## Delivery + +- Branch: `fix/appraisal-page-clean-canonicals-lane1` (pushed to origin) +- Commit: `df69628` +- Files: `public/agents.html`, `public/pricing.html`, + `public/specimen.html`, `scripts/check-site.mjs`, + `.lane/reports/fix-appraisal-page-clean-canonicals-lane1.md` diff --git a/public/agents.html b/public/agents.html index 44495da3..2e3117d3 100644 --- a/public/agents.html +++ b/public/agents.html @@ -5,13 +5,13 @@ TinyStudio — The Desk - + - + @@ -42,8 +42,8 @@ }, { "@type": "WebPage", - "@id": "https://tinystudio.io/agents.html#webpage", - "url": "https://tinystudio.io/agents.html", + "@id": "https://tinystudio.io/agents#webpage", + "url": "https://tinystudio.io/agents", "name": "TinyStudio — The Desk", "description": "Seven specialists, one human signature. Each does a single job on your actual pages and numbers; nothing reaches you until a person has read and signed it.", "inLanguage": "en", diff --git a/public/pricing.html b/public/pricing.html index 5e513b12..825f772d 100644 --- a/public/pricing.html +++ b/public/pricing.html @@ -5,13 +5,13 @@ TinyStudio — Pricing & terms - + - + @@ -42,8 +42,8 @@ }, { "@type": "WebPage", - "@id": "https://tinystudio.io/pricing.html#webpage", - "url": "https://tinystudio.io/pricing.html", + "@id": "https://tinystudio.io/pricing#webpage", + "url": "https://tinystudio.io/pricing", "name": "TinyStudio — Pricing & terms", "description": "One price, plainly stated: the appraisal is free, the desk is $2,500 a month on a three-month minimum, and the delivery guarantee is written out in full.", "inLanguage": "en", diff --git a/public/specimen.html b/public/specimen.html index bcb96b72..53eb4cec 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -5,13 +5,13 @@ TinyStudio — The Website Appraisal — specimen - + - + @@ -42,8 +42,8 @@ }, { "@type": "WebPage", - "@id": "https://tinystudio.io/specimen.html#webpage", - "url": "https://tinystudio.io/specimen.html", + "@id": "https://tinystudio.io/specimen#webpage", + "url": "https://tinystudio.io/specimen", "name": "TinyStudio — The Website Appraisal, specimen", "description": "The appraisal, with the client's name removed: a real audit of a clinic homepage, four findings with evidence and fixes, read and signed by Nish.", "inLanguage": "en", diff --git a/scripts/check-site.mjs b/scripts/check-site.mjs index a130cbf8..d863990a 100644 --- a/scripts/check-site.mjs +++ b/scripts/check-site.mjs @@ -1557,9 +1557,9 @@ if (!worker.includes("https://cloudflareinsights.com")) { const socialSharePages = [ ["homepage", siteHome, "https://tinystudio.io/"], ["audit page", siteAudit, "https://tinystudio.io/audit"], - ["desk page", read("public/agents.html"), "https://tinystudio.io/agents.html"], - ["pricing page", read("public/pricing.html"), "https://tinystudio.io/pricing.html"], - ["specimen page", read("public/specimen.html"), "https://tinystudio.io/specimen.html"] + ["desk page", read("public/agents.html"), "https://tinystudio.io/agents"], + ["pricing page", read("public/pricing.html"), "https://tinystudio.io/pricing"], + ["specimen page", read("public/specimen.html"), "https://tinystudio.io/specimen"] ]; const SOCIAL_IMAGE_URL = "https://tinystudio.io/og-image.png"; @@ -1631,9 +1631,9 @@ for (const [pageName, pageHtml, pageUrl] of socialSharePages) { const structuredDataPages = [ ["homepage", siteHome, "https://tinystudio.io/"], ["audit page", siteAudit, "https://tinystudio.io/audit"], - ["desk page", read("public/agents.html"), "https://tinystudio.io/agents.html"], - ["pricing page", read("public/pricing.html"), "https://tinystudio.io/pricing.html"], - ["specimen page", read("public/specimen.html"), "https://tinystudio.io/specimen.html"] + ["desk page", read("public/agents.html"), "https://tinystudio.io/agents"], + ["pricing page", read("public/pricing.html"), "https://tinystudio.io/pricing"], + ["specimen page", read("public/specimen.html"), "https://tinystudio.io/specimen"] ]; const ORGANIZATION_ID = "https://tinystudio.io/#organization"; @@ -1863,15 +1863,14 @@ for (const [pageName, pageHtml] of internalLinkPages) { // link sits inside the head and points at the absolute https://tinystudio.io // address of the page. The deployed worker 307-redirects every .html form to // its clean extensionless twin, so a canonical must name the address that -// serves 200, never the redirecting form: the audit page's canonical is the -// clean /audit, while the remaining pages keep their .html form here until -// they are migrated the same way. +// serves 200, never the redirecting form: every page's canonical names its +// clean extensionless address. const canonicalPages = [ ["homepage", siteHome, "https://tinystudio.io/"], ["audit page", siteAudit, "https://tinystudio.io/audit"], - ["desk page", read("public/agents.html"), "https://tinystudio.io/agents.html"], - ["pricing page", read("public/pricing.html"), "https://tinystudio.io/pricing.html"], - ["specimen page", read("public/specimen.html"), "https://tinystudio.io/specimen.html"] + ["desk page", read("public/agents.html"), "https://tinystudio.io/agents"], + ["pricing page", read("public/pricing.html"), "https://tinystudio.io/pricing"], + ["specimen page", read("public/specimen.html"), "https://tinystudio.io/specimen"] ]; const canonicalLinkPattern = /]*\brel\s*=\s*["']canonical["'][^>]*>/gi;