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 all commits
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="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.">
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="apple-touch-icon" href="/apple-touch-icon.png" />
<meta property="og:title" content="TinyStudio — What the appraisal checks">
<meta property="og: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.">
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="apple-touch-icon" href="/apple-touch-icon.png" />
<meta property="og:title" content="TinyStudio — The Website Appraisal">
<meta property="og: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.">
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="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.">
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="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.">
Expand Down
53 changes: 53 additions & 0 deletions scripts/check-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,59 @@ for (const [pageName, pageHtml] of internalLinkPages) {
}
}

// ---- 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"> — parsed across the whole document,
// ignoring commented-out markup and accepting single or double quotes — that
// link sits inside the head and points 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 canonicalLinkPattern = /<link\b[^>]*\brel\s*=\s*["']canonical["'][^>]*>/gi;

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 Parse canonical as a relation token

When a page retains the expected tag but gains a conflicting tag such as <link rel="alternate canonical" href="https://other.example/">, the check still passes because this pattern only recognizes a rel value consisting solely of canonical. HTML treats rel as a space-separated token list, so crawlers can still see the second canonical and receive conflicting signals; tokenize the attribute or match canonical at token boundaries so the duplicate guard covers valid multi-token values.

Useful? React with 👍 / 👎.

const canonicalHrefPattern = /\bhref\s*=\s*["']([^"']*)["']/i;

const seenCanonicals = new Map();
for (const [pageName, pageHtml, expected] of canonicalPages) {
// A commented-out canonical is inert markup; it must neither satisfy the
// guarantee nor trip the duplicate check.
const liveHtml = pageHtml.replace(/<!--[\s\S]*?-->/g, "");
const links = [...liveHtml.matchAll(canonicalLinkPattern)].map((match) => match[0]);
if (links.length !== 1) {
failures.push(`Canonical link must appear exactly once across ${pageName} (found ${links.length}).`);
continue;
}
const head = liveHtml.match(/<head[^>]*>([\s\S]*?)<\/head>/i)?.[1] ?? "";
const headLinks = [...head.matchAll(canonicalLinkPattern)].length;
if (headLinks !== 1) {
failures.push(`Canonical link on ${pageName} must sit inside the head (found ${headLinks} in head, ${links.length} total).`);
}
const href = links[0].match(canonicalHrefPattern)?.[1] ?? "";
const trimmed = href.trim();
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