Skip to content
This repository was archived by the owner on Sep 28, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
df69628
fix(public): point appraisal-page canonicals, og:url and JSON-LD WebP…
nish3451 Aug 15, 2026
ad952df
docs(evidence): record appraisal-page canonical/JSON-LD migration to …
nish3451 Aug 15, 2026
0a08d54
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
ca823c2
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
b0ba5a9
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
37c0293
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
e0d6053
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
9ee603d
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
c6c36a5
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
19f184a
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
97d7ead
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
68074f5
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
d5daf52
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
87851b0
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
d1467a3
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
8e691cb
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
da8aead
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 15, 2026
2f0933b
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 16, 2026
4d66b4e
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
bdb20d6
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
3b74ea5
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
95e3b24
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
b13e864
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
5ca8c37
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
33b9f6c
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
7d3a3cd
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
8b87ad3
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
03180fc
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
360389b
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 17, 2026
2465da2
Merge branch 'main' into fix/appraisal-page-clean-canonicals-lane1
nish3451 Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .lane/reports/fix-appraisal-page-clean-canonicals-lane1.md
Original file line number Diff line number Diff line change
@@ -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`
94 changes: 94 additions & 0 deletions docs/evidence/appraisal-canonicals-clean-urls-lane1-2026-08-15.md
Original file line number Diff line number Diff line change
@@ -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 `<link rel="canonical">`, `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 `<link rel="canonical">` in `<head>` 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`
8 changes: 4 additions & 4 deletions public/agents.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="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.">
<title>TinyStudio — The Desk</title>
<link rel="canonical" href="https://tinystudio.io/agents.html">
<link rel="canonical" href="https://tinystudio.io/agents">
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta property="og:title" content="TinyStudio — The Desk">
<meta property="og:description" content="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.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://tinystudio.io/agents.html">
<meta property="og:url" content="https://tinystudio.io/agents">
<meta property="og:image" content="https://tinystudio.io/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
Expand Down Expand Up @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions public/pricing.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="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.">
<title>TinyStudio — Pricing &amp; terms</title>
<link rel="canonical" href="https://tinystudio.io/pricing.html">
<link rel="canonical" href="https://tinystudio.io/pricing">
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta property="og:title" content="TinyStudio — Pricing &amp; terms">
<meta property="og:description" content="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.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://tinystudio.io/pricing.html">
<meta property="og:url" content="https://tinystudio.io/pricing">
<meta property="og:image" content="https://tinystudio.io/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
Expand Down Expand Up @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions public/specimen.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="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.">
<title>TinyStudio — The Website Appraisal — specimen</title>
<link rel="canonical" href="https://tinystudio.io/specimen.html">
<link rel="canonical" href="https://tinystudio.io/specimen">
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta property="og:title" content="TinyStudio — The Website Appraisal, specimen">
<meta property="og:description" content="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.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://tinystudio.io/specimen.html">
<meta property="og:url" content="https://tinystudio.io/specimen">
<meta property="og:image" content="https://tinystudio.io/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
Expand Down Expand Up @@ -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",
Expand Down
23 changes: 11 additions & 12 deletions scripts/check-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1530,9 +1530,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";

Expand Down Expand Up @@ -1604,9 +1604,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";
Expand Down Expand Up @@ -1799,15 +1799,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 = /<link\b[^>]*\brel\s*=\s*["']canonical["'][^>]*>/gi;
Expand Down
Loading