Skip to content

plan: bind Escape to cancel in-progress turn when harness canvas is focused #705

Description

@btipling

Status: IMPLEMENTED
Source: #703
Type: Single issue — pure Zig (Wasm-only)

Summary

Bind Escape to bridge.queueCancelFromUi() in the harness event scan — the same cancel path as the ■ Stop icon. Today Stop is pointer-only; Escape does nothing to the in-flight round.

Where

One event-scan addition in ui.zig (the same for (0..es.len) loop at ~L617–664 that handles Ctrl+Enter and ↑/↓ history), plus a docs row.

Design

Condition Behavior
Busy + Escape .down + queue_editing_index == null bridge.queueCancelFromUi() + mark handled
Busy + Escape .down + queue-row editor open skipqueue_band.handleEscape() already dismisses the editor (runs during queue_band.paint)
Idle + Escape no-op — do not clear composer, do not leave the page
.repeat Escape events skip — only the initial .down fires cancel (same as ■ single-fire)

Why this is safe

queueCancelFromUi() already has a no-op guard when lifecycle != .busy and !has_pending_submit (bridge.zig:288–289), so even a double-fire or a mis-placed Escape during idle is harmless. The queue-edit guard (queue_editing_index == null) matches the existing arrow-history guard at line 647 — the queue-row editor owns Esc.

Placement in the event scan

Insert the Escape handler after the Ctrl+Enter block (~L635) and before the arrow-history block (~L647). Escape has the highest priority after the send chord: if the operator presses Esc, don't also walk history or pass the key to textEntry.

Layers

Layer Change
Harness (Wasm) ui.zig +4–6 lines in event scan
Vercel backend No change
DOM No change
  • No protocol bump — v9 cancel path reused as-is
  • No bridge changesqueueCancelFromUi already exposed
  • No TS changes → no vitest/typecheck/di-gate/build

Docs

  • docs/harness-limits.md: Keyboard table → new row: **Escape** (harness focused, Busy) | Cancel the in-progress turn (same as ■ Stop). Idle: no-op. Queue-row editor: dismisses editor first (does not abort turn)

Ops & docs

  • Cloud ops: N/A — no Production mutate
  • Caps: No new caps, no existing cap changed
  • Zig gates only: zig fmt, zig build harness -Doptimize=Debug, zig build test-rich-invariants, build-harness CI

Testing

Manual smoke plan:

  1. Busy + Esc: Send prompt, press Esc while spinning → turn cancels (same as ■ Stop). Composer text preserved.
  2. Idle + Esc: Press Esc with no turn running → nothing happens.
  3. Queue edit + Esc: Edit a queued row, press Esc → editor closes (existing queue_band behavior). Press Esc again while still Busy → turn cancels.
  4. DOM nav overlay + Esc: Open NavMenu → Esc closes menu (host DOM, not canvas).

No host-testable unit test — frame() requires a running bridge + lifecycle state.

Suggested next step

/plan-review → review, then /implement-plan to ship.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions