qwen-code/docs/design/compact-mode/compact-mode-design.md
ChiGao 70396d1276
feat: optimize compact mode UX — shortcuts, settings sync, and safety (#3100)
* feat: optimize compact mode UX — shortcuts, settings sync, and safety 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>

* refactor: remove frozenSnapshot dead code and Chinese design doc

- Remove frozenSnapshot state, useEffect, and all related logic from
  AppContainer, MainContent, CompactModeContext, and test files
- Simplify MainContent to always render live pendingHistoryItems
- Delete compact-mode-design-zh.md (redundant Chinese translation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR review feedback for compact mode optimization

- Add refreshStatic() call after setCompactMode in SettingsDialog
  so already-rendered Static history updates immediately
- Fix outdated column split comment in KeyboardShortcuts (5+4+4)
- Update design doc: remove all frozenSnapshot references, renumber
  optimization recommendations, fix file reference descriptions
- Add missing i18n keys for de.js and ja.js locales
- Add test for SettingsDialog compact mode sync with CompactModeContext

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent subagent confirmation from being hidden in compact mode

hasConfirmingTool only checks ToolCallStatus.Confirming, but subagent
approvals arrive via resultDisplay.pendingConfirmation while the tool
status remains Executing. Add hasSubagentPendingConfirmation to the
showCompact guard so tool groups with pending subagent confirmations
are always force-expanded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: force show subagent confirmation result in compact mode

The previous fix (47ee03c) correctly force-expanded the tool group
wrapper when a subagent had pending confirmation, but each inner
ToolMessage still hid its resultDisplay due to compactMode check,
which hid the AgentExecutionDisplay containing the inline confirmation
UI.

Add isAgentWithPendingConfirmation to forceShowResult conditions so
the inner AgentExecutionDisplay is rendered even in compact mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(compact-mode): merge consecutive tool groups across hidden items

In compact mode, sequential tool calls across multiple LLM turns each
produced a separate bordered box, defeating the "compact" intent. The
model typically emits a `gemini_thought` between consecutive tool calls,
which is hidden in compact mode — so visually the boxes look adjacent,
but in `history` they are separated by hidden items.

This commit adds render-time merging of consecutive tool_group history
items, where "consecutive" allows hidden-in-compact items
(`gemini_thought`, `gemini_thought_content`) between them.

Key pieces:
- New `mergeCompactToolGroups` utility that merges adjacent mergeable
  tool_groups, skipping hidden items between them. Force-expand
  conditions (Confirming/Error tools, subagent pending confirmation,
  user-initiated, focused embedded shell) preserve group boundaries so
  authorization prompts, errors, and shell focus stay visible.
- `MainContent.tsx` applies the merger only when `compactMode === true`
  (verbose mode is unchanged) and calls `refreshStatic()` when a merge
  consolidates items, because Ink's `<Static>` is append-only and
  cannot replace already-committed terminal content.
- `CompactToolGroupDisplay.tsx` shows a `× N` count when a merged
  group contains more than one tool, matching the existing single-turn
  multi-tool display style.
- 19 unit tests covering empty/single/multiple groups, hidden-item
  skipping (the 8-tool real-world scenario), force-expand boundaries,
  mixed tool types, and complex sequences.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:29:24 +08:00

18 KiB

Compact Mode Design: Competitive Analysis & Optimization

Ctrl+O compact/verbose mode toggle — competitive analysis with Claude Code, current implementation review, and optimization recommendations.

User documentation: Settings — ui.compactMode.

1. Executive Summary

Qwen Code and Claude Code both provide a Ctrl+O shortcut for toggling between compact and detailed tool output views, but the design philosophy, default state, and interaction model differ fundamentally. This document provides a deep source-level comparison, identifies UX gaps, and proposes optimizations for Qwen Code.

Dimension Claude Code Qwen Code
Default mode Compact (verbose=false) Verbose (compactMode=false)
Toggle semantics Temporary peek at details Persistent preference switch
Persistence Session-only, resets on restart Persisted to settings.json
Scope Global screen switch (prompt ↔ transcript) Per-component rendering toggle
Frozen snapshot None (no concept) None (removed)
Per-tool expand hint Yes ("ctrl+o to expand") Yes ("Press Ctrl+O to show full tool output")

2. Claude Code Implementation Analysis

2.1 Architecture

Claude Code uses a screen-based approach rather than a component-level rendering toggle:

┌──────────────────────────────────┐
│         AppState (Zustand)       │
│  verbose: boolean (default: false)│
│  screen: 'prompt' | 'transcript' │
└──────────┬───────────────────────┘
           │
     ┌─────┴──────┐
     │  Ctrl+O    │  toggles screen mode
     │  Handler    │  NOT a rendering flag
     └─────┬──────┘
           │
     ┌─────▼──────────────┐
     │    REPL.tsx         │
     │  screen='prompt'  → compact view (default)
     │  screen='transcript'→ detailed view
     └────────────────────┘

2.2 Key Source Files

Component File Key Logic
Toggle handler src/hooks/useGlobalKeybindings.tsx:90-132 Switches screen between 'prompt' and 'transcript'
Keybinding src/keybindings/defaultBindings.ts:44 app:toggleTranscript
State definition src/state/AppStateStore.ts:472 verbose: false (session-only)
Expand hint src/components/CtrlOToExpand.tsx:29-46 Per-tool "(ctrl+o to expand)" text
Message filter src/components/Messages.tsx:93-151 filterForBriefTool() for compact view
Permission src/components/permissions/PermissionRequest.tsx Rendered in overlay layer, never hidden

2.3 Design Decisions

  1. Compact is the default. Users see a clean interface out of the box; detail is opt-in.
  2. Session-scoped. verbose resets to false on every new session — Claude Code assumes users generally prefer the compact view and only need details temporarily.
  3. Screen-level toggle. Ctrl+O doesn't change how components render; it switches the entire display between a "prompt" screen (compact) and a "transcript" screen (detailed).
  4. No frozen snapshot. There is no snapshot freezing concept. When toggling, the display updates immediately with current state.
  5. Permission dialogs are separate. Tool approvals are rendered in a dedicated overlay layer that is never affected by the verbose/compact toggle.
  6. Per-tool hint. CtrlOToExpand component shows a contextual hint on individual tools when they produce large output, suppressed in sub-agents.

2.4 User Flow

Session start → compact mode (default)
     │
     ├─ Tool outputs are summarized in a single line
     ├─ Large tool output shows "(ctrl+o to expand)" hint
     │
     ├─ User presses Ctrl+O
     │     └─→ Screen switches to transcript (detailed view)
     │         └─ User sees all tool output, thinking, etc.
     │
     ├─ User presses Ctrl+O again
     │     └─→ Screen switches back to prompt (compact)
     │
     └─ Session ends → verbose resets to false

3. Qwen Code Implementation Analysis

3.1 Architecture

Qwen Code uses a component-level rendering flag that each UI component reads from context:

┌─────────────────────────────────────┐
│      CompactModeContext             │
│  compactMode: boolean (default: false)│
│  setCompactMode: (v) => void        │
└──────────┬──────────────────────────┘
           │
     ┌─────┴──────┐
     │  Ctrl+O    │  toggles compactMode
     │  Handler    │  persists to settings
     └─────┬──────┘
           │
     ┌─────▼──────────────────┐
     │  Each component reads  │
     │  compactMode and       │
     │  decides how to render │
     └────────────────────────┘
           │
     ┌─────▼──────────────────────────────┐
     │  ToolGroupMessage                   │
     │    showCompact = compactMode        │
     │      && !hasConfirmingTool          │
     │      && !hasErrorTool               │
     │      && !isEmbeddedShellFocused     │
     │      && !isUserInitiated            │
     └────────────────────────────────────┘

3.2 Key Source Files

Component File Key Logic
Toggle handler AppContainer.tsx:1684-1690 Toggles compactMode, persists to settings
Context CompactModeContext.tsx compactMode, setCompactMode
Tool group ToolGroupMessage.tsx:105-110 showCompact with 4 force-expand conditions
Tool message ToolMessage.tsx:346-350 Hides displayRenderer in compact mode
Compact display CompactToolGroupDisplay.tsx:49-108 Single-line summary with status + hint
Confirmation ToolConfirmationMessage.tsx:113-147 Simplified 3-option compact approval
Tips Tips.tsx:14-29 Startup tip rotation includes compact mode hint
Settings sync SettingsDialog.tsx:189-193 Syncs with CompactModeContext + refreshStatic
MainContent MainContent.tsx:60-76 Renders live pendingHistoryItems
Thinking HistoryItemDisplay.tsx:123-133 Hides gemini_thought in compact mode

3.3 Design Decisions

  1. Verbose is the default. Users see all tool output and thinking by default.
  2. Persistent preference. compactMode is saved to settings.json and survives across sessions.
  3. Component-level rendering. Each component reads compactMode from context and adjusts its own rendering.
  4. Force-expand protection. Four conditions override compact mode to ensure critical UI elements are always visible (confirmations, errors, shell, user-initiated).
  5. No snapshot freezing. The toggle always shows live output — no frozen snapshots.
  6. Settings dialog sync. Toggling compact mode from Settings updates React state immediately via setCompactMode.
  7. Non-intrusive discoverability. Compact mode is introduced via the startup Tips rotation rather than a persistent footer indicator, avoiding UI clutter.

3.4 User Flow

Session start → verbose mode (default)
     │
     ├─ All tool outputs, thinking, details visible
     │
     ├─ User presses Ctrl+O (or toggles in Settings)
     │     └─→ compactMode = true, persisted
     │         ├─ Tool groups show single-line summary
     │         ├─ Thinking/thought content hidden
     │         └─ Confirmations, errors, shell still expanded
     │
     ├─ User presses Ctrl+O again
     │     └─→ compactMode = false, persisted
     │         └─ All details visible again
     │
     └─ Next session → same mode as last session

4. Key Differences Deep Dive

4.1 Default Mode Philosophy

Aspect Claude Code (compact default) Qwen Code (verbose default)
First impression Clean, minimal — professional feel Information-rich — full transparency
Learning curve User must learn Ctrl+O to see details User can immediately see everything
Target audience Experienced users who trust the tool Users who want to understand what's happening
Information overload Avoided by default Possible for new users
Discoverability Per-tool "(ctrl+o to expand)" hints Startup Tips rotation + ? shortcuts + /help

Analysis: Claude Code's compact default works because its user base is generally experienced developers who trust the tool and don't need to see every tool invocation. Qwen Code's verbose default is appropriate for its earlier stage where building user trust through transparency is important.

4.2 Persistence Model

Aspect Claude Code Qwen Code
Persisted? No — session-only Yes — to settings.json
Rationale Verbose is temporary peek Mode is user preference
Restart behavior Always starts compact Starts with last-used mode

Analysis: Claude Code treats detail viewing as a momentary need — you look, then go back. Qwen Code treats it as a stable preference — some users always want details, others always want compact. Both are valid; Qwen Code's approach is more flexible.

4.3 Confirmation Protection

Aspect Claude Code Qwen Code
Mechanism Overlay/modal layer (structurally separate) Force-expand conditions in showCompact
Coverage Complete — approvals can never be hidden Complete — 4 conditions cover all interactive states
Compact confirmation UI N/A (overlay is always full) Simplified 3-option RadioButtonSelect

Analysis: Claude Code's architectural separation (overlay layer) is more robust. Qwen Code's force-expand approach is effective but requires each new interactive state to be explicitly added to the condition list.

4.4 Rendering Approach

Aspect Claude Code Qwen Code
Toggle scope Screen-level (prompt ↔ transcript) Component-level (each component decides)
Granularity All-or-nothing Fine-grained per component
Flexibility Low — global switch High — components can override
Consistency Guaranteed Depends on each component's implementation

Analysis: Qwen Code's component-level approach is more flexible (e.g., force-expand for specific conditions) but requires more discipline to maintain consistency. Claude Code's screen-level approach is simpler and guarantees consistent behavior.

5. Optimization Recommendations

5.1 [P0] Keep Verbose as Default — No Change Needed

Qwen Code's verbose default is the right choice for its current stage. Users who are new to the tool need transparency to build trust. As the product matures, consider making compact the default (like Claude Code).

5.2 [P1] Per-Tool Expansion for Large Outputs

Claude Code shows "(ctrl+o to expand)" on individual tools that produce large output. Qwen Code currently only has a global toggle. Consider:

  • When a single tool produces output exceeding N lines, show a per-tool "expand" hint in compact mode.
  • Scope: future enhancement, not current priority.

5.3 [P2] Consider Session-Scoped Override

Some users may want compact mode as their default but occasionally need verbose for a specific session. Consider supporting both:

  • settings.json → persistent default (current behavior)
  • Ctrl+O during session → temporary override for current session only (Claude Code behavior)
  • On session restart → revert to settings.json value

This gives users the best of both worlds. Implementation would require separating "settings default" from "session override" state.

5.4 [P2] Structural Separation for Confirmations

Currently, confirmation protection relies on showCompact conditions in ToolGroupMessage. Consider a more robust approach:

  • Render confirmations in a separate layer (like Claude Code's overlay approach).
  • This would make it architecturally impossible for compact mode to affect confirmations.
  • Lower priority since the current force-expand approach works correctly.

6. Current Implementation Status

After the feat/compact-mode-optimization branch changes:

Feature Status Notes
Startup Tips hint Done Compact mode tip in Tips rotation (non-intrusive)
Ctrl+O in keyboard shortcuts (?) Done Added to KeyboardShortcuts component
Ctrl+O in /help Done Added to Help component
Settings dialog sync Done Syncs compactMode with CompactModeContext
No snapshot freezing Done Toggle always shows live output
Confirmation protection Done Force-expand + WaitingForConfirmation guard
Shell protection Done !isEmbeddedShellFocused force-expand
Error protection Done !hasErrorTool force-expand
User docs updated Done settings.md, keyboard-shortcuts.md

7. File Reference

Qwen Code

File Purpose
packages/cli/src/ui/AppContainer.tsx Toggle handler, state initialization, context provider
packages/cli/src/ui/contexts/CompactModeContext.tsx Context definition
packages/cli/src/ui/components/messages/ToolGroupMessage.tsx Force-expand logic
packages/cli/src/ui/components/messages/ToolMessage.tsx Per-tool output hiding
packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx Compact view rendering
packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx Compact confirmation UI
packages/cli/src/ui/components/MainContent.tsx Pending history items rendering
packages/cli/src/ui/components/Tips.tsx Startup tip with compact mode hint
packages/cli/src/ui/components/Help.tsx /help shortcut entry
packages/cli/src/ui/components/KeyboardShortcuts.tsx ? shortcut entry
packages/cli/src/ui/components/SettingsDialog.tsx Settings sync
packages/cli/src/ui/components/HistoryItemDisplay.tsx Thinking content hiding
packages/cli/src/config/settingsSchema.ts Setting definition
packages/cli/src/config/keyBindings.ts Ctrl+O binding

Claude Code (Reference)

File Purpose
src/hooks/useGlobalKeybindings.tsx Toggle handler
src/state/AppStateStore.ts State definition (verbose: false)
src/components/CtrlOToExpand.tsx Per-tool expand hint
src/components/Messages.tsx Brief message filter
src/screens/REPL.tsx Screen-level mode switching
src/components/permissions/PermissionRequest.tsx Overlay-based confirmation