Skip to content

feat: preflight native-ref fast path with shared interaction guards (ADR 0011)#1082

Merged
thymikee merged 1 commit into
mainfrom
feat/adr0011-native-ref-preflight
Jul 4, 2026
Merged

feat: preflight native-ref fast path with shared interaction guards (ADR 0011)#1082
thymikee merged 1 commit into
mainfrom
feat/adr0011-native-ref-preflight

Conversation

@thymikee

@thymikee thymikee commented Jul 4, 2026

Copy link
Copy Markdown
Member

Closes the three native-ref gap cells from the ADR 0011 matrix (part of #1081): occlusion, offscreen, and nonHittable.

Problem

click @ref / fill @ref take a fast path (maybeTapRefTarget / maybeFillRefTarget) straight to backend.tapTarget/fillTarget, skipping the runtime guards. A backend fast path can silently "succeed", so delegation-on-error never triggers — a covered or off-screen ref was reported as a successful tap.

Approach

The ref came from the stored daemon session snapshot, so the node is already in hand. preflightNativeRefInteraction (exported from src/commands/interaction/runtime/resolution.ts, next to the guards it reuses) runs before the backend call in both fast paths:

  • occlusionisSnapshotNodeInteractionBlocked via the existing assertInteractionNotBlocked, producing the exact buildCoveredInteractionError shape the runtime path throws (Ref @eN is covered by another visible element…, ref/interactionBlocked details).
  • offscreenisNodeVisibleOnScreen via the existing assertVisibleRefTarget, producing the exact offscreen_ref shape (reason: 'offscreen_ref', rect/viewport details, scroll hint).
  • nonHittabledescribeNonHittableTarget annotation only: targetHittable/hint attach to the fast-path result. Promotion to a hittable ancestor stays a runtime-path behavior — the preflight never changes which element the backend acts on.

Deliberate semantics

  • A ref that fails occlusion/offscreen preflight errors with the same shapes the runtime path produces — cross-path consistency is the point of ADR 0011.
  • No-snapshot no-op rule: if the session has no snapshot, the ref does not resolve in it, or the node lacks a usable rect, the preflight is a no-op and the fast path proceeds exactly as before. The preflight never adds a capture round trip — zero extra device/daemon traffic by construction.

Registry

The three native-ref cells flip from waived('gap: …') to { kind: 'runtime', via: … } pointing at the shared guard symbols, and leave the pinned gap list in src/contracts/__tests__/interaction-guarantees.test.ts (16 → 13 acknowledged gaps).

Tests

New cases in src/commands/interaction/runtime/interactions.test.ts:

  • off-screen ref via click fast path → offscreen_ref error, zero backend calls
  • off-screen ref via fill fast path → offscreen_ref error, zero backend calls
  • covered ref via click fast path → covered-element error, zero backend calls
  • non-hittable ref → tapTarget still called, result carries targetHittable: false + hint
  • no-snapshot session → fast path proceeds untouched (backend called, no annotation)

All preflight tests assert captureSnapshot is never invoked.

Verification

  • pnpm format:check, pnpm typecheck, pnpm lint — clean
  • pnpm exec fallow audit --base origin/main — no issues in changed files
  • pnpm exec vitest run src/commands src/daemon src/contracts — 141 files / 1222 tests pass
  • pnpm exec vitest run test/integration — 27 files / 89 tests pass

Refs #1081

…ADR 0011)

click @ref / fill @ref dispatch straight to backend.tapTarget/fillTarget,
and a backend fast path can silently "succeed" — delegation-on-error never
triggers there. The ref came from the stored session snapshot, so the node
is already in hand: preflightNativeRefInteraction runs the SAME shared
guards the runtime path uses against that node before the backend call.

- occlusion: isSnapshotNodeInteractionBlocked via assertInteractionNotBlocked
  (same covered-element error shape as the runtime path)
- offscreen: isNodeVisibleOnScreen via assertVisibleRefTarget (same
  offscreen_ref error shape)
- nonHittable: annotation only — targetHittable/hint attach to the result;
  promotion stays a runtime-path behavior and the tapped element never changes

No snapshot in the session, an unresolvable ref, or a missing/unusable rect
make the preflight a no-op: it never adds a capture round trip.

Registry: the three native-ref gap cells flip to kind 'runtime' and leave
the pinned gap list.

Refs #1081
@thymikee

thymikee commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

Reviewed by running. The design holds to the ADR's preflight rules exactly: guards reuse the runtime path's own throwers (zero new error-construction sites, shapes byte-identical to the runtime-ref path), the no-op conditions (no session/snapshot/ref/rect) are all fail-open with tests overriding captureSnapshot to throw — proving zero added round trips by construction — and promotion correctly stays runtime-only, with the fast path gaining only the annotation. Registry flip and pin-list removal (16→13) are correct. Re-ran interaction + contracts suites in the worktree: green. LGTM — CI pending.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.5 MB 1.5 MB +442 B
JS gzip 485.8 kB 485.9 kB +120 B
npm tarball 589.3 kB 589.5 kB +156 B
npm unpacked 2.1 MB 2.1 MB +442 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 25.1 ms 25.2 ms +0.1 ms
CLI --help 45.9 ms 45.9 ms -0.0 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/2948.js +337 B +89 B
dist/src/2426.js +105 B +31 B

@thymikee thymikee merged commit b9fb4db into main Jul 4, 2026
21 checks passed
@thymikee thymikee deleted the feat/adr0011-native-ref-preflight branch July 4, 2026 12:24
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-07-04 12:25 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant