Skip to content
4 changes: 2 additions & 2 deletions public/audit.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@
<h1>We read your page <em>as a customer with intent reads it.</em></h1>
<p class="sub">Four passes, by hand, on the surfaces that decide whether someone who arrived ready to buy stays that way. You get a short, plain document — each fault named, in order of what it costs you, with the fix beside each one. No deck. No dashboard.</p>
<form class="lead two" id="start" action="/api/signups" method="post">
<input type="text" name="website" required inputmode="url" autocomplete="url" placeholder="yourwebsite.com" pattern="(https?://)?([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(:[0-9]+)?(/[^\s]*)?" title="Enter your domain, like example.com">
<input type="email" name="email" required placeholder="Your work email">
<input type="text" name="website" required inputmode="url" autocomplete="url" placeholder="yourwebsite.com" aria-label="Your website domain" pattern="(https?://)?([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(:[0-9]+)?(/[^\s]*)?" title="Enter your domain, like example.com">
<input type="email" name="email" required placeholder="Your work email" aria-label="Your work email">
<button>Request the appraisal</button>
</form>
<p class="micro">Thirty seconds to ask. Findings inside five working days. Yours to keep either way.</p>
Expand Down
2 changes: 1 addition & 1 deletion public/brief-requested.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Request received — The Tiny Studio</title>
<title>Request received — TinyStudio</title>
<meta name="robots" content="noindex, nofollow">
<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">
Expand Down
4 changes: 2 additions & 2 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
<h1>Most of them leave <em>before they ever get in touch.</em></h1>
<p class="sub">We read the one page your revenue depends on the way a customer with intent reads it, and show you the exact points at which they go.</p>
<form class="lead two" id="start" action="/api/signups" method="post">
<input type="text" name="website" required inputmode="url" autocomplete="url" placeholder="yourwebsite.com" pattern="(https?://)?([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(:[0-9]+)?(/[^\s]*)?" title="Enter your domain, like example.com">
<input type="email" name="email" required placeholder="Your work email">
<input type="text" name="website" required inputmode="url" autocomplete="url" placeholder="yourwebsite.com" aria-label="Your website domain" pattern="(https?://)?([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(:[0-9]+)?(/[^\s]*)?" title="Enter your domain, like example.com">
<input type="email" name="email" required placeholder="Your work email" aria-label="Your work email">
<button>Show me where our site undersells us</button>
</form>
<p class="micro">Thirty seconds to ask. Findings inside five working days. No call at any point.</p>
Expand Down
2 changes: 1 addition & 1 deletion public/pricing.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8">
<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>
<title>TinyStudio — Pricing &amp; terms</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">
Expand Down
57 changes: 57 additions & 0 deletions scripts/check-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1547,6 +1547,63 @@ for (const [pageName, pageHtml, expected] of canonicalPages) {
}
}

// ---- Document titles (brand consistency) ----------------------------------
// Two served pages still branded themselves "The Tiny Studio" — the spaced
// name the site's own identity copy disavows (it collides with "The Tiny
// Studio LA" and other unrelated businesses) — while every other title said
// "TinyStudio": /pricing served "Pricing & terms — The Tiny Studio" and
// /brief-requested served "Request received — The Tiny Studio", both
// byte-identical on origin/main. Title tags are a first-order SERP signal, so
// every one of the six served appraisal pages must now name the brand in its
// document title and must never return the spaced "The Tiny Studio" form.
// The retired /agent-desk surface is deliberately excluded: its title frames
// itself as retired and it is noindex.
const titlePages = [
["homepage", siteHome],
["audit page", siteAudit],
["desk page", read("public/agents.html")],
["pricing page", read("public/pricing.html")],
["specimen page", read("public/specimen.html")],
["brief-requested page", read("public/brief-requested.html")]
];

for (const [pageName, pageHtml] of titlePages) {
const title = pageHtml.match(/<title>([^<]*)<\/title>/i)?.[1] ?? "";

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 Ignore commented-out titles when enforcing branding

If an editor leaves a branded <title>TinyStudio…</title> inside an HTML comment before restoring a live The Tiny Studio title, this first-match regex selects the inert commented tag and the check passes even though the browser serves the stale branding. Strip HTML comments before matching—like the canonical check immediately above already does—and preferably inspect the live <head> content.

Useful? React with 👍 / 👎.

if (!title) {
failures.push(`Document title must exist on ${pageName}.`);
continue;
}
if (!title.includes("TinyStudio")) {
failures.push(`Document title on ${pageName} must name TinyStudio (found ${JSON.stringify(title)}).`);
}
if (title.includes("The Tiny Studio")) {

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 Compare the forbidden title case-insensitively

If a title regresses to a capitalization variant such as The tiny studio — TinyStudio, this check passes even though the page still returns the spaced brand form the guard is intended to prohibit. The existing identity scan above normalizes both sides to lowercase; apply the same normalization here so casing changes cannot bypass this regression check.

Useful? React with 👍 / 👎.

failures.push(`Document title on ${pageName} must not use the spaced "The Tiny Studio" form (found ${JSON.stringify(title)}).`);
}
}

