Skip to content

fix(tui): navigate the transcript with keypad Home and End - #57114

Closed
argszero wants to merge 1 commit into
anomalyco:devfrom
argszero:numpad-prompt-home
Closed

argszero wants to merge 1 commit into
anomalyco:devfrom
argszero:numpad-prompt-home

Conversation

@argszero

@argszero argszero commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #55533. The same bug was reported separately as #57113, already closed as a duplicate of #55533.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Under the kitty keyboard protocol a terminal reports NumPad navigation keys as their own functional key codes. @opentui/core 0.6.8 decodes them as kppageup (CSI 57421 u), kppagedown (CSI 57422 u), kphome (CSI 57423 u) and kpend (CSI 57424 u), and @opentui/keymap accepts all four as named single-stroke keys — but no default binding claimed any of them. So the keypad key next to a working key was silently dropped: in a session, plain End jumps to the last message and NumPad End does nothing.

This PR lists each keypad key beside the key it mirrors, in packages/tui/src/config/keybind.ts:

command before after
session.first ctrl+g,home,alt+home ctrl+g,home,alt+home,kphome
session.last ctrl+alt+g,end ctrl+alt+g,end,kpend
session.page.up pageup,ctrl+alt+b pageup,ctrl+alt+b,kppageup
session.page.down pagedown,ctrl+alt+f pagedown,ctrl+alt+f,kppagedown

Why the session navigation commands and not the prompt. These are the navigation keys whose plain counterpart already has exactly one meaning in the session view, and that meaning is transcript navigation. Plain Home and End are deliberately reserved for the transcript: input.buffer.home and input.buffer.end default to none, and config-v2.test.tsx pins that with reserves home and end for navigation. Binding the keypad keys to the buffer commands instead would give NumPad Home a different meaning from the plain Home key beside it, which is the split #55533 asks to be rid of. Users who want the prompt behaviour still get it from the keybinds override the issue itself shows.

Scope. The NumPad arrow keys are not included: up and down are shared by the prompt, history, dialogs and the session, so mirroring them changes far more than the reported End/Home/PageUp/PageDown case. alt+end (session.messages_last_user) is not mirrored either, since there is no natural keypad chord for it.

An earlier PR of mine, #55870, proposed the same keypad mirrors; I closed it when the prompt half looked like the target. This PR is that direction, aimed at the transcript half #55533 actually reports.

How did you verify your code works?

  • New test mirrors plain navigation keys on the keypad in config-v2.test.tsx asserts, for each of the four commands, that the resolved bindings contain both the plain key and the keypad key. It runs against resolve() from src/config, the table the TUI loads (src/app.tsx). config.test.tsx pins the legacy src/config/v1 table, which only config migration still reads (packages/cli/src/config/migrate.ts), so it is unchanged by design.
  • Red before green: with the four default values reverted to their previous strings, the new test fails (1 fail, 35 pass); with the change, config, config-v2 and keybind pass (37 pass, 0 fail).
  • The key names and codepoints above are read from the installed @opentui/core 0.6.8 (functional-key table) and @opentui/keymap 0.6.8 (named single-stroke key set), not from memory. The branch installs the versions the workspace catalog pins.
  • tsc -b clean for packages/tui; oxlint and prettier --check clean on both changed files.
  • I could not press a physical NumPad key while testing: this machine has no numeric keypad. The link from key to command therefore rests on the core's codepoint table and the keymap's named-key set cited above, plus tui: NumPad End/Home ignored under kitty keyboard protocol (Windows Terminal reports KP_END 57424) #55533's reproduction, which shows the terminal sending ^[[57424u while the binding never fires.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #56890

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot 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.

Looks good. I reproduced the problem on the base in a session prompt driven with opencode-drive: NumPad Home and End (kitty CSI 57423 u / CSI 57424 u) were ignored. With this change they move the caret to the start and end of the buffer. The new test fails on the base and passes here; config-v2, keymap, config and plugin-keymap tests pass, and tsc -b is clean.

The keys are bound through the existing managed-textarea layer, so they only apply while a textarea has focus. That's the right place, and #55870 doesn't touch these commands.

Public API: no keys are added or removed. Only the default bindings change: input.buffer.home none → kphome and input.buffer.end none → kpend. This is backwards compatible: user configs that set either command still win.

