feat(tui): dock the input box to the bottom of the terminal - #3022
feat(tui): dock the input box to the bottom of the terminal#3022droid-ash wants to merge 3 commits into
Conversation
The interactive TUI now always renders on the alternate screen: the transcript scrolls inside a ScrollView while the editor and the rest of the chrome stay docked at the bottom, so the input box no longer scrolls out of view when reviewing earlier output. The layout already existed behind KIMI_CODE_TUI_FULL_SCREEN; this makes it the only mode and drops the env gate. Quitting still replays the transcript through a main-screen renderer, so native scrollback ends up with the conversation.
🦋 Changeset detectedLatest commit: 95d25bc The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45cdd5a5ed
ℹ️ 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".
| // chrome stay docked at the bottom while the transcript scrolls above them. | ||
| // `stopUiForExit` replays the transcript through a main-screen renderer on | ||
| // the way out, so native scrollback still ends up with the conversation. | ||
| const ui = new TuiAltScreen(terminal, undefined, undefined, { |
There was a problem hiding this comment.
Preserve Home/End editing in the default TUI
Because createTUIState now always constructs TuiAltScreen, every interactive session gets the viewport input listener by default; I checked packages/pi-tui/src/tui-alt-screen.ts and it consumes home/end whenever the primary scroll view can scroll, while packages/pi-tui/src/keybindings.ts also uses those same keys for editor line start/end. In any conversation long enough to make the transcript scrollable, pressing Home or End while typing in the docked editor will jump the transcript instead of moving the cursor, so the default input box regresses for common terminal-editing keys unless the viewport bindings are made conditional/remapped or editor focus is allowed to win.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed and fixed in 4f49d99.
You are right about the mechanism. TUI.handleInput runs inputListeners before dispatching to focusedComponent, and a listener returning {consume: true} returns early, so once getPrimaryScrollView().canScroll is true the viewport claimed home/end and the editor never saw them. Harmless while the docked layout was opt-in; a real regression now that it is the default.
Fix: createTUIState installs a KeybindingsManager that remaps tui.altScreen.top / tui.altScreen.bottom to shift+home / shift+end. The editor is focused for nearly the whole session, so home/end belong to it; scroll-to-top/bottom keeps a binding rather than being dropped. shift+home / shift+end are recognized key ids and were unbound.
Added a test in fullscreen-layout.test.ts covering both halves: with a scrollable transcript, home/end move the editor cursor to col 0 / end-of-line, and shift+home / shift+end still scroll to top and resume following output. It fails on the previous commit.
The alternate screen's viewport input listener runs before the focused component, so its default home/end bindings swallowed the editor's line-start / line-end keys once the transcript grew long enough to scroll. That was harmless while the docked layout was opt-in; it is a regression now that every session uses it. Remap the viewport's scroll-to-top/bottom to shift+home / shift+end so home/end reach the editor, and cover both halves with a test.
Mouse capture in the alternate screen replaces the terminal's own drag-selection, and pi-tui's copy path wrote OSC 52 only. Many terminals refuse OSC 52 writes by default, so on those the "Copied!" flash claimed a copy that never reached the clipboard — harmless while the docked layout was opt-in, but not once every session uses it. Add a TuiAltScreenOptions.onCopy hook called alongside the OSC 52 write and wire kimi-code's native clipboard binding to it. OSC 52 remains the fallback for hosts with no reachable native clipboard (SSH, headless), which is the split MoonshotAI#2721 asked for.
|
Follow-up on the escape-hatch note I left in the description — I have retracted it, and pushed two fixes. Retracting the I also dropped the idea of an automatic inline fallback: it would mean reinstating the dual layout path this PR deletes, to serve non-TTY / Two real regressions found and fixed instead, both consequences of promoting this layout to the default:
Both are covered by tests that fail without the fix. Full CLI suite 2943 passing, pi-tui suite 975 passing, typecheck clean, lint unchanged. The |
|
@liruifengv — could you kick off the workflow run here when you have a moment? No checks have run yet, so there is nothing green to review against. This builds directly on your #2830: it takes the docked alternate-screen layout you added behind KIMI_CODE_TUI_FULL_SCREEN and makes it the only mode, which is why it lands in your area. The two supporting fixes (home/end reaching the editor, and selections copying via the native clipboard with OSC 52 as fallback) are both consequences of that layout becoming the default. No rush on the review itself — it is a default-behaviour change and the minor-vs-major question in the description is worth a real opinion. Just want CI moving. |
|
@wbxl2000 — following up since no workflow run has started here yet; could you kick one off? There is nothing green to review against until then. This builds on @liruifengv's #2830, making the docked alternate-screen layout the only mode. No rush on the review itself — it is a default-behaviour change and the |
Related Issue
Resolve #2959
Resolve #891
Resolve #875
Resolve #2721
Resolve #2195
Problem
The input box is not docked to the bottom of the terminal — it renders directly after the last message. Two things follow from that.
At rest, the input sits wherever the conversation happens to end, with dead space below it:
While scrolling back through a long turn, the input scrolls away with the content. You have to scroll all the way back down before you can type again, which breaks the read-then-type loop:
#875 additionally asks for the panels attached to the input (plan, context status, hints) to stay pinned with it. #2721 asks for a fullscreen mode with a stable, app-controlled rendering surface, plus app-level selection that actually copies.
What changed
The interactive TUI renders on the alternate screen. The transcript scrolls inside a
ScrollView; the editor and the rest of the chrome — activity, todo, queue, btw, footer — sit in a dock pinned to the bottom, so the input box is always visible and always in the same place.createTUIStateconstructsTuiAltScreenand mounts that layout root. There is no mode switch:buildLayout()is gone,mountFooter()appends to the dock, anddockContaineris non-optional.Quitting is unaffected.
stopUiForExitleaves the alternate screen with the frame preserved and replays the transcript through aTuiMainScreen, so the conversation still lands in native scrollback.Scrolling the conversation area is served by page / half-page / line scrolling, the mouse wheel,
Ctrl-Shift-Up/Downto jump between prompts, andCtrl-Shift-Fto search the transcript.Two details the docked layout depends on:
home/endbelong to the editor.TUI.handleInputruns input listeners before the focused component, and a listener returning{consume: true}returns early — so a scrollable transcript would otherwise let the viewport claim keys the editor binds to line start / line end. Viewport scroll-to-top/bottom is bound toshift+home/shift+endinstead.TuiAltScreenOptions.onCopyis called alongside the OSC 52 write and kimi-code wires its native clipboard binding to it; OSC 52 remains the fallback where no native clipboard is reachable (SSH, headless). This is the split Feature request: fullscreen TUI mode with mouse text selection/copy (like Claude Code) #2721 asks for.KIMI_CODE_TUI_FULL_SCREENno longer selects anything, so its row is dropped from the env-vars docs in both locales. Historical changelog entries that mention it are left alone.On #2195 (opt out of the alternate screen)
That issue asks for either inline rendering or "In-TUI transcript scrolling (PageUp/PageDown or mouse wheel) as an alternative." The docked layout provides the second in full, and the transcript still reaches native scrollback on exit. Its premise was also mistaken when filed: the
script -qec kimicapture on #2721 shows 0.34.0 emitted no\x1b[?1049h, so the TUI was already inline and the reported scroll problem had another cause.An automatic inline fallback for terminals that cannot do the alternate screen was considered and left out: it would reinstate the dual layout path this PR deletes, to serve non-TTY /
TERM=dumbinteractive sessions that were already unsupported. Happy to add either that or atui.tomlopt-out if you would rather have one — both are small.Notes for reviewers
Bump level. The changeset is
minor. This changes default rendering for every user and turns a documented env var into a no-op, somajoris arguable — flag it and I will change it.Wider audience for two open bugs. #2193 (VS Code integrated terminal jumps to the top on mouse scroll) and #2324 (flicker during terminal resize) are not introduced here, but they reach every user rather than opt-in users. #2193 looks like the right follow-up.
pi-tui divergence.
onCopyis a local divergence from upstream pi-tui, recorded as entry 9 inpackages/pi-tui/AGENTS.mdwith its guarding test.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.