// ---- Intake field labels (activation) -------------------------------------
// Both appraisal intake forms (homepage and /audit) labelled their fields
// only with placeholder text, which disappears the moment a buyer starts
// typing and is not a persistent programmatic label. Each intake input must
// carry a non-empty aria-label so the field keeps its name for assistive
// tech and for the browser's own validation announcements, no matter what
// the field contains.
const intakePages = [
["homepage", siteHome],
["audit page", siteAudit]
];

for (const [pageName, pageHtml] of intakePages) {
for (const input of pageHtml.matchAll(/<input\b[^>]*>/gi)) {
const tag = input[0];
if (!/\bname="(?:website|email)"/.test(tag)) continue;

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 Require the email field before validating its label

If the email input is deleted or renamed on either intake page, this filter simply finds no email field and npm run check still passes; the resulting form then submits without body.email, which signupResponse rejects as invalid_email, making that intake unusable. Assert that each page contains exactly one website and one email input before checking their accessible labels.

Useful? React with 👍 / 👎.

const aria = tag.match(/\baria-label="([^"]*)"/)?.[1] ?? "";
Comment on lines +1599 to +1600

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 intake attributes independent of quoting

If a live intake field is rewritten with single-quoted or differently cased attributes—both valid in HTML—this exact name="..." filter skips the field entirely, allowing its aria-label to be removed while npm run check still passes. For example, changing the homepage email field to name='email' without an accessible label bypasses this guard, so attribute extraction should accept valid HTML quoting and casing.

Useful? React with 👍 / 👎.

if (!aria.trim()) {
failures.push(`Intake input on ${pageName} must carry a persistent programmatic aria-label (placeholder-only labels disappear as buyers type): ${tag}`);
}
Comment on lines +1596 to +1603

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the intake-field check fail closed.

The loop validates only inputs that it finds. It does not fail when a required website or email input is missing. The attribute checks also accept only double quotes with no spaces around =. Valid markup such as <input name = 'email' aria-label = ''> can bypass the check.

Track the expected field names, use format-tolerant attribute parsing, and fail when either expected field is absent or unlabeled.

Suggested validation change
 for (const [pageName, pageHtml] of intakePages) {
+  const expectedNames = new Set(["website", "email"]);
+  const seenNames = new Set();
   for (const input of pageHtml.matchAll(/<input\b[^>]*>/gi)) {
     const tag = input[0];
-    if (!/\bname="(?:website|email)"/.test(tag)) continue;
-    const aria = tag.match(/\baria-label="([^"]*)"/)?.[1] ?? "";
+    const name = tag.match(/(?:^|\s)name\s*=\s*(["'])([\s\S]*?)\1/i)?.[2]?.toLowerCase();
+    if (!expectedNames.has(name)) continue;
+    seenNames.add(name);
+    const aria = tag.match(/(?:^|\s)aria-label\s*=\s*(["'])([\s\S]*?)\1/i)?.[2] ?? "";
     if (!aria.trim()) {
       failures.push(`Intake input on ${pageName} must carry a persistent programmatic aria-label (placeholder-only labels disappear as buyers type): ${tag}`);
     }
   }
+  for (const name of expectedNames) {
+    if (!seenNames.has(name)) {
+      failures.push(`Missing intake input on ${pageName}: ${name}.`);
+    }
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const [pageName, pageHtml] of intakePages) {
for (const input of pageHtml.matchAll(/<input\b[^>]*>/gi)) {
const tag = input[0];
if (!/\bname="(?:website|email)"/.test(tag)) continue;
const aria = tag.match(/\baria-label="([^"]*)"/)?.[1] ?? "";
if (!aria.trim()) {
failures.push(`Intake input on ${pageName} must carry a persistent programmatic aria-label (placeholder-only labels disappear as buyers type): ${tag}`);
}
for (const [pageName, pageHtml] of intakePages) {
const expectedNames = new Set(["website", "email"]);
const seenNames = new Set();
for (const input of pageHtml.matchAll(/<input\b[^>]*>/gi)) {
const tag = input[0];
const name = tag.match(/(?:^|\s)name\s*=\s*(["'])([\s\S]*?)\1/i)?.[2]?.toLowerCase();
if (!expectedNames.has(name)) continue;
seenNames.add(name);
const aria = tag.match(/(?:^|\s)aria-label\s*=\s*(["'])([\s\S]*?)\1/i)?.[2] ?? "";
if (!aria.trim()) {
failures.push(`Intake input on ${pageName} must carry a persistent programmatic aria-label (placeholder-only labels disappear as buyers type): ${tag}`);
}
}
for (const name of expectedNames) {
if (!seenNames.has(name)) {
failures.push(`Missing intake input on ${pageName}: ${name}.`);
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-site.mjs` around lines 1581 - 1588, Update the intake-page
validation loop around pageHtml.matchAll to track required website and email
fields per page, parse name and aria-label attributes with whitespace- and
quote-tolerant matching, and mark each discovered field as present. After
scanning each page, add failures for any required field that is missing or whose
parsed aria-label is empty, preserving the existing diagnostic context.

}
}

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