One point: the new test file can be dropped so the change stays minimal (see the inline comment).

Related: #55533, #56890

Before

After

@@ -0,0 +1,139 @@
import { expect, test } from "bun:test"

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.

This adds about 140 lines (a mock session server, transcript scroll checks, and a second test that rebinds the transcript jumps) for a two-value default change. The updated assertion in config-v2.test.tsx already pins the new defaults, so this file can be dropped to keep the change minimal.

@argszero

Copy link
Copy Markdown
Contributor Author

Trimmed in 05bdc4d7c3. The file is now one test: the plain-versus-keypad comparison and the session.first / session.last override it needed are gone, along with about 30 lines.

I kept one test instead of dropping the file, because config-v2.test.tsx pins the two defaults but cannot observe whether the binding reaches the buffer. That distinction is the bug in #57113: the keypad codes were decoded and then dropped, so an assertion that only records the table's value was green while the keys did nothing — it recorded none, which was the broken state. The remaining test feeds the real kitty sequences through the session route and asserts the caret moved, and it fails when both defaults go back to none (verified by reverting them).

Also rebased onto dev (f36cda5bc8). tsc -b, oxlint and prettier --check are clean on the changed files, and config-v2 plus keymap pass.

@argszero

Copy link
Copy Markdown
Contributor Author

Rebased onto current dev (e965774fa0) as ecbb8d2d97; the diff is unchanged, so everything below is measured on today's tree rather than on the older base this branch started from.

Link corrected. This PR said Closes #57113, which the bot has since closed as a duplicate of #55533. #55533 is still open, labelled reproduced, and is the report I claimed. The closing keyword now points at #55533, so merging this closes the issue that actually tracks the bug.

The reported bug, reproduced on current dev. Following the report's steps — type aa, Shift+Enter, bb, then feed CSI 57423 u (NumPad Home) + x, then CSI 57424 u (NumPad End) + y:

state prompt contents scrollTop
only the two defaults reverted to none aa\nbbxy changes
with this PR xaa\nbby unchanged

The red line is the issue's own "Observed" text character for character: both keypad keys are dropped and the characters land at the caret. The one assertion is expect(prompt.plainText).toBe("xaa\nbby") in

  • packages/tui/test/session-prompt-keypad-home-end.test.tsx — keypad Home and End move the prompt cursor

which fails with Expected: "xaa\nbby" / Received: "aa\nbbxy" when the two defaults are reverted, and passes with them in place. It drives the real session route through createAppFixture rather than asserting the table, because "the command is bound" and "the key reaches the buffer" are different claims and only the second one is the bug. config-v2.test.tsx separately pins the defaults: 21 pass.

Gates on this head: tsc -b clean, oxlint 0 warnings / 0 errors, prettier --check clean on all three changed files.

The change itself is still two default values in packages/tui/src/config/keybind.ts (input.buffer.home: none → kphome, input.buffer.end: none → kpend), plus the one v2 assertion that pinned them as unbound.

@opencode-agent opencode-agent Bot 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.

The prompt fix still works on the rebased head. In a session prompt driven with opencode-drive, NumPad Home and End are ignored on the base and move the caret to the start and end of the buffer here (before and after screenshots attached). The new test and config-v2 pass, and tsc -b is clean.

The test file was cut from two tests to one, but my earlier point was to drop it, so that thread stays open.

New: the description now says Closes #55533, but this change doesn't fix that issue. #55533 is about NumPad End doing nothing in the transcript, plus the other keypad navigation keys. I ran its reproduction against this branch: after Home, sending CSI 57424 u still leaves the transcript at the top with "Jump to latest ↓" showing, the same as on the base. That's expected, because kpend/kphome are only bound in the prompt's text box, not on session.first/session.last or any other keypad key. Please change the line to Refs #55533 so merging this doesn't close the issue while it's still broken.

Public API: no keys are added or removed. Only the default bindings change: input.buffer.home none → kphome and input.buffer.end none → kpend. This is backwards compatible: user configs that set either command still win.

Before

After

@argszero

Copy link
Copy Markdown
Contributor Author

Both points taken, and you were right on the second one — I made that link worse, not better.

