From 6ab1be35115f4cafde34a482a6a4829099bcf414 Mon Sep 17 00:00:00 2001 From: sneak Date: Sat, 3 Oct 2026 15:30:08 +0000 Subject: [PATCH] Tap-target check expects a pin button per WAN host row (closes #46) The viewport harness's tap-target check required at least 10 visible pin buttons while 26 render, so pin buttons missing from up to 16 rows went unnoticed. It now expects one per WAN host row. The host row count the harness gathers, which the app-rendered check also reads, counts only the WAN host rows, since the local host rows have no pin button. Each control's minimum is now worked out from the gathered facts. TODO.md's harness entry no longer says every check guards itself: the overflow, viewport-edge and clipped-text checks rely on app-rendered. Model: opus-5-5 --- TODO.md | 13 ++++++++++--- test/viewport/README.md | 9 +++++---- test/viewport/checks.js | 27 ++++++++++++++------------- test/viewport/facts.js | 4 +++- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/TODO.md b/TODO.md index 76644bc..e34de1b 100644 --- a/TODO.md +++ b/TODO.md @@ -23,6 +23,11 @@ latest run passes. # Completed Steps +- 2026-10-03: the tap-target check in `make frontend-viewport-test` expects one + visible pin button per WAN host row (issue #46), where it expected at least 10 + of the 26, so pin buttons missing from only some rows now fail it. The host + row count the harness gathers, which the `app-rendered` check also reads, now + counts only the WAN host rows: the local host rows have no pin button - 2026-10-03: each target's row shows its result as soon as its check ends (issue #91), where every row waited for the round's slowest check, up to 24 seconds at a 30-second interval. Every row is still redrawn, and sorting, the @@ -237,9 +242,11 @@ latest run passes. - 2026-08-09: automated responsive-layout harness (`make frontend-viewport-test`): digest-pinned headless Chrome driven over CDP against the built `dist/`, viewport widths derived from the breakpoints in - `src/styles.css` ([#13](https://git.eeqj.de/sneak/netwatch/issues/13)). Every - check carries a presence guard so none of them can pass against a page it is - not actually measuring. Found two real layout defects, filed as + `src/styles.css` ([#13](https://git.eeqj.de/sneak/netwatch/issues/13)). The + tap-target and host-row checks each fail when they measured nothing; the + overflow, viewport-edge and clipped-text checks have no such guard of their + own and rely on the `app-rendered` check, which fails the run when the app did + not render. Found two real layout defects, filed as [#42](https://git.eeqj.de/sneak/netwatch/issues/42) and [#43](https://git.eeqj.de/sneak/netwatch/issues/43) - 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints, Makefile diff --git a/test/viewport/README.md b/test/viewport/README.md index d37a688..9ee28a5 100644 --- a/test/viewport/README.md +++ b/test/viewport/README.md @@ -49,10 +49,11 @@ sizes straddling the breakpoint for the rotation case. excluded: it is a design choice, not breakage. - **tap-targets-44px** — every interactive control is at least 44x44 CSS px on touch viewports, _and_ each selector in the control list matched at least the - number of visible elements it declares. The second half is what stops the - check passing vacuously: with size alone, a renamed class would take its - controls out of the measured set and the check would report "all 0 controls - are at least 44x44" and pass. See below. + number of visible elements it declares: one of each single control, and one + pin button per WAN host row. The second half is what stops the check passing + vacuously: with size alone, a renamed class would take its controls out of the + measured set and the check would report "all 0 controls are at least 44x44" + and pass. See below. - **host-rows-stacked / host-rows-side-by-side** — the rows genuinely reflow. Computed `flex-direction` _and_ the actual geometry are checked, and in the narrow layout the info block and the sparkline must each occupy essentially diff --git a/test/viewport/checks.js b/test/viewport/checks.js index 14c7ebf..4f5648a 100644 --- a/test/viewport/checks.js +++ b/test/viewport/checks.js @@ -15,7 +15,8 @@ export const MIN_TAP_TARGET_PX = 44; // The controls named in the definition of done, plus the pause button. // Each carries the smallest number of *visible* instances the page has to -// contain for the tap-target oracle to be measuring anything at all. +// contain, worked out from the facts gathered from that page, for the +// tap-target oracle to be measuring every control it should. // // Without those floors the check is inert: `undersized` is empty both when // every control is large enough and when the selectors have gone stale and @@ -24,13 +25,12 @@ export const MIN_TAP_TARGET_PX = 44; // for all three singleton controls vanishing at once — so the floor is per // selector, and one stale selector out of four fails the check. export const INTERACTIVE_CONTROLS = [ - { selector: "#pause-btn", minCount: 1 }, - { selector: "#interval-select", minCount: 1 }, - // One per pinnable host row. `app-rendered` already requires at least - // 10 host rows, so a count below that means the pin buttons stopped - // being rendered per row rather than that there were fewer hosts. - { selector: ".pin-btn", minCount: 10 }, - { selector: "#debug-toggle", minCount: 1 }, + { selector: "#pause-btn", minCount: () => 1 }, + { selector: "#interval-select", minCount: () => 1 }, + // One per WAN host row, so pin buttons missing from even one row fail + // the check rather than only a drop below some fixed number. + { selector: ".pin-btn", minCount: (facts) => facts.wanRowCount }, + { selector: "#debug-toggle", minCount: () => 1 }, ]; export const INTERACTIVE_SELECTORS = INTERACTIVE_CONTROLS.map( @@ -105,8 +105,8 @@ export function evaluateChecks(facts, viewport, probes) { // never rendered. Everything below is only meaningful if this holds. check( "app-rendered", - facts.rowCount >= 10 && facts.numericLatencies >= 5, - `${facts.rowCount} host rows, ${facts.numericLatencies} showing a numeric latency`, + facts.wanRowCount >= 10 && facts.numericLatencies >= 5, + `${facts.wanRowCount} WAN host rows, ${facts.numericLatencies} showing a numeric latency`, ); const viewportWidth = Math.min(facts.innerWidth, facts.documentClientWidth); @@ -162,7 +162,8 @@ export function evaluateChecks(facts, viewport, probes) { seen.set(target.selector, (seen.get(target.selector) ?? 0) + 1); } const missing = INTERACTIVE_CONTROLS.filter( - (control) => (seen.get(control.selector) ?? 0) < control.minCount, + (control) => + (seen.get(control.selector) ?? 0) < control.minCount(facts), ); const undersized = facts.tapTargets.filter( @@ -184,11 +185,11 @@ export function evaluateChecks(facts, viewport, probes) { const detail = []; if (missing.length > 0) { detail.push( - "oracle is not measuring the page: " + + "oracle is not measuring every control: " + summarise( missing, (c) => - `${c.selector} matched ${seen.get(c.selector) ?? 0} visible element(s), expected at least ${c.minCount}`, + `${c.selector} matched ${seen.get(c.selector) ?? 0} visible element(s), expected at least ${c.minCount(facts)}`, 4, ), ); diff --git a/test/viewport/facts.js b/test/viewport/facts.js index f266e6c..5c124f8 100644 --- a/test/viewport/facts.js +++ b/test/viewport/facts.js @@ -173,7 +173,9 @@ export function collectLayoutFacts(options) { clipped, tapTargets, rows, - rowCount: document.querySelectorAll(".host-row").length, + // WAN host rows only: each has a pin button, and the tap-target + // check expects one per row. The local host rows have none. + wanRowCount: document.querySelectorAll("#wan-hosts .host-row").length, numericLatencies: Array.from( document.querySelectorAll(".latency-value"), ).filter((el) => /\d/.test(el.textContent)).length,