Skip to content

feat(boxen): C M5 #813 -- input_decoder Kitty keyboard protocol enable + CSI-u decode#820

Merged
jsavin merged 2 commits into
developfrom
phase-c-m5-kitty-protocol
Jun 30, 2026
Merged

feat(boxen): C M5 #813 -- input_decoder Kitty keyboard protocol enable + CSI-u decode#820
jsavin merged 2 commits into
developfrom
phase-c-m5-kitty-protocol

Conversation

@jsavin

@jsavin jsavin commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary

Implements milestone M5 of the Phase C input decoder per
planning/phase_c/INPUT_DECODER_PLAN.md section 7. M5 adds Kitty
keyboard protocol support for terminals that advertise it; terminals
that do not (Terminal.app, iTerm2) silently ignore the enable and the
M2 CSI parser continues to handle their input.

  • input_decoder_kitty_enable() writes \x1b[=1u (progressive
    enhancement: disambiguate escape codes) to tty_fd when tty_fd >= 0.
    Idempotent so duplicate enables do not pile up writes. Test-seam path
    (tty_fd == -1) still records the bit only.
  • decode_csi() gains a case 'u': that decodes the two-part CSI-u
    form (\e[KEYCODE u and \e[KEYCODE;MODIFIER u) to boxen_event_t.
    Functional keycodes (9 Tab, 13 Enter, 27 Escape, 127 Backspace) route
    to the matching BOXEN_KEY_*; printable keycodes pass through as
    ev.key.ch with C0/C1/surrogate/out-of-range rejection so the CSI-u
    surface does not smuggle control bytes that the standard CSI path
    already filters.
  • Six new behavioral tests (un-skips the M5 SKIP stubs and adds a
    burst round-trip): A, A+Alt, Enter+Ctrl, Escape+Alt, lowercase a,
    and a three-event inject combining bare + modified forms.

Probe-and-read for the terminal's kitty acknowledgement is deferred
to M6 where the real read(2) loop lives. M5 ships only the decoder's
ability to handle CSI-u when it arrives, per the milestone scope in
plan section 7.

Out of Scope (Deferred)

  • M6: cutover that replaces tb2_poll_event with input_decoder_poll
    and runs the kitty probe-and-read.
  • M7: /mouse toggle slash command.
  • Three-part CSI-u form (KEYCODE;MOD;EVENT u) -- post-launch.
  • Extended functional keycode table (arrows, F-keys via CSI-u).
    Kitty terminals already emit these via standard CSI in the
    no-progressive-enhancement mode; M5 supports the subset needed
    for the M5 acceptance tests.

Test Plan

  • Unit tests: 955/955 (was 953 pre-M5; +1 from un-skipping stubs,
    +1 net from the burst round-trip test)
  • input_decoder_tests: 129/129 passing, 0 SKIP stubs remaining
  • M2/M3/M4 regression: no test failures introduced
  • Integration suite (running)
  • /gate review

Plan reference: section 3.9 (protocol), section 4.3 (state machine
extension), section 7 (M5 scope), section 6.2 (M5 coverage row).

Closes #813

jsavin added 2 commits June 29, 2026 21:38
…e + CSI-u decode

Implements milestone M5 of the Phase C input decoder per
planning/phase_c/INPUT_DECODER_PLAN.md section 7.

- input_decoder_kitty_enable writes "\x1b[=1u" (progressive enhancement:
  disambiguate escape codes) to tty_fd when tty_fd >= 0; idempotent so
  duplicate enables do not pile up writes.  Test-seam path (tty_fd == -1)
  still records the bit only.
- decode_csi gains a 'u' case that decodes the two-part CSI-u form
  (\e[KEYCODE u and \e[KEYCODE;MODIFIER u) to boxen_event_t.  Functional
  keycodes (9 Tab, 13 Enter, 27 Escape, 127 Backspace) route to the
  matching BOXEN_KEY_*; printable keycodes pass through as ev.key.ch
  with C0/C1/surrogate/out-of-range rejection so the CSI-u surface
  doesn't smuggle control bytes that the standard CSI path already
  filters.
- Six new behavioral tests (un-skips the M5 SKIP stubs and adds a
  burst round-trip): A, A+Alt, Enter+Ctrl, Escape+Alt, lowercase a,
  and a three-event inject combining bare + modified forms.

Probe-and-read for the terminal's kitty acknowledgement is deferred to
M6 where the read(2) loop lives; terminals that do not support kitty
silently ignore the enable and the decoder operates as if kitty_enable
had never been called -- standard CSI parsing from M2 still handles
their input.

Unit tests: 955/955 (was 953 pre-M5 -- the six new tests plus a +1 from
un-skipping the stubs net the difference).
Plan reference: section 3.9 (protocol), section 4.3 (state machine
extension), section 7 (M5 scope), section 6.2 (M5 coverage row).

Closes #813
…erage

Addresses P2 polish items from the /gate review of PR #820:

- Six negative-path tests assert BOXEN_ERR_TIMEOUT + a +1 bump on the
  parse_errors counter for each CSI-u rejection branch:
    \e[u         -- no keycode
    \e[0u        -- zero keycode (printable-range underflow)
    \e[7u        -- C0 control 0x07 (BEL) outside the functional table
    \e[55296u    -- surrogate codepoint U+D800
    \e[128u      -- C1 control U+0080
- Boundary-pin: \e[160u (U+00A0 NBSP) must accept, paired with the
  \e[128u rejection to fence both sides of the C1 control range so a
  regression that moved the fence by one would fail at least one test.
- Functional-key modifier coverage: \e[9;3u (Tab+Alt) and \e[127;2u
  (Backspace+Shift); previously only Enter+Ctrl and Escape+Alt were
  tested.
- Idempotency contract: test_kitty_enable_idempotent calls
  input_decoder_kitty_enable twice and asserts the bit stays set
  without observable side effect; the test-seam path (tty_fd == -1)
  suppresses the would-be duplicate write so we pin the in-memory
  invariant.

Unit tests: 964/964 (was 955 after the M5 implementation commit; +9 net
new tests).  Integration: 21-failure baseline unchanged; failures
unrelated to input_decoder.

Plan reference: section 6.5 (TDD discipline -- behavioral asserts for
every protocol entry in scope).
@jsavin

jsavin commented Jun 30, 2026

Copy link
Copy Markdown
Owner Author

Gate Review: phase-c-m5-kitty-protocol (PR #820)

Verdict: PASS WITH NOTES (addressed in gate-fix commit)

Tests

  • unit passed (964/964 at HEAD)
  • integration passed (21-failure baseline matched; failures unrelated to input_decoder)

Reviewers

  • bar-raiser ran (always)
  • security ran (always; binary parsing + TTY write surface)
  • concurrency ran (auto: matched path frontier-cli/**)
  • swiftui skipped (excluded; pure C codebase)

P0 -- Blocking

None.

P1 -- Important

None.

P2 -- Polish (addressed in this PR)

# Finding File Flagged by Resolution
1 CSI-u rejection branches (no-keycode, zero, C0, C1, surrogate) had no test coverage. A regression that started emitting events for these inputs would slip past CI until a user-facing symptom surfaced. tests/input_decoder_tests.c bar-raiser, security Added six negative-path tests asserting BOXEN_ERR_TIMEOUT + a +1 bump on parse_errors for each rejection branch.
2 C1 control range fence (keycode < 0xA0) had no boundary pin. A regression that moved the boundary by one would not fail any test. tests/input_decoder_tests.c bar-raiser Added paired tests for \e[128u (reject) and \e[160u (NBSP, accept) to pin both sides of the C1 fence.
3 Functional-key modifier path (Tab+Alt, Backspace+Shift) had no explicit coverage; only Enter+Ctrl and Escape+Alt were tested. tests/input_decoder_tests.c bar-raiser Added \e[9;3u (Tab+Alt) and \e[127;2u (Backspace+Shift).
4 input_decoder_kitty_enable idempotency contract was implemented but had no behavioral test pinning the "second call is a no-op" invariant. tests/input_decoder_tests.c bar-raiser, security Added test_kitty_enable_idempotent which calls the function twice and asserts the bit stays set without observable side effect (test-seam suppresses the would-be duplicate write).

Bar-raiser observations (no action required)

  • The > 0x10FFFF upper-bound check inside the CSI-u path is unreachable under the current csi_parse_params saturation cap of 100000; documentary defense, fine to keep.
  • The 'u' case body is ~80 lines including comments; extracting decode_kitty_csi_u() would mirror the helper pattern used by decode_sgr_mouse / decode_ss3. Not blocking -- the case is self-contained and well-commented in its current location.

Security observations (no action required)

  • kitty_enable write payload is a static const string ("\x1b[=1u", 5 bytes); no format strings, no user data, no injection surface.
  • The 5-byte write is well within PIPE_BUF guarantees on macOS/Linux; partial-write handling is not required and the rationale is documented at the call site.
  • Idempotency guard prevents enable-sequence floods if the slash command is spammed.
  • All decoder rejection paths bump parse_errors, providing an observable signal for protocol-rejection (complement of dropped_bytes for back-pressure rejection).

Concurrency observations (no action required)

  • kitty_enable adds a write(2) to tty_fd. The single-owner + GIL-held contract from input_decoder.h lines 27-39 covers this; the new call site is no riskier than the existing input_decoder_set_mouse write path (lines 409-417).
  • The new decode_csi 'u' branch is pure in-memory dispatch; no new globals, no new threading surface.
  • No changes to the M2 langbackgroundtask-style yield model.

Stats

  • Files reviewed: 3 (input_decoder.c, input_decoder.h, input_decoder_tests.c)
  • Findings: 0 P0, 0 P1, 4 P2 (all addressed in this PR's gate-fix commit)
  • Reviewers ran: 3 of 4 configured (swiftui excluded per project config)
  • Tests: 964/964 unit, integration matches 21-failure baseline

@jsavin jsavin merged commit feade59 into develop Jun 30, 2026
1 check passed
@jsavin jsavin deleted the phase-c-m5-kitty-protocol branch June 30, 2026 04:46
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.

Phase C input decoder M5: Kitty keyboard protocol enable + decode

1 participant