Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
8f5eb98
fix(cli): default to virtualized terminal history
ZevGit Jun 23, 2026
738cac3
Merge branch 'main' into fix/cli-scroll-flicker
wenshao Jun 24, 2026
b35fef7
Merge remote-tracking branch 'upstream/main' into fix/cli-scroll-flicker
ZevGit Jun 25, 2026
604e733
fix(cli): remove redundant alternate screen exit handler
ZevGit Jun 26, 2026
cc5d436
Merge remote-tracking branch 'upstream/main' into fix/cli-scroll-flicker
ZevGit Jun 26, 2026
22f100f
Merge branch 'main' into fix/cli-scroll-flicker
wenshao Jun 27, 2026
769e196
fix(cli): merge main into scroll-flicker branch, resolving useTermina…
qwen-code-dev-bot Jul 4, 2026
8632a62
fix(cli): keep non-interactive output off VP mode
ZevGit Jul 5, 2026
975d216
fix(cli): stabilize VP tests in CI environments
ZevGit Jul 6, 2026
874fb6c
Merge remote-tracking branch 'upstream/main' into fix/cli-scroll-flicker
ZevGit Jul 6, 2026
50187da
test(cli): resolve SDK daemon source in vitest
ZevGit Jul 6, 2026
81daecf
fix(cli): normalize CI env checks for VP mode
ZevGit Jul 6, 2026
e1cc1c4
Merge remote-tracking branch 'upstream/main' into fix/cli-scroll-flicker
ZevGit Jul 6, 2026
ddc0c31
fix(cli): keep default VP mouse interactions enabled
ZevGit Jul 6, 2026
6953ae4
Merge branch 'main' into fix/cli-scroll-flicker
ZevGit Jul 6, 2026
c4485e2
fix(cli): resolve merge conflicts with main for virtualized terminal …
qwen-code-dev-bot Jul 9, 2026
999a609
Merge branch 'main' into fix/cli-scroll-flicker
wenshao Jul 10, 2026
9e0919b
Merge branch 'main' into fix/cli-scroll-flicker
wenshao Jul 10, 2026
f4cdecc
fix(cli): align VP mouse behavior with runtime state
ZevGit Jul 10, 2026
cc24b0a
Merge remote-tracking branch 'upstream/main' into fix/cli-scroll-flicker
ZevGit Jul 13, 2026
0c9df2b
fix(cli): stabilize virtual viewport runtime state
ZevGit Jul 13, 2026
a22120a
test(cli): cover virtual viewport fallbacks
ZevGit Jul 13, 2026
ad92da5
Merge remote-tracking branch 'upstream/main' into fix/cli-scroll-flicker
ZevGit Jul 14, 2026
cef51a6
docs(cli): clarify virtual viewport requirements
ZevGit Jul 14, 2026
66a1499
Merge remote-tracking branch 'upstream/main' into fix/cli-scroll-flicker
ZevGit Jul 24, 2026
2a097de
Merge branch 'main' into fix/cli-scroll-flicker
ZevGit Jul 24, 2026
d18f38a
Merge upstream/main into fix/cli-scroll-flicker
ZevGit Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/design/ctrl-o-detail-expand/design.md

Large diffs are not rendered by default.

41 changes: 29 additions & 12 deletions docs/design/virtual-viewport/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Deferred to follow-up PRs:

- **Scrollbar drag + click-to-position** — needs screen-absolute element coords, blocked on a stock-ink-7 limitation (see V.4 / V.7).
- **In-app `/` search** — claude-code's `TranscriptSearchBar` pattern (V.5).
- **Alternate-buffer mode** — `contexts/ScrollProvider.tsx`-style focus / lock, with full alt-screen takeover (V.6).
- **Dedicated alternate-buffer setting** — VP already enters alternate screen; revisit a separate toggle only if compatibility reports require it.

### Setting (V.2)

