phase: 04_controller_page_responsive
**Final verification pass** (all three task reports PASS; tasks already in `complete/`). One real defect found and fixed.
**Commands / results.** `bash tests/run_tests.sh` → `RESULT: all suites passed`, **5 104** checks (page **220**). `SANITIZE=1 bash tests/run_tests.sh` → passed. `bash .agents/validate.sh` → `validation OK`; bridge/console/rollback `SUCCESS`; `page bytes: 59262`; `README/AGENTS claims: OK`; `repo hygiene: OK`. Real-browser leg (headless Chromium, served assembled bytes): no horizontal scroll at 360/390/768/1440, ARM first-in-`main` and sticky, log height-capped, all 5 folds openable, all 3 changed page statements execute.
**Fix.** The folded notes' `<summary>` — the only route to five pinned safety sentences — rendered **17 px** on coarse pointers, under the page's own 44 px thumb floor, while every byte-level assertion stayed green. `src/web_page.h`: `button.k,.pill,input,summary{min-height:44px}` + `summary{padding:13px 0}`; `tests/test_page.js` +1 check (219→220); mutation-tested (reverting the CSS fails host *and* browser legs, then restored byte-for-byte).
**Criteria.** 1 ✓ browser-measured (physical phone still unobserved). 2 ✓ 220 checks + prose untouched. 3 ✓ page 59 286→**59 262 B** (−24 vs phase start), README = gate figure. 4 ✓ one `test_page` row at 220, intro 5 104 = measured. 5 ✓ gate green; phases 01–03 assertions unchanged apart from the re-pinned byte constant.
**Docs.** Re-pinned 59 262 (README, `config.h`, `test_conn.cpp`), totals 5 103→5 104, README Tests row 220; re-measured footprint rows (flash 1 257 901 / 1 178 141 / 1 157 593, TLS +6 176/+100 308); fixed stale "48 KB"/"~50 KB" (×4 src comments, ×2 README) and the 51 626-byte page-delivery row; added **bug 14** with root cause and renamed *Thirteen*→*Fourteen bugs* in README + AGENTS.md's two stale references; added the Phase 04 browser+device row to `.agents/HW_VERIFICATION.md`.
**Deviations.** Device leg stays open: live board serves 51 626 B (pre-phase image); no reflash attempted, per the standing no-unattended-reflash rule — no byte-equality claim made. `ruff check tools tests`: 29 findings, identical at HEAD and `--isolated`, `E9,F` clean, no repo lint config or gate step → left untouched. `.agents/validate.sh` is **untracked and has never been committed** (`git log --all --` empty) though README/AGENTS/PLAN cite it 13× — harness needs `git add` it; I did not stage anything.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Phase 04 — The controller page: less to read, works on a phone
|
||||
|
||||
**Source:** `TODO.md` L4 — "Clean up keyrelay interface, don't need as much text, make it
|
||||
responsive"
|
||||
**Story:** `n/a`
|
||||
**Context:** `src/web_page.h` (markup + `<style>` + script), `tests/test_page.js`, README
|
||||
"Controller page"; `AGENTS.md` "The page is served from flash"
|
||||
|
||||
## Objective
|
||||
|
||||
Reorganise the page so the controls are the thing you see: single column and thumb-sized on a
|
||||
phone, unchanged on a laptop, and the explanatory prose folded away to where it is asked for
|
||||
instead of sitting between the operator and the ARM button. **No safety statement gets
|
||||
weaker or shorter** — they move, they do not shrink, and each one gets a test that fails if it
|
||||
disappears.
|
||||
|
||||
## Why the prose is load-bearing anyway
|
||||
|
||||
Every long hint in this page exists because someone got hurt without it: the arming banner
|
||||
(Enter goes to another machine), the `Esc`-frees-the-pointer note (Pointer Lock is invisible),
|
||||
the layout caveat (characters are positions), the KDE paragraph (those three buttons are the
|
||||
laptop's until the board steals them). Trimming them is the right call for usability and the
|
||||
wrong call for safety *unless* the trimming is pinned. So this phase writes the assertions
|
||||
before it deletes the sentences.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `03_session_takeover` (todo) — its TAKE OVER affordance and the `tls` pill/fingerprint line
|
||||
are new elements in the same markup; folding prose around them is easier once they exist,
|
||||
and the id-coverage check has to be updated once.
|
||||
|
||||
## Tasks
|
||||
|
||||
1. `01_responsive_layout.md` — CSS and viewport only: breakpoints, thumb-sized targets, sticky
|
||||
ARM bar, safe-area insets, collapsed log; no markup restructure, no id changes.
|
||||
2. `02_prose_trim_pinned.md` — the safety assertions first, then the fold: long hints into
|
||||
`<details>`, the footer shortened, the connection/mouse/paste paragraphs cut to one line
|
||||
each with the reasoning one tap away.
|
||||
3. `03_suite_size_and_docs.md` — `test_page.js` extended for the new structure, the README's
|
||||
page-size claim and its duplicated `test_page` row corrected, totals made to agree, and the
|
||||
`curl` byte check re-derived.
|
||||
|
||||
## Testing & Quality
|
||||
|
||||
- `tests/test_page.js` is the only thing that can see this page before a browser does
|
||||
(`AGENTS.md`: a raw string is invisible to every compiler). It must cover, in addition to
|
||||
what it covers today:
|
||||
- **safety strings still present**: the arming warning names Enter and "every key"; the
|
||||
mouse hint names `Esc` and release; the paste hint names "stop typing"/disarm cancelling a
|
||||
run; the layout note says positions/US layout; the default-passphrase shout still exists.
|
||||
- every element id the script reaches for exists in the markup (existing rule, re-verified
|
||||
after the restructure);
|
||||
- the pinned page constants (`TEXT_FRAME_MAX`, `TEXT_JOB_MAX`) still equal `config.h`;
|
||||
- the script still behaves identically: arming, state frames, paste chunking, mouse
|
||||
capture/movement/button ordering, `E12`.
|
||||
- Coverage **>90%** on the page script's changed branches; CSS is checked by assertions on the
|
||||
served bytes (breakpoints present, sticky ARM bar) plus a phone observation.
|
||||
- Device (best effort, one attempt): `curl -sk https://keyrelay.local/ | wc -c` equals the
|
||||
byte count the gate prints, and a phone shows one column with the ARM bar reachable and the
|
||||
log not pushing it off screen.
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
- [ ] The page works at 360 px wide and at 1440 px wide without horizontal scroll, ARM is
|
||||
reachable with a thumb, and the log is collapsible.
|
||||
- [ ] Every safety statement above is still on the page **and** asserted by `test_page.js`.
|
||||
- [ ] The page is smaller in bytes than it is today, and README's size claim matches.
|
||||
- [ ] README Tests table has one `test_page` row with the real count (the duplicate stale row
|
||||
at 47 checks is removed), and the intro's total check count matches the suite output.
|
||||
- [ ] `bash .agents/validate.sh` green; no behaviour change in phases 01–03.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Task 01 — Responsive layout: CSS and viewport, nothing else
|
||||
|
||||
**Phase:** `04_controller_page_responsive` · **Source:** `TODO.md:4` — "Clean up keyrelay
|
||||
interface, don't need as much text, make it responsive" (the layout half)
|
||||
**Story:** `n/a`
|
||||
|
||||
## Objective
|
||||
|
||||
The page lays out for the screen it is on: one column and thumb-sized controls on a phone, the
|
||||
current dense grid on a laptop, the ARM bar always reachable, the viewport configured so the
|
||||
layout is not scaled-bitmap small.
|
||||
|
||||
## Work
|
||||
|
||||
1. `src/web_page.h`, `<style>`:
|
||||
- `main` grid: keep `auto-fit`/`minmax(240px,1fr)` above the breakpoint; at `@media (max-width:
|
||||
720px)` force one column and reduce padding.
|
||||
- Thumb targets: `button.k`, `input`, `.pill`, checkboxes get `min-height:44px` (and
|
||||
`min-width` where a target is narrower) on coarse pointers —
|
||||
`@media (pointer: coarse)` where that reads better than a width query.
|
||||
- `input[type=password], input[type=text], textarea { font-size:16px }` on small screens:
|
||||
below 16 px iOS Safari zooms the page on focus and never zooms back out. Name that in the
|
||||
comment — it is why the size is not "13 px looks nicer".
|
||||
- ARM bar: `position:sticky; top:0; z-index` so it survives scrolling on a phone; keep the
|
||||
armed styling exactly as it is (`body.armed` outline is an indicator, not decoration).
|
||||
- `#log`: `max-height` on small screens, and `overscroll-behavior:contain` so scrolling the
|
||||
log does not scroll the page.
|
||||
- `touch-action:manipulation` on the interactive elements (kills the 300 ms double-tap
|
||||
zoom) and `user-select:none` where a press is a control, not text.
|
||||
- `padding-bottom: calc(14px + env(safe-area-inset-bottom))` on `main`, plus
|
||||
`viewport-fit=cover` added to the existing `<meta name="viewport">` (without it `env()`
|
||||
is always 0). Do **not** add `maximum-scale`/`user-scalable=no` — pinch zoom stays.
|
||||
2. `tests/test_page.js` — assertions on the served bytes, in the existing style: the viewport
|
||||
meta contains `viewport-fit=cover` and does not disable scaling; a `max-width:720px`
|
||||
breakpoint exists; the ARM bar is sticky; the coarse-pointer target floor exists; the
|
||||
input font-size floor exists. These are layout claims, so they are checked where the page
|
||||
can be seen without a browser.
|
||||
3. `src/web_page.h` header comment — one paragraph: the page is for a laptop *and* a phone
|
||||
held in one hand over a target you are standing at, and the sticky ARM bar exists because
|
||||
the state must be unmissable at any scroll position.
|
||||
|
||||
- ASSUMPTION: breakpoints at 720 px plus a `pointer: coarse` query, rather than a full design
|
||||
system or container queries. One breakpoint the README can describe beats five nobody can
|
||||
remember.
|
||||
- ASSUMPTION: dark theme only, as today. A light theme is not on anyone's list and doubles
|
||||
every colour assertion.
|
||||
- ASSUMPTION: the log stays in the page (collapsed by its own height, not hidden behind a tab)
|
||||
because the log is where `E9`/`E4` explanations appear; hiding it is hiding the diagnostics.
|
||||
|
||||
## Testing & Quality
|
||||
|
||||
- `node tests/test_page.js` green, including the new byte-level layout assertions; the id
|
||||
coverage check unchanged (no ids added or renamed by this task).
|
||||
- `bash .agents/validate.sh` green; the page byte count printed by the gate changes and is
|
||||
recorded (the README claim is task 03's job).
|
||||
- Device (best effort, one attempt): a phone at 360–430 px logical width shows one column, the
|
||||
ARM bar stays visible while scrolling, focusing the passphrase field does not zoom the page.
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
- [ ] No horizontal scroll at 360 px, 768 px or 1440 px (observed on a phone or recorded as
|
||||
`skipped (no device)` with the exact check).
|
||||
- [ ] ARM reachable without scrolling from the top of the page on a phone, and it is still the
|
||||
first element in `main`.
|
||||
- [ ] Focus on the passphrase input does not zoom (or the report says unobserved).
|
||||
- [ ] All four layout assertions present in `test_page.js`; full test suite green.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Task 02 — Cut the reading, keep the warnings: assertions first
|
||||
|
||||
**Phase:** `04_controller_page_responsive` · **Source:** `TODO.md:4` — "Clean up keyrelay
|
||||
interface, **don't need as much text**, make it responsive" (the prose half)
|
||||
**Story:** `n/a`
|
||||
|
||||
## Objective
|
||||
|
||||
The page stops being a wall of explanation while every safety statement it carries survives
|
||||
verbatim — each one now guarded by a test that fails if a later edit deletes it. Write the
|
||||
assertions, then delete the prose.
|
||||
|
||||
## Work
|
||||
|
||||
1. `tests/test_page.js` — **first**, before touching the markup: add a named check per
|
||||
statement that must survive, matching on normalised text so a rewrap does not trip it:
|
||||
- arming forwards **every** key including Enter, and only while armed;
|
||||
- `Esc` frees the pointer and releasing frees the buttons;
|
||||
- disarm / stop / focus loss cancels a run of pasted text;
|
||||
- characters are positions → the target must be on a US layout;
|
||||
- the default-passphrase shout (driven by `dflt:1`, not static text);
|
||||
- nothing is stored/scheduled/replayed, in whatever wording survives;
|
||||
- the takeover cannot arm (added by phase 03 — guard it now so this task cannot lose it).
|
||||
Run the suite: it must pass *before* the trim (proving the sentences are there) and after
|
||||
(proving the trim did not remove them).
|
||||
2. `src/web_page.h` markup:
|
||||
- Each long paragraph becomes a one-line summary plus `<details><summary>why</summary>…`:
|
||||
the KDE/snap-left explanation, the mouse "movement is relative" paragraph, the send-text
|
||||
paragraph, the chords paragraph, and the footer's shortcut list. The **arming banner**
|
||||
stays visible — it is the one paragraph that must never be behind a tap.
|
||||
- The Connection card's passphrase paragraph → one line ("the board checks this on every
|
||||
connection; without it nothing can arm") with the rest in `<details>`.
|
||||
- Shorten `title` attributes that are currently multi-sentence (they are already duplicated
|
||||
in the `<details>`); a `title` is a tooltip, not a manual.
|
||||
- Collapse the Log card's option row and the State card's `kv` rows into a two-line summary
|
||||
at narrow widths — counters stay visible; they are the "is it typing" evidence.
|
||||
- Keep the DOM ids and the class hooks the CSS in task 01 targets; keep
|
||||
`#banner`, `#arm`, `#log`, `#paste`, `#mPad` and the chord buttons' `data-chord`
|
||||
attributes byte-identical (`test_proto.cpp` asserts every labelled chord against the
|
||||
combination its label promises).
|
||||
3. `<details>` in the shim — `tests/test_page.js`'s DOM shim is id-based, so `<details>` needs
|
||||
nothing to parse, but the safety assertions must read text content across it: make the
|
||||
matcher pull from the raw markup string (which the suite already has for the id check)
|
||||
rather than from the shim's element tree.
|
||||
4. `README.md` — Controller page section: say the hints are folded and that the safety notes
|
||||
are pinned by `tests/test_page.js` ("the test asserts the warning is still on the page"),
|
||||
which is the kind of sentence that keeps the next person from tidying them away.
|
||||
|
||||
- ASSUMPTION: `<details>` rather than a help modal or a tour: zero script, keyboard
|
||||
accessible, printable, works with no JS, and it degrades to visible text in a browser that
|
||||
ignores it — which is the safe direction for a safety note.
|
||||
- ASSUMPTION: "don't need as much text" means the *explanations*, not the controls. The chord
|
||||
buttons, the VT keys and the KDE row stay: the operator who does not need them today needs
|
||||
them the day a window manager steals Alt+Tab.
|
||||
|
||||
## Testing & Quality
|
||||
|
||||
- The new safety-string assertions pass before and after the trim; both runs in the report.
|
||||
- Every one of them is demonstrated to fail when the corresponding sentence is deleted (then
|
||||
restored) — otherwise they are decoration, which is the failure mode `AGENTS.md` calls out.
|
||||
- `node tests/test_page.js`, `tests/test_proto.cpp` (chord labels!) and the sanitiser run
|
||||
green; page byte count drops and is recorded for task 03.
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
- [ ] Safety statements present and pinned; each pin proven to fail on deletion.
|
||||
- [ ] Visible prose on the laptop view is roughly a third of what it was; every removed
|
||||
paragraph is reachable in a `<details>` in the served bytes.
|
||||
- [ ] `data-chord` bytes, `#banner`, `#arm`, `#paste`, `#mPad` and every script-reached id intact.
|
||||
- [ ] Full test suite green; `test_proto.cpp` untouched.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Task 03 — Make the numbers agree again: suite coverage, page size, README
|
||||
|
||||
**Phase:** `04_controller_page_responsive` · **Source:** `TODO.md:4` — "Clean up keyrelay
|
||||
interface, don't need as much text, make it responsive" (the verification half: a page claim
|
||||
is only true when something measures it)
|
||||
**Story:** `n/a`
|
||||
|
||||
## Objective
|
||||
|
||||
The restructure leaves the documentation and the suite telling the truth about the page as it
|
||||
is now: one `test_page` row with the real count, the size claim matching the served bytes, and
|
||||
coverage for the branches the restructure touched.
|
||||
|
||||
## Work
|
||||
|
||||
1. `tests/test_page.js` — coverage for the restructured page:
|
||||
- id coverage after the restructure (the rule already exists; make sure it still runs
|
||||
against the **markup** and not the shim, which hands out an element for any id);
|
||||
- the `<details>` content is still in the served bytes (a folded note is not a deleted one);
|
||||
- pinned constants: `TEXT_FRAME_MAX`/`TEXT_JOB_MAX` in the page still equal `kTextFrameMax`
|
||||
/ `kTextJobCap` in `src/config.h` (the suite does this today — re-run it and confirm it
|
||||
did not quietly stop matching after the edit);
|
||||
- behaviour unchanged: arming, `S=` state frames, paste chunking and refusal, mouse capture
|
||||
and button ordering, `E12`, the fingerprint line, `busy`.
|
||||
2. `README.md`:
|
||||
- Tests table: **one** `test_page` row, its real count, description covering the mouse, the
|
||||
takeover and the folded safety notes. Delete the stale duplicate row that still claims 47
|
||||
checks — the table currently lists the same suite twice with two different numbers, which
|
||||
is exactly the "two paths that can disagree" shape `AGENTS.md` warns about.
|
||||
- Controller page: "~35 KB" becomes the measured number, with the sentence that says how it
|
||||
was measured (`curl -sk https://keyrelay.local/ | wc -c` against the byte count
|
||||
`.agents/validate.sh` prints).
|
||||
- Intro bullet `**3406 checks**` → the new total from the suite output, not arithmetic.
|
||||
- Quick start / Troubleshooting: any sentence describing the old layout ("the page shows a
|
||||
banner under the ARM bar", pill lists) corrected to what the page does now.
|
||||
3. `AGENTS.md` — stale statements only: the page-size check line already changes in phase 02
|
||||
(`https -k`); if the byte count is quoted anywhere, update it or point at the gate that
|
||||
prints it rather than restating a number that will drift again.
|
||||
4. `.agents/HW_VERIFICATION.md` — fill the phone row: 360 px, one column, ARM reachable, no
|
||||
focus zoom, folded notes expandable — or `skipped (no device)` with the commands.
|
||||
|
||||
- ASSUMPTION: the totals in README are taken from a captured suite run pasted into the report,
|
||||
not added by hand. The intro count and the table sum have disagreed before.
|
||||
|
||||
## Testing & Quality
|
||||
|
||||
- `bash tests/run_tests.sh` and `SANITIZE=1 tests/run_tests.sh` green; `bash
|
||||
.agents/validate.sh` green including both firmware builds.
|
||||
- The README's page-size claim and check count are reproducible from commands in this
|
||||
repository, and the report shows both commands with their output.
|
||||
- No change to `src/*.h` other than `src/web_page.h` markup/style; no behaviour change in
|
||||
phases 01–03 (spot-check TLS, `busy`, and the paste path).
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
- [ ] One `test_page` row in the Tests table with the real count; no duplicate anywhere.
|
||||
- [ ] README page size == the served page's byte count (or the report says the board was
|
||||
unreachable and quotes the PROGMEM count the gate prints).
|
||||
- [ ] Intro check total matches the suite's own output.
|
||||
- [ ] `test_page.js` covers: id coverage, folded content present, pinned constants, behaviour
|
||||
unchanged; full test suite green.
|
||||
Reference in New Issue
Block a user