feat(cli): enable built-in status line preset by default for new users#5792
feat(cli): enable built-in status line preset by default for new users#5792pomelo-nwu wants to merge 5 commits into
Conversation
The built-in default preset (model, git branch, context usage, current dir) already existed but was only activated when users explicitly ran /statusline. New users had no status line at all until they discovered the slash command. Return DEFAULT_STATUS_LINE_PRESET_CONFIG when ui.statusLine is unset so useful context is visible out-of-the-box. An explicit ui.statusLine: null opts out entirely for users who prefer a minimal footer. Closes #5789
|
Thanks for the PR! Template looks good ✓ — all required sections present, bilingual, before/after evidence included. On direction: this is a clean discoverability fix that directly addresses issue #5789. The status line shows genuinely useful always-on information (model, branch, context usage) that other agent CLIs surface by default. Hiding it behind a slash command new users have no reason to discover is a real gap. The feature already exists — this just turns it on. Well within qwen-code's core mission of terminal UX. Roadmap-aligned ( On approach: the scope is tight and minimal — +34/-14 across 4 files, all directly serving the stated goal. The Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ — 所有必填部分齐全,双语,包含前后对比证据。 方向:这是一个干净的可发现性改进,直接解决 issue #5789。Status line 展示始终有用的高价值信息(模型、分支、上下文用量),其他 agent CLI 默认都会展示。把它藏在新用户没有理由发现的 slash command 后面是真正的差距。功能已经存在——本 PR 只是把它打开。完全在 qwen-code 的核心使命范围内,与 方案:范围紧凑且最小化——4 个文件 +34/-14,所有改动都直接服务于目标。用 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal (before reading diff): To enable the status line by default, I'd modify Comparison with PR: The PR's approach matches this exactly. The diff is surgical — adds a No correctness bugs, no security concerns, no regressions. The Reuse check: The PR correctly reuses the existing Test ResultsUnit tests (with PR diff applied): All 85 tests across the two affected files pass. Direct logic verification (Node.js, importing from source): The PR's new early-return for Real-Scenario Testing (tmux)Ink's alternate-screen-buffer rendering on headless Linux doesn't produce capturable footer content via Before (main branch, no PR)No status line visible in footer. (Status line row not capturable in this environment.) After (PR #5792 applied)Same capture — status line row below capturable viewport in headless tmux. The unit tests (68/68) and the author's macOS visual evidence confirm the rendering works correctly on a real terminal. 中文说明代码审查独立方案(读 diff 前):为默认启用 status line,我会在 与 PR 对比:PR 方案完全匹配。Diff 精准——在 无正确性 bug,无安全问题,无回归。 复用检查:PR 正确复用了已有的 测试结果单元测试(应用 PR diff 后):useStatusLine 68/68 通过,Footer 17/17 通过,共 85 个测试全部通过。 直接逻辑验证: 真实场景测试(tmux)Headless Linux 环境下 Ink 的备用屏幕缓冲区渲染无法通过 — Qwen Code · qwen3.7-max |
|
This is a textbook example of a well-scoped PR. The problem is real (status line hidden behind an undiscoverable slash command), the solution is minimal (two early returns, no new abstractions), and the tests are thorough (68 hook tests + 17 footer tests, all passing). The PR matches my independent proposal exactly. The The one thing I couldn't fully verify in tmux was the visual footer rendering — Ink's alternate-screen-buffer output isn't capturable in this headless Linux environment. But the logic is well-covered by unit tests, the direct Node.js verification confirms the preset config shape and rendering pipeline, and the author's macOS screenshots show the expected output. I'm confident this works. Approving. ✅ 中文说明这是一个教科书级别的良好范围 PR。问题是真实的(status line 隐藏在一个无法发现的 slash command 后面),方案是最小化的(两个提前返回,没有新抽象),测试是充分的(68 个 hook 测试 + 17 个 footer 测试,全部通过)。 PR 完全匹配我的独立方案。 唯一无法在 tmux 中完全验证的是 footer 视觉渲染——在此 headless Linux 环境下 Ink 的备用屏幕缓冲区输出无法捕获。但逻辑被单元测试充分覆盖,直接 Node.js 验证确认了预设配置结构和渲染管道,作者的 macOS 截图展示了预期输出。我有信心这是正确的。 批准 ✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
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. |
| }, | ||
| "statusLine": { | ||
| "description": "Status line display configuration. Use `type: \"preset\"` with built-in item ids, or `type: \"command\"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh. Set `respectUserColors: true` to preserve ANSI color codes in command output instead of applying dim/theme styling. Set `hideContextIndicator: true` to hide the built-in context usage indicator in the footer right section.", | ||
| "description": "Status line display configuration. Use `type: \"preset\"` with built-in item ids, or `type: \"command\"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh. Set `respectUserColors: true` to preserve ANSI color codes in command output instead of applying dim/theme styling. Set `hideContextIndicator: true` to hide the built-in context usage indicator in the footer right section. When unset (default), the built-in default preset (model, git branch, context usage, current dir) is shown automatically; set to `null` to explicitly disable the status line.", |
There was a problem hiding this comment.
[Critical] This description now tells users "set to null to explicitly disable the status line", but the sibling "type": "object" declaration on the line directly below rejects null — JSON Schema treats null as its own type, not a subtype of object. Anyone following the documented opt-out will see a VSCode validation squiggle ("Incorrect type. Expected 'object'"). The most visible user-facing regression of this PR.
Please also change the sibling key from "type": "object" to:
"type": ["object", "null"],— qwen3.7-max via Qwen Code /review
| | undefined, | ||
| description: | ||
| 'Status line display configuration. Use `type: "preset"` with built-in item ids, or `type: "command"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh. Set `respectUserColors: true` to preserve ANSI color codes in command output instead of applying dim/theme styling. Set `hideContextIndicator: true` to hide the built-in context usage indicator in the footer right section.', | ||
| 'Status line display configuration. Use `type: "preset"` with built-in item ids, or `type: "command"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh. Set `respectUserColors: true` to preserve ANSI color codes in command output instead of applying dim/theme styling. Set `hideContextIndicator: true` to hide the built-in context usage indicator in the footer right section. When unset (default), the built-in default preset (model, git branch, context usage, current dir) is shown automatically; set to `null` to explicitly disable the status line.', |
There was a problem hiding this comment.
[Suggestion] The updated description here documents null as the explicit opt-out, but the default: type annotation above (around line 703) is still CommandConfig | PresetConfig | undefined — null is not in the union. InferSettings derives Settings['ui']['statusLine'] from this field, so the type system says null can never appear, making raw === null in useStatusLine.ts:125 an unreachable narrowing from TypeScript's point of view. Future consumers of settings.merged.ui?.statusLine won't be prompted to handle the null case, and a cleanup pass could legitimately delete the "dead" guard.
Please widen the default: cast to include null:
default: undefined as
| (
| { type: 'command'; command: string; /* ... */ }
| { type: 'preset'; items: string[]; /* ... */ }
| null
)
| undefined,— qwen3.7-max via Qwen Code /review
Drop redundant context-remaining (complementary to context-used), swap full-path current-dir for project-name, strip provider prefix from model display name, wrap git branch in git:() notation, and use middle dot as separator for cleaner visual rhythm. Before: [ModelStudio Standard] glm-5.2 | main | Context 91.6% left | /Users/pomelo/Desktop/workspace/qwen-code | Context 8.4% used After: glm-5.2 · git:(main) · qwen-code · Context 8.4% used
Reorder preset priority so project-name leads, followed by git-branch (joined with a space, no separator), then model and context. Add ➜ arrow prefix to project-name so it reads as a directory indicator. Before: qwen3.6-plus · git:(main) · qwen-code · Context 8.4% used After: ➜ qwen-code git:(main) · qwen3.6-plus · Context 8.4% used Also refactors buildStatusLinePresetParts to share a formatPresetItem helper with buildStatusLinePresetLines, enabling context-aware separators (space vs middle dot) between adjacent items.
- Reduce ➜ (two spaces) to ➜ (one space) before project name - Use · separator between all items uniformly, including between project-name and git-branch Output: ➜ qwen-code · git:(main) · qwen3.6-plus · Context 8.4% used
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Additional findings (not mappable to specific diff lines):
-
[Suggestion]
DEFAULT_STATUS_LINE_PRESET_CONFIGincludescontext-usedbut doesn't sethideContextIndicator: true, causing context usage to appear in both the status line and the footer right section for new users. Consider addinghideContextIndicator: trueto the default config. -
[Suggestion] The
statusline-setupbuilt-in subagent prompt (packages/core/src/subagents/builtin-agents.ts) only documentstype: "command"configuration. It doesn't mentionnullas the opt-out or that removingui.statusLinenow enables the default preset. A user asking to "disable" the status line would get the opposite result. -
[Suggestion] Command-mode passes
model.display_namewith the raw provider prefix (e.g.,[ModelStudio Standard] glm-5.2), while preset mode strips it viastripProviderPrefix. This inconsistency means the same model appears differently depending on the mode.
— qwen3.7-max via Qwen Code /review
| | undefined, | ||
| description: | ||
| 'Status line display configuration. Use `type: "preset"` with built-in item ids, or `type: "command"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh. Set `respectUserColors: true` to preserve ANSI color codes in command output instead of applying dim/theme styling. Set `hideContextIndicator: true` to hide the built-in context usage indicator in the footer right section.', | ||
| 'Status line display configuration. Use `type: "preset"` with built-in item ids, or `type: "command"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh. Set `respectUserColors: true` to preserve ANSI color codes in command output instead of applying dim/theme styling. Set `hideContextIndicator: true` to hide the built-in context usage indicator in the footer right section. When unset (default), the built-in default preset (model, git branch, context usage, current dir) is shown automatically; set to `null` to explicitly disable the status line.', |
There was a problem hiding this comment.
[Suggestion] The description says the default preset shows "(model, git branch, context usage, current dir)" but the actual defaultSelected items are model-with-reasoning, git-branch, project-name, context-used. Specifically, "current dir" is wrong — current-dir had defaultSelected removed and was replaced by project-name.
| 'Status line display configuration. Use `type: "preset"` with built-in item ids, or `type: "command"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh. Set `respectUserColors: true` to preserve ANSI color codes in command output instead of applying dim/theme styling. Set `hideContextIndicator: true` to hide the built-in context usage indicator in the footer right section. When unset (default), the built-in default preset (model, git branch, context usage, current dir) is shown automatically; set to `null` to explicitly disable the status line.', | |
| 'Status line display configuration. Use `type: "preset"` with built-in item ids, or `type: "command"` with a shell command. Optional command `refreshInterval` (seconds, >= 1) re-runs the command on a timer so external data stays fresh. Set `respectUserColors: true` to preserve ANSI color codes in command output instead of applying dim/theme styling. Set `hideContextIndicator: true` to hide the built-in context usage indicator in the footer right section. When unset (default), the built-in default preset (model, git branch, project name, context usage) is shown automatically; set to `null` to explicitly disable the status line.', |
Apply the same fix in settings.schema.json.
— qwen3.7-max via Qwen Code /review
|
|
||
| it('ignores a stale preset override when settings no longer have status line config', () => { | ||
| setStatusLineConfig(undefined); | ||
| it('ignores a stale preset override when status line is disabled (null)', () => { |
There was a problem hiding this comment.
[Critical] This test was changed from setStatusLineConfig(undefined) to setStatusLineConfig(null), but no test covers the undefined + statusLineConfigOverride interaction. With the new behavior, getStatusLineConfig(undefined) returns DEFAULT_STATUS_LINE_PRESET_CONFIG (type 'preset'), so a stale preset override from UI state would silently replace the default preset for new users.
Add a test that sets setStatusLineConfig(undefined) with mockUIState.statusLineConfigOverride = { type: 'preset', items: ['model'] } and asserts the correct behavior (either the default preset items render or the override is applied — this should be explicitly decided).
— qwen3.7-max via Qwen Code /review
| // `null` explicitly disables the status line; `undefined` (unset) falls | ||
| // through to the built-in default preset below so new users get useful | ||
| // context out-of-the-box (issue #5789). | ||
| if (raw === null) { |
There was a problem hiding this comment.
[Critical] The null opt-out is not fully propagated. StatusLineDialog.tsx calls normalizeStatusLinePresetConfig(null) which returns undefined, so the dialog pre-selects default items and Save writes a preset config that silently re-enables the status line. Additionally, getEffectiveStatusLineScope uses a truthy check on statusLine, so null (explicitly disabled) is treated the same as undefined — the dialog can't detect which scope holds the opt-out and writes to the wrong scope.
The /statusline dialog needs to: (1) recognize null as a disabled state in buildInitialSelectedKeys, and (2) use key-existence checks (not truthiness) in getEffectiveStatusLineScope.
— qwen3.7-max via Qwen Code /review
| } | ||
|
|
||
| function stripProviderPrefix(name: string): string { | ||
| return name.replace(/^\[[^\]]*\]\s*/, ''); |
There was a problem hiding this comment.
[Suggestion] For input like '[Provider X]' (bracket group with no trailing text), this regex produces an empty string. The empty value is then pushed to parts in buildStatusLinePresetParts, producing a leading separator in the joined output (e.g., ' · git:(main) · ...'). Other items guard with if (data.X) before pushing; the model cases do not.
| return name.replace(/^\[[^\]]*\]\s*/, ''); | |
| return name.replace(/^\[[^\]]*\]\s*/, '') || name; |
— qwen3.7-max via Qwen Code /review
| it('renders the default preset when no statusLine config is set', () => { | ||
| const { result } = renderHook(() => useStatusLine()); | ||
| expect(child_process.exec).not.toHaveBeenCalled(); | ||
| expect(result.current.lines).toEqual([ |
There was a problem hiding this comment.
[Suggestion] This test only covers the happy path with branch: 'main' and a plain model name. Two scenarios specific to the new default preset are untested:
branch: undefined— verifiesgit-branchis omitted without a dangling separator- Provider-prefixed model name (e.g.,
'[ModelStudio] Test Model') — verifiesstripProviderPrefixintegration with the default preset end-to-end
— qwen3.7-max via Qwen Code /review
Prepend the context window capacity (e.g. 131.1k, 1.0m) before the usage percentage so users see both total capacity and how much they've burned at a glance. Before: Context 8.4% used After: 1.0m Context 8.4% used

What this PR does
Enables the built-in status line preset automatically for new users who haven't configured
ui.statusLine. Previously, the status line was completely invisible until a user discovered and ran/statusline— a slash command they had no reason to know existed. Now the default preset (model name, git branch, context usage, working directory) renders out-of-the-box, giving new users immediate context about their session.The built-in
DEFAULT_STATUS_LINE_PRESET_CONFIGalready existed in the codebase — it was used as the fallback when a user opened the/statuslinedialog. This PR simply applies that same default in the rendering hook whenui.statusLineis unset, so the dialog's "starting point" matches what users actually see on first launch.Users who have already configured
/statuslineor setui.statusLineare not affected — their existing config takes precedence. A new explicit opt-out is also added: settingui.statusLine: nullin settings.json disables the status line entirely, for users who prefer a minimal footer. This distinction is necessary because "unset" (undefined) and "intentionally off" used to be indistinguishable; nowundefinedmeans "use default" andnullmeans "turn off".Why it's needed
The status line shows high-value, always-relevant information — which model am I on, how much context have I used, which branch am I on. Hiding this behind a slash command that new users have no reason to discover is a missed opportunity at the moment it matters most: the first session. The default preset is computed fully in-memory (no subprocess cost), so enabling it adds immediate value with negligible overhead. The core problem was discoverability, not capability — the feature already existed, it just wasn't turned on.
Closes #5789.
Reviewer Test Plan
How to verify
ui.statusLinefrom your settings.json (or use a freshQWEN_HOME).qwenin interactive mode./statusline./statusline, customize items, save — confirm your custom config persists and overrides the default."ui": { "statusLine": null }in settings.json — confirm the status line disappears (explicit opt-out).Evidence (Before & After)
BEFORE — unconfigured user (main branch): no status line in the footer.
AFTER — unconfigured user (this PR): default preset shown out-of-the-box.
Visual screenshots from tmux captures are attached as a separate comment below.
Tested on
Environment
npm run dev(tsx from worktree source). Unit tests:cd packages/cli && npx vitest run src/ui/hooks/useStatusLine.test.ts— 68 passed.Risk & Scope
nullopt-out provides a clean escape hatch, but it requires knowing about the setting. This is acceptable because the default preset is unobtrusive (1–2 footer rows) and shows genuinely useful information.ui.statusLineconfigs are untouched. The only behavioral change is for the "unset" case, which transitions from "nothing shown" to "default preset shown" — this is the intended improvement.Linked Issues
Closes #5789
中文说明
本 PR 做了什么
为未配置
ui.statusLine的新用户自动启用内置 status line 预设。此前,status line 在用户发现并运行/statusline之前完全不可见——一个新用户没有理由知道存在的 slash 命令。现在默认预设(模型名称、git 分支、上下文用量、工作目录)开箱即渲染,让新用户立即获得会话上下文。内置的
DEFAULT_STATUS_LINE_PRESET_CONFIG已存在于代码库中——当用户打开/statusline对话框时作为回退使用。本 PR 仅在渲染 hook 中,当ui.statusLine未设置时应用相同的默认值,使对话框的"起点"与用户首次启动时实际看到的一致。已配置
/statusline或设置了ui.statusLine的用户不受影响——现有配置优先。同时新增显式退出:在 settings.json 中设置ui.statusLine: null可完全禁用 status line,适合偏好极简 footer 的用户。这一区分是必要的,因为"未设置"(undefined) 和"主动关闭"此前无法区分;现在undefined表示"用默认",null表示"关闭"。为什么需要
Status line 展示高价值、始终相关的信息——当前用哪个模型、上下文用了多少、在哪个分支。把这些藏在新用户没有理由发现的一个 slash 命令后面,在最需要的时候(第一次会话)错失了展示机会。默认预设完全在内存中计算(无子进程开销),开启它能以极低成本带来即时价值。核心问题是可发现性,不是能力——功能已经存在,只是没打开。
关闭 #5789。
风险与范围
null退出提供了干净的逃逸阀,但需要知道这个设置。这是可接受的,因为默认预设不突兀(footer 1-2 行)且展示真正有用的信息。ui.statusLine配置不受影响。唯一的行为变更针对"未设置"的情况,从"不显示"变为"显示默认预设"——这是预期的改进。