Skip to content

fix: support standard line editing keys in the CLI inputs, and load PR details from v2 - #117

Merged
nimrodkor merged 5 commits into
baz-scm:mainfrom
benglewis:claude/baz-cli-key-bindings-20d514
Aug 4, 2026
Merged

fix: support standard line editing keys in the CLI inputs, and load PR details from v2#117
nimrodkor merged 5 commits into
baz-scm:mainfrom
benglewis:claude/baz-cli-key-bindings-20d514

Conversation

@benglewis

@benglewis benglewis commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Problem

The usual command line editing keys were broken in every text input in the CLI:

  • Home / End did nothing at all
  • Cmd + Left / Right (macOS) did nothing, or moved one character
  • Alt + Left / Right moved a single character instead of a word
  • Ctrl + A / Ctrl + E did nothing

The cause is that Ink's useInput cannot express these keys. It normalises every keypress into a small set of flags: Home and End arrive with an empty input string and no flags set, so they are indistinguishable from each other and from noise, and Alt/Ctrl/Cmd on the arrow keys all collapse into a single meta flag — so a handler could only ever treat key.leftArrow as "move one character left".

Three separate inputs were affected, and they had three different levels of support:

Input Before
PR search box (PullRequestSelector) Append and backspace only — no cursor at all
"Chat with PR" box (ReviewMenu) ink-text-input: plain arrows only
PR chat input (ChatInput) Plain arrows, plus Alt+B/F handled as meta || ctrl

Fix

