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 1 commit
Commits
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
1 change: 1 addition & 0 deletions public/agents.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<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="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" data-fonts-css href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,300;0,9..144,400;1,9..144,200;1,9..144,300&family=Karla:wght@300;400;500;600;700&display=swap">
<script src="fonts.js" defer></script>
Expand Down
1 change: 1 addition & 0 deletions public/audit.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="What the appraisal actually checks: four passes by hand, the evidence embedded — including a controlled AI-search test with verbatim answers and cited pages.">
<title>TinyStudio — The Website Appraisal</title>
<link rel="canonical" href="https://tinystudio.io/audit.html">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" data-fonts-css href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,300;0,9..144,400;1,9..144,200;1,9..144,300&family=Karla:wght@300;400;500;600;700&display=swap">
<script src="fonts.js" defer></script>
Expand Down
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="TinyStudio: the free leak audit of high-ticket service homepages. Each fault named in order of what it costs you, with the fix beside it. Six a month.">
<title>TinyStudio — The Website Appraisal</title>
<link rel="canonical" href="https://tinystudio.io/">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" data-fonts-css href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,300;0,9..144,400;1,9..144,200;1,9..144,300&family=Karla:wght@300;400;500;600;700&display=swap">
<script src="fonts.js" defer></script>
Expand Down
1 change: 1 addition & 0 deletions public/pricing.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<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>Pricing &amp; terms — The Tiny Studio</title>
<link rel="canonical" href="https://tinystudio.io/pricing.html">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" data-fonts-css href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,300;0,9..144,400;1,9..144,200;1,9..144,300&family=Karla:wght@300;400;500;600;700&display=swap">
<script src="fonts.js" defer></script>
Expand Down
1 change: 1 addition & 0 deletions public/specimen.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<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="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="style" data-fonts-css href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,300;0,9..144,400;1,9..144,200;1,9..144,300&family=Karla:wght@300;400;500;600;700&display=swap">
<script src="fonts.js" defer></script>
Expand Down
41 changes: 41 additions & 0 deletions scripts/check-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,47 @@ for (const [pageName, pageHtml] of metaDescriptionPages) {
}
}

// ---- Canonical URLs (dogfood) ----------------------------------------------
// The leak audit this site sells also flags a homepage whose served HTML
// carries no canonical URL, leaving search engines to guess which address is
// the page (finding 6631c0ab0454, "Missing canonical URL on home"), so the
// site's own five public pages must not carry that fault either. Each page
// keeps exactly one <link rel="canonical"> inside its head, pointing at the
// absolute https://tinystudio.io address the page is served under (the .html
// form; the worker also serves extensionless twins).
const canonicalPages = [
["homepage", siteHome, "https://tinystudio.io/"],
["audit page", siteAudit, "https://tinystudio.io/audit.html"],
["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"]
Comment on lines +1297 to +1300

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Canonicalize secondary pages to their final clean URLs

When search engines crawl these four pages, the new guard forces each canonical to its .html address even though the internal-link guard immediately above identifies those addresses as redirecting and names /audit, /agents, /pricing, and /specimen as the final clean destinations. Canonical URLs should point directly to those final URLs; otherwise every secondary page emits a canonical-to-redirect signal, and this check prevents correcting the tags.

Useful? React with 👍 / 👎.

];

const seenCanonicals = new Map();
for (const [pageName, pageHtml, expected] of canonicalPages) {
const head = pageHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
const links = [...head.matchAll(/<link\b[^>]*\brel="canonical"[^>]*>/gi)].map((match) => match[0]);
if (links.length !== 1) {
failures.push(`Canonical link must appear exactly once in the head of ${pageName} (found ${links.length}).`);
continue;
}
const href = links[0].match(/\bhref="([^"]*)"/i)?.[1] ?? "";
const trimmed = href.trim();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
if (!trimmed) {
failures.push(`Canonical link on ${pageName} must not have an empty href.`);
continue;
}
if (trimmed !== expected) {
failures.push(`Canonical link on ${pageName} must point at ${expected} (found ${trimmed}).`);
}
const prior = seenCanonicals.get(trimmed);
if (prior) {
failures.push(`Canonical URL on ${pageName} must be unique; it duplicates ${prior}.`);
} else {
seenCanonicals.set(trimmed, pageName);
}
}

for (const migration of ["migrations/0002_agent_runs.sql", "migrations/0003_agent_usage_limits.sql"]) { if (!existsSync(new URL(`../${migration}`, import.meta.url))) {
failures.push(`Missing migration: ${migration}`);
continue;
Expand Down
Loading