feat(boxen): C M4 #812 -- input_decoder bracketed paste + BOXEN_EV_PASTE#819
Merged
Conversation
Implements milestone M4 of the Phase C input decoder (planning/phase_c/ INPUT_DECODER_PLAN.md sections 3.8, 5.3, 4 (PASTE_ACTIVE state), 7 (M4 spec)). ABI change (contained to this milestone): - boxen.h: add BOXEN_EV_PASTE event type and `paste` union arm (heap-allocated `data` + `len`; consumer owns the free). Decoder: - input_decoder.c: PASTE_ACTIVE state with streaming close-marker matcher (\x1b[201~). Paste buffer grows geometrically from 256 bytes up to the 256 KiB cap; oversize pastes truncate and emit a single BOXEN_LOG_W per paste. ESC bytes inside the paste body are treated as literal content (never re-parsed as CSI). CR/LF normalization at emit time: CR / CRLF / LF all collapse to LF in the emitted data. - Streaming close-match (independent of paste_buf) ensures pastes exceeding the cap still terminate cleanly when the close marker arrives -- otherwise the decoder would wedge in PASTE_ACTIVE forever. REPL: - boxen_repl_run_one_tick: handle BOXEN_EV_PASTE by inserting ev.paste.data at the input bar cursor, mirroring the typed-char insertion path (memmove tail + memcpy + advance cursor). Truncates at BOXEN_REPL_INPUT_MAX-1 with the same convention as typed chars. Cancels pending slash-palette debounce and history navigation; frees ev.paste.data unconditionally (consumer ownership per boxen.h). Tests: - tests/input_decoder_tests.c: 6 new behavioral tests (simple paste, CR/LF normalization, 256 KiB truncation + BOXEN_LOG_W, embedded ESC literal, empty paste, split-across-inject). Net 127 tests pass (was 121 + 4 SKIP stubs M4 -> 6 behavioral + 4 SKIP stubs M5). - tests/boxen_repl_tests.c: 5 new dispatch tests (empty input, mid-buffer insert, oversize truncation, empty paste no-op, palette debounce cancellation). Net 51 tests pass (was 46). - tests/Makefile: link boxen_log.c into input_decoder_tests so BOXEN_LOG_W resolves and tests can install boxen_set_log_hook to observe the truncation warning. Verification: - Unit suite: 953/953 passed (was 946 + 11 new - 4 SKIP-stub removals). - Integration suite: 21 failures, matches the pre-existing baseline. All failing tests are unrelated to M4 (html, tcp, startup). - Manual smoke deferred to M6 cutover (decoder not yet wired into backend_tb2.c). Closes #812
…cleanup Addresses /gate P2 findings on PR #819. Decoder (input_decoder.c): - paste_buf_append: gate the realloc retry loop behind dec->paste_truncated so a sustained OOM scenario doesn't call malloc on every dropped byte (one realloc attempt per paste -- success or failure latches the result). Cleaner control flow: the early return on paste_truncated covers BOTH the cap path and the prior-OOM path with one check. Tests (boxen_repl_tests.c): - test_paste_oversize_truncates_at_input_cap: drop the redundant `big[2048 - 1] = 'q'` write -- memset already filled every byte. Cosmetic. Verification: input_decoder_tests 127/127, boxen_repl_tests 51/51.
Owner
Author
Gate Review: phase-c-m4-bracketed-pasteVerdict: PASS (gate-fixes applied)Tests
Reviewers
Reviewers ran in the main session (no Task tool surface in this P0 — BlockingNone. P1 — ImportantNone. P2 — Nice to Have (FIXED in second commit)
Both fixes applied in commit Detailed Reviewer Notesbar-raiser (code quality, bugs, test coverage)
security (memory safety, input validation, IO bounds)
concurrency (threading, races, GIL contract)
Stats
Next StepsBranch is ready to merge from a /gate perspective. Manual smoke (Cmd-V into a live REPL) is deferred to M6 cutover — the decoder is not yet wired into |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Milestone M4 of the Phase C input decoder: bracketed-paste support and a new
BOXEN_EV_PASTEevent. This is the protocol that solves the "Cmd-V into the REPL under mouse mode shreds the paste into per-keystroke events" problem (the core pain that PR #808 surfaced and that prior milestones were prerequisites for).The decoder now buffers everything between
\e[200~and\e[201~into a heap-grown paste buffer (geometric growth from 256 bytes up to a 256 KiB hard cap), normalizes CR/LF line endings to LF at emit time, and emits a singleBOXEN_EV_PASTEevent with the consumer-owneddatapointer. The REPL'sboxen_repl_run_one_tick()inserts the paste at the input bar's cursor position, mirroring the typed-char insertion path.Plan reference:
planning/phase_c/INPUT_DECODER_PLAN.mdsections 3.8 (bracketed paste), 5.3 (paste as the copy/paste bridge), 4 (PASTE_ACTIVE state), 7 (M4 spec).ABI Change (contained to this milestone)
boxen.h:BOXEN_EV_PASTEstruct { char *data; size_t len; } paste;datais heap-allocated by the decoder; ownership transfers to the event consumer, which mustfree(ev.paste.data). This is the onlyboxen_event_tvariant that transfers heap ownership. The existing event-dispatch sites inboxen.calready have adefault:arm and don't need updating; the REPL is the first (and currently only) consumer.Implementation Notes
\x1b[201~have matched independently ofpaste_buf. Once the truncation cap is hit, we stop appending bytes to the buffer but keep scanning the byte stream for the close marker — otherwise a paste larger than the cap would wedge the decoder in PASTE_ACTIVE forever. Bytes that partially match the close marker are held back and only committed topaste_bufif the match aborts (so the close marker never appears in the emitted payload).\e[Ais delivered as the literal 3 bytes, not a KEY_UP event.paste_truncatedlatches).Test Plan
./tools/run_headless_tests.sh— 953/953 passed (was 946 + 11 new tests; 4 M4 SKIP stubs removed).cd tests && make test-integration— 21 failures, matches pre-existing baseline. All failing tests are unrelated to M4 (html, tcp, startup).input_decoder_tests: 6 new behavioral tests (simple, CR/LF normalization, 256 KiB truncation + BOXEN_LOG_W, embedded ESC literal, empty paste, split-across-inject).boxen_repl_tests: 5 new dispatch tests (empty input, mid-buffer insert, oversize truncation, empty paste no-op, palette debounce cancellation).make -C frontier-cli): clean.backend_tb2.c.What's Next
tb2_poll_eventwith the custom decoder inbackend_tb2.c. This is the milestone that makes paste actually work in a running REPL./mousetoggle + on-demand mouse-mode policy.Closes #812