Closes → Refs #55533. Changed in the body, so closingIssuesReferences is now empty and merging this will not close #55533. I checked your measurement against the code rather than just accepting it: on this head, kpend/kphome appear only on input.buffer.home / input.buffer.end, while session.first binds ctrl+g,home,alt+home and session.last binds ctrl+alt+g,end — no kp* name anywhere on the transcript commands, so NumPad End while scrolled up is untouched by this PR. Your opencode-drive run and that reading agree.

For the record on how I got it wrong: I had originally written Closes #57113, then saw the bot close #57113 as a duplicate of #55533 and moved the keyword to the canonical issue without re-reading what #55533 actually covers. The lesson is mine — a duplicate relationship means the root cause is shared, not that the scope is.

Test file dropped in 1ae10ff39e, per your inline comment. The diff is now two files, four lines each way: the two defaults in packages/tui/src/config/keybind.ts and the one config-v2.test.tsx assertion that pinned them as unbound. The mock session server and the transcript scroll checks are gone.

The red-before-green gate survives the drop, which is why I am comfortable removing it: with the two defaults reverted to none, config-v2.test.tsx fails 20 pass / 1 fail; with the change it passes 21/21. tsc -b clean, oxlint 0/0, prettier --check clean, and keymap + config pass (18/18).

I also left the reproduction I measured earlier in the thread — reverted defaults produce aa\nbbxy against xaa\nbby, which is #57113's "Observed" text — but that is evidence, not a test I am asking you to carry.

@opencode-agent opencode-agent Bot 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.

Both earlier points are addressed: the extra test file is gone, and #55533 is now referenced instead of closed. I re-ran the session prompt check: NumPad Home and End move the caret to the start and end of the buffer here and are ignored on the base (screenshots attached). config-v2 passes and tsc -b is clean. No further concerns.

Public API: no keys are added or removed. Only the default bindings change: input.buffer.home none → kphome and input.buffer.end none → kpend. This is backwards compatible: user configs that set either command still win.

Before

After

NumPad Home, End, PageUp and PageDown arrive as `kphome`, `kpend`,
`kppageup` and `kppagedown` (kitty `CSI 57423 u`, `CSI 57424 u`, and
`CSI 57421 u` / `CSI 57422 u`), while the session bindings list only the
plain `home`, `end`, `pageup` and `pagedown`. The keypad key beside a
working key therefore does nothing: pressing NumPad End in the transcript
does not jump to the last message.

List the keypad key beside the key it mirrors on `session.first`,
`session.last`, `session.page.up` and `session.page.down`. Plain Home and
End stay reserved for transcript navigation, so `input.buffer.home` and
`input.buffer.end` keep their empty defaults.
@argszero argszero changed the title fix(tui): let keypad Home and End move the prompt cursor fix(tui): navigate the transcript with keypad Home and End Oct 24, 2026

@opencode-agent opencode-agent Bot 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.

This fixes the transcript case in #55533. I ran the issue's reproduction with opencode-drive on the base and on this branch. On the base, NumPad End (CSI 57424 u) does nothing after scrolling to the top. Here it jumps to the latest message, NumPad Home goes back to the first message, and NumPad PageDown scrolls down a page (screenshots attached). The config-v2, config and keybind tests pass, and tsc -b prints no errors.

The test file I asked to drop earlier is gone, and the new config-v2 test is small. No further concerns.

Public API: no keys are added or removed. Four default bindings change, each gaining its keypad key:

  • session.first: ctrl+g,home,alt+home → ctrl+g,home,alt+home,kphome
  • session.last: ctrl+alt+g,end → ctrl+alt+g,end,kpend
  • session.page.up: pageup,ctrl+alt+b → pageup,ctrl+alt+b,kppageup
  • session.page.down: pagedown,ctrl+alt+f → pagedown,ctrl+alt+f,kppagedown

This is backwards compatible: user configs that set these commands still replace the defaults.

Before

After

@argszero

Copy link
Copy Markdown
Contributor Author

@kitlangton — this is ready for a human look. Merging it closes #55533, because the transcript half of that report is now covered: NumPad End jumps to the last message, NumPad Home to the first, and NumPad PageUp/PageDown scroll a page.

Do you want it as-is, or is there anything you would rather see reworked before it lands?

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.

tui: NumPad End/Home ignored under kitty keyboard protocol (Windows Terminal reports KP_END 57424)

1 participant