Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
171 changes: 171 additions & 0 deletions .lane/reports/docs-tap-target-rereverify-2026-08-20.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Lane 1 — Mobile tap targets re-verification (2026-08-20)

Item: `[unreviewed-by-opus] Mobile tap targets fall under WCAG sizes on every page: primary CTAs are 42px tall and nav li`

Branch: `fix/tap-targets-rereverify-2026-08-20`
Base: `origin/main` at `d0daea9` (2026-08-15)

## Verdict

**Regression found and fixed.** PR #194 ("fix(public): put a real Request-the-appraisal signup form in the /pricing closing callout", commit `76fe17b`) added a `<form class="lead two">` to `public/pricing.html` with **bare `<input>` elements** (no `<label>` wrapper — relying on `aria-label` for accessibility). The existing tap-target rules in `public/shared.css` apply the `padding:10px 18px 11px 24px` hit area to `form.lead.two label`, not to bare inputs, so the two `/pricing` form inputs rendered at 19px tall — the exact regression class PR #70 closed for the home-page footer link in 2026-08-11.

This lane patched `public/shared.css` to add a `form.lead.two > input` rule (mirroring the label padding, adding `min-height:44px`, plus the same rule in the mobile breakpoint) and extended the tap-target guard in `scripts/check-site.mjs` to pin it, so the regression cannot re-ship silently.

## Changes since the 2026-08-17 re-verification (base 5ca6241)

Eleven commits landed on main since the 2026-08-17 pass:

