Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
345e79a
fix(public): net the live site against WCAG 2.2 tap-target drift with…
nish3451 Aug 14, 2026
272cd26
docs(lane1): report the live tap-target net — PR #152
nish3451 Aug 14, 2026
e96d548
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
c5c38dd
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
0d7026a
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
456c7d5
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
c216e91
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
3edf6c2
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
b0b3279
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
c9cb8a8
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
bf27275
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 14, 2026
fbaf915
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
a6d86fc
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
7866477
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
c1374a3
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
a21e770
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
25b385b
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
acfc1f0
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
939f488
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
26abcc2
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
509a8fa
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
64fe476
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
508cb21
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
bddde4d
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
0473013
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
8ee8554
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 15, 2026
9baf212
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 17, 2026
0c9c837
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 17, 2026
2c20217
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 17, 2026
64dcdb6
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 17, 2026
ca274b5
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 17, 2026
960e26e
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 17, 2026
0a9f84b
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 17, 2026
4759149
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 18, 2026
ed9611f
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 19, 2026
20aa629
Merge branch 'main' into fix/lane1-public-tap-targets-live-net
nish3451 Aug 19, 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
27 changes: 19 additions & 8 deletions .github/workflows/live-site-check.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
name: Live Site Check

# Guards the deployed tinystudio.in against soft-404s and stale bundles
# without blocking pull requests. The deploy lane (deploy-public-site.yml)
# verifies the live site after every publish; this workflow is the nightly
# net that catches a stale or misconfigured deployment within a day even
# when nothing merges. It is deliberately NOT part of `npm run ci` - that
# blocking chain must stay green on repo state alone.
# Guards the deployed tinystudio.in against soft-404s, WCAG 2.2 tap-target
# drift (SC 2.5.8 24px minimum), and stale bundles without blocking pull
# requests. The deploy lane (deploy-public-site.yml) verifies the live site
# after every publish; this workflow is the nightly net that catches a stale
# or misconfigured deployment within a day even when nothing merges. It is
# deliberately NOT part of `npm run ci` - that blocking chain must stay green
# on repo state alone.
#
# This job is expected to FAIL while the deployment is stale (currently the
# June-20 bundle); the red run is the loud signal to re-deploy from
# origin/main, not a reason to disable the workflow.

on:
schedule:
# Nightly: catch stale or broken deployments (soft-404s, offline site)
# within a day even when no pull requests are open.
# Nightly: catch stale or broken deployments (soft-404s, sub-24px link
# targets, offline site) within a day even when no pull requests are open.
- cron: "23 3 * * *"
workflow_dispatch:

Expand Down Expand Up @@ -46,3 +47,13 @@ jobs:
cache: npm
- run: npm ci --ignore-scripts
- run: node scripts/test-public-live-contact-heading-hierarchy.mjs
live-tap-targets:
runs-on: [self-hosted, linux, x64, vps-verify]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 20
cache: npm
- run: npm ci --ignore-scripts
- run: node scripts/check-public-live-tap-targets.mjs
41 changes: 41 additions & 0 deletions .lane/reports/fix/lane1-public-tap-targets-live-net.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Lane report: fix/lane1-public-tap-targets-live-net

## Item

In-content links stay below the WCAG 2.2 24px tap-target minimum — merged PR #22 fixes only.

## Root cause (verified live, 2026-08-14)

The tap-target fix is correct **in source** and merged on main twice:

- PR #22 (`5298652`) — footer links ≥24px
- PR #25 (`b0f5f06`) — in-content links ≥24px
- `scripts/test-public-link-targets.mjs` (static guard) → **77 checks, 0 failures** on origin/main

The live site does not carry the fix. `https://tinystudio.in/styles.css` still serves the pre-fix June-20 bundle, which is missing all five tap-target rules — rendered in-content links are ~17px. This is the same stale-deployment gap documented in the soft-404 lane (no Cloudflare Pages token → release lane fails closed, nightly `Live Site Check` red by design).

