Skip to content

fix(picker): detect 2.1.283's indented picker rows and republish colour-only selection changes - #66

Merged
Juliusolsson05 merged 6 commits into
mainfrom
fix/slash-picker-colour-selection
Sep 27, 2026
Merged

Juliusolsson05 merged 6 commits into
mainfrom
fix/slash-picker-colour-selection

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Refs Juliusolsson05/agent-code#1253 (the app PR that bumps this package fixes it).

Problem

Recorded on real Claude Code 2.1.283 (test/fixtures/slash-picker/, see its README):

  1. The picker was never detected. 2.1.283 indents every picker row by two spaces. detectSlashPicker split the UNtrimmed row on 2+ spaces, so parts[0] was the empty indent, and every row was rejected by the id check.
  2. A colour-only selection change was dropped. Arrowing through the picker can move only the highlight colour, with byte-identical text. HeadlessTerminal.scheduleFlush's gate compares text only, so no frame fired, the picker was never reparsed, and claude.slash-picker kept the old selected row.

What merges

  • SlashPickerParser: trims both ends before splitting.
  • HeadlessTerminal: an optional gateSignature hook, a third term for the change gate, read only when the text is unchanged.
  • ClaudeCodeHeadless: supplies the selected picker row as that signature, and only while a picker is visible. With no picker it returns a constant '', so the common frame stays attribute-blind. Serialising attributes per frame is what pinned main at ~80% CPU (agent-code#390).

Tests

  • ClaudeCodeHeadless.slashPicker.test.ts: replays the recording (open /co → Down: colour only → Down: scroll → Up → Esc) through ClaudeCodeHeadless, and asserts the published claude.slash-picker condition's selected row after each step. This also pins the producer that was unpinned.
  • Mutations, each red: the untrimmed parser split; the signature returning ''; the gate ignoring the signature; an empty picker in the evaluator input.
  • Suites: the full core suite passes (20 files, 138 tests). tsc --noEmit is clean.

Fixture: decoded and read in full. It was recorded with /co sent in one write, so personal skills never paint. The plan name and the temp cwd suffix are redacted at the same byte length, and screen diffs before and after redaction differ only in those two texts.

🤖 Generated with Claude Code

Juliusolsson05 and others added 3 commits September 27, 2026 00:52
…sh colour-only selection changes

agent-code#1253, on a real Claude Code 2.1.283 recording
(test/fixtures/slash-picker):

- SlashPickerParser split the untrimmed row on 2+ spaces; 2.1.283 indents
  picker rows by two spaces, so parts[0] was empty and no row was ever
  detected.
- HeadlessTerminal's text-only flush gate dropped the colour-only repaint
  of arrowing through the picker. An owner-supplied gateSignature (the
  selected row, only while a picker is visible) joins the gate; the
  common frame stays attribute-blind (agent-code#390).

The test replays the recording through ClaudeCodeHeadless and asserts the
published claude.slash-picker condition; the parser trim, the signature,
the gate term and the evaluator's picker input are each mutation-pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ible

agent-code#1253 review b (surviving mutant): computing the signature on
every flush passed every test. Identical no-picker frames now must parse
the picker only once per EMITTED frame (21 calls vs 1 frame without the
visibility guard).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the emitted one after parsing

agent-code#1253 review a: the signature was read on changed-text frames
too (a wasted grid parse per picker frame), and the value recorded for an
emitted frame was the PRE-parse one, so a picker that just opened emitted
a duplicate frame on the next no-op write. gateSignature(source): 'live'
only when the text is unchanged; 'emitted' right after the screen handler
parsed. Both pinned on the real recording (duplicate frame: 5 vs 4; parses
on a scroll: 2 vs 1 per frame).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the snapshotIntervalMs default

agent-code#1253 review c: the new option was missing from the public
options table; the table also still said 16 ms although the default has
been 100 ms since 673e786.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Review disposition (package head 07eecec; app #1382 pins 2d9a0db, which differs only by an API.md doc commit). a, b and c (pi) are all MERGE-READY. a was MERGE-READY with two minors, fixed, and verified MERGE-READY in round 2. The package's exact-head CI is green: Contract, Node 20.19 and 24, Tier commands, Coverage, quality-gate.

Fixed

  • b (surviving mutant): computing the signature with no picker visible passed every test. A cost test now repaints identical no-picker frames. The parser may run only once per EMITTED frame; without the visibility guard it ran 21 times for 1 frame.
  • a (minor): the signature was read on changed-text frames too, a wasted grid parse per picker frame. gateSignature(source) now reads 'live' only when the text is unchanged. On a scroll, live-always gave 2 parses per frame, and the fix gives 1.
  • a (minor): the signature recorded for an emitted frame was the PRE-parse value, so a picker that had just opened emitted a duplicate frame on the next no-op write. 'emitted' is now recorded after the screen handler has parsed. On the recording, 5 frames became 4.
  • c (minor): the comments described a conditional read that did not exist. True of 848bb52, which c reviewed; 2d9a0db made the read conditional, and the comments now match.
  • c (minor): gateSignature was missing from API.md's options table. Added, and the table's stale snapshotIntervalMs default (16 → 100 ms, pre-existing) is corrected.

Confirmations from c: the performance envelope is in budget (≤70 µs per scan while a picker is open, ≤10 Hz, and one property read otherwise). Fixes #1253 is whole: both asks are implemented and pinned. The 2.1.283 drift meant the picker never showed before this, and nothing in the renderer relied on that broken state.

Merge order: claude-code-headless#66 first. Then #1382 is re-pointed to #66's merge commit, and its CI re-runs, before READY.

🤖 Generated with Claude Code

@Juliusolsson05

Copy link
Copy Markdown
Owner Author

B6 gate record: GATE PASS #66 (3dab12f, 0 behind main, checks green, reviews in agent-code temp/review-1253). Merging with --merge.

@Juliusolsson05
Juliusolsson05 merged commit 487f029 into main Sep 27, 2026
6 checks passed
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