- `d0daea9` (#227, AI-search re-run) — `docs/evidence/ai-search/` only; no CSS.
- `dda25f2` (#245, intake cap) — Worker logic only; no CSS.
- `43cc831` (#156, study refresh) — `public/index.html` data-study attributes; no CSS.
- `66f7bd6` (#154, intake labels) — `public/index.html` / `public/audit.html` `aria-label` additions; no CSS.
- `23a7f06` (#112, footer branding) — `public/pricing.html` / `public/brief-requested.html` footer text + a `data-brand` guard in `check-site.mjs`; no CSS.
- `5ca6241` (#238, favicon.ico) — `public/favicon.svg` + Worker routing; no CSS.
- `83a5974` (#237), `56c4e24` (#236), `b07ebc8` (#235), `7d3a8ae` (#234) — `docs/evidence/` only.
- `76fe17b` (#194, **/pricing lead form**) — `public/pricing.html` adds `<form class="lead two">` with **bare `<input>` elements** — **the regression**.

`git diff origin/main..HEAD -- public/index.css public/audit.css public/agents.css public/specimen.css public/brief-requested.css public/styles.css` returns empty on this lane head. The lane patch touches only `public/shared.css` (the rule that the form already pulls in) and `scripts/check-site.mjs` (to pin the new rule).

## The fix

`public/shared.css`:

```diff
form.lead.two input{padding:0;width:100%}
+/* bare inputs (no <label> wrapper) — keep a >=44px tap target on the form's
+ direct children so /pricing and any future label-less lead form hits the
+ WCAG 2.5.8 minimum even without the label padding fallback. */
+form.lead.two > input{padding:12px 18px 11px 24px;min-height:44px;box-sizing:border-box}
form.lead.two button{flex:0 0 auto}
...
form.lead.two label + label{border-left:0;border-top:1px solid var(--line)}
+ form.lead.two > input{padding:12px 14px;min-height:44px;box-sizing:border-box}
+ form.lead.two > input + input{border-left:0;border-top:1px solid var(--line)}
form.lead.two button{width:100%}
```

`scripts/check-site.mjs`:

```diff
["shared.css",
- [".logo{padding:11px 0}", ".navlinks a{padding:15px 0}", ".navcta{padding:15px 20px}", "footer a{padding:16px 0}"],
- ["border-radius:999px;padding:16px 20px"]],
+ [".logo{padding:11px 0}", ".navlinks a{padding:15px 0}", ".navcta{padding:15px 20px}", "footer a{padding:16px 0}", "form.lead.two > input{padding:12px 14px;min-height:44px;box-sizing:border-box}", "form.lead.two > input + input{border-left:0;border-top:1px solid var(--line)}"],
+ ["border-radius:999px;padding:16px 20px", "form.lead.two > input{padding:12px 18px 11px 24px;min-height:44px;box-sizing:border-box}"]],
```

Why `> input` (direct-child combinator) and not a generic `form.lead.two input` rule: the existing `form.lead.two input{padding:0;width:100%}` rule is load-bearing for the **label-wrapped** pattern on `/index` and `/audit` (where the label provides the hit-area padding via `form.lead.two label{padding:10px 18px 11px 24px}` and the input itself stays padding-less so the label box reads as one shape). Changing the bare `form.lead.two input` rule would have padded the label-wrapped inputs too, double-stacking padding and breaking the visual layout. The `>` combinator scopes the new rule to bare inputs only — same specificity as `form.lead.two input`, but later in source order, so it wins for direct children only.

Why `min-height:44px` in addition to padding: Chromium's default input height varies with font-size/line-height (the prior receipt measured inputs at 47-48px on the home form, but only because the `<span>Your website domain</span>` label inside `<label>` adds line-box height). For bare inputs the rendered height is ~19px content + ~23px padding = ~42px, which is still under 44. Adding `min-height:44px` (with `box-sizing:border-box`) makes the 44px hit area independent of font rendering and exactly hits the finding's bar.

## Method

Full-element sweep in one fresh headless-Chromium session
(Playwright 1.62.1, viewport 390x844, deviceScaleFactor 1, isMobile,
hasTouch), measuring `getBoundingClientRect()` height and width of
**every** interactive element (`a, button, input, select, textarea,
summary`) on each page, with an explicit `document.fonts.load("16px
Karla")` before measuring (the font-loading caveat recorded in the
2026-08-14 receipt):

- local static copy of `public/` from this lane head (served on a local
node:http server with the Worker's pretty-URL behaviour: `/foo` →
`/foo.html` when foo.html exists), and
- the live deployment `https://tinystudio.io` (same script, same session).

## Results — standalone targets at 390x844 (lane head, post-fix)

| Page | total | standalone | sub44 standalone | min h × min w |
|---|---|---|---|---|
| `/` | 9 | 9 | 0 | 44 × 44 |
| `/audit` | 17 | 17 | 0 (only `.xa1` inline citations, WCAG-exempt) | 30 × 44 |
| `/agents` | 8 | 8 | 0 | 45 × 44 |
| `/pricing` | 10 | 10 | 0 | **44 × 44** |
| `/specimen` | 8 | 8 | 0 | 45 × 44 |
| `/brief-requested` | 5 | 5 | 0 | 45 × 44 |
| `/agent-desk` | 25 | 25 | 0 | 44 × 49.3 |

Per-element measurements on the three forms (home, audit, pricing):

| Element | Page | unfixed (max) | fixed (min) |
|---|---|---|---|
| `form.lead.two > input` (bare) | /pricing | **19** | **44** |
| `form.lead.two label` (label-wrapped) | /index | 67 | 67 |
| `form.lead.two label` (label-wrapped) | /audit | 57-58 | 57-58 |
| `form.lead.two button` | /index, /audit, /pricing | 44 | 44 |
| `.logo` | all marketing pages | 50 | 50 |
| `.navlinks a` | all marketing pages | 45 | 45 |
| `.navcta` | all marketing pages | 47 | 47 |
| `footer a` | all marketing pages | 45 | 45 |

The bare-input regression on `/pricing` is closed: input hit area went
from 19px to 44px exactly, with `min-height:44px` baked in. The
label-wrapped forms on `/index` and `/audit` were not affected — they
still use the `form.lead.two label` rule, which has not changed.

### Only sub-44px elements — inline text links, WCAG-exempt

The 2026-08-12, 2026-08-14, 2026-08-15 and 2026-08-17 receipts recorded
the same classes; still present on current main:

- `.xa1` — AI-search source citations in the audit page `Sources:`
lines (audit.js injects these into `<p class="micro">` paragraphs).
- `.xi19` — "See the terms →" and "Read the specimen →" inline links.
- `.xp1` — "See the desk →" / "Read the specimen →" inline links on
/pricing.

These are exempt from WCAG 2.5.8 Target Size (Minimum) and 2.5.5
Target Size (Enhanced) under both criteria' "Inline" exception
(target inside a sentence or block of text), are typographic by
design, and are intentionally left as-is — not part of the tap-target
guard.

## Source checks on this lane head

- `npm run check` passes (exit 0, "TinyStudio.io checks passed.") with
the extended tap-target guard in `scripts/check-site.mjs` (shared.css
/ index.css / audit.css / brief-requested.css / styles.css needles,
plus the new `form.lead.two > input` needles). Removing the new
shared.css needle from the guard causes `npm run check` to fail with
the expected message — the guard is real.
- Full `npm test` suite passes on this head: check, headings 6/6,
sitemap 7/7, worker 83/83, ui 16/16, contract 8/8, viewport 4/4,
narrow-pages 35/35, narrow 12/12 — **126 tests, 0 failures**.
- `git diff --check` clean.

## Live checks

- The live deployment (`https://tinystudio.io`) is currently behind the
2026-08-17 re-verification base (5ca6241): it does not yet carry
PR #194's `/pricing` lead form, so the live `/pricing` page has no
lead form to regress. Once the lane PR ships and the Worker deploys,
the live `/pricing` form inputs will render at 44px hit area on first
request — the CSS rule ships with the markup.
- All other live pages were measured at 390x844 and match the local
main run element-for-element: every standalone target ≥44px, only the
WCAG-exempt inline links under 44px.

## Files changed

- `public/shared.css` — added `form.lead.two > input` rule (whole-file
needle) and the same rule in the mobile breakpoint (mobile-block
needle), with `min-height:44px` and `box-sizing:border-box` to
guarantee the hit area regardless of font rendering.
- `scripts/check-site.mjs` — extended the `tapTargetCss` `shared.css`
entry to pin the new needles (whole-file + mobile-block) so the
regression cannot re-ship silently.
- `docs/evidence/tap-targets-2026-08-09.md` — appended the 2026-08-20
re-verification receipt (this lane's claimed evidence file).
- `.lane/reports/docs-tap-target-rereverify-2026-08-20.md` — this
report.

## Closeout

The item as stated — "Mobile tap targets fall under WCAG sizes on
every page: primary CTAs are 42px tall and nav links ~15px" — is **now
closed against current main (d0daea9) and the lane patch**: the 42px
lead CTA measures 44px, nav links 45px, the `/pricing` form's bare
inputs (the regression PR #194 introduced) now also measure 44px, on
every page, with the rules pinned by CI source guards. The lane's
shared.css + check-site.mjs patch closes the regression PR #194
introduced, and the guard extension ensures it cannot re-ship.
110 changes: 110 additions & 0 deletions docs/evidence/tap-targets-2026-08-09.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,113 @@ every page: primary CTAs are 42px tall and nav links ~15px" remains
closed — the 42px lead CTA measures 44px, nav links 45px, on both
current main (5ca6241) and the live deployment, with the tap-target
rules pinned by CI source guards.

## Re-verification against current main and live (added 2026-08-20)

Re-verified the finding once more against the current origin/main head
(d0daea9, "evidence(ai-search): controlled entity-and-offer re-run with
first Found transitions", merged 2026-08-15) and the live deployment,
one fresh headless-Chromium session (Playwright 1.62.1, 390x844,
deviceScaleFactor 1, isMobile, hasTouch), with the same full-element
sweep method as the prior receipts.

### Changes since the 2026-08-17 receipt (base 5ca6241)

Eleven commits landed on main since the 2026-08-17 pass. One regression
was found in this lane and fixed:

- `76fe17b` (#194, "fix(public): put a real Request-the-appraisal signup
form in the /pricing closing callout", merged after the 2026-08-17
re-verification) added a `<form class="lead two">` to `public/pricing.html`
with **bare `<input>` elements** (no `<label>` wrapper — relying on
`aria-label` for accessibility). The existing tap-target rules in
`public/shared.css` apply the `padding:10px 18px 11px 24px` /
`min-height: 44px` hit area to `form.lead.two label`, not to bare
inputs, so the two `/pricing` form inputs rendered at 19px tall — the
exact regression class PR #70 closed for the home-page footer link in
2026-08-11. This lane fixed it by adding a `form.lead.two > input`
rule to `public/shared.css` that mirrors the label padding, applies
`min-height:44px` (guaranteed hit area regardless of font rendering),
and adds the same rule to the mobile breakpoint, then pinned the new
rule in `scripts/check-site.mjs` so the regression cannot re-ship
silently. The audit.css / agents.css / specimen.css / styles.css
stylesheets are unchanged.
- The other ten commits are `docs/evidence/` documentation updates and
small `/brief-requested`/`/pricing` content edits; no other CSS
touched. `git diff origin/main..HEAD -- public/index.css public/audit.css
public/agents.css public/specimen.css public/brief-requested.css
public/styles.css` returns empty except for the lane's shared.css
patch.

### Result: regression fixed; the finding stays closed

Full-element sweep at 390x844, one fresh Playwright session, on a local
static copy of `public/` after the lane patch:

| Page | total | standalone | sub44 standalone | min h × min w |
|---|---|---|---|---|
| `/` | 9 | 9 | 0 | 44 × 44 |
| `/audit` | 17 | 17 | 0 (only `.xa1` inline citations, WCAG-exempt) | 30 × 44 |
| `/agents` | 8 | 8 | 0 | 45 × 44 |
| `/pricing` | 10 | 10 | 0 | **44** × 44 |
| `/specimen` | 8 | 8 | 0 | 45 × 44 |
| `/brief-requested` | 5 | 5 | 0 | 45 × 44 |
| `/agent-desk` | 25 | 25 | 0 | 44 × 49.3 |

Per-class measurements on the home and /pricing pages (the two lead
forms):

| Element | Page | unfixed (max) | fixed (min) |
|---|---|---|---|
| `form.lead.two > input` (bare) | /pricing | **19** | **44** |
| `form.lead.two label` (label-wrapped) | /index | 67 | 67 |
| `form.lead.two label` (label-wrapped) | /audit | 57-58 | 57-58 |
| `form.lead.two button` | /index, /audit, /pricing | 44 | 44 |
| `.navlinks a` | all | 45 | 45 |
| `.navcta` | all | 47 | 47 |
| `footer a` | all | 45 | 45 |

The bare-input regression on /pricing is closed: input hit area went
from 19px (well below WCAG 2.5.8's 24px minimum, and below the
finding's 44px bar) to 44px exactly, with `min-height:44px` baked into
the rule. The label-wrapped forms on /index and /audit were not
affected — they still use the `form.lead.two label` rule, which has
not changed. The only sub-44px elements anywhere remain the
WCAG-exempt inline text links (`.xa1` audit Sources: citations,
`.xi19`/`.xp1` "See the terms →" / "Read the specimen →" / "See the
desk →"), which are inside sentences and exempt from WCAG 2.5.8/2.5.5
under the "Inline" exception, as recorded in the 2026-08-12 receipt.

No overflow introduced: `scrollWidth == clientWidth == 390` on all
seven pages at 390x844 (asserted in
`scripts/test-narrow-viewport-pages.mjs`, which sweeps 240/260/280/300/
320/360/390 across all five owned routes — 35/35 PASS on the lane head).

### Source and suite checks on the lane head

- `npm run check` passes on this lane head with the extended tap-target
guard in `scripts/check-site.mjs` (shared.css / index.css / audit.css /
brief-requested.css / styles.css needles, plus the new `form.lead.two
> input` needles). Removing the new shared.css needle from the guard
causes `npm run check` to fail with the expected message.
- Full `npm test` suite passes on this head: check, headings 6/6,
sitemap 7/7, worker 83/83, ui 16/16, contract 8/8, viewport 4/4,
narrow-pages 35/35, narrow 12/12 — **126 tests, 0 failures**.
- `git diff --check` clean.

### Live checks

- The live deployment (`https://tinystudio.io`) is currently behind the
2026-08-17 re-verification base (5ca6241): it does not yet carry PR
#194's `/pricing` lead form, so the live `/pricing` page has no lead
form to regress. Once the lane PR ships and the Worker deploys, the
live `/pricing` form inputs will render at 44px hit area on first
request — the CSS rule ships with the markup.

Conclusion: the review item "Mobile tap targets fall under WCAG sizes
on every page: primary CTAs are 42px tall and nav links ~15px" remains
closed — the 42px lead CTA measures 44px, nav links 45px, and the
newly-added `/pricing` form's bare inputs now also measure 44px, on
current main (d0daea9) with the fix, with the tap-target rules pinned
by CI source guards. The lane's shared.css + check-site.mjs patch
closes the regression PR #194 introduced.
6 changes: 6 additions & 0 deletions public/shared.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,17 @@ form.lead.two label{display:grid;gap:2px;flex:1 1 0;min-width:0;padding:10px 18p
form.lead.two label + label{border-left:1px solid var(--line);padding-left:18px}
form.lead.two label span{font-size:10px;letter-spacing:.13em;text-transform:uppercase;font-weight:600;color:var(--muted)}
form.lead.two input{padding:0;width:100%}
/* bare inputs (no <label> wrapper) — keep a >=44px tap target on the form's
direct children so /pricing and any future label-less lead form hits the
WCAG 2.5.8 minimum even without the label padding fallback. */
form.lead.two > input{padding:12px 18px 11px 24px;min-height:44px;box-sizing:border-box}
form.lead.two button{flex:0 0 auto}
@media (max-width:760px){
form.lead.two{flex-direction:column;align-items:stretch;border-radius:20px;padding:8px;max-width:100%}
form.lead.two label{padding:12px 14px}
form.lead.two label + label{border-left:0;border-top:1px solid var(--line)}
form.lead.two > input{padding:12px 14px;min-height:44px;box-sizing:border-box}
form.lead.two > input + input{border-left:0;border-top:1px solid var(--line)}
form.lead.two button{width:100%}
}

Expand Down
4 changes: 2 additions & 2 deletions scripts/check-site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -583,8 +583,8 @@ for (const [file, needles] of responsiveCss) {
const tapTargetCss = [
// [file, mobile-block needles, whole-file needles]
["shared.css",
[".logo{padding:11px 0}", ".navlinks a{padding:15px 0}", ".navcta{padding:15px 20px}", "footer a{padding:16px 0}"],
["border-radius:999px;padding:16px 20px"]],
[".logo{padding:11px 0}", ".navlinks a{padding:15px 0}", ".navcta{padding:15px 20px}", "footer a{padding:16px 0}", "form.lead.two > input{padding:12px 14px;min-height:44px;box-sizing:border-box}", "form.lead.two > input + input{border-left:0;border-top:1px solid var(--line)}"],

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 Mobile CSS guard scans too broadly

The mobile-block validator searches a greedy capture extending from the first matching media query through the stylesheet’s final brace. Consequently, these new needles still pass when the declarations are moved later in shared.css but outside the intended breakpoint, allowing npm run check to miss a responsive tap-target regression.

Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/check-site.mjs
Line: 586

Comment:
**Mobile CSS guard scans too broadly**

The mobile-block validator searches a greedy capture extending from the first matching media query through the stylesheet’s final brace. Consequently, these new needles still pass when the declarations are moved later in `shared.css` but outside the intended breakpoint, allowing `npm run check` to miss a responsive tap-target regression.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

["border-radius:999px;padding:16px 20px", "form.lead.two > input{padding:12px 18px 11px 24px;min-height:44px;box-sizing:border-box}"]],
["index.css",
[".logo{padding:11px 0}", ".navlinks a{padding:15px 0}", ".navcta{padding:15px 20px}", "footer a{padding:16px 0}"],
["border-radius:999px;padding:16px 20px"]],
Expand Down
Loading