Expand All @@ -132,27 +132,44 @@ ui: {
/**
* Enables virtualized history rendering for long conversations.
* When true, only items in the visible viewport are rendered through React;
* scrolled-out items remain in the terminal scrollback buffer.
* scrolled-out items stay in the in-app scrollback model instead of the
* host terminal scrollback buffer.
*
* Default: false. Opt-in until proven stable on long conversations.
* Default: true. Users can opt out if they prefer host terminal scrollback.
*/
useTerminalBuffer?: boolean; // alias kept compat with gemini-cli
}
```

`MainContent.tsx` reads the setting and switches paths:
`AppContainer.tsx` freezes the startup decision so it stays in sync with
Ink's `alternateScreen` lifetime:

```tsx
const useTerminalBuffer = uiState.settings?.ui?.useTerminalBuffer ?? false;
const [useTerminalBuffer] = useState(() =>
shouldUseVirtualViewport(
settings.merged.ui?.useTerminalBuffer,
config.getScreenReader(),
isInteractiveTerminal(),
),
);
```

`MainContent.tsx` then reads the frozen UI state and switches paths:

```tsx
const useVirtualScroll = uiState.useTerminalBuffer;

if (useTerminalBuffer) {
if (useVirtualScroll) {
return <ScrollableList .../>; // virtualized
}

return <Static .../>; // existing path, untouched
```

The legacy `<Static>` path stays as-is — no regression risk for users who don't opt in.
The legacy `<Static>` path stays available for users who explicitly opt out,
for screen-reader mode, and for non-interactive output such as piped stdout or
CI. Because the decision controls Ink's alternate screen, changes to
`ui.useTerminalBuffer` require a restart.

## 6. Key adaptations from gemini-cli source

Expand Down Expand Up @@ -316,10 +333,10 @@ Same pattern in qwen-code. Required for virtualization to actually skip re-rende
| **V.3** | test(integration): capture-suite regressions for streaming / resize / shell | port 3 capture scripts from PR #3663 | ~2000 (test-only) | #4146 | pending |
| **V.4** | feat(cli): scrollbar drag + click-to-position | SGR mouse hit-test on scrollbar column. Needs screen-absolute coords — either upstream `getBoundingBox` to ink 7 or own yoga walker. Auto-hide animation already shipped in #4146. | ~400 | #4146 | deferred — coord blocker |
| **V.5** | feat(cli): in-app `/` search | viewport-bound highlight + n/N navigation (claude-code's `TranscriptSearchBar` pattern) | ~300 | #4146 | deferred |
| **V.6** | feat(cli): alternate-buffer mode (full alt-screen takeover) | additional setting `ui.useAlternateBuffer` | ~500 | #4146 | deferred — separate UX decision required |
| **V.6** | feat(cli): dedicated alternate-buffer toggle | no separate setting planned for the default flow; VP already enters alternate screen, revisit only if compatibility reports require it | — | #4146 | deferred — compatibility-driven only |
| **V.7** | research: preserve host terminal scrollback (dual-write) | `@jrichman/ink`'s `overflowToBackbuffer` is fork-only. Options: upstream PR to ink 7, own dual-write, or accept loss. Investigation. | — | #4146 | structurally blocked on stock ink 7 |

V.3 (integration tests) is the remaining critical-path item before flipping the default. V.4–V.6 close the remaining gemini-cli-parity gaps; V.7 is open research because the underlying ink prop we'd need (`overflowToBackbuffer`) only exists in gemini-cli's `@jrichman/ink` fork.
V.3 (integration tests) remains desirable for long-session regression coverage but is no longer a gating prerequisite for the default flip. V.4–V.6 close the remaining gemini-cli-parity gaps; V.7 is open research because the underlying ink prop we'd need (`overflowToBackbuffer`) only exists in gemini-cli's `@jrichman/ink` fork.

## 8. Verification plan

Expand All @@ -342,10 +359,10 @@ End-to-end (after V.3):
## 9. Open questions / decisions needed

1. **Setting name**: `ui.useTerminalBuffer` (gemini-cli compat) vs `ui.virtualizedHistory` (more descriptive)?
2. **Default value**: ship as `false` (opt-in) or stage rollout via env var first?
2. **Default value**: resolved as `true` (default-on) with `false` as an explicit opt-out.
3. **Static-item heuristic**: gemini-cli marks only `header` as static. Should we also mark completed Gemini messages, tool results that are no longer in `pendingHistoryItems`, etc.?
4. **Mouse support**: gemini-cli's `ScrollProvider` includes mouse drag for scrollbar. Worth porting now or skip until V.4?
5. **Compatibility with #3905**: ~~PR #3905 (Ctrl+O freeze fix) is open and modifies the same `MainContent.tsx`. Coordinate merge order — likely V.2 rebases on top of #3905.~~ **Resolved**: #3905's progressive-replay landed in `main` and is preserved in the legacy `<Static>` branch of `MainContent.tsx`; the VP branch supersedes it for opt-in users because the freeze trigger (full Static remount) no longer applies.
5. **Compatibility with #3905**: ~~PR #3905 (Ctrl+O freeze fix) is open and modifies the same `MainContent.tsx`. Coordinate merge order — likely V.2 rebases on top of #3905.~~ **Resolved**: #3905's progressive-replay landed in `main` and is preserved in the legacy `<Static>` branch of `MainContent.tsx`; the VP branch supersedes it for default users because the freeze trigger (full Static remount) no longer applies.
6. **Compatibility with `chore/re-upgrade-ink-7-0-3`**: PR #4146 stacks on it. After #4119 (the ink 7.0.3 re-upgrade PR) merges to `main`, PR #4146's base will re-target to `main`.

## 10. Risks
Expand All @@ -361,7 +378,7 @@ End-to-end (after V.3):
## 11. Approval checklist

- [x] Architectural direction approved — port from gemini-cli (§4)
- [x] Setting name + default decided — `ui.useTerminalBuffer`, default `false` (opt-in)
- [x] Setting name + default decided — `ui.useTerminalBuffer`, default `true` (opt-out)
- [x] Static-item heuristic — `isStaticItem={(item) => item.id > 0}` (completed history items)
- [x] Mouse-support scope — deferred to V.4; keyboard-only scroll in #4146
- [x] Merge ordering with #3905 (§9.5) — #3905 already in `main`; #4146 preserves the legacy progressive-replay path and supersedes it only for VP users
Expand Down
4 changes: 2 additions & 2 deletions docs/users/reference/keyboard-shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ This document lists the available keyboard shortcuts in Qwen Code.

## History scrollback

Active only when `ui.useTerminalBuffer` is enabled (Settings → UI → Virtualized History). In that mode conversation history is rendered inside an in-app viewport instead of the host terminal scrollback, so the keys below replace the terminal's native scroll.
Active when `ui.useTerminalBuffer` is enabled (Settings → UI → Virtualized History), screen reader mode is off, and Qwen Code is running in a compatible interactive terminal (`stdout` is a TTY, CI is inactive, and `TERM` is not `dumb`), which is the default for ordinary non-screen-reader sessions. In that mode conversation history is rendered inside an in-app viewport instead of the host terminal scrollback, so the keys below replace the terminal's native scroll.

| Shortcut | Description |
| --------------- | ---------------------------------------------------- |
Expand All @@ -87,7 +87,7 @@ When `ui.useTerminalBuffer` is on, the terminal forwards mouse events to qwen-co

Inside tmux, some terminals translate trackpad or wheel gestures into plain `Up Arrow` and `Down Arrow` sequences before qwen-code sees them. Those bytes are identical to real arrow-key presses, so qwen-code cannot tell whether you meant to scroll the viewport or navigate prompt history.

If trackpad scrolling changes the prompt history in tmux, enable `ui.useTerminalBuffer`; then use `Shift+Up` / `Shift+Down`, or the mouse wheel when tmux forwards wheel events to the app. If you prefer host scrollback, adjust your tmux mouse bindings for wheel events.
If trackpad scrolling changes the prompt history in tmux, make sure `ui.useTerminalBuffer` is enabled; then use `Shift+Up` / `Shift+Down`, or the mouse wheel when tmux forwards wheel events to the app. If you prefer host scrollback, adjust your tmux mouse bindings for wheel events.

## IDE Integration

Expand Down
2 changes: 1 addition & 1 deletion docs/users/support/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ This guide provides solutions to common issues and debugging tips, including top
- **Trackpad scrolling in tmux changes prompt history instead of scrolling the conversation**
- **Issue:** In a tmux session, trackpad or wheel scrolling may cycle through previous prompts, similar to pressing `Up Arrow` or `Down Arrow`.
- **Cause:** tmux can translate wheel gestures into plain arrow-key sequences. Those sequences are indistinguishable from real arrow-key presses by the time qwen-code receives them.
- **Solution:** Enable `ui.useTerminalBuffer`; then use `Shift+Up` / `Shift+Down`, or the mouse wheel when tmux forwards wheel events to the app. If you prefer host scrollback, adjust your tmux mouse bindings for wheel events.
- **Solution:** If screen reader mode is disabled, make sure `ui.useTerminalBuffer` is enabled; then use `Shift+Up` / `Shift+Down`, or the mouse wheel when tmux forwards wheel events to the app. If you prefer host scrollback, adjust your tmux mouse bindings for wheel events.

## IDE Companion not connecting

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/config/settingsSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,9 +432,9 @@ describe('SettingsSchema', () => {
getSettingsSchema().ui.properties.useTerminalBuffer;
expect(useTerminalBuffer).toBeDefined();
expect(useTerminalBuffer.type).toBe('boolean');
expect(useTerminalBuffer.default).toBe(false);
expect(useTerminalBuffer.default).toBe(true);
expect(useTerminalBuffer.showInDialog).toBe(true);
expect(useTerminalBuffer.requiresRestart).toBe(false);
expect(useTerminalBuffer.requiresRestart).toBe(true);
});

it('should expose response tokens/sec as an opt-in UI setting', () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1043,10 +1043,10 @@ const SETTINGS_SCHEMA = {
type: 'boolean',
label: 'Virtualized History (reduces flicker on long sessions)',
category: 'UI',
requiresRestart: false,
default: false,
requiresRestart: true,
default: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] This default flip leaves docs/design/ctrl-o-detail-expand/design.md inconsistent with the shipped architecture: its current-state sections still call native scrollback/<Static> the default and point to the old raw gemini.tsx settings gate. Update those passages to describe VP/alternate-screen as the default interactive path, the explicit opt-out/screen-reader/non-interactive exceptions, and the startup decision in startInteractiveUI.tsx.

— Codex $qreview via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0c9df2b. The Ctrl+O design now describes VP/root alternate-screen as the default interactive path, the explicit opt-out, screen-reader, CI, non-TTY, and TERM=dumb fallbacks, and the single startup decision shared by startInteractiveUI and AppContainer. The transcript enter/exit sections and implementation references were updated to match the current architecture.

description:
'Render conversation history in an in-app scrollable viewport instead of the terminal scrollback buffer. Recommended if you see flicker, scroll-storm, or interface freeze on long sessions, after Ctrl+O, after Ctrl+E / Ctrl+F (expand), after window resize, or when alt-tabbing back. Scroll with Shift+↑/↓ (line), PgUp/PgDn (page), Ctrl+Home/End (top/bottom), or the mouse wheel. Also enables mouse interactions: click an option in a menu/dialog to select it, hover to highlight it, and click in the prompt to position the cursor. Does NOT use the host terminal scrollback while enabled. Drag to select text in the viewport (double/triple click selects a word/line), copied on release. To use the terminal’s own selection instead, hold Shift (or Option on macOS) while dragging.',
'Render conversation history in an in-app scrollable viewport instead of the terminal scrollback buffer. Enabled by default in compatible interactive terminals to avoid flicker, scroll-storm, and interface freeze on long sessions, after Ctrl+O, after Ctrl+E / Ctrl+F (expand), after window resize, or when alt-tabbing back. Screen reader mode and non-interactive output such as piped stdout or CI use append-only terminal output instead. Scroll with Shift+↑/↓ (line), PgUp/PgDn (page), Ctrl+Home/End (top/bottom), or the mouse wheel. Also enables mouse interactions: click an option in a menu/dialog to select it, hover to highlight it, and click in the prompt to position the cursor. Does NOT use the host terminal scrollback while enabled. Drag to select text in the viewport (double/triple click selects a word/line), copied on release. To use the terminal’s own selection instead, hold Shift (or Option on macOS) while dragging.',
showInDialog: true,
},
showScrollbar: {
Expand Down
Loading
Loading