feat: to #2767, support verbose and compact mode swither with ctrl-o#2770
feat: to #2767, support verbose and compact mode swither with ctrl-o#2770tanzhenxin merged 4 commits intoQwenLM:mainfrom
Conversation
tanzhenxin
left a comment
There was a problem hiding this comment.
Code Review
Hey @chiga0 — thanks for the effort on this, the compact/verbose toggle is an interesting idea!
However, we have some concerns that need to be addressed before this can move forward.
Default behavior change is too disruptive
The biggest issue: this PR changes the default display behavior. Currently, thinking blocks (gemini_thought, gemini_thought_content) are always visible. After this PR, they're hidden by default (verboseMode: false). This is a significant UX regression for existing users who rely on seeing the model's reasoning. We can't accept a change that silently alters the default experience like this.
If you'd like to continue with this feature, the default should be verboseMode: true (preserving current behavior), with compact mode as an opt-in.
Critical issues
-
useCallbackdependency regression —AppContainer.tsx- The dependency
settings.merged.general?.debugKeystrokeLoggingwas replaced withsettings(the full object). Sincesettingscreates a new reference on every render, this defeatsuseCallbackmemoization entirely and causeshandleGlobalKeypressto be recreated every render. Please keep the original granular dependency and accesssetValueseparately.
- The dependency
-
Compact mode hides actively-executing shell output —
ToolMessage.tsx- In compact mode,
effectiveDisplayRendereris unconditionally set to{ type: 'none' }, which hides the liveansioutput of running shell commands. Users would be typing into a shell blind — they can see the input prompt but not the command output. At minimum,ansi-type displays should be exempted when a tool is actively executing.
- In compact mode,
Suggestions
-
Asymmetric frozen snapshot behavior — Frozen snapshot is only created when toggling TO verbose mode during streaming, not when toggling to compact. Switching to compact mid-stream causes tool outputs to disappear abruptly.
-
Copyright header — New file
VerboseModeContext.tsxsays "Copyright 2025 Google LLC". New files should reflect the Qwen Code project. -
Missing translations — Non-English locales (except Chinese) use the untranslated English string "verbose".
Please address the default behavior change and the critical issues. Happy to re-review once updated!
- Change default verboseMode to true (preserving current UX behavior) - Fix compact mode hiding active shell output (add forceShowResult + isUserInitiated) - Fix asymmetric frozen snapshot (freeze on ANY toggle during streaming) - Fix copyright header in VerboseModeContext.tsx (Google LLC → Qwen) - Add proper translations for all 6 locales (de/ja/pt/ru/zh/en) - Rewrite CompactToolGroupDisplay with bordered box, i18n hint, shell detection - Fix Pending status color (theme.text.secondary instead of theme.status.success) - Fix description casing: ctrl+o → Ctrl+O - Add explanatory comment for useCallback settings dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Hi @tanzhenxin — thanks for the thorough review! I've pushed a follow-up commit ( Changes & Rationale1. Default behavior preserved ✅ (Critical)Before:
2. useCallback dependency — addressed ✅ (Critical)Before: // `settings` is a stable LoadedSettings instance (not recreated on render).
// ESLint requires it here because the callback calls settings.setValue().
// debugKeystrokeLogging is read at call time, so no stale closure risk.
settings,
3. Active shell output no longer hidden ✅ (Critical)Before: In compact mode,
4. Symmetric frozen snapshot ✅ (Suggestion)Before: Frozen snapshot only created when toggling to verbose during streaming. Switching to compact mid-stream caused tool outputs to disappear abruptly. -if (newValue && streamingState !== StreamingState.Idle) {
+// Symmetric freeze: capture snapshot on ANY toggle during streaming
+if (streamingState !== StreamingState.Idle) {5. Copyright header fixed ✅ (Suggestion)Before: 6. Translations completed ✅ (Suggestion)Before: Non-English locales used untranslated English "verbose".
7. CompactToolGroupDisplay rewrittenAlso improved the compact display component:
Test Results
Please let me know if anything else needs attention! |
wenshao
left a comment
There was a problem hiding this comment.
The new compact mode introduces a critical UX/Security issue: when a tool enters the Confirming state (e.g., asking for permission to edit a file or run a command), the tool group expands, but the actual diff/command output inside <ToolMessage> remains hidden because verboseMode is false. This forces the user to blindly approve changes unless they explicitly press Ctrl+O. Similarly, when a tool fails (Error state), the error details are hidden.
I suggest force-expanding the group on errors, and force-showing the result on both Confirming and Error statuses.
For example, update showCompact to include !hasErrorTool and update <ToolMessage /> forceShowResult prop:
<ToolMessage
// ...
forceShowResult={
isUserInitiated ||
tool.status === ToolCallStatus.Confirming ||
tool.status === ToolCallStatus.Error
}
/>— gemini-3.1-pro-preview
packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx
Outdated
Show resolved
Hide resolved
wenshao
left a comment
There was a problem hiding this comment.
PR #2770 Code Review
审查结论:Comment(评论) — 无严重问题,实现质量良好。
概述
该 PR 为 Qwen Code CLI 添加了紧凑/详细模式切换功能(Ctrl+O),通过新的 VerboseModeContext 控制工具输出和思维链的显示。架构设计合理,React Context + frozenSnapshot 的方案干净利落。
审查统计
4 个并行审查维度共报告 11 个发现,去重合并后 7 个独立发现,经独立验证后 4 个确认、3 个驳回。无 Critical 级别问题。
确认的建议(4 条)
- 状态图标映射重复 —
CompactToolGroupDisplay.renderStatusIcon与ToolStatusIndicator存在代码重复,且已产生行为偏差(Pending 颜色不同、缺少 aria-label) - Context value 未 memoize —
VerboseModeProvider使用内联对象导致 5 个消费者在 AppContainer 任意状态变化时不必要重渲染 - refreshStatic() 全量重渲染 — 长会话中切换模式可能造成 UI 卡顿,建议仅在有 thought 条目时触发
- 未使用的 i18n 字符串 — 6 个语言文件中定义了切换提示语但未被代码引用
驳回的发现(3 条)
toolAwaitingApproval在 compact 路径是死代码 → React Hooks 必须无条件调用,属必要模式- VerboseModeProvider 裸 Provider 模式不一致 →
ShellFocusContext也使用同样模式,纯值注入的 Context 无需包装组件 - Compact 缺少 error borderColor → 验证后发现 compact 和 expanded 视图的 borderColor 逻辑完全一致,属误报
Reviewed by glm-5.1
wenshao
left a comment
There was a problem hiding this comment.
Code Review
Two issues found — one bug, one consistency concern. Architecture and overall approach look solid.
Review by Copilot CLI + Opus 4.6
|
Great approach — compact mode completely eliminates flicker by not rendering tool output at all. However, verbose mode still has no height cap, so switching to verbose with large outputs (e.g. How upstream Gemini CLI solved this (after the fork point)Since Qwen Code's last upstream sync (v0.8.2, Oct 2025), Gemini CLI added a 4-layer anti-flicker system in March 2026:
Suggestion: combine both approachesYour compact/verbose toggle solves "whether to show output"; Gemini's height limiting solves "how much to show". The ideal state is both:
Key files to backport from upstream:
Relevant PRs in upstream Gemini CLI:
Detailed analysis: tool-output-height-limiting-deep-dive.md |
…toggle - Fix default value: compact mode (verboseMode=false) is now the default, matching PR description and intended UX - Extract shared ToolStatusIndicator component to eliminate duplicate status icon rendering between ToolMessage and CompactToolGroupDisplay - Memoize VerboseModeProvider context value to prevent unnecessary re-renders of all consumer components - Clear frozenSnapshot on WaitingForConfirmation state to ensure tool confirmation UI remains interactive during mid-stream toggle - Replace magic string 'Shell' with SHELL_NAME constant in ToolMessage - Remove unused i18n translation keys (verbose/compact mode messages) - Update snapshots for Footer and ToolGroupMessage tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…atus Per maintainer review (tanzhenxin): default verboseMode reverted to true to preserve existing behavior — compact mode is opt-in via Ctrl+O. Also addresses wenshao's security concern: in compact mode, tool groups now force-expand on Error status (in addition to existing Confirming handling), and ToolMessage force-shows result for both Confirming and Error statuses so users always see diffs before approval and error details for debugging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@tanzhenxin Thanks for the review! Addressed in 1d639c9:
Additionally in 1d639c9, per @wenshao's suggestion: compact mode now force-expands on |
|
@wenshao Thanks for the detailed reviews! Re: Confirming/Error force-expand — Fixed in 1d639c9. Compact mode now:
Re: verbose mode height limiting / SlicingMaxSizedBox backport — Great analysis! Agree that combining compact/verbose toggle with height caps would be ideal. This is a larger effort that should be tracked separately. Filed as a follow-up item. |
| @@ -0,0 +1,108 @@ | |||
| /** | |||
There was a problem hiding this comment.
[Suggestion] CompactToolGroupDisplay has no test coverage. ToolGroupMessage.test.tsx doesn't wrap with VerboseModeProvider, so the compact-mode branch (showCompact) is never exercised.
The component contains non-trivial logic: getOverallStatus priority resolution, getActiveTool selection, description truncation, and conditional border colors — all unguarded by tests.
Suggested fix: Add VerboseModeProvider to the test's renderWithProviders helper (defaulting to { verboseMode: true, frozenSnapshot: null }), then add test cases:
- Render with
verboseMode: false+ Success tool → verify compact display renders - Render with
verboseMode: false+ Confirming tool → verify expanded view (not compact) - Unit test
getOverallStatuspriority ordering
Reviewed by glm-5.1 via Qwen Code /review
| import { useConfig } from '../contexts/ConfigContext.js'; | ||
| import { useVimMode } from '../contexts/VimModeContext.js'; |
There was a problem hiding this comment.
[Suggestion] Footer.test.tsx doesn't wrap <Footer /> in VerboseModeProvider. Footer now calls useVerboseMode() to conditionally render the "verbose" label, but tests rely on the default context value (verboseMode: true). No test verifies the label disappears when in compact mode.
Suggested fix:
import { VerboseModeProvider } from '../contexts/VerboseModeContext.js';
// In renderWithWidth, wrap Footer:
<VerboseModeProvider value={{ verboseMode: true, frozenSnapshot: null }}>
{/* ... existing providers ... */}
<Footer />
</VerboseModeProvider>
// Add test:
it('does not show verbose label when verboseMode is false', () => {
// render with verboseMode: false, assert 'verbose' not in output
});Reviewed by glm-5.1 via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
Review Summary
PR #2770: feat: verbose/compact mode toggle (Ctrl+O)
24 files changed (+438/-112). The implementation is clean and well-structured:
- React Context (
VerboseModeProvider) distributes state correctly with memoized value - Frozen snapshot mechanism handles mid-stream toggles (only during
Respondingstate) - Force-show logic preserves visibility for shell, confirming, and error tools
- Shared
ToolStatusIndicatorcomponent avoids duplication - All previously reported issues (stale closure, memoization, default value, etc.) have been addressed
Deterministic checks: Lint clean, tsc clean on changed files, all 3723 tests pass.
2 suggestions (no blockers):
- Missing test coverage for
CompactToolGroupDisplay— component has non-trivial logic but zero tests Footer.test.tsxmissingVerboseModeProviderwrapper — no test for compact-mode footer
Reviewed by glm-5.1 via Qwen Code /review
tanzhenxin
left a comment
There was a problem hiding this comment.
Review
Nice work on this — clean architecture with React Context, and good responsiveness to feedback across multiple rounds.
One edge case to flag: toggling Ctrl+O while an interactive shell is active freezes the pending area with activeShellPtyId=undefined and embeddedShellFocused=false (MainContent.tsx lines 53-56). This causes the shell prompt to disappear until streaming ends. Consider either disabling Ctrl+O while a PTY shell is focused, or preserving the shell focus state in the frozen snapshot.
Verdict
COMMENT — Mergeable. The shell focus issue is a niche edge case that can be addressed in a follow-up.
… improvements - Add Ctrl+O to keyboard shortcuts list (?) and /help command - Sync compact mode toggle from Settings dialog with CompactModeContext - Protect tool approval prompts from being hidden in compact mode (MainContent forces live rendering during WaitingForConfirmation) - Remove snapshot freezing on toggle — treat as persistent preference, not temporary peek (differs from Claude Code's session-scoped model) - Add compact mode tip to startup Tips rotation for non-intrusive discovery - Remove compact mode indicator from footer to reduce UI clutter - Add competitive analysis design doc (EN + ZH) comparing with Claude Code - Update user docs (settings.md) and i18n translations (en/zh/ru/pt) Relates to #3047, #2767, #2770 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… improvements - Add Ctrl+O to keyboard shortcuts list (?) and /help command - Sync compact mode toggle from Settings dialog with CompactModeContext - Protect tool approval prompts from being hidden in compact mode (MainContent forces live rendering during WaitingForConfirmation) - Remove snapshot freezing on toggle — treat as persistent preference, not temporary peek (differs from Claude Code's session-scoped model) - Add compact mode tip to startup Tips rotation for non-intrusive discovery - Remove compact mode indicator from footer to reduce UI clutter - Add competitive analysis design doc (EN + ZH) comparing with Claude Code - Update user docs (settings.md) and i18n translations (en/zh/ru/pt) Relates to #3047, #2767, #2770 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TLDR
Adds a compact/verbose mode toggle (Ctrl+O) to Qwen Code. In compact mode (default), tool result output and model thinking-chain are hidden, keeping the
terminal clean during long agentic runs. Pressing Ctrl+O switches to verbose mode, revealing full tool output and thoughts. The setting persists across
sessions via
~/.qwen/settings.json, and averboselabel appears in the footer when active.Screenshots / Video Demo
Compact mode (default) — tool calls show name + status only, no output noise:
✓ ReadFile src/index.ts
✓ Edit src/index.ts
✓ Bash npm run build
Verbose mode (Ctrl+O) — full tool output visible,
verboseindicator in footer:✓ ReadFile src/index.ts
1 import React from 'react';
...
✓ Bash npm run build
▎ build
▎ tsc --project tsconfig.json
▎ Done in 2.3s
Dive Deeper
Architecture: Rendering-layer filtering via React Context. No changes to the data model or streaming pipeline —
VerboseModeContextdistributesverboseModeandfrozenSnapshotstate fromAppContainerto:HistoryItemDisplay— gatesgemini_thought/gemini_thought_contentrenders behindverboseModeToolMessage— computeseffectiveDisplayRenderer: passes through in verbose mode, returns{ type: 'none' }in compact mode (interactive shellprompt is unaffected)
MainContent— usesfrozenSnapshotto freeze the pending items viewport while streaming, preventing layout jitter when toggling mid-stream;auto-unfreezes when streaming completes
Footer— showsverboselabel (localised) when verbose mode is activeKey design decisions:
verboseMode: false) — matches the target UX of a clean terminal by defaultpendingHistoryItemsuseMemo was moved earlier inAppContainer(beforehandleGlobalKeypress) to avoid stale closure / TDZ issues when referenced inthe Ctrl+O handler
refreshStatic()is called on toggle to remount Ink's<Static>component, applying the mode change retroactively to already-rendered historyIdentityFileapproach for SSH key (non-breaking, no changes to auth)Localization:
verbosekey added toen,zh,de,ja,ru,ptlocale files.Reviewer Test Plan
npm run build— should pass with zero TypeScript errorsnpm test— all 238 test files should passlist all files in src). Tool result output should be hidden; only tool name +status icon shown.
verboselabel should appear in the footer. Re-run a prompt — full tool output should now be visible.verboselabel disappears, tool output hidden.~/.qwen/settings.jsonfor"ui": { "verboseMode": true }.auto-unfreeze.
<think>blocks should be hidden. In verbose mode, they shouldappear.
Ctrl+Ffocus) still works normally in compact mode — the shell input prompt must not be gated byverbose mode.
Testing Matrix
Validated on macOS (Apple Silicon) via
npm run.Linked issues / bugs
Closes #2767