From 41b7da1cbfb68a90f5a8e4c2c2812c5c73c4655a Mon Sep 17 00:00:00 2001 From: Nish <257724087+nish3451@users.noreply.github.com> Date: Tue, 11 Aug 2026 06:17:27 +0530 Subject: [PATCH] fix(public): guard the live deployment against WCAG 2.2 tap-target drift PR #22 (footer) and #25 (in-content) brought every link on the public pages up to the WCAG 2.2 SC 2.5.8 24px minimum in public/styles.css. The grok backlog item re-opened because the live site was still serving the pre-fix stylesheet: rendered audits of the deployed pages found ~17px in-content links (e.g. support emails, privacy-choices, top-nav) while the worktree renders every target >= 24px at 320/390/1280px. Add scripts/test-public-live-tap-targets.mjs, wired into npm test and npm run ci: it fetches the deployed https://tinystudio.in/styles.css and re-asserts the same five link rules the local suite requires (.top-nav a, .plain-list a, .product-links a, .rail-item strong a, .footer-links a: block-level box, min-height 24px, >= 4px vertical padding). Network-tolerant: skips when the site is unreachable, fails loudly when it serves a stylesheet that misses the rules. Currently fails against the live deployment (11 checks) until the live site is refreshed from origin/main - that failure is the point. --- package.json | 4 +- scripts/test-public-live-tap-targets.mjs | 100 +++++++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 scripts/test-public-live-tap-targets.mjs diff --git a/package.json b/package.json index 340ea7d9..1cd5e59d 100644 --- a/package.json +++ b/package.json @@ -85,9 +85,9 @@ "product:truth": "node scripts/check-product-truth.mjs", "mobbin:run": "node scripts/run-design-system-proving-lab.mjs", "mobbin:check": "node scripts/run-design-system-proving-lab.mjs --dry-run", - "ci": "node scripts/test-service-engine.mjs && node scripts/test-sales-intake-contract.mjs && node scripts/test-active-offer-projection.mjs && node scripts/test-active-operator-surfaces.mjs && node scripts/test-direction-proof-gate.mjs && node scripts/test-client-readiness-contract.mjs && node scripts/test-validated-service-client.mjs && node scripts/test-client-acceptance-gates.mjs && node scripts/check-product-truth.mjs && node scripts/check-human-service-kit.mjs && node scripts/test-design-system-proving-lab.mjs && node scripts/check-retention-automation.mjs && node scripts/test-retention-automation.mjs && node scripts/check-agency-defaults.mjs && node scripts/check-outbound-claim-safety.mjs && node scripts/check-outbound-send-readiness.mjs && node scripts/test-outbound-send-readiness.mjs && node scripts/test-public-conversion-signal.mjs && node scripts/test-public-structured-data.mjs && node scripts/test-public-brand-disambiguation.mjs && node scripts/test-public-heading-hierarchy.mjs && node scripts/test-public-link-targets.mjs && node scripts/test-public-social-preview.mjs && node scripts/test-public-soft-404.mjs && find scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check", + "ci": "node scripts/test-service-engine.mjs && node scripts/test-sales-intake-contract.mjs && node scripts/test-active-offer-projection.mjs && node scripts/test-active-operator-surfaces.mjs && node scripts/test-direction-proof-gate.mjs && node scripts/test-client-readiness-contract.mjs && node scripts/test-validated-service-client.mjs && node scripts/test-client-acceptance-gates.mjs && node scripts/check-product-truth.mjs && node scripts/check-human-service-kit.mjs && node scripts/test-design-system-proving-lab.mjs && node scripts/check-retention-automation.mjs && node scripts/test-retention-automation.mjs && node scripts/check-agency-defaults.mjs && node scripts/check-outbound-claim-safety.mjs && node scripts/check-outbound-send-readiness.mjs && node scripts/test-outbound-send-readiness.mjs && node scripts/test-public-conversion-signal.mjs && node scripts/test-public-structured-data.mjs && node scripts/test-public-brand-disambiguation.mjs && node scripts/test-public-heading-hierarchy.mjs && node scripts/test-public-link-targets.mjs && node scripts/test-public-live-tap-targets.mjs && node scripts/test-public-social-preview.mjs && node scripts/test-public-soft-404.mjs && find scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check", "check": "npm test", - "test": "node scripts/test-service-engine.mjs && node scripts/test-sales-intake-contract.mjs && node scripts/test-active-offer-projection.mjs && node scripts/test-active-operator-surfaces.mjs && node scripts/test-direction-proof-gate.mjs && node scripts/test-client-readiness-contract.mjs && node scripts/test-validated-service-client.mjs && node scripts/test-client-acceptance-gates.mjs && node scripts/check-product-truth.mjs && node scripts/check-human-service-kit.mjs && node scripts/test-design-system-proving-lab.mjs && node scripts/check-retention-automation.mjs && node scripts/test-retention-automation.mjs && node scripts/check-agency-defaults.mjs && node scripts/check-outbound-claim-safety.mjs && node scripts/check-outbound-send-readiness.mjs && node scripts/test-outbound-send-readiness.mjs && node scripts/test-public-conversion-signal.mjs && node scripts/test-public-structured-data.mjs && node scripts/test-public-brand-disambiguation.mjs && node scripts/test-public-heading-hierarchy.mjs && node scripts/test-public-link-targets.mjs && node scripts/test-public-social-preview.mjs && node scripts/test-public-soft-404.mjs && find scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check" + "test": "node scripts/test-service-engine.mjs && node scripts/test-sales-intake-contract.mjs && node scripts/test-active-offer-projection.mjs && node scripts/test-active-operator-surfaces.mjs && node scripts/test-direction-proof-gate.mjs && node scripts/test-client-readiness-contract.mjs && node scripts/test-validated-service-client.mjs && node scripts/test-client-acceptance-gates.mjs && node scripts/check-product-truth.mjs && node scripts/check-human-service-kit.mjs && node scripts/test-design-system-proving-lab.mjs && node scripts/check-retention-automation.mjs && node scripts/test-retention-automation.mjs && node scripts/check-agency-defaults.mjs && node scripts/check-outbound-claim-safety.mjs && node scripts/check-outbound-send-readiness.mjs && node scripts/test-outbound-send-readiness.mjs && node scripts/test-public-conversion-signal.mjs && node scripts/test-public-structured-data.mjs && node scripts/test-public-brand-disambiguation.mjs && node scripts/test-public-heading-hierarchy.mjs && node scripts/test-public-link-targets.mjs && node scripts/test-public-live-tap-targets.mjs && node scripts/test-public-social-preview.mjs && node scripts/test-public-soft-404.mjs && find scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check" }, "engines": { "node": ">=20" diff --git a/scripts/test-public-live-tap-targets.mjs b/scripts/test-public-live-tap-targets.mjs new file mode 100644 index 00000000..bfd0aefc --- /dev/null +++ b/scripts/test-public-live-tap-targets.mjs @@ -0,0 +1,100 @@ +import { readFileSync } from "node:fs" +import { fileURLToPath } from "node:url" +import { dirname, join } from "node:path" + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..") +const read = (p) => readFileSync(join(ROOT, p), "utf8") + +let failures = 0 +let checks = 0 +const ok = (cond, msg) => { + checks++ + if (cond) console.log(` ok ${msg}`) + else { + failures++ + console.error(` FAIL ${msg}`) + } +} + +// The deployed stylesheet must keep every in-content and footer link rule at +// the WCAG 2.2 (SC 2.5.8) 24px minimum. The local suite +// (test-public-link-targets.mjs) asserts the same rules against +// public/styles.css; this guard re-asserts them against the stylesheet the +// live site actually serves, so a stale or drifted deployment fails loudly +// instead of silently re-opening the tap-target backlog item. +const LIVE_CSS_URL = "https://tinystudio.in/styles.css" +const FETCH_TIMEOUT_MS = 10_000 + +// Same selectors test-public-link-targets.mjs requires of the local sheet. +const TAP_TARGET_SELECTORS = [ + ".top-nav a", + ".plain-list a", + ".product-links a", + ".rail-item strong a", + ".footer-links a" +] + +const targetRuleOf = (css, selector) => { + const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + const pattern = selector + .split(",") + .map((s) => esc(s.trim())) + .join("\\s*,\\s*") + const match = css.match(new RegExp(`${pattern}\\s*\\{([^}]*)\\}`)) + return match ? match[1] : null +} + +const assert24pxRule = (selector, rule) => { + ok(rule !== null, `live styles.css has a ${selector} rule`) + if (rule) { + ok(/display:\s*(inline-block|inline-flex|block)/.test(rule), `live ${selector} links are block-level boxes (hit area covers the line box)`) + ok(!/display:\s*inline\s*;/.test(rule), `live ${selector} links are not plain inline boxes`) + ok(/min-height:\s*24px/.test(rule), `live ${selector} links declare min-height: 24px`) + const paddingMatch = rule.match(/padding:\s*([^;]+)/) + ok(paddingMatch !== null, `live ${selector} links declare vertical padding`) + if (paddingMatch) { + const vertical = parseFloat(paddingMatch[1].trim().split(/\s+/)[0]) + ok(vertical >= 4, `live ${selector} vertical padding is at least 4px (${vertical}px), so 16px text + padding >= 24px`) + } + } +} + +console.log("test-public-live-tap-targets: the deployed tinystudio.in stylesheet keeps every link rule at the WCAG 2.2 24px minimum") + +let css = null +try { + const res = await fetch(LIVE_CSS_URL, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) }) + if (!res.ok) { + console.log(` ok skipped: ${LIVE_CSS_URL} answered ${res.status}, deployment not reachable - no tap-target assertions run`) + } else { + css = await res.text() + } +} catch (err) { + console.log(` ok skipped: ${LIVE_CSS_URL} unreachable (${err?.cause?.code ?? err?.name ?? "network error"}) - no tap-target assertions run`) +} + +if (css !== null) { + console.log(`A. every link rule in the served ${LIVE_CSS_URL} enforces the 24px minimum`) + for (const selector of TAP_TARGET_SELECTORS) { + assert24pxRule(selector, targetRuleOf(css, selector)) + } + if (failures === 0) { + console.log(" the deployed stylesheet carries every tap-target rule from public/styles.css") + } else { + console.log(" the deployed stylesheet is stale: it misses tap-target rules that public/styles.css already has (see FAIL lines above). Refresh the live deployment from origin/main.") + } +} + +console.log("B. npm test/ci wiring") +const pkg = JSON.parse(read("package.json")) +ok( + pkg.scripts.test.includes("test-public-live-tap-targets.mjs"), + "npm test runs the public live tap-target guard" +) +ok( + pkg.scripts.ci.includes("test-public-live-tap-targets.mjs"), + "npm run ci runs the public live tap-target guard" +) + +console.log(`\n${checks} checks, ${failures} failures`) +process.exit(failures === 0 ? 0 : 1)