You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
pi-tui negotiates the kitty keyboard protocol at startup (falling back to modifyOtherKeys). On supporting terminals the Escape key no longer arrives as a bare \x1b byte — it arrives as CSI-u \x1b[27u. pi-tui's matchesKey(data, 'escape') normalizes all three encodings (legacy, kitty CSI-u, modifyOtherKeys), and ctx.mayflyKeymap.matches(data, ACTION_CANCEL) delegates to it.
Any panel that instead compares raw bytes — data === '\x1b' — silently stops reacting to Escape on those terminals, while plain-letter shortcuts (e.g. q) keep working, which makes the failure look bizarre to the user. Headless PTY tests do not reproduce it because a PTY never negotiates kitty, so Esc stays in the legacy encoding there.
This exact bug shipped in the /btw view (fixed in #3 by routing Escape/arrows through mayflyKeymap).
Architecture observation
Key registration is single-sourced (core/keymap.ts + the INTERACTION_KEY_ACTIONS batch in interaction/keys.ts). Key dispatch is not: every panel hand-writes its own handleInput, and nothing enforces going through the keymap. A raw '\x1b' literal imports nothing, so the "only core imports pi-tui" rule cannot catch it.
Known offenders (raw data === '\x1b' outside core)
packages/mayfly/src/interaction/attach-view.ts:278 — /agents attach view: Esc never closes on kitty terminals (q still works). Its arrow-key constants (lines 40-43) have the same exposure.
packages/mayfly/src/interaction/canonical-panel.ts:122 — the adapter's no-focus-target fallback: onUnhandledEscape never fires for kitty Esc. (Focused compiler surfaces are fine — ui-compiler.ts:1883 already uses matchesKey(Key.escape).)
packages/mayfly/src/interaction/plan-review-panel.ts:106 — Esc during revision editing: the compiler exits its own edit state but the panel's editing flag stays set, leaving the two out of sync.
Related gap: page-up/page-down/home/end are not registered as keymap actions at all, so every consumer raw-compares \x1b[5~ etc. (select-list.ts:128-131, frontend-panel.ts, attach-view.ts, ui-compiler.ts:1985-1988). Today kitty happens to keep those encodings identical, but nothing guarantees that.
Register page-up/page-down/home/end as interaction actions and migrate the raw comparers.
Consider an oxlint rule banning \x1b literals in interaction/ and transcript/ so new panels cannot bypass the keymap.
Longer term, if more hand-rolled buffer panels appear, consider a shared panel shell that owns the standard Esc/q/arrows dispatch.
Reproduction
On a kitty-protocol terminal (kitty, WezTerm, Ghostty, …): open /agents → attach to a subagent → press Esc. Nothing happens; q (empty buffer) closes it.
Background
pi-tui negotiates the kitty keyboard protocol at startup (falling back to modifyOtherKeys). On supporting terminals the Escape key no longer arrives as a bare
\x1bbyte — it arrives as CSI-u\x1b[27u. pi-tui'smatchesKey(data, 'escape')normalizes all three encodings (legacy, kitty CSI-u, modifyOtherKeys), andctx.mayflyKeymap.matches(data, ACTION_CANCEL)delegates to it.Any panel that instead compares raw bytes —
data === '\x1b'— silently stops reacting to Escape on those terminals, while plain-letter shortcuts (e.g.q) keep working, which makes the failure look bizarre to the user. Headless PTY tests do not reproduce it because a PTY never negotiates kitty, so Esc stays in the legacy encoding there.This exact bug shipped in the
/btwview (fixed in #3 by routing Escape/arrows throughmayflyKeymap).Architecture observation
Key registration is single-sourced (
core/keymap.ts+ theINTERACTION_KEY_ACTIONSbatch ininteraction/keys.ts). Key dispatch is not: every panel hand-writes its ownhandleInput, and nothing enforces going through the keymap. A raw'\x1b'literal imports nothing, so the "only core imports pi-tui" rule cannot catch it.Known offenders (raw
data === '\x1b'outside core)packages/mayfly/src/interaction/attach-view.ts:278—/agentsattach view: Esc never closes on kitty terminals (q still works). Its arrow-key constants (lines 40-43) have the same exposure.packages/mayfly/src/interaction/canonical-panel.ts:122— the adapter's no-focus-target fallback:onUnhandledEscapenever fires for kitty Esc. (Focused compiler surfaces are fine —ui-compiler.ts:1883already usesmatchesKey(Key.escape).)packages/mayfly/src/interaction/plan-review-panel.ts:106— Esc during revision editing: the compiler exits its own edit state but the panel'seditingflag stays set, leaving the two out of sync.Related gap: page-up/page-down/home/end are not registered as keymap actions at all, so every consumer raw-compares
\x1b[5~etc. (select-list.ts:128-131,frontend-panel.ts,attach-view.ts,ui-compiler.ts:1985-1988). Today kitty happens to keep those encodings identical, but nothing guarantees that.Suggested direction
mayflyKeymap(ACTION_CANCEL,ACTION_MOVE_UP/DOWN), mirroring fix(mayfly): rebuild /btw as an editor-slot BtwView #3.\x1bliterals ininteraction/andtranscript/so new panels cannot bypass the keymap.Reproduction
On a kitty-protocol terminal (kitty, WezTerm, Ghostty, …): open
/agents→ attach to a subagent → press Esc. Nothing happens;q(empty buffer) closes it.