A previous lane attempt (PR #107, `fix/lane1-in-content-link-targets-live-net-20260812`) landed the same change shape but is **CONFLICTING** against current main (which now has a third live-check job) and never merged. This lane re-lands it fresh, rebased on origin/main.

## What I changed

| File | Change |
|---|---|
| `scripts/check-public-live-tap-targets.mjs` | New live guard: fetches deployed `styles.css`, re-asserts the five link rules the local suite requires (block-level box, `min-height: 24px`, ≥4px vertical padding). Non-2xx or missing rule = loud FAIL; network failure only = skip; `SKIP_LIVE_CHECKS=1` honored. |
| `package.json` | `site:check-live` now runs soft-404 + tap-target checks. Not added to `npm test`/`npm ci` (blocking chains stay green on repo state alone, per PR #84). |
| `.github/workflows/live-site-check.yml` | New nightly + manual-dispatch `live-tap-targets` job. |

## Evidence

- `node --check scripts/check-public-live-tap-targets.mjs` → clean
- `node scripts/check-public-live-tap-targets.mjs` against the live site → **19 checks, 11 failures, exit 1** (reproduces the item: deployed sheet is stale, sub-24px targets)
- `node scripts/test-public-link-targets.mjs` → 77 checks, 0 failures (repo correct)
- `node scripts/test-public-soft-404.mjs` → 19 checks, 0 failures
- `node scripts/test-deploy-public-site-workflow.mjs` → 10 checks, 0 failures
- `node scripts/test-pages-release.mjs` → 38 checks, 0 failures
- `git diff --check` → clean

## Deliverable

PR: https://github.com/nish3451/tinystudio-in/pull/152 (open)

The red nightly run is the intended staleness alarm, not a reason to disable. Once the Cloudflare Pages token is provisioned (release-lane blocker, documented in `deploy-public-site.yml`), re-deploy from origin/main; this check must then report 0 failures and the item closes.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"client:proof-review": "node scripts/review-client-proof.mjs",
"retention:automation-check": "node scripts/check-retention-automation.mjs",
"site:prepare": "node scripts/prepare-static-site-bundle.mjs",
"site:check-live": "node scripts/check-public-live-soft-404.mjs",
"site:check-live": "node scripts/check-public-live-soft-404.mjs && node scripts/check-public-live-tap-targets.mjs",
"site:check-live-contact-heading-hierarchy": "node scripts/test-public-live-contact-heading-hierarchy.mjs",
"claims:check": "node scripts/check-outbound-claim-safety.mjs",
"config:check": "node scripts/check-agency-defaults.mjs",
Expand Down
142 changes: 142 additions & 0 deletions scripts/check-public-live-tap-targets.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Guard the LIVE public site against WCAG 2.2 (SC 2.5.8) tap-target drift:
// the stylesheet tinystudio.in actually serves must keep every in-content
// and footer link rule at the 24px minimum.
//
// The static test (test-public-link-targets.mjs) only proves
// public/styles.css in the repo; it cannot catch a stale or misconfigured
// deployment. This check fetches the deployed stylesheet 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), so a deployment that still
// serves pre-fix CSS (the June-20 bundle) fails loudly instead of silently
// re-opening the tap-target backlog item. It runs as part of
// `npm run site:check-live` and from the nightly live-site-check workflow.
//
// It is deliberately NOT part of `npm run test` / `npm run ci`: those
// blocking chains must stay green on repo state alone (see PR #84), while
// the live site is deployed by an external mechanism (Cloudflare Pages).
// Blocking CI on the live site would keep every pull request red whenever
// the deployment is stale.
//
// Failure semantics: a non-2xx HTTP response and a stylesheet that misses
// any required rule both FAIL this check (a deployment serving no rules is
// a failed deployment, not an unknown). Only a genuine network-level
// failure (site unreachable) skips with a notice, so offline machines do
// not go red; the nightly live-site-check workflow still catches the
// same outage via check-public-live-soft-404.mjs.
//
// Escape hatch for machines without network access:
// SKIP_LIVE_CHECKS=1 npm run site:check-live

import { readFileSync } from "node:fs"
import { fileURLToPath } from "node:url"
import { dirname, join } from "node:path"

if (process.env.SKIP_LIVE_CHECKS === "1") {
console.log("check-public-live-tap-targets: SKIP_LIVE_CHECKS=1, skipping live site checks")
process.exit(0)
}

const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..")
const read = (p) => readFileSync(join(ROOT, p), "utf8")

const LIVE_CSS_URL = "https://tinystudio.in/styles.css"
const FETCH_TIMEOUT_MS = 10_000
const FETCH_ATTEMPTS = 2

let failures = 0
let checks = 0
const ok = (cond, msg) => {
checks++
if (cond) console.log(` ok ${msg}`)
else {
failures++
console.error(` FAIL ${msg}`)
}
}

// 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("check-public-live-tap-targets: the deployed tinystudio.in stylesheet keeps every link rule at the WCAG 2.2 24px minimum")

let css = null
try {
for (let attempt = 1; attempt <= FETCH_ATTEMPTS; attempt++) {
try {
const res = await fetch(LIVE_CSS_URL, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) })
if (!res.ok) {
failures++
checks++
console.error(` FAIL ${LIVE_CSS_URL} answered HTTP ${res.status}: the deployed stylesheet is missing or inaccessible, so none of the tap-target rules are being served`)
process.exit(1)
}
css = await res.text()
break
} catch (err) {
if (attempt === FETCH_ATTEMPTS) throw err
await new Promise((r) => setTimeout(r, 1000))
}
}
} 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 deployed stylesheet 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. wiring")
const pkg = JSON.parse(read("package.json"))
ok(
(pkg.scripts["site:check-live"] ?? "").includes("check-public-live-soft-404.mjs") &&
(pkg.scripts["site:check-live"] ?? "").includes("check-public-live-tap-targets.mjs"),
"npm run site:check-live runs the soft-404 and tap-target live checks"
)
const workflow = read(".github/workflows/live-site-check.yml")
ok(workflow.includes("check-public-live-tap-targets.mjs"), "live-site-check.yml runs the live tap-target check (nightly + manual dispatch)")

console.log(`\n${checks} checks, ${failures} failures`)
if (failures > 0) {
console.error("\nThe deployed stylesheet drifted below the WCAG 2.2 24px tap-target minimum. Re-deploy the public site from origin/main and re-run this check.")
}
process.exit(failures === 0 ? 0 : 1)