Repository navigation
[Bug]: CLI - interactive TUI (regular mode): no way to scroll back the transcript — the wheel is not handled by the TUI and PageUp/PageDown are bound to the composer #535
Description
Product or interface
CLI - interactive TUI
Version
0.6.12
Platform
Linux
OS version and architecture
Ubuntu 24.04.4 LTS x86_64
Issue area
UI / UX
Desktop log upload ID (optional)
No response
Steps to reproduce
- Run the interactive TUI in regular mode — the default.
mcode, with no--tui-mode fullscreen. - Run it over SSH, inside tmux, with tmux's own mouse reporting off (
set -g mouse off, which is the tmux default). Environment:TERM=tmux-256color, tmux 3.4, Node v22.22.2. - Produce a transcript taller than the viewport — send a few messages, or let an answer stream until the output overflows.
- Try to scroll the transcript back up:
- Mouse wheel — nothing happens at all. The wheel is never handled.
- PageUp — the composer scrolls (the text in the input box moves); the transcript does not.
- PageDown — same, in the other direction.
- For contrast, run the same session in fullscreen mode —
mcode --tui-mode fullscreen. There the wheel scrolls the transcript, and PageUp/PageDown scroll it too.
No keys, tokens or account details are needed to reproduce; synthetic text is enough to overflow the viewport.
Expected and actual behavior
Expected: in regular mode there is some way to scroll the transcript back — the wheel, or a key binding.
Actual: in regular mode there is none. The transcript is effectively write-only: as soon as it is taller than the viewport there is no way back through it. There are two separate causes, and both have to be fixed for regular mode to be usable.
1. The TUI only asks the terminal for mouse events when the alt screen is active.
In the shipped bundle (@minimax-ai/code, chunks/launcher-*.js), the mouse-reporting enable sequence — \x1B[?1000h\x1B[?1002h\x1B[?1004h\x1B[?1006h, plus \x1B[?1003h when not under tmux/screen — is written in beforeTerminalStart(). That same method writes \x1B[?1049h (enter alt screen) and sets altScreenActive = true. The matching disable (\x1B[?1006l\x1B[?1004l\x1B[?1003l\x1B[?1002l\x1B[?1000l) is written in beforeTerminalStop() and is guarded by if (this.altScreenActive).
The regular-mode path never writes the enable sequence (this.mouseEnabled defaults to true, but it is only consulted inside the alt-screen branch). So the terminal never sends wheel events, and the TUI never receives one to handle.
2. PageUp/PageDown are bound to the composer, not to the transcript.
The viewport bindings that scroll the transcript — tui.altScreen.pageUp / tui.altScreen.pageDown — live in the alt-screen namespace and are dispatched by handleViewportInput(), which is the fullscreen path.
In regular mode the composer has focus, so interactionActive is false and pageUp/pageDown resolve to tui.editor.pageUp / tui.editor.pageDown (default keys pageUp and pageDown), whose handler calls this.pageScroll(-1) / this.pageScroll(1) — the editor's own scroll. The interaction.scroll-up / interaction.scroll-down bindings (also pageUp / pageDown) only apply while the interaction layer has focus, which is not the normal regular-mode state; the context resolver treats interaction and composer as mutually exclusive.
Notes
- This is distinct from [Bug]: interactive TUI fullscreen: mouse-wheel scroll is a no-op while the transcript is following the tail #481. That one is about fullscreen mode, in a terminal that reports mouse input, where the wheel is a no-op while the transcript is following the tail. This report is about regular mode, where the wheel is never delivered at all and the keyboard fallback is captured by the composer.
- It also relates to Chat TUI auto-scrolls to bottom unconditionally — request followTail toggle / scroll-lock #320 / [Bug] TUI chat transcript auto-scrolls to bottom on every token, blocking user from reading previous output #348 / [Bug] TUI chat transcript auto-scrolls to bottom on every token, blocking user from reading previous output #349 (auto-scroll to the bottom): those asked for a way to stop the transcript following the tail. This is the adjacent gap — in regular mode there is no scrollback to switch to.
- Workaround for users today: run
mcode --tui-mode fullscreen. There, the wheel and PageUp/PageDown both scroll the transcript. - A fix would need at least one of: enabling mouse reporting in regular mode (and handling the wheel), and/or binding a transcript-scroll key in regular mode that is not captured by the composer.
Activity
1anZhang commentedon Oct 24, 2026
Hi @sandeep-alluru, thanks a lot for the incredibly detailed report — the repro steps and the code walkthrough made this very easy to look into 🙏
Your reading of the code is accurate. The behavior you're seeing comes from a deliberate design choice in regular mode, though, rather than a missing handler:
So the missing piece in your setup is tmux itself. tmux runs inside the outer terminal's alternate screen, so the outer terminal's scrollback isn't reachable, and with
mouse off(the tmux default) the wheel never gets to tmux's own pane history. You'd see the same thing with any plain main-buffer program, e.g.catof a long file.Ways to scroll back in regular mode under tmux
Enable tmux mouse support (recommended):
Then
tmux source-file ~/.tmux.conf. tmux's defaultWheelUpPanebinding enters copy mode when the pane isn't in the alternate screen and hasn't requested mouse events, which is exactly the case for mcode in regular mode. The wheel will then scroll the transcript right away, and you can still select and copy.Use tmux copy mode from the keyboard: press
prefix+[(by defaultCtrl-b [), then usePageUp/PageDownor the arrow keys. Pressqto exit.Use fullscreen mode, as you found:
mcode --tui-mode fullscreen, or set it permanently via/settings. Here the TUI manages its own transcript viewport, so the wheel and PageUp/PageDown scroll it directly, no tmux config needed.One small caveat: when the terminal is resized, regular mode does a full redraw that rebuilds the visible history, so what tmux's copy mode shows may be re-laid out (and older scrollback cleared) after a resize. This is a known limitation we're still working to reduce.
We'll also look into adding a note about tmux to the docs so others don't run into this. Thanks again for the thorough write-up, and please let us know if any of the above doesn't work for you!
sandeep-alluru commentedon Oct 25, 2026
Thanks — this is a very clear explanation, and it matches what I found in the shipped 0.6.12 bundle.
The two mouse-enable constants (
CPunder tmux/screen/zellij,wPelsewhere) are written from exactly one call site, and that site lives in the renderer class whosemodeis"fullscreen"; the"regular"renderer is a separate class and never writes them, and the mode is chosen bytuiMode === "fullscreen" ? "fullscreen" : "regular". So regular mode genuinely does not request mouse events, and your account of it is right — including why PageUp/PageDown are the composer's.One thing I want to pin down, because it changes what I run: the fullscreen renderer enables mouse reporting by default (
mouseEnabled = s.mouse ?? !0), and under tmux it still sendsCP—1000/1002/1004/1006. Withset -g mouse onin my tmux config the wheel is forwarded to the app, so fullscreen should scroll its own viewport, which is what I want, since regular mode's full redraw on resize rebuilds the visible history and clears older scrollback — that is the part that actually bites me, since I resize panes constantly.So the ask: would you consider exposing the
mouseoption the fullscreen renderer already reads as a user-facing setting, so fullscreen mode can be run without requesting mouse events at all? It looks like a one-line change froms.mouse ?? !0to a config-backed default. And if the resize redraw's scrollback handling is on the roadmap, a note in the docs about the tmux setup for regular mode would save other people the same detour.On contributing: I have the repo cloned and can work against
pnpm verify. I see CONTRIBUTING says contributions are currently accepted from collaborators only, so please let me know the preferred route — a branch you pull from, a patch attached to the issue, or a collaborator invite — and I'll put it up whichever way is easiest for you.