From b9be0e6acdd62a31f035e95e2e81152144ea1085 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Sun, 9 Aug 2026 08:32:44 +0530 Subject: [PATCH 1/2] seo: add canonical URLs to the five public pages (finding 6631c0ab0454) --- public/agents.html | 1 + public/audit.html | 1 + public/index.html | 1 + public/pricing.html | 1 + public/specimen.html | 1 + scripts/check-site.mjs | 41 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 46 insertions(+) diff --git a/public/agents.html b/public/agents.html index d838ab80..5ab878a7 100644 --- a/public/agents.html +++ b/public/agents.html @@ -5,6 +5,7 @@ TinyStudio — The Desk + diff --git a/public/audit.html b/public/audit.html index 504a425c..b90ac77c 100644 --- a/public/audit.html +++ b/public/audit.html @@ -5,6 +5,7 @@ TinyStudio — The Website Appraisal + diff --git a/public/index.html b/public/index.html index 25bd282a..2e62c8c9 100644 --- a/public/index.html +++ b/public/index.html @@ -5,6 +5,7 @@ TinyStudio — The Website Appraisal + diff --git a/public/pricing.html b/public/pricing.html index 6a4c7594..76384f52 100644 --- a/public/pricing.html +++ b/public/pricing.html @@ -5,6 +5,7 @@ Pricing & terms — The Tiny Studio + diff --git a/public/specimen.html b/public/specimen.html index d4817f27..445de018 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -5,6 +5,7 @@ TinyStudio — The Website Appraisal — specimen + diff --git a/scripts/check-site.mjs b/scripts/check-site.mjs index 16b2ffb8..53668368 100644 --- a/scripts/check-site.mjs +++ b/scripts/check-site.mjs @@ -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 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"] +]; + +const seenCanonicals = new Map(); +for (const [pageName, pageHtml, expected] of canonicalPages) { + const head = pageHtml.match(/]*>([\s\S]*?)<\/head>/i)?.[1] ?? ""; + const links = [...head.matchAll(/]*\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(); + 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; From 1f65f268b9fd0e6ca925891b5eb99052a5f4f831 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Sun, 9 Aug 2026 08:36:31 +0530 Subject: [PATCH 2/2] ci: harden canonical guard against body duplicates, comments, and quote variants --- scripts/check-site.mjs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/scripts/check-site.mjs b/scripts/check-site.mjs index 53668368..7cbed638 100644 --- a/scripts/check-site.mjs +++ b/scripts/check-site.mjs @@ -939,9 +939,11 @@ for (const [pageName, pageHtml] of metaDescriptionPages) { // 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 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). +// keeps exactly one — 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"], @@ -950,15 +952,25 @@ const canonicalPages = [ ["specimen page", read("public/specimen.html"), "https://tinystudio.io/specimen.html"] ]; +const canonicalLinkPattern = /]*\brel\s*=\s*["']canonical["'][^>]*>/gi; +const canonicalHrefPattern = /\bhref\s*=\s*["']([^"']*)["']/i; + const seenCanonicals = new Map(); for (const [pageName, pageHtml, expected] of canonicalPages) { - const head = pageHtml.match(/]*>([\s\S]*?)<\/head>/i)?.[1] ?? ""; - const links = [...head.matchAll(/]*\brel="canonical"[^>]*>/gi)].map((match) => match[0]); + // A commented-out canonical is inert markup; it must neither satisfy the + // guarantee nor trip the duplicate check. + const liveHtml = pageHtml.replace(//g, ""); + const links = [...liveHtml.matchAll(canonicalLinkPattern)].map((match) => match[0]); if (links.length !== 1) { - failures.push(`Canonical link must appear exactly once in the head of ${pageName} (found ${links.length}).`); + failures.push(`Canonical link must appear exactly once across ${pageName} (found ${links.length}).`); continue; } - const href = links[0].match(/\bhref="([^"]*)"/i)?.[1] ?? ""; + const head = liveHtml.match(/]*>([\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.`);