A shared line editor, src/lib/input/line-editor.ts:

  • tokenizeKeySequences(chunk) — splits a stdin chunk into one string per keypress (chunk boundaries are not key boundaries: a + Left can arrive as one a ESC [ D, and a paste arrives whole)
  • parseKeySequence(sequence) — escape sequence → semantic editing action
  • applyEditorAction(state, action) — action → new { text, cursor }, returning the same object for no-ops so a re-render can be skipped

useKeySequences subscribes to the raw stdin chunks Ink already reads, tokenizes them and calls back once per key. LineInput wraps that into a drop-in text input with a real cursor; it replaces ink-text-input in the menu chat box (dependency dropped) and gives the PR search box a cursor for the first time. ChatInput keeps its own throttled sliding-window rendering and uses the hook directly.

Bindings, covering the encodings emitted by Terminal.app, iTerm2, Ghostty, WezTerm, Alacritty, VS Code and tmux:

Keys Action
Home, Cmd + Left, Ctrl + A Start of line
End, Cmd + Right, Ctrl + E End of line
Alt + Left/Right, Ctrl + Left/Right, Alt + B / Alt + F One word
Left/Right, Ctrl + B / Ctrl + F One character
Backspace, Delete (Fn + Backspace) Delete one character
Alt + Backspace, Ctrl + W, Alt + D Delete one word
Ctrl + U, Ctrl + K Delete to start / end of line

Enter, Tab, Escape, ? help, / command hints, the @ mention autocomplete, ↑↓ list navigation and Ctrl+G merge all behave exactly as before. Unknown escape sequences are ignored instead of risking being typed into the buffer, without swallowing the text around them, and a line break inside pasted text becomes a space rather than submitting half a message.

Also: PR loading was broken (405)

Opening any pull request failed with Request failed with status code 405. fetchPRDetails was left on /api/v1/changes/{id} by the v1 → v2 migration (#114) and that route no longer serves GET. It now calls /api/v2/changes/{id} and maps the camelCase payload onto PullRequestDetails — the detail endpoint spells its review fields reviewState/assignee, unlike the list endpoint's state/reviewer, so it needs its own mapper.

Still on v1 and not touched here: changes/{id}/approve, changes/{id}/merge, changes/{id}/merge-status, discussions/{id} and comments. merge-status still answers 200 on v1; the v2 equivalents of the others reject a change UUID with params/number, so they need the real v2 signature rather than a guess.

Testing

  • src/lib/input/line-editor.spec.ts — 39 unit tests over tokenizing (including sequences split across chunks), the sequence parser, grapheme boundaries, word boundaries and the editing actions (npm test: 60 passed)
  • Driven against the real CLI in a pty, asserting on the rendered cursor, in all three inputs — the PR search box, the "Chat with PR" box and the PR chat input: Home, End, Cmd + Left/Right, Alt + Left/Right, Ctrl + Left, Ctrl + A/E, Ctrl + W and Ctrl + U all behave correctly
  • Chunk boundaries, sent as single or split writes to the pty: ab + Left + X in one chunk gives aXb; fix + CR submits fix; ESC [ then D as two writes moves left without typing a D; the same with a 300ms gap types the D, as it should
  • Emoji and combining marks: with a😀é👨‍👩‍👧 the cursor highlights each glyph whole and backspace removes one glyph at a time, including the ZWJ family and the accented letter
  • Opening a real PR now reaches the overview (7 files changed, +405/-7) instead of the 405
  • npm run lint, npm run format:check, npm run build clean

🤖 Generated with Claude Code

@baz-reviewer

baz-reviewer Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Generated description

Replace the CLI’s text inputs with a shared raw-key line editor that powers LineInput, useKeySequences, ChatInput, ReviewMenu, and PullRequestSelector so Home/End, word jumps, and readline-style deletions work consistently. Update fetchPRDetails to call the v2 PR endpoint and map its camelCase response into PullRequestDetails.

Topics
TopicDetails
CLI line editing Implement shared line-editing for the CLI inputs by tokenizing raw stdin, mapping terminal key sequences to editing actions, and rendering a real cursor in LineInput, ChatInput, ReviewMenu, and PullRequestSelector.
Modified files (9)
  • package-lock.json
  • package.json
  • src/components/LineInput.tsx
  • src/flows/Review/ReviewMenu.tsx
  • src/hooks/useKeySequences.ts
  • src/lib/input/line-editor.spec.ts
  • src/lib/input/line-editor.ts
  • src/pages/PRSelector/PullRequestSelector.tsx
  • src/pages/chat/ChatInput.tsx
Latest Contributors(0)
UserCommitDate
PR details load Switch fetchPRDetails to the v2 change details endpoint and translate the response fields into the app’s pull request model, restoring PR opening after the migration.
Modified files (1)
  • src/lib/clients/baz.ts
Latest Contributors(0)
UserCommitDate

Review this PR on Baz | Customize your next review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b01dc09e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/input/line-editor.ts
Comment thread src/pages/chat/ChatInput.tsx Outdated
@benglewis
benglewis marked this pull request as draft July 27, 2026 16:46
benglewis and others added 2 commits August 2, 2026 20:40
Home, End, Cmd+Left/Right and Alt+Left/Right did nothing (or moved a
single character) while chatting with a PR.

Ink's `useInput` cannot express these keys: Home and End arrive with an
empty `input` and no key flags at all, and Alt, Ctrl and Cmd modifiers on
the arrow keys are collapsed into a single `meta` flag. The chat input now
subscribes to the raw stdin chunks Ink already reads and maps the escape
sequences itself, in a pure module that is unit tested.

Supported: Home/End, Cmd+Left/Right, Ctrl+A/Ctrl+E, Alt+Left/Right,
Ctrl+Left/Right and Alt+B/Alt+F for word movement, Ctrl+B/Ctrl+F for
single characters, forward delete, Alt+Backspace/Ctrl+W, Alt+D, and
Ctrl+U/Ctrl+K.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Home/End, Ctrl+A/Ctrl+E, Alt+Arrow and Cmd+Arrow did nothing in the PR
search box, the "Chat with PR" box and the PR chat input.

Ink's `useInput` cannot express these keys: it reports Home and End as an
empty input with no key flags, and collapses Alt and Cmd into one `meta`
flag, so Alt+Left was handled as a plain left arrow. Read the raw stdin
sequences Ink already emits instead and map them to editing actions:

- Home / Ctrl+A / Cmd+Left  -> start of line
- End / Ctrl+E / Cmd+Right  -> end of line
- Alt+Arrow, Ctrl+Arrow, Alt+B/F -> one word left/right
- Ctrl+W, Alt+Backspace, Ctrl+U, Ctrl+K, Fn+Delete -> readline deletions

The parser and buffer operations live in `lib/input/line-editor.ts` and
are shared by the chat input and the new `LineInput` component, which
replaces `ink-text-input` (arrow keys only) in the menu chat box and adds
a real cursor to the PR search box.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@benglewis
benglewis force-pushed the claude/baz-cli-key-bindings-20d514 branch from 5b01dc0 to e58ffbb Compare August 2, 2026 17:55
benglewis and others added 2 commits August 2, 2026 21:22
Chunk boundaries are not key boundaries. Typing quickly coalesces `a` and
Left into one `a ESC [ D` chunk, and a paste arrives whole, bracketed by
`ESC [ 200~` when the terminal supports it. Neither matched a binding, and
the handler dropped any chunk containing ESC, so the typed or pasted text
was silently lost.

`tokenizeKeySequences` now splits a chunk into one string per keypress
before it is mapped, unwrapping bracketed paste. A newline inside pasted
text becomes a space, since this is a single line input, but a newline
that ends the chunk is still Enter.

Also guard `setRawMode` with `isRawModeSupported`, so mounting an input
cannot throw where raw mode is unavailable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opening any pull request failed with "Request failed with status code
405": `fetchPRDetails` was left on `/api/v1/changes/{id}` by the v1 to v2
migration (baz-scm#114), and that route no longer serves GET.

Point it at `/api/v2/changes/{id}` and map the camelCase payload onto
`PullRequestDetails`. The detail endpoint spells its review fields
`reviewState`/`assignee`, unlike the list endpoint's `state`/`reviewer`,
so it needs its own mapper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@benglewis benglewis changed the title fix: support standard line editing keys in the chat input fix: support standard line editing keys in the CLI inputs, and load PR details from v2 Aug 2, 2026
@benglewis
benglewis marked this pull request as ready for review August 2, 2026 18:28
@baz-reviewer

baz-reviewer Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

⚠️ Advanced Security cannot run on this PR.

Your organization's Advanced Security usage limit has been reached. To continue using Advanced Security reviews, please upgrade your plan or increase your usage limits in your account settings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a5ce592d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/LineInput.tsx Outdated
Comment thread src/hooks/useKeySequences.ts Outdated
Comment thread src/hooks/useKeySequences.ts
Comment thread src/lib/input/line-editor.ts Outdated
Comment thread src/lib/input/line-editor.ts
Comment thread src/flows/Review/ReviewMenu.tsx
Comment thread src/lib/input/line-editor.ts Outdated
Three problems the tokenizer brought to light, from review:

- A sequence split across two stdin reads (`ESC [` then `D`) had its prefix
  thrown away and the tail typed as a literal `D`. `tokenizeKeySequences`
  now returns an incomplete tail as `remainder` and `useKeySequences` holds
  it for the next chunk, flushing after 30ms so a lone ESC is still the
  Escape key. A paste that spans chunks waits for its closing marker.

- `LineInput` read the buffer from its `value` prop, which the parent has
  not re-rendered yet part way through a chunk, so `fix\r` submitted the
  empty value and `ab` + Left + `X` collapsed to `X`. The buffer now lives
  in a ref, so each key sees the result of the one before it.

- Cursor movement and deletion stepped one UTF-16 code unit, splitting
  emoji and combining marks. Both now step whole graphemes via
  `Intl.Segmenter`, and the cursor cell renders the whole grapheme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@benglewis

Copy link
Copy Markdown
Contributor Author

baz review

@baz-reviewer

baz-reviewer Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@benglewis does not have a seat in baz. To configure ask an admin to assign them a seat in account settings

1 similar comment
@baz-reviewer-dev

Copy link
Copy Markdown

@benglewis does not have a seat in baz. To configure ask an admin to assign them a seat in account settings

@nimrodkor

Copy link
Copy Markdown
Contributor

baz re review

@baz-reviewer-dev

Copy link
Copy Markdown

@benglewis does not have a seat in baz. To configure ask an admin to assign them a seat in account settings

1 similar comment
@baz-reviewer

baz-reviewer Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@benglewis does not have a seat in baz. To configure ask an admin to assign them a seat in account settings

@nimrodkor nimrodkor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TY @benglewis !

@nimrodkor
nimrodkor merged commit 279ad78 into baz-scm:main Aug 4, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants