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 9 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
4555252
fix(public): give both appraisal intake fields persistent labels and …
nish3451 Aug 10, 2026
151cd77
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
376c9e1
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
a629d22
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
861c4c3
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
b207ccb
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
4e3dd6a
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
ca82a49
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
4305aa5
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
05b193d
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 10, 2026
e84317f
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 11, 2026
da55336
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 11, 2026
e833312
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 11, 2026
6c9b00e
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
aeb9046
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
e579060
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
d02d0e4
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
0a46aaa
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
38b00bc
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
ce8b2e6
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
b904de2
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
b0f7efe
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
beeac3c
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 12, 2026
3e44c6e
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
fc193df
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
06e7e10
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
0359f56
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
086da94
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
2271355
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
40691a7
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
53f8dcb
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
55efdd5
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
3e7d669
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
d28b460
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
7dcf48b
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
d5be818
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
2ed1508
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
c3942ea
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
b371b1b
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
a5601c3
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
9b09d8c
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
5c7b92b
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
389e347
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
4cc8a1a
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
6d128ca
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
e2710c5
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
3c13999
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
49e1393
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
eadbad1
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 13, 2026
741838c
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
c79cc4c
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
4c6d4fd
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
0e4a87a
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
bf80e42
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
599c124
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
71797e0
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
7592db0
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
b390d69
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
9ca4688
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
3f6698d
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
503c2a0
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
67ba587
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
ef2ee4b
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
6c5f0c3
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
28a3d92
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
a2695b4
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
8a9463f
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
7c534df
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
20439d0
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
e25378e
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
70176ec
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
cbf7f58
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
acb62b1
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
c4527e3
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
98b7cd1
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
5544b27
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
22f535e
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
f88b2af
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
ade833b
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
8890934
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 14, 2026
be782cd
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
8aa70e2
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
cc2f929
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
3afad78
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
a680be1
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
370fad5
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
fbf7373
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
08745f9
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
f2b3041
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
33d2a97
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
edb99ce
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
d55177d
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
318ffd7
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 2026
73f4ec9
Merge branch 'main' into fix/intake-labels-and-stale-titles
nish3451 Aug 15, 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
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">

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 Match the accessible name to the visible website prompt

For speech-input users, the visible prompt is yourwebsite.com, but the new aria-label overrides that text in the accessibility tree with Your website domain, which does not contain the visible wording; commands based on what the user sees may therefore fail to target the field. This mismatch is copied to both appraisal forms, so use a visible <label> or ensure the accessible name includes the placeholder text.

Useful? React with 👍 / 👎.

<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 @@ -1495,6 +1495,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 inert titles before validating the document title

When a page contains an earlier commented-out title such as <!-- <title>TinyStudio — old</title> -->, this raw-source regex selects that match and can let a missing or misbranded live title pass the check. Strip HTML comments and inspect the <head>—as the canonical check immediately above already does—before asserting the title, ideally also verifying that exactly one live title exists.

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")) {
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 each intake field before checking its label

If the email input is deleted or renamed, this continue leaves the page with zero matching email fields and records no failure, even though signupResponse rejects submissions without body.email (src/worker.js:352-359). I verified that deleting both email inputs still lets node scripts/check-site.mjs pass, so the new guard cannot prevent a regression that breaks the appraisal intake; assert that each page contains exactly one website and one email field before validating their labels.

Useful? React with 👍 / 👎.

const aria = tag.match(/\baria-label="([^"]*)"/)?.[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 Accept associated labels in the intake guard

When a developer replaces these attributes with associated visible <label> elements or aria-labelledby, the controls still have persistent programmatic names, but this regex resolves aria to an empty string and makes npm run check fail. The guard therefore hard-codes aria-label and rejects valid accessibility improvements; validate all supported accessible-name mechanisms instead.

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 +1848 to +1854

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 robust to valid HTML syntax.

Lines 1542-1547 only recognize exact double-quoted name and aria-label attributes. A valid name='website' input is skipped, and a valid aria-label='...' value is reported as missing. The loop also passes when a page has no matching inputs. Use a quote- and whitespace-tolerant matcher or parser, and assert that each page contains the expected website and email fields.

Proposed matcher fix
-    if (!/\bname="(?:website|email)"/.test(tag)) continue;
-    const aria = tag.match(/\baria-label="([^"]*)"/)?.[1] ?? "";
+    const nameMatch = tag.match(/\bname\s*=\s*(['"])(website|email)\1/i);
+    if (!nameMatch) continue;
+    const aria = tag.match(/\baria-label\s*=\s*(['"])(.*?)\1/i)?.[2] ?? "";
🤖 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 1541 - 1547, Update the intake input
validation loop in the site-check logic to parse HTML attributes with flexible
whitespace and either single or double quotes, so both name and aria-label
values are recognized. Track matching fields per page and add failures when the
expected website and email inputs are absent, while preserving the existing
requirement for non-empty programmatic aria-labels.

}
}
}

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