Pointer-interaction TDDs for desktop UI (hit-test routing · pointer capture · wheel scroll-step · gesture cancellation · title-bar press)
develop specs/develop/pointer-interaction-tdds.kmd
The Koder Stack test generators MUST emit pointer-interaction tests for every POINTER-DRIVEN DESKTOP UI surface of every component — the class of surface that gesture-tdds.kmd explicitly puts out of scope ("pointer-only desktop surfaces"). These are mouse/trackpad invariants that don't depend on the toolkit: a fresh press hit-tests to the region UNDER the pointer (never a region holding capture); a discrete wheel notch scrolls a fixed number of lines, invariant to font/cell size; a new gesture cancels an in-flight one and releases capture; a title-bar press double-clicks to maximize/restore and single-drags to rearrange. Created as the regression-tests.kmd §7 generator-coverage backfill for a cluster of kterm (Kroma) interaction bugs that had no generator to catch their class. FIRST target is Kroma — the canonical Koder pointer-desktop UI engine — whose generation target (the "Kora generates TDDs for its own Rust components" capability) is kroma#092: it reuses the #088 introspection export as the assert oracle and synthetic Event::Pointer* into the tile/widget tree as the input driver.
Quando esta spec se aplica
Triggers primários
- Author or regenerate pointer-interaction TDDs for a desktop surface
Todos os triggers
- Ship or change a pointer-driven desktop UI surface (Kroma app, native desktop shell)
- Author or regenerate pointer-interaction TDDs
- A surface handles pointer press/drag/wheel/capture across multiple regions (tiling/split/panes/overlays)
Corpo da especificação
Spec — Pointer-interaction TDDs for desktop UI
Scope
Every pointer-driven desktop Koder UI surface MUST ship pointer-interaction TDDs
under <module>/tests/regression/pointer/. This is the complement of
gesture-tdds.kmd (touch) — it owns the mouse/trackpad invariants that the gesture
spec lists "out of scope (pointer-only desktop surfaces)".
Primary targets:
- Kroma apps (
kroma-kterm, kolide, kompose, every Kroma-rendered surface) — the Kore-native render engine. Generation target = kroma#092 (syntheticEvent::Pointer*into the tile/widget tree +#088introspection as the assert oracle, headless). - Flutter-desktop / web pointer surfaces — via the existing pointer driver
(
koder-vdev pointer, synthetic-input-and-virtual-devices.kmd modality #2), absolute coordinates (R3 determinism).
Out of scope: touch (gesture-tdds.kmd), CLI/TUI, server-only services. A surface that is both touch and pointer ships both category sets.
R1 — Generation contract
For every component whose manifest declares a pointer-driven desktop surface with 2+
hit-test regions (panes/tiles/splits/overlays) OR a scrollable viewport OR a draggable
title bar, /k-test MUST emit categories P1–P5 below into
<module>/tests/regression/pointer/. Driven per surface (R1 of synthetic-input): Kroma →
the Kore-native harness (kroma#092); Flutter/web → koder-vdev pointer. All input uses
absolute coordinates (synthetic-input R3) so hit-test fidelity is exact + repeatable.
Categories
P1 — Fresh-press hit-targets the region under the pointer (capture independence)
When a new pointer press occurs at (x,y) while another region holds pointer capture
(an in-flight drag/selection), the press routes to the region under (x,y) — never the
captor, and never the first region in tree/z order. Test: hold capture in region A
(begin a drag), press in region B → assert B receives the press and A does not; any
context action (menu) anchors in B at (x,y). Capture (is_forced) keeps only the
ONGOING drag's moves/up reaching its region. Origin: kterm right-click opened the
context menu in the wrong pane (fix a9c5063cc0).
P2 — Wheel scroll-step is N lines/notch, font-zoom invariant
A discrete wheel notch scrolls a fixed number of lines (Stack default 3, Tilix/VTE
parity), scaled by the surface's real cell/line height so the line count is invariant to
font zoom; a trackpad pixel-delta stays pixel-precise (not stepped). Test: one notch at
cell height H → N lines; the same notch at 2H → still N lines; pixel-delta P → P px.
Origin: kterm scrolled 1 line/notch, sluggish vs Tilix (fix dfbbe50f62).
P3 — A new gesture cancels the in-progress one
A press of a different button (or the start of a new gesture) ends any in-flight
drag/selection and releases capture before starting its own action — so a stale gesture
never keeps tracking the pointer. Test: begin a left-drag selection, then right-press →
assert the selection ended (not "selecting") AND the menu opened; subsequent moves do not
extend the (now-ended) selection. Origin: kterm right-press left the selection tracking
the mouse (fix a9c5063cc0).
P4 — Capture handoff / no stuck capture
Pointer capture is released on the terminating event (up/cancel); a press that lands outside the captor does not leave the captor stuck holding capture (which would broadcast every later move to it). Test: drive a drag, drop outside the captor's bounds → assert capture cleared; a following move reaches only the region under it.
P5 — Title-bar / header press semantics
On a surface with a draggable region header (tile/pane title bar): a double-click on the header toggles maximize/restore of that region; a single-press drag grabs the header to rearrange the region to a drop edge; a press on the header does NOT start text selection in the content. Test: double-click header → region maximized, double-click again → restored; press-drag header from region A onto region B's edge → A moves to that edge. Origin: kterm's 24-jun binary predated header drag-rearrange + double-click-maximize (present + tested in origin; this category guards the class against future regression).
R2 — Verdict + gate
Each category emits deterministic, headless tests (logic/state, no human eye). A failing
category is a release-gate fail (/k-test --release / /k-ship), same as gesture/visual.
Per regression-tests.kmd §7, a component-local regression (the floor) plus generated
P1–P5 (the ceiling) are both required where applicable.
Ratification
RATIFIED 2026-07-07 (owner /k-go). Created per regression-tests.kmd §7 (Generator-coverage
backfill, owner directive 2026-06-07) from the kterm pointer-interaction bug cluster (2026-06-30). The
generation target that makes these reach Kroma (Kore-native Rust) is the epic kroma#092 — now complete
and declaration-driven: /k-test-gen-pointer reads the pointer-desktop surface declared in a module's
koder.manifest.json (interaction-visual-parity.kmd §S1) and emits P1–P5 as Rust tests via the
kroma_test_state harness + the #088 introspection oracle. As a mandatory: true spec it is now wired as a
spec-triggers.kmd gate (action: ship/change a pointer-driven desktop UI surface, or author/regenerate
pointer-interaction TDDs) — the CLAUDE.md trigger table lists it.
Referências
specs/develop/gesture-tdds.kmdspecs/develop/synthetic-input-and-virtual-devices.kmdspecs/develop/visual-regression-tdds.kmdpolicies/regression-tests.kmdpolicies/heavy-work-isolation.kmdengines/sdk/kroma/backlog/pending/092-kore-native-pointer-interaction-tdd-generation.md