# Task 01 — Touch pad: the capture is the finger **Phase:** `05_mobile_controller` · **Source:** `TODO.md:5` — "Needs to be mobile compatible — … **Mobile trackpad for touch trackpad.**" **Story:** `n/a` ## Objective `#mPad` becomes a touch pad: press and hold it and the target's pointer moves with your finger, drag two fingers to scroll, and the moment the touch ends the movement stops and every button lets go. Pointer Lock does not exist for touch, so the capture is the touch itself. ## Work 1. `src/web_page.h`, the mouse block: - `touchstart` on `#mPad`: requires `armed` and `mouseAvailable()` (`s.mouse === 1`) — the same two gates as `takeMouse()`, and the same `E10`/`E11` refusal path if the board disagrees. Sets `mTouchActive`, records the anchor point, and takes **no** button. - `touchmove`: compute the delta from the previous sample, feed the existing movement pipeline (the code behind `flushMouse()`) so the ±127 cap and the multi-frame split are the ones already tested — do not write a second splitter. Coalesce per report the way `mousemove` does, and drop movement when there is no link rather than queueing it (the page's existing rule: a queue of pointer motion delivered after you stopped moving is the pointer version of a stuck key). - Two-touch drag → wheel/pan through the same quantiser the desktop `wheel` handler uses (notches with the remainder carried), not movement. `touchmove` with two fingers must not also emit dx/dy. - `touchend`/`touchcancel`, and `blur`/`visibilitychange`/disarm/link loss: end the capture, `dropMouse()` (which releases every button through the funnel), reset the anchor. `touchcancel` is the browser taking the gesture away — incoming call, notification shade, a palm — and it must land exactly like lifting the finger. - A visible button row beside the pad: **Left / Right / Middle**, held while pressed (`touchstart` down, `touchend` up), carrying the existing `MOUSE_BTN` bits. Tap on the pad itself never clicks. The row is 44 px and shows which bits are down, so "left is held" is a thing you can see before you ask what is stuck. - The pad's label changes with state (`not captured` / `captured — move to move, lift to release`) since a phone has no cursor to indicate either. 2. `refreshMouseUi()` / `paintMouse()` / `mouseAvailable()` — teach them the touch state so the pills say what is true. `mCaptured` (Pointer Lock) and `mTouchActive` are different states that must render distinctly and never both claim the pointer. 3. Keep the desktop path byte-for-byte identical: `mousemove`, the Pointer Lock request, `Esc`, `wheel` on a mouse — phase 04's assertions and today's `test_page.js` mouse checks must stay green without edit. 4. `tests/test_page.js` — extend the shim for multi-point touch and add: capture requires armed + `mouse:1`; a drag produces one `M=` per flush with no idle frames; a large drag splits with the net displacement exact; two fingers produce wheel/pan and no dx/dy; `touchend` and `touchcancel` each release every button and stop movement; button-row bits equal `MOUSE_BTN`; every emitted frame parses with the firmware's own parser. 5. `README.md` — Controller page: the pad row of the element table describes the touch behaviour; "What capture does and does not do" gains the touch sentence (this is elaborated properly in task 04 — here, only what this task changes). - ASSUMPTION: one finger moves, two fingers scroll, and clicking is an explicit held button rather than a tap. Tap-to-click on a pad whose whole job is movement produces clicks the operator did not ask for, and on a remote keyboard a click the target did not expect is the worst class of surprise. - ASSUMPTION: no "tap-and-hold to drag" (holding a button while moving). It needs a second simultaneous gesture and any mis-timing strands a button on the target; the button row gives the same capability with visible state. - ASSUMPTION: scale of finger-delta to HID delta is 1:1 (one CSS pixel → one report unit) at first. If it feels slow on hardware, the tuning knob goes in `config.h`-style naming on the page side with the measured value in the comment — the board must not invent a pointer speed of its own. ## Testing & Quality - `node tests/test_page.js`: the new touch checks pass and the existing mouse checks are unmodified (prove it: the diff of that test file adds, it does not change, mouse assertions). - `bash .agents/validate.sh` green; `test_proto.cpp`, `test_mouse.cpp`, `test_output.cpp` untouched. - Device (best effort, one attempt): a phone on the board's AP — movement lands, lift stops it, and `watch_target.py` shows no stuck button across 20 press/release cycles. ## Completion Criteria - [ ] Press-and-hold on the pad moves the target's pointer; lifting stops it and releases every button (observed, or recorded `skipped (no device)` with the commands). - [ ] Two-finger drag scrolls and never moves the pointer. - [ ] Left/Right/Middle are held-while-pressed and visible; tap on the pad clicks nothing. - [ ] Desktop Pointer Lock path unchanged and still covered by the pre-existing checks. - [ ] Full test suite green.