Pular para o conteúdo

Exhaustive interaction-driven visual parity (drive every event over every element, capture the dynamic result, compare against a reference)

develop specs/develop/interaction-visual-parity.kmd

DRAFT — parent design the owner named (2026-07-05). The Koder UI-parity tooling (`/k-manifest` · `/k-parity` · `/k-mirror` + the `/k-test-gen-*` engine) today compares mostly STATIC structure/snapshots and drives only a SUBSET of pointer events against ONE surface class (the Kroma tiling terminal, via `kroma_test_state` + the #088 introspection oracle). It does NOT drive EVERY mouse/pointer interaction over EACH inventoried element and observe the real dynamic behaviour (movement, resize, hover/press effects, popovers, animation) to compare it against the reference app. This spec defines that missing capability — the unifying "drive-and-compare" layer that sits above the existing interaction-test families. Implementation is deferred pending owner prioritisation vs. the Kolide-on-Kroma slices; this document exists to scope and ratify the direction first.

Quando esta spec se aplica

Todos os triggers

Corpo da especificação

Exhaustive interaction-driven visual parity (DRAFT)

The gap (evidence)

The owner asked (2026-07-05): do our frameworks behind /k-parity / /k-mirror / /k-manifest execute every mouse event — hover, hint/tooltip, right-click, left-click, wheel up/down, drag-and-drop, hot-corner, … — over each inventoried graphic element, to visually compare the real dynamic behaviour (movements, resizes, effects)? An audit of origin/master (2026-07-05) found no:

  • /k-manifest inventories elements; the per-element behaviors field is free-text, not a structured, drivable enumeration of supported events/gestures/states.
  • /k-mirror compares static trees — a one-shot #088 KROMA_INTROSPECT dump vs. the reference AT-SPI tree — and its perf dimension only reads the RFC-012 corpus. It does not synthesise events per element. Its "drive-to-state" mode is the open kroma#124 (pending).
  • /k-parity is a static feature × variant matrix (zero runtime).
  • The only live event-driving path is kroma_test_state (engines/sdk/kroma/src/test_state.rs) + the #088 introspection oracle (kroma#092) — and it drives a subset (press/release, right-click routing, selection drag, header drag/double-click, wheel_up) against the kterm surface only. The /k-test-gen-* generator engine that would emit these systematically targets Flutter/Compose/web, not Kroma-native Rust (kroma#092 "Why").

Current per-event coverage for Kroma-native UI (the baseline this spec must close):

Event / behaviourToday
left-click / press-release / selection-drag / header drag+double-click (maximize)DRIVEN-LIVE, hand-written, kterm only (pointer-interaction-tdds P1/P3/P5)
right-click / context-menu routingDRIVEN-LIVE, kterm only
wheel-updriver live, scroll-offset oracle deferred (px math unit-only)
wheel-downGAP (no primitive)
hover (effect/state)SPEC-ONLY (states.kmd overlays; only a negative assert exists)
hint / tooltipGAP (states.kmd R3 = "not enforced")
hot-corner (as an interaction)SPEC-ONLY (exists only as a motion ripple in animation-parity)
generic double-click / drag-and-drop / free resizeGAP
visual comparison of the dynamic result (movement/resize/effect/animation-over-time, per element, vs a reference)GAP for Kroma (motion/animation-parity targets Flutter/web)

Intent

Every interactive element in a Koder UI surface's inventory MUST be exercised with the full set of input events it can receive, and the dynamic result of each (geometry delta, state overlay, popover open, animation curve, effect) captured and compared — both against the element's spec and, where a reference app exists, against the reference's observed behaviour. "Compare what really happens in practice", not just the at-rest structure.

The four pillars (each maps to an existing/pending piece — this spec unifies them)

  1. Declare (manifest). /k-manifest emits, per element, a structured interaction descriptor: the events it accepts (hover/hint/click{left,right,middle}/double-click/press/wheel{up,down}/ drag/drop/long-press/hot-corner), its hit-region(s), and the observable it produces (state overlay / popover / geometry change / animation). Replaces the free-text behaviors string with a drivable schema. (New — extends /k-manifest; feeds kroma#092 slice 1.)
  2. Drive (harness). A synthetic-input harness drives that full event set headless against a running surface, for every Kroma surface class (kterm, kolide panel/overview/notifications/greeter/settings, kompose), not just kterm. Extends kroma_test_state (test_state.rs) with the missing primitives (hover, wheel_down, tooltip-dwell, generic double_click, drag_and_drop, hot_corner) reading back through the #088 introspection oracle. (Extends kroma#092 beyond its slice-1 subset + surface.)
  3. Capture + compare (mirror). /k-mirror gains a drive-to-state + visual-capture mode: for each inventoried element it fires the event, records the dynamic result (grim frame(s) / frame-diff / animation curve via the motion-parity prongs) and diffs it against the reference app's observed result. Supersedes the static-snapshot limitation (builds on kroma#124 drive-to-state + animation-parity-tdds temporal prongs + visual-regression-tdds capture).
  4. Generate (test engine). /k-test-gen-* emits these as regression tests per declared element (the reflexive "Kora generates its own TDDs" capability — kroma#092 slice 5 generalised from pointer to the full event set + visual/animation oracles).

Relationship to the existing interaction-test family (this is the superset, not a replacement)

  • pointer-interaction-tdds.kmd (P1–P5) — the pointer-desktop subset (capture routing, wheel-step, gesture-cancel, title-bar). Becomes the first driven slice under this umbrella.
  • gesture-tdds.kmdtouch (explicitly excludes pointer-desktop). The touch analogue of pillar 2.
  • animation-parity-tdds.kmd / motion — the temporal comparison engine pillar 3 reuses to capture the dynamic result.
  • visual-regression-tdds.kmdstatic goldens; pillar 3 reuses its capture for the per-event frames.
  • interaction/states.kmd — the hover/pressed/focus/hint contract pillar 2 must drive-and-verify per element (today only specified, not driven).

Slices (draft — refine on ratification; NOTHING implemented yet)

  • S1 — Manifest interaction schema (pillar 1): the drivable descriptor + /k-manifest emitter. Blocks kroma#092 slice 1.
  • S2 — Full-event harness (pillar 2): extend kroma_test_state with the missing primitives + generalise beyond kterm. Extends kroma#092.
  • S3 — Mirror drive-to-state + visual capture (pillar 3): kroma#124 + the per-event frame/curve capture and reference diff.
  • S4 — Reflexive generation (pillar 4): kroma#092 slice 5, the full-event + visual/animation generator.
  • S5 — Roll out per Kroma surface (kterm → kolide surfaces → kompose), each producing a per-element interaction-parity report vs. its reference.

S1 — Interaction descriptor schema (normative — implemented 2026-07-07)

S1 (pillar 1, "Declare") is the drivable contract every later pillar consumes: the harness (S2) drives exactly the events a descriptor lists, the mirror (S3) captures exactly the observable it names, and the generator (S4/#092) templatizes one test per descriptor entry. It supersedes the free-text behaviors string for interactive elements — behaviors was prose a machine can't drive; this is an enumerated, drivable structure. behaviors remains allowed for non-interactive narration (e.g. "auto-refreshes every 30s"); anything a pointer/gesture can trigger moves to interactions.

R1 — Per-object interactions array

Each object in a koder.manifest.json (see commands/k-manifest.md FASE 3) MAY carry an interactions array. Every interactive element (button, menu, field, tab, divider, header, hot-corner, resizable region…) MUST carry one; a non-interactive element (static text/image) omits it. Each entry:

{
  "event":      "<one of the closed event enum below>",
  "hit_region": "self" | "<named-subregion>" | { "x": <n>, "y": <n>, "w": <n>, "h": <n> },
  "observable": {
    "kind":   "<one of the closed observable enum below>",
    "detail": "<the assertion the driver checks after firing — concrete, e.g. 'context menu anchored within this element's rect', 'height grows by one tile row', 'hover overlay = 8% white'>"
  },
  "spec_reference": "<the .kmd that governs this behaviour, or null>"
}

R2 — Closed event enum (drivable set)

hover · hint (tooltip-dwell) · click_left · click_right · click_middle · double_click · press · release · wheel_up · wheel_down · drag · drop · long_press · hot_corner. A descriptor MUST NOT invent events outside this set (an unknown event is a schema error, not an extension point — extend the enum here first). This mirrors S2's harness primitives one-to-one, so "declared but undrivable" is impossible by construction.

R3 — Closed observable-kind enum (what the oracle asserts)

state_overlay (hover/press/focus visual per interaction/states.kmd) · popover (menu/tooltip/dialog opens) · geometry_change (move/resize/maximize — a rect delta) · animation (a temporal curve, captured via the motion-parity prongs) · selection (text/item selection state) · navigation (surface/route change) · none (accepted but no observable — e.g. a swallowed event). Each maps to a #088-introspection assertion: geometry_change→rect delta, popover→new node + anchor, state_overlay→state flag/colour, animation→frame/curve diff, selection→selection range, navigation→active-surface id.

R4 — Emitter (/k-manifest) and consumer (#092 slice 1)

interactions is an LLM-deferred field (like behaviors): koder-manifest gen scaffolds the object skeleton and reserves the field; the /k-manifest LLM pass fills it from the source (the widget's event handlers + its governing spec). The kroma#092 slice-1 "manifest surface declaration" consumes this array directly — each {event, hit_region, observable} is one pointer-interaction TDD the k-test-gen-pointer generator emits. S1 delivers the schema + the emitter wiring; S2 delivers the harness that drives it.

Not in scope

Touch/gesture parity (owned by gesture-tdds.kmd); the reference-app collectors themselves (AT-SPI / gschema, already in /k-mirror); perf/latency of each action (owned by ui-interaction-perf.kmd / RFC-012 — though pillar 3 can co-capture timing).

Status

Parent design proposed 2026-07-05. S1 (the interaction descriptor schema, §S1 above) ratified + implemented 2026-07-07 (owner /k-go): the drivable schema is normative and wired into /k-manifest; kroma#195 tracks it and unblocked kroma#092 slice 1. S2–S5 UNGATED 2026-07-07 (owner /k-go: "rodar o 156 agora de uma vez") — the timing call vs. Kolide-on-Kroma is resolved in favour of running the epic now at full breadth; S2 (full-event harness) is under way (kroma#197), then S3–S5. Still not a mandatory gate (no spec-triggers entry) until the epic ratifies end-to-end. Tracking epic: engines/sdk/kroma/backlog/in-progress/156-exhaustive-interaction-visual-parity-epic.md.