feat(ui): add customizable status line with /statusline command#2923
feat(ui): add customizable status line with /statusline command#2923
Conversation
Allow users to configure a custom shell command to display in the UI footer status line.
📋 Review SummaryThis PR introduces a customizable status line feature that allows users to display custom information in the UI footer by executing a read-only shell command periodically. The implementation includes a new React hook ( 🔍 General Feedback
🎯 Specific Feedback🔴 Critical
🟡 High
🟢 Medium
🔵 Low
✅ Highlights
|
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
Rewrite the status line feature (originally by Gemini 3.1 Pro) to align
with the upstream design:
- Settings: change from plain string to object `{ type, command, padding? }`
- Hook: event-driven with 300ms debounce instead of 5s polling; pass
structured JSON context (session, model, tokens, vim) via stdin;
generation counter to ignore stale exec callbacks; EPIPE guard on stdin
- Footer: render status line as dedicated row with dimColor + truncate;
suppress "? for shortcuts" hint when status line is active
- Add `/statusline` slash command that delegates to a statusline-setup agent
- Add `statusline-setup` built-in agent with PS1 conversion instructions
- Remove unrelated changes (whitespace, formatting, package-lock, test file)
- Fix copyright headers (Google LLC → Qwen)
- Fix config path references (~/.qwen-code → ~/.qwen)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a user-configurable “status line” in the CLI footer by executing a configured shell command and displaying its output, plus a /statusline helper command backed by a new built-in subagent to guide setup.
Changes:
- Extend settings schemas to support
ui.statusLine(type/command/padding). - Add a CLI
useStatusLinehook and render an extra footer row when configured. - Add
/statuslineslash command and a new built-instatusline-setupsubagent (and update subagent tests).
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/vscode-ide-companion/schemas/settings.schema.json | Adds JSON schema for ui.statusLine configuration. |
| packages/cli/src/config/settingsSchema.ts | Registers ui.statusLine in the CLI settings schema metadata. |
| packages/cli/src/ui/hooks/useStatusLine.ts | New hook that runs the configured command (stdin JSON) and returns display text/padding. |
| packages/cli/src/ui/components/Footer.tsx | Renders an additional footer row for the custom status line; integrates the new hook. |
| packages/cli/src/ui/commands/statuslineCommand.ts | New /statusline command that prompts creation of a statusline-setup subagent. |
| packages/cli/src/services/BuiltinCommandLoader.ts | Registers the new built-in /statusline command. |
| packages/core/src/subagents/builtin-agents.ts | Adds built-in statusline-setup subagent with instructions for configuring the setting. |
| packages/core/src/subagents/subagent-manager.test.ts | Updates expectations to include the new built-in subagent. |
| packages/cli/src/ui/utils/TableRenderer.tsx | Formatting-only change. |
| packages/cli/src/ui/utils/MarkdownDisplay.tsx | Formatting-only change. |
| packages/cli/src/commands/channel/pidfile.test.ts | Whitespace cleanup in tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The hand-edited schema didn't match the auto-generated output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
useStatusLine hook requires SettingsContext, which was missing from the test render wrapper, causing all Footer tests to crash. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Validate padding is finite number >= 0 instead of blind cast - Initialize prevStateRef with current values to prevent double exec on mount - Kill previous child process before starting new one; kill on unmount - Fix agent prompt: settings path is ui.statusLine, not root-level - Fix agent prompt: remove multi-cat examples, stdin can only be read once - Update Footer left-section comment to reflect new behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
When vim mode is toggled off, vimMode stays the same but the status line should stop including vim data. Use effectiveVim (undefined when disabled) as the tracked value instead of raw vimMode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Set cwd to config.getTargetDir() so commands like pwd/git run in the correct workspace directory - Strip only trailing newline instead of trim() to preserve intentional leading/trailing whitespace in command output - Match footer's marginLeft/marginRight on the status line row so it aligns with the rest of the footer content Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
useStatusLine hook accesses sessionStats.metrics.tools.totalCalls, sessionStats.metrics.files.totalLinesAdded, and currentModel which were missing from the mock UIState, causing a TypeError crash during render and making 4 Footer tests fail in CI.
- useStatusLine: clamp used/remaining percentage to [0,100], track totalLinesRemoved as trigger, clean up debounceRef on unmount - AppContainer: use drainQueue from useMessageQueue instead of manual messageQueueRef to avoid stale-ref reads between renders - builtin-agents: add WRITE_FILE tool to statusline-setup agent, improve PS1 parsing instructions (unquoted assignments, \[/\]/\e escapes), strip ANSI colors, remove unreachable symlink instruction - CompactToolGroupDisplay: fix misleading hint "show full tool output" to "toggle verbose mode" across all 6 locales - AppContainer.test: add missing drainQueue mock
…pty commands - Compute remaining_percentage as round(100 - used) to guarantee used + remaining always sums to exactly 100.0 - Reject empty or whitespace-only command strings in config validation
…git locks - Instruct agent to use "bash script.sh" pattern instead of direct execution (agent cannot chmod +x without SHELL tool) - Replace vague "skip optional locks" with concrete GIT_OPTIONAL_LOCKS=0 - Simplify "parent agent" framing to direct user-facing message
BZ-D
left a comment
There was a problem hiding this comment.
Overall a solid feature addition. The hook design with generation-based stale callback rejection and debounced event-driven updates is well thought out. A few issues below — mostly around edge cases in the percentage calculation, missing schema validation, and an i18n gap.
- Log non-EPIPE stdin errors at debug level instead of silently swallowing them - Add proper JSON schema properties for statusLine (type, command, padding) with enum, required, and additionalProperties constraints - Add missing i18n entry for /statusline command description
Move status line from a dedicated row below the footer into the footer's left section, replacing "? for shortcuts" when active. High-priority messages (Ctrl+C/D, Esc, vim INSERT, shell mode) still override the status line. Move approval mode indicator to a separate row below the footer, shown only when mode is non-default. This eliminates the empty gap in default mode and matches upstream layout.
|
Implemented in |
The status line is now inlined in the footer's left section, so horizontal padding is no longer applicable. Remove padding from StatusLineConfig, settings schema, JSON schema, and docs.
Add flexGrow/flexShrink to left section so it takes available space
but yields to right items. Add flexShrink={0} to right section so
context usage, verbose, sandbox indicators are never compressed.
Add overflow="hidden" to left section for clean truncation.
Allow status line text to wrap to the next line when the terminal is too narrow, preserving complete information instead of truncating.
Restructure footer to match Claude Code's layout: - Left column: status line (top, truncated) + hints/mode (bottom) - Both rows coexist instead of being mutually exclusive - Status line uses wrap="truncate" to guarantee single line - Approval mode returns to the hints row (inline, not separate row) - Left column uses flexShrink for narrow terminals
Wrap leftBottomContent in Text with wrap="truncate" so the hints/mode row stays on a single line, matching upstream behavior. This guarantees the footer is at most 2 rows (status line + hints).
AutoAcceptIndicator and ShellModeIndicator returned Box>Text, which prevented the parent Text wrap="truncate" from working (ink cannot nest Box inside Text). Change both to return plain Text elements so the footer's truncation applies correctly on narrow terminals.
- Hide "? for shortcuts" when a custom status line is configured (status line already occupies the top row, hint is redundant) - Hide status line during Ctrl+C/D exit prompts to keep footer at one row during exit flow - Matches upstream Claude Code suppressHint + exitMessage behavior
- Update architecture diagram to show status line in footer left section instead of separate row below - Document 1-row (default mode) and 2-row (non-default mode) layouts - Note suppressHint behavior and truncation - Update settings reference description
|
Lack of unit tests of statuslineCommand.ts and useStatusLine.ts. Need to supplement tests for them, which should cover:
For statuslineCommand, should cover the metadata test, no param default hint test, empty default hint test, custom params pass test etc. If you have any other test ideas, just supplement to your unit test cases. |
packages/cli/src/ui/components/messages/CompactToolGroupDisplay.tsx
Outdated
Show resolved
Hide resolved
Restore "Press Ctrl+O to show full tool output" which is clearer than "toggle verbose mode" per PR review feedback.
The previous commit reverted the code to use the original i18n key but the locale files still had the renamed key, causing translations to not resolve.
|
I'll do e2e tests manually, please supplement necessary unit tests for useStatsusLine.ts and statuslineCommand.ts |
…mmand Cover config validation, command execution with exec options, stdin JSON payload, stale generation rejection, debouncing, config removal, cleanup on unmount, EPIPE handling, command hot-reload, all state change triggers (token count, model, branch, vim toggle, file lines), and process management.
|
LGTM. After comprehensive conversations and testing, I think the PR can be merged. Thank you for contribution. |













1. Summary
Allow users to configure a custom shell command to display context-aware information in the status line area beneath the footer.
1.1 Architecture
1.2 How it works
1.3 Configuration
The command receives structured JSON via stdin:
{ "session_id": "abc-123", "cwd": "/home/user/project", "model": { "id": "qwen-3-235b" }, "context_window": { "context_window_size": 131072, "last_prompt_token_count": 45000 }, "vim": { "mode": "INSERT" } }1.4 /statusline command
Users can run /statusline to launch a setup agent that reads their shell PS1 and auto-generates a statusLine config.
2. Changes
ui({ type, command, padding? })3. Test plan
3.1 Quick start
3.2 Manual test checklist
ui.statusLineconfig above, launch CLIcat | jq -r '.cwd'/model, select different modelsleep 10 && echo done/statusline/statuslinein CLIui.statusLinefrom settings, restartui.statusLine3.3 Unit tests
4. Comparison with Claude Code upstream
This implementation follows the upstream Claude Code status line design, adapted for qwen-code's architecture. Key differences:
4.1 Intentional simplifications
cost,rate_limits,workspace.added_dirs,worktree,remote,output_style,version— these can be added incrementally as the corresponding systems are ported<Ansi>component for colored output; we usedimColorfor nowstatusLineTextin global AppState for cross-component access; we keep it local to the hook since only Footer consumes it4.2 Potential follow-ups
workspace,cost,versionfields to stdin JSON<Ansi>component for colored status line outputuseStatusLinehookstatuslineCommand