Gesture TDDs for mobile UI (single/multi-finger tap · drag · parallel multi-finger drag · pinch/rotate · swipe/fling · system-edge gestures)
develop specs/develop/gesture-tdds.kmd
The Koder Stack test generators MUST emit touch-gesture tests for every MOBILE UI variant (Android/iOS, and any touch surface) of every component: single-finger tap/long-press/double-tap, single-finger touch-and-drag, MULTI-finger simultaneous tap, and MULTI-finger PARALLEL touch-and-drag (two-finger scroll, pinch/zoom, rotate), plus directional swipe/fling and system-edge gestures. Each gesture asserts the surface RESPONDS correctly (navigation/state/scroll-offset/zoom-level), gestures are reachable, and parallel multi-finger input is honored. Driven per surface: Flutter `integration_test`/`WidgetTester` multi-pointer gestures; Android Compose `performTouchInput`; iOS XCUITest; and native shell / Flutter-Linux / mobile-on-Kompose end-to-end via `koder-vdev` touch (synthetic-input-and-virtual-devices.kmd modality #4). Companion to visual-regression-tdds.kmd (Category E scroll uses the drag primitive).
Quando esta spec se aplica
Triggers primários
- Author or regenerate gesture TDDs for a mobile/touch surface
Todos os triggers
- Ship or change a touch-driven UI on a mobile (Android/iOS) Koder variant
- Author or regenerate gesture TDDs
- A surface handles tap/drag/swipe/pinch/multi-finger gestures
Corpo da especificação
Spec — Gesture TDDs for mobile UI
Scope
Every touch-capable Koder UI variant MUST ship gesture TDDs under
<module>/tests/regression/gesture/. Primary targets:
- Flutter mobile (
app/mobile/— Android + iOS). - Android native (Compose / Views).
- iOS native (when shipped).
- Native shell / mobile-on-Kompose and Flutter-Linux touch —
end-to-end via
koder-vdevtouch (modality #4).
Out of scope: pointer-only desktop surfaces (covered by the pointer driver
- visual-regression), CLI/TUI, server-only services. A desktop surface that ALSO accepts touch ships the gesture categories its touch handlers exercise.
R1 — Gesture categories
A surface ships the categories its handlers exercise. Each category asserts the surface's response, not merely that the event was sent.
Category G1 — Single-finger taps
Tap, double-tap, long-press at the target's center. Assert the bound action fires exactly once (tap → activate; double-tap → its distinct action; long-press → context menu / selection). A tap that lands but triggers nothing (dead control) fails; a tap that triggers twice (double-fire) fails.
Category G2 — Single-finger drag / swipe / fling
Touch-and-drag from A to B. Covers: scroll (the driver for visual-regression Category E reachability), directional swipe (dismiss, reveal, page), and fling (momentum scroll). Assert the resulting scroll offset / navigation / dismissal matches the gesture direction + distance, and that a short drag below the slop threshold is treated as a tap, not a drag (G7).
Category G3 — Multi-finger simultaneous tap
Two- and three-finger taps (each finger its own contact, down together). Assert the multi-finger binding fires (e.g. two-finger tap → secondary action / a11y) and that it is NOT misread as N separate single taps.
Category G4 — Multi-finger PARALLEL drag
Two (or more) fingers dragging at the same time, one interpolated
frame containing every contact (koder-vdev touch multidrag /
WidgetTester multi-pointer). Covers two-finger pan/scroll. Assert all
contacts move in parallel and the surface honors the multi-finger pan
(not just the primary finger).
Category G5 — Pinch / zoom / rotate
Two-finger parallel drag whose paths converge (pinch-in / zoom-out), diverge (pinch-out / zoom-in), or arc (rotate). Assert the zoom level / rotation of the target (map, image, canvas — e.g. KVG/Kad) changes monotonically with the gesture and clamps at its min/max.
Category G6 — System / edge gestures
Edge-swipe back, app-switch, top/bottom-edge reveals — where the surface
participates. Assert the surface reacts correctly AND does not swallow a
system gesture it shouldn't (respecting app-layout/safe-area.kmd gesture
insets).
Category G7 — Disambiguation / conflict
Tap-vs-drag slop threshold, scroll-vs-swipe, single-vs-double-tap timeout, drag-vs-long-press. Assert the surface resolves ambiguous input to the intended gesture (a 3 px wobble during a tap is still a tap; a slow press that then moves is a drag, not a long-press).
R2 — Driver per surface
| Surface | Gesture driver |
|---|---|
| Flutter mobile (Android/iOS) | integration_test + WidgetTester TestGesture (multi-pointer: startGesture per finger → parallel moveBy) |
| Android native (Compose) | performTouchInput { down/moveTo/up }, multi-pointer via pointer IDs; UiAutomator for system gestures |
| iOS native | XCUITest gesture APIs (tap, press(forDuration:thenDragTo:), multi-touch) |
| Native shell / Flutter-Linux / mobile-on-Kompose | koder-vdev touch (modality #4): touch tap/multitap/drag/multidrag over a real /dev/uinput multitouch device on the s.khost1 seat |
The native end-to-end path (koder-vdev) is the one that proves the REAL
touch stack (compositor → toolkit → app), the same way the pointer driver
supersedes the animation debug hook. Flutter/Compose/XCUITest drive the
widget layer where a device seat isn't available.
R3 — Determinism & isolation
- Absolute, fixed contact coordinates (mapped from the target's center) —
reproducible hits (
synthetic-input-and-virtual-devices.kmd § R3). - Parallel multi-finger frames are emitted atomically (all contacts in one SYN_REPORT) so timing is deterministic, not finger-by-finger races.
- The
koder-vdevpath runs on s.khost1, never the owner's laptop (a real multitouch device would hijack the seat — spec § R6). - Each gesture emits a trigger marker (AV timeline) so the gesture's visual/audio response can be cross-checked (animation/audio parity).
R4 — Generation contract
/k-test MUST emit the gesture suite when a module has a touch
surface:
koder.toml[ui]withsurface/surfacesincludingmobile(Flutter Android/iOS) ortouch = true.- A
pubspec.yamlwithflutter+ anapp/mobile/target. - An Android
AndroidManifest.xmlwith a touchscreen<activity>. - A native shell declaring touch input.
Generated tests live under <module>/tests/regression/gesture/, named
<interaction>-<category>.{dart,kt,swift,sh}. The generator scaffolds the
per-surface driver (R2) and, for the native path, the koder-vdev calls +
the assertion on the surface's observable response. At minimum every
mobile surface gets G1 (tap) and, for any scrollable surface, G2
(drag) — the latter shared with visual-regression Category E.
R5 — Run cadence
| Trigger | Categories | Where | On failure |
|---|---|---|---|
Pre-commit (/k-commit) | G1 + G2 --fast (widget-level) | laptop/CI | Block commit |
| Nightly CI | G1–G7 (widget) + native G1–G5 via koder-vdev | s.khost1 / emulator | Open ticket auto |
Pre-release (/k-ship) | G1–G7 all surfaces | s.khost1 / emulator | Block release |
R6 — Accessibility
- Every gesture-only action MUST have a non-gesture alternative where a11y requires it (a pinch-zoom surface also offers buttons; a swipe-to-delete also offers a menu). The gesture suite asserts the alternative exists for flagged actions.
- Long-press / multi-finger timings respect the platform a11y timing settings.
R7 — Forbidden / anti-patterns
- ❌ Asserting a gesture by sending events and checking nothing about the response — assert the surface's observable reaction (R1).
- ❌ Emitting multi-finger contacts finger-by-finger across frames when the gesture is parallel — one frame per step, all contacts (R3).
- ❌ Running the
koder-vdevnative path on the owner's laptop (R3/§R6). - ❌ Shipping a gesture-handler fix without a gesture TDD that would have
caught it (
policies/regression-tests.kmd).
Tests of the test contract
| ID | Test |
|---|---|
| T1 | A Flutter app/mobile/ module gets tests/regression/gesture/ populated by /k-test --gen-only (≥ G1 tap + G2 drag). |
| T2 | G1: a tap on a button fires its action exactly once; a dead control fails. |
| T3 | G2: a drag scrolls a long list to the bottom; the last item becomes reachable (ties to visual-regression Category E). |
| T4 | G3: a two-finger tap fires the two-finger binding and is NOT counted as two single taps. |
| T5 | G4: a two-finger PARALLEL drag pans the surface; both contacts move together (verified via the koder-vdev multidrag read-back on s.khost1). |
| T6 | G5: a two-finger pinch-out raises the zoom level monotonically and clamps at max. |
| T7 | G7: a 3 px wobble during a tap is resolved as a tap, not a drag. |
| T8 | Native path: koder-vdev touch multidrag injects parallel contacts proven by BTN_TOUCH + per-slot ABS_MT_TRACKING_ID/POSITION read-back (s.khost1). |
Relationship to existing specs
- Driven by
synthetic-input-and-virtual-devices.kmdmodality #4 (thekoder-vdevtouch driver — single/multi-finger tap + drag + parallel multidrag, implemented KSTACK-183). - Feeds
visual-regression-tdds.kmdCategory E — the G2 drag is the scroll-reachability driver on touch surfaces. - Cross-checks
animation-parity-tdds.kmd/audio-parity-tdds.kmdvia the shared trigger marker (a gesture's visual/audio response). - Respects
app-layout/safe-area.kmdgesture insets (G6). - Bound by
policies/regression-tests.kmd+heavy-work-isolation.kmd.
R8 — Existing gaps (snapshot at ratification)
At ratification (2026-06-07) no Koder mobile surface ships a gesture suite.
The native driver (koder-vdev touch, modality #4) exists and is
validated on s.khost1 (KSTACK-183 — tap/multitap/drag/multidrag +
selftest read-back); the per-surface Flutter/Compose/XCUITest emitters and
the /k-test wiring are the remaining build-out. Track closure in
registries/synthetic-input-coverage.md (#4) + the per-module gesture
coverage.
Open follow-ups
/k-testgesture emitters per surface (Flutter/Compose/XCUITest/native).- Wire G2 as the touch-surface driver for visual-regression Category E.
- Android-emulator lane on s.khost1 for the device-level G1–G7.
Referências
specs/develop/synthetic-input-and-virtual-devices.kmdspecs/develop/visual-regression-tdds.kmdspecs/develop/animation-parity-tdds.kmdspecs/app-layout/safe-area.kmdpolicies/regression-tests.kmdpolicies/heavy-work-isolation.kmd