fix(cli): prevent dialog clipping in short terminals - #9040
Conversation
|
Thanks for the PR — this is a re-run on the current head ( Template looks good ✓ — all required sections present, with a real reviewer test plan and a full Chinese translation. Problem: observed, not theoretical. #9037 is still open and carries the reporter's screenshots of the clipped Direction: aligned. A dialog that paints rows over each other and leaves options unreachable by keyboard is a straightforward defect — there is no product-direction question here, and nothing about it pulls focus. Upstream CHANGELOG: no entry for dialog height budgets or short-terminal fitting; the status-line area is actively worked upstream, but nothing there is a signal either way on this change. Size: Stage 0 does not apply. Nothing under Approach: the Risk: no elevated risk signals — none of the changed files match the revert-correlated path list, so no extra review depth is prescribed on that basis. One thing to keep in view anyway: the macOS and Windows unit legs are skipped on fork PRs, so a change whose entire subject is terminal geometry is only proven on Linux by CI. Moving on to code review. 🔍 中文说明感谢贡献!这是在当前 head( 模板完整 ✓ —— 所有必需章节齐全,包含真实的 reviewer test plan 和完整中文翻译。 问题: 是已观测到的 bug,不是理论性加固。#9037 仍处于 open 状态,并附有报告者提供的 方向: 对齐。对话框自我覆写、选项无法用键盘访问,是一个明确的缺陷——这里不存在产品方向争议,也不会分散重点。上游 CHANGELOG 中没有关于对话框高度预算或短终端适配的条目;status line 区域上游一直在活跃改动,但对本变更不构成任何方向的信号。 规模: Stage 0 不适用。没有触及 方案: 风险: 无升级风险信号——改动文件均未命中与 revert 相关的路径清单,因此不需要基于该信号加深审查。但有一点值得留意:fork PR 的 macOS 与 Windows 单元测试任务会被跳过,所以一个主题完全是终端几何的改动,CI 只在 Linux 上得到了验证。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewMy independent proposal first — written from the title and the "Why it's needed" section, before reading the diff: count the dialog's fixed chrome rows, hand the interactive list One regression-class finding, still open at this head@wenshao's runtime report of 2026-08-29 (run at
Same root cause, one hundred lines down. What this costs is informational rather than data loss: after this PR there is no in-product way to learn which settings scope is holding a skill down. Relatedly, the The fix is small, and the value it needs is already computed: Two things I checked that turned out fine
The Not repeating the review laneThe Not verified: that the row arithmetic still holds at this head. @wenshao's A/B ran at Sandboxed verification would settle exactly that: Files changed (19 of 19 shown)
CI evidenceAll 43 check-runs on
Two of those greens matter for this diff specifically: 中文说明代码审查先说我自己的独立方案(只看标题和「为什么需要它」,未看 diff 时写下的):统计对话框固定界面行数,把 一条回归级发现,在当前 head 上仍然存在@wenshao 在 2026-08-29 的运行时报告(跑在
同一根因,往下约一百行。 代价是信息层面的,不是数据丢失:本 PR 之后,产品内再也没有办法得知是哪个 settings scope 锁住了某个技能。相关地,本 PR 从九个语言文件中删除的 修复很小,而且所需的值已经算好了: 我核查过、确认没问题的两点共享
不重复审查通道已有的内容
未验证: 行数算术在当前 head 上是否仍然成立。@wenshao 的 A/B 跑在 沙箱验证正好能解决这一点: CI 证据
— Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 3/5 — the fix is real and was independently proven end-to-end, but a behaviour regression that a maintainer called a pre-merge condition is still open at this head, and whether to land it anyway is his call rather than mine. Stepping back, this PR does what it says it does. @wenshao's A/B harness — one worktree, one My independent proposal and the PR agree on nearly everything, which is normally a good sign. The one place they diverge is the locked-skills collapse, and it diverges in the direction that loses information: gating on "a budget exists" instead of "the budget is too small" makes the read-only locked section dead code in the shipped UI at every height, including a 40-row terminal under no pressure at all, and leaves So I am not approving, and I am not requesting changes either. Not approving, because a regression the maintainer explicitly conditioned merge on is unresolved at the commit I reviewed, and approving would attest to a state that does not exist. Not requesting changes, because the PR already carries a standing One thing I want to flag plainly, because it is exactly what gets lost at round 16: the review lane deferred this same finding under its convergence posture — "recorded, not requested in this round". Convergence pressure is the right instinct for missing test witnesses and doc wording, and this repo's own rule says to stop landing Suggestions after roughly five rounds. But that rule's carve-out is "correctness, security, data loss, regressions", and this is a regression. Deferring it because the round count got high is the failure mode the rule exists to prevent. If the answer is "yes, we accept the trade, ship it and restore the listing later", that is a perfectly legitimate answer — it just should be reached deliberately rather than by attrition. Worth saying out loud as well: sixteen rounds on a dialog-height fix is a lot, and the second half of this PR — the Deferring to the maintainer; the reason and the specific ask are in the comment below. 中文说明Confidence: 3/5 —— 修复是真实的,并且已被独立地端到端证明有效;但一条被 maintainer 列为合并前条件的行为回归在当前 head 上仍未解决,而「是否照样合并」应由他决定,不该由我决定。 退一步看,这个 PR 确实做到了它声称的事。@wenshao 的 A/B 装置——同一个 worktree、同一份 我的独立方案与本 PR 在几乎所有点上一致,这通常是好迹象。唯一的分歧是锁定技能区块的折叠,而分歧方向是丢信息的一侧:以「存在预算」而非「预算太小」作为门控,使得只读的锁定技能区块在实际发布的 UI 中于任何高度都成为死代码,包括毫无压力的 40 行终端;同时 所以我不批准,也不请求变更。不批准,是因为在我审查的这个 commit 上,一条 maintainer 明确作为合并条件的回归尚未解决,批准等于为一个并不存在的状态背书。不请求变更,是因为本 PR 已经带有审查通道留下的、仍然生效的 有一点我想直说,因为这正是在第 16 轮最容易被丢掉的东西:审查通道在其收敛姿态下把同一条发现延后了——「已记录,本轮不要求修改」。对缺失的测试见证和文档措辞来说,收敛压力是正确的直觉,本仓库自己的规则也说大约五轮之后就不该再落地 Suggestion。但那条规则的例外项是「正确性、安全、数据丢失、回归」,而这一条正是回归。因为轮次变高就把它延后,恰恰是这条规则要防止的失效模式。如果答案是「是的,我们接受这个取舍,先合并,之后再恢复该列表」,那完全站得住脚——只是这个决定应当是被明确做出的,而不是被消耗战磨出来的。 也值得说出口的是:一个对话框高度修复走到 16 轮已经很多了,而本 PR 的后半部分—— 已转交 maintainer;具体原因与请求见下方评论。 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
⏸️ Deferring to @wenshao — needs a human call on this one. This supersedes my earlier defer note on this PR, which was about a red What I cannot resolve from the diff, the tests, and the PR description is your Finding 1, and I re-verified it against the current head rather than trusting the August report. Both positions cannot be right, and you are the one who set the condition — so the call is yours: hold the PR for the one-condition fix (decide the collapse from I have not approved and have not submitted a second 中文说明⏸️ 转交 @wenshao —— 这一条需要人工判断。 本条取代我早前在本 PR 上的延后说明,那一条讲的是 我无法从 diff、测试和 PR 描述中解决的,是你的发现 1;我没有采信 8 月那份报告,而是针对当前 head 重新做了验证。 这两个立场不可能同时成立,而条件是你设的——所以决定权在你:要么让 PR 等这一个条件的修复(用 我没有批准,也没有提交第二份 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): "This PR fixes issue #9037: it prevents the /statusline and…": did not read each of the six pre-existing StatusLineDialog tests that keep availableTerminalHeight={18} (new formula gives them a 3-row window vs the old 10) …; "This PR fixes issue #9037: it prevents the /statusline and…": no executed Ink render to confirm the subtitle-wrap clip in finding 1 (geometry verified from source only).; "This PR fixes issue #9037: it prevents the /statusline and…": staticExtraHeight 's exact runtime composition not traced; used only to argue small-budget reachability.; "This PR fixes issue #9037: it prevents the /statusline and…": none — all checks I started completed within budget.; "This PR fixes issue #9037: it prevents the /statusline and…": full tsc typecheck of packages/cli not run (vitest compiled the changed files; the diff introduces no interface/prop changes, so type-level risk is minimal)..
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"This PR fixes issue #9037: it prevents the /statusline and…":did not read each of the six pre-existing StatusLineDialog tests that keep availableTerminalHeight={18} (new formula gives them a 3-row window vs the old 10) …;"This PR fixes issue #9037: it prevents the /statusline and…":no executed Ink render to confirm the subtitle-wrap clip in finding 1 (geometry verified from source only).;"This PR fixes issue #9037: it prevents the /statusline and…":staticExtraHeight 's exact runtime composition not traced; used only to argue small-budget reachability.;"This PR fixes issue #9037: it prevents the /statusline and…":none — all checks I started completed within budget.;"This PR fixes issue #9037: it prevents the /statusline and…":full tsc typecheck of packages/cli not run (vitest compiled the changed files; the diff introduces no interface/prop changes, so type-level risk is minimal).。
— qwen3.8-max via Qwen Code /review (v0.21.10)
f5484fc to
1ba8a96
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — no check was left unfinished at the ceiling..
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…":none — no check was left unfinished at the ceiling.。
— qwen3.8-max via Qwen Code /review (v0.21.11)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): "PR #9040 fixes issue #9037 by preventing the /statusline…": nothing meaningful left over. Used about 20 tool calls.; "PR #9040 fixes issue #9037 by preventing the /statusline…": none — all planned checks completed within budget..
中文说明
未探索到全部深度(达到工具调用预算):"PR #9040 fixes issue #9037 by preventing the /statusline…":nothing meaningful left over. Used about 20 tool calls.;"PR #9040 fixes issue #9037 by preventing the /statusline…":none — all planned checks completed within budget.。
— qwen3.8-max via Qwen Code /review (v0.21.11)
Cap MultiSelect labels to the render width and truncate counted single-line texts so the height budget holds at any terminal width; give the interactive unlocked list first claim on flexible rows and let the locked block collapse to (or drop) its hint instead of overflowing.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": none — finished all planned checks.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget.; chunk 2: none — labelCap's 24-cell floor ( Math.max(DESCRIPTION_COLUMN, …) ) means option rows themselves wrap below ~38 cols, which also breaks the budget, but that com…; "You are review agent reverse-audit — Reverse audit agent…": none — all planned checks completed within budget..
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
[Critical] R4-2: The search row's query text (<Text>{query || ...}</Text> in the Search: row of packages/cli/src/ui/components/skills/SkillsManagerDialog.tsx, ~line 683) has no wrap="truncate", but SKILLS_DIALOG_FIXED_ROWS counts that row as exactly one content row — so a query longer than the content width wraps the counted row to two rows and the dialog overflows its budget, violating the allocation comment's own invariant "FIXED_ROWS + list + locked never exceeds the budget". The subtitle and footer received wrap="truncate" in this revision; the search row was missed (round-3 blocker R3-3's suggested fix explicitly named "subtitle, search row, footer"). Failure scenario: executed Ink render at this commit: /skills with 1 unlocked + 1 locked skill at availableTerminalHeight={13} renders exactly 13 rows (fits); typing a 95-character query matching both skills wraps the search row and renders 14 rows vs a 13-row budget; adding wrap="truncate" flips 14 → 13. Threshold scales with width (query ≥ ~65 cells at an 80-column terminal); at any filled budget the wrapped row clips the footer/locked hint under overflow="hidden". (This finding could not be anchored inline: the query Text sits in unchanged code outside every diff hunk.) Suggested fix: {query || (…)} — the same treatment StatusLineDialog's "> ${query}" row already received in this PR.
中文说明
仅完成部分审查,审查缺口已披露。
未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…":none — finished all planned checks.;"You are review agent reverse-audit — Reverse audit agent…":none — all checks above completed within budget.;chunk 2:none — labelCap's 24-cell floor ( Math.max(DESCRIPTION_COLUMN, …) ) means option rows themselves wrap below ~38 cols, which also breaks the budget, but that com…;"You are review agent reverse-audit — Reverse audit agent…":none — all planned checks completed within budget.。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
[Critical] R4-2: The search row's query text (<Text>{query || ...}</Text> in the Search: row of packages/cli/src/ui/components/skills/SkillsManagerDialog.tsx, ~line 683) has no wrap="truncate", but SKILLS_DIALOG_FIXED_ROWS counts that row as exactly one content row — so a query longer than the content width wraps the counted row to two rows and the dialog overflows its budget, violating the allocation comment's own invariant "FIXED_ROWS + list + locked never exceeds the budget". The subtitle and footer received wrap="truncate" in this revision; the search row was missed (round-3 blocker R3-3's suggested fix explicitly named "subtitle, search row, footer"). Failure scenario: executed Ink render at this commit: /skills with 1 unlocked + 1 locked skill at availableTerminalHeight={13} renders exactly 13 rows (fits); typing a 95-character query matching both skills wraps the search row and renders 14 rows vs a 13-row budget; adding wrap="truncate" flips 14 → 13. Threshold scales with width (query ≥ ~65 cells at an 80-column terminal); at any filled budget the wrapped row clips the footer/locked hint under overflow="hidden". (This finding could not be anchored inline: the query Text sits in unchanged code outside every diff hunk.) Suggested fix: {query || (…)} — the same treatment StatusLineDialog's "> ${query}" row already received in this PR.
— qwen3.8-max via Qwen Code /review (v0.21.11)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "PR #9040 fixes issue #9037: it prevents the /statusline…": none (finished within ~7 of ~42 tool calls).; "PR #9040 fixes issue #9037: it prevents the /statusline…": none — all planned checks completed within budget.; "PR #9040 fixes issue #9037: it prevents the /statusline…": none — all checks I started were completed within budget.; "PR #9040 fixes issue #9037: it prevents the /statusline…": none — finished within budget; "PR #9040 fixes issue #9037: it prevents the /statusline…": none — all checks I started completed within budget., and 6 more.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"PR #9040 fixes issue #9037: it prevents the /statusline…":none (finished within ~7 of ~42 tool calls).;"PR #9040 fixes issue #9037: it prevents the /statusline…":none — all planned checks completed within budget.;"PR #9040 fixes issue #9037: it prevents the /statusline…":none — all checks I started were completed within budget.;"PR #9040 fixes issue #9037: it prevents the /statusline…":none — finished within budget;"PR #9040 fixes issue #9037: it prevents the /statusline…":none — all checks I started completed within budget.,另有 6 条。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.21.11)
Mirrors the /statusline compact path in /skills: budgets at or below the fixed frame drop border, paddingY, and footer so the always-rendered list row fits. Truncates every remaining counted text (translated titles, Type to search, Preview) at any width, folds \v/\f in status line fields, and notes hidden locked skills in the subtitle when no locked row fits (round-5 review).
Covers the compact-frame budgets (11/10/8), the locked-only query at a frame-floor budget, the ca-locale 26-column title, the subtitle locked count at residual === 1, \v/\f folding, and diverges the two width sources in the DialogManager wiring test (round-5 review).
|
Round-5 review: all seven findings addressed in ba35a4c + 42303a5. No deferrals. R5-1 (Critical) — irreducible 12-row frame at budgets ≤ 11. The /skills dialog now mirrors the /statusline compact path: when the budget is at or below the fixed frame it drops the border, vertical padding, and footer (6 rows), so the always-rendered list row fits with room to spare down to a 6-row budget. This also repairs the secondary symptom — at those budgets a locked-only search previously left the list area blank, and the freed rows now surface the locked matches and the hidden-count hint again. Tests pin budgets 11/10/8 and the locked-only search at budget 11. R5-2 (Critical) — translated title wraps. The title renders with truncation, so the 23-cell ca translation (and any other locale) stays on one row at any width. A render test drives the ca locale at 26 columns and asserts the 12-row budget holds; no blank residual row survived in the executed renders. R5-3 — /statusline budget below ~25 columns. The title and "Type to search" prompt now truncate like every other counted row; the constant's width caveat is gone since the count is valid at any width. R5-4 — \v/\f not folded. The status-line collapse now folds all whitespace (matching the skills-side one-line helper), so vertical-tab and form-feed characters in POSIX paths can no longer split the status line into multiple terminal rows. Covered by a new preset test. R5-5 — wiring test blind to the width source. The DialogManager test now diverges the two candidate width sources (prop 120 vs main area 60), so a mis-wiring to the raw terminal width fails the row-count assertion. R5-6 — locked skills vanish at residual === 1. When the mixed layout has no row left for the locked block, the subtitle now carries the count as "(+N locked)" instead of leaving the locked skills untraceable. Pinned at budget 12. R5-7 — Preview label. The block label truncates like its siblings. 中文说明第 5 轮评审:七条 findings 已在 ba35a4c + 42303a5 全部处理,无延后项。 R5-1(Critical)—— 预算 ≤ 11 时不可再简的 12 行框架。 /skills 对话框现镜像 /statusline 的紧凑路径:预算不高于固定框架时去掉边框、纵向内边距与页脚(共 6 行),强制列表行得以容纳,下限至 6 行预算。次要症状同步修复——此前该预算下仅匹配锁定技能的搜索会留下空白列表区,释放的行数现在能重新显示锁定匹配与隐藏计数提示。测试固定了 11/10/8 三档预算及预算 11 下的锁定搜索。 R5-2(Critical)—— 译文标题折行。 标题改为截断渲染,23 格的加泰罗尼亚语译文(及任何语言)在任意宽度下保持单行。新增渲染测试以 ca 语言、26 列驱动并断言 12 行预算成立;执行渲染中未出现残留空行。 R5-3 —— /statusline 预算在约 25 列以下失效。 标题与 "Type to search" 提示现与其他被计数行一样截断;常量注释中的宽度限制说明已删除,行数在任意宽度下有效。 R5-4 —— \v/\f 未折叠。 状态行折叠现覆盖全部空白字符(与 skills 侧单行助手一致),POSIX 路径中的垂直制表符/换页符不能再把状态行拆成多个终端行。新增预设测试覆盖。 R5-5 —— 接线测试无法区分宽度来源。 DialogManager 测试现使两个候选宽度来源取不同值(prop 120 vs 主区域 60),误接到原始终端宽度会使行数断言失败。 R5-6 —— residual === 1 时锁定技能无痕消失。 混合布局中锁定区块分不到行时,副标题以 "(+N locked)" 携带计数,不再无迹可寻。已在预算 12 下固定。 R5-7 —— Preview 标签。 该块标签与同级文本一样截断。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.
Not explored to full depth (tool budget reached): "PR #9040 修复 #9037:防止 /statusline 与 /skills…": none — all planned checks completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all planned checks completed (~20 tool calls).; "You are review agent reverse-audit — Reverse audit agent…": did not exercise the compact/escape paths interactively in a real TTY (tmux) — covered only by the unit tests above.; "You are review agent reverse-audit — Reverse audit agent…": did not audit buildStatusLinePresetParts internals field-by-field beyond the join-and-collapse (the collapse runs post-join, which bounds all fields regardles….
中文说明
已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。
未探索到全部深度(达到工具调用预算):"PR #9040 修复 #9037:防止 /statusline 与 /skills…":none — all planned checks completed within budget.;"You are review agent reverse-audit — Reverse audit agent…":none — all planned checks completed (~20 tool calls).;"You are review agent reverse-audit — Reverse audit agent…":did not exercise the compact/escape paths interactively in a real TTY (tmux) — covered only by the unit tests above.;"You are review agent reverse-audit — Reverse audit agent…":did not audit buildStatusLinePresetParts internals field-by-field beyond the join-and-collapse (the collapse runs post-join, which bounds all fields regardles…。
— qwen3.8-max via Qwen Code /review (v0.21.11)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
Not explored to full depth (tool budget reached): "PR #9040 fixes #9037 by preventing the /statusline and…": none — all checks above completed within budget. The remainder of SkillsManagerDialog.test.tsx (past diff line 1026) is outside my assigned range and belongs …; "PR #9040 fixes #9037 by preventing the /statusline and…": none — all checks above completed within budget.; "PR #9040 fixes #9037 by preventing the /statusline and…": none — all checks I started were completed within budget.; chunk 6: running packages/cli vitest suite for statusLinePresets.test.ts — worktree has no node_modules installed; string-semantics verification was done directly in…; "PR #9040 fixes #9037 by preventing the /statusline and…": none — all checks I started were completed within budget (~17 of ~38 calls)., and 2 more.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:reverse audit — did not converge within the reverse-audit round cap of 5。
未探索到全部深度(达到工具调用预算):"PR #9040 fixes #9037 by preventing the /statusline and…":none — all checks above completed within budget. The remainder of SkillsManagerDialog.test.tsx (past diff line 1026) is outside my assigned range and belongs …;"PR #9040 fixes #9037 by preventing the /statusline and…":none — all checks above completed within budget.;"PR #9040 fixes #9037 by preventing the /statusline and…":none — all checks I started were completed within budget.;chunk 6:running packages/cli vitest suite for statusLinePresets.test.ts — worktree has no node_modules installed; string-semantics verification was done directly in…;"PR #9040 fixes #9037 by preventing the /statusline and…":none — all checks I started were completed within budget (~17 of ~38 calls).,另有 2 条。
— qwen3.8-max via Qwen Code /review (v0.21.11)
Round-7 R7-1: the /skills compact frame still floored at 6 rows (title + subtitle + search + list margin + the forced list row), so height budgets <= 5 overflowed and overflow="hidden" clipped the interactive list itself. Add a bare tier that sheds the remaining chrome at budgets <= 5 so only the list area renders, mirroring the /statusline compact path. The hidden search row also bypasses and suppresses query mutation so a retained query cannot filter invisibly.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 4)": none — I finished every check I started within the tool budget..
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
中文说明
仅完成部分审查,审查缺口已披露。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 4)":none — I finished every check I started within the tool budget.。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.21.12)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
— qwen3.8-max via Qwen Code /review (v0.21.12)
|
@yiliang114 这个 CI bot 似乎出现问题了;审查请求已经三天仍然没有回复。麻烦帮忙看一下,谢谢! |
|
@qwen-code /review |
|
Qwen Code review request accepted. Review is running in workflow run. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R12 compact-mode search-row key gates unwitnessed (StatusLineDialog.tsx:200) — already reported as R4-12 (comment 3777951848) at the same location on the current head; same territory also covered by R7-9 (comment 3782616721)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
Deferred under the convergence posture (round 12, not a blocker) — recorded, not requested in this round:
packages/cli/src/ui/components/shared/MultiSelect.test.tsx:37 — [test] New MultiSelect test is inert against the PR's own change (passes with the whole change reverted)packages/cli/src/ui/components/StatusLineDialog.tsx:141 — [test] The ?? 18 height fallback is untested (mutant survives the whole suite)packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:279 — [probe] persistChanges refresh ordering unpinned — a Promise.all mutant stays green (ORDER MATTERS comment)packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:330 — [probe] Esc-during-loading guard unwitnessed — guard-removed mutant shows a spurious saved toastpackages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:267 — [probe] handlePick disabled-row Enter branch untested — mutant fills the buffer with the disabled skillpackages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:319 — [probe] Loading/error truncate guard unwitnessed — 34-char fixture message is too short to wrappackages/cli/src/ui/components/StatusLineDialog.tsx:291 — [probe] No-match message wrap="truncate" unwitnessed — mutant renders 20 rows vs a 16-row budgetpackages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:207 — [probe] disableVimNav wiring unwitnessed — j/k in a query double-handled under the mutant, Enter picks the wrong skillpackages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:252 — [probe] persistChanges failure branches untested — deleting both try/catch blocks keeps the suite greenpackages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:66 — [probe] Untrusted-workspace branch untested — isTrusted hardcoded true, guard deletion survives the suite
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
收敛姿态下延后(第 12 轮,非阻断)——已记录,本轮不要求修改:共 10 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.3)
Runtime verification report — PR #9040I built the PR into a real CLI bundle and drove the actual TUI in tmux PTYs at fixed terminal sizes, A/B against a bundle whose only difference is that the four production files plus Verdict: the fix is real and holds up under every size and width I threw at it. One behavioural regression is worth fixing before merge (Finding 1), plus one minor counter bug and one test-coverage gap. 1. The reported bug reproduces, and the PR fixes it
The Height sweep at 100 columns (
Note the
2. The reviewer test plan checks out
3. Finding 1 — the locked-skills section is removed at every terminal size, not only constrained onesThis is the one I would fix before merging.
const constrained = availableTerminalHeight !== undefined;
// …
const filteredLocked = useMemo(() => {
if (constrained) return []; // :252and
Confirmed at runtime on a 40-row terminal (dialog budget 35 rows — no height pressure at all): The user-visible cost: with four skills disabled at Suggested shape of the fix: decide the collapse from the budget rather than from "a budget exists" — keep the locked rows while 4. Finding 2 —
|
| # | mutation | result |
|---|---|---|
| M1 | constrained → false |
killed (11) |
| M2 | bare threshold 5 → 4 |
killed (4) |
| M3 | compact threshold <= 11 → < 11 |
killed (1) |
| M4 | /statusline full-layout threshold 16 → 15 |
killed (2) |
| M5 | compact maxItemsToShow +1 |
killed (2) |
| M6 | MultiSelect ignores truncateLabels |
killed (3) |
| M7 | oneLine() → identity |
killed (1) |
| M8 | bare no longer bypasses a retained query |
killed (2) |
| M9 | backspace not suppressed in bare mode | killed (1) |
| M10 | /statusline Esc: hasFullLayout && query → query |
survived |
| M11 | /statusline printable chars: drop the hasFullLayout && guard |
survived |
| M12 | separator no longer excluded from search | killed (1) |
| M13 | vim nav disabled by a hidden query in bare mode | killed (1) |
| M14 | full-layout maxItemsToShow +1 |
killed (3) |
M10 and M11 are the /statusline counterparts of guards the /skills suite does pin (keeps bare-mode keys active without changing a retained query). The shipped code is correct on both — I verified it with the live resize round-trip in section 2 — so this is purely a missing test, reachable in practice by resizing the terminal while the dialog is open. Worth a couple of lines in StatusLineDialog.test.tsx.
Summary
| Fixes #9037 | ✅ verified end-to-end at 8/10/12/14/16/18/20/24 rows and 46 columns |
| Reviewer test plan | ✅ all four steps reproduce |
| Regressions found | matchedCount under-reports (Finding 2) |
| Test strength | 70/70 pass; 12 of 14 mutants killed; two /statusline compact-mode guards untested |
| Recommendation | Fix Finding 1 (a one-condition change) before merge; Findings 2–3 fold into it |
Harness details
- Both arms built from one worktree at
d1590f1awithnpm run bundle.dist-pr= PR sources;dist-base=StatusLineDialog.tsx,MultiSelect.tsx,SkillsManagerDialog.tsx,statusLinePresets.tsandsrc/i18n/locales/reverted to52cfb18(merge base), everything else byte-identical. - Isolated
HOME, so no real user settings were touched. Fixture: 9 project skills in<ws>/.qwen/skills/(one with a multi-line YAML block-scalar description) + the bundled skills, and 4 user-level skills listed inskills.disabledat User scope to produce the locked set. - Driven with
tmux new-session -x <cols> -y <rows>andsend-keys, observed withcapture-paneonly (nevertee, which would flip the render mode). Screenshots arecapture-pane -eANSI rendered to PNG, full pane height preserved. - Live resize done with
tmux resize-windowwhile the dialog was open. - One harness note for anyone reproducing this: if you build the worktree with a
node_moduleslink farm,ink-testing-librarymust be a real copy rather than a symlink, otherwise it resolves a secondinkinstance and every keyboard-driven test fails with an empty query. That artifact — not the PR — is what makes 15 tests look red on a naive setup.
中文版报告
PR #9040 运行时验证报告
我把这个 PR 构建成真实的 CLI bundle,在固定尺寸的 tmux PTY 中驱动真实 TUI,并与另一个 bundle 做 A/B —— 后者唯一的差别是把四个生产文件加上 src/i18n/locales/* 回退到 merge base(52cfb18)。其余(同一 worktree、同一 node_modules、同一资源)完全相同,因此唯一的变量就是本 PR 的 diff。
结论:修复是真实有效的,在我测试的所有高度与宽度下都成立。有一个行为回归建议在合并前修掉(发现 1),另有一个次要计数错误和一处测试覆盖缺口。
1. 问题可复现,PR 确实修好了
/statusline 在 100×12 下:修复前标题、搜索标签和整个可选列表全部被破坏;修复后是干净的紧凑列表(见上文第一张图)。
更值得注意的是 24 行的情况——这是很多人日常使用的高度,而修复前的对话框依然溢出:标题消失、查询行覆盖了 Type to search(变成 >ype to search)、一个预设行被另一行覆写、Preview 标签消失(见上文第二张图)。
高度扫描表见上文英文部分(列为「可见可选行数 / 检测到的覆写残影数」)。
请注意 /skills 的标题:在修复前的构建中,Manage Skills 标题在我测试的所有高度(包括 40 行)都被裁掉了,因为换行的描述行把对话框顶部挤出了 overflow="hidden" 盒子。PR 之后,从 12 行起标题都在。
2. 审阅者测试计划全部通过
- 紧凑布局下每个预设都可达。 100×12 下按 16 次
Down可以从Use theme colors走到最后一个预设session-id,窗口正常滚动,没有跳过任何项。 Enter仍会保存。 100×12 下Down×6 →Space→Enter把含total-input-tokens的ui.statusLine.items写入了用户设置,实时状态栏同帧生效。/skillsbare 层功能完整。 100×10 下:输入zzz不会用隐藏的查询去过滤(列表不变);Down→Space→Esc把{"skills":{"disabled":["proj-skill-2"]}}持久化到工作区设置;Enter在首次渲染时和搜索过滤后都能正确选中高亮的技能(activeValue→onConfirm(_, active)的重构在行为上是干净的);在刚被切换为关闭的行上按Enter会持久化改动且不填充输入框。- 实时缩放往返。 在 30 行的完整布局下输入
git→ 缩放到 12 行:列表未被过滤(隐藏查询被正确旁路);在紧凑模式下输入zzz:查询没有被污染;缩放回 30 行:搜索行仍然恰好是> git;紧凑模式下按Esc会关闭对话框,而不是静默清掉隐藏的查询。生产代码这四点都是对的——测试覆盖方面见发现 3。 - 宽度感知成立。 46×12 下,修复前的对话框会换行并把页脚画进自己的边框;PR 按渲染宽度截断,预算得以保持。
oneLine()是必要的。 一个 YAMLdescription为多行块标量的技能,在修复前会静默占用两行列表。
3. 发现 1 —— 锁定技能区块在任何终端尺寸下都被移除,而不只是受限时
这是我建议在合并前修掉的一点。
SkillsManagerDialog.tsx:236 判断的是「预算是否存在」,而不是「预算够不够」:
const constrained = availableTerminalHeight !== undefined;
// …
const filteredLocked = useMemo(() => {
if (constrained) return []; // :252而对这个对话框来说,availableTerminalHeight 只有在 uiState.constrainHeight === false 时才是 undefined:
DialogManager.tsx:83——const constrainedDialogHeight = constrainHeight ? dialogMaxHeight : undefined;DialogManager.tsx:492——SkillsManagerDialog以availableTerminalHeight={constrainedDialogHeight}挂载AppContainer.tsx:3285——constrainHeight初始为trueAppContainer.tsx:4157-4161—— 任何按键在分发前都会把它设回true,而Ctrl+S(SHOW_MORE_LINES)是唯一能清除它的操作。之后还需要再敲七次键才能打开/skills,所以对话框挂载时它必然又是true。
已在 40 行终端(对话框预算 35 行,完全没有高度压力)上运行时确认:在空提示符下按 Ctrl+S 再执行 /skills,以及在对话框已打开时按 Ctrl+S,两种情况都仍然只渲染 (+4 locked)。也就是说整个 filteredLocked.length > 0 区块——Locked by higher-scope settings (cannot toggle here): 标题、逐条技能行、[locked: <scope>] 标签——在实际发布的 UI 中都是死代码;而本 PR 从九个语言包中删掉的 All available skills are locked at a higher scope (see below). 文案是被彻底移除,而不是被搬到别处。
用户可见的代价:在有四个技能于 User 作用域被禁用的情况下,40 行终端上搜索 locked,以前会回答 4 / 23 skills 并指出是哪个作用域锁住了它们;PR 之后回答 0 / 23 skills + No skills match the search.,而同一行副标题却写着 (+4 locked)。用户在产品内再也无法得知究竟是哪个设置作用域压住了某个技能。
建议的修法:让折叠由预算大小决定,而不是由「存在预算」决定——只要 residual 还放得下就保留锁定行,放不下时再退回到 (+N locked) 徽标(这个徽标本身是一个真正的改进)。
4. 发现 2 —— 隐藏锁定行时 matchedCount 少算
SkillsManagerDialog.tsx:557 仍然计算 filteredUnlocked.length + filteredLocked.length,但在受限路径上 filteredLocked 恒为 [],于是 {{matched}} / {{total}} 的分母包含了锁定技能而分子不包含,就出现了上面的 0 / 23。与发现 1 同源,应当随同一处修改一起解决。
5. 发现 3 —— 两个存活变异体(测试覆盖问题,不是缺陷)
四个被改动文件中的 70 个测试在本地全部通过,arena/、DialogManager.test.tsx、layouts/ 和 layoutUtils.test.ts 的 30 个测试也全部通过。packages/cli 的 tsc --noEmit 除了既有的 TS5101 baseUrl 弃用告警外是干净的,因此 PR 描述中「包级 TypeScript 检查仍被阻断」的说明至少对 packages/cli 不成立。
我对 PR 自带的测试套件跑了 14 个变异体,杀死 12 个——对一个渲染类改动来说这是相当强的结果。完整表格见上文英文部分。
M10 与 M11 是 /skills 套件已经钉住的守卫(keeps bare-mode keys active without changing a retained query)在 /statusline 侧的对应物。发布代码在这两点上都是正确的——我用第 2 节的实时缩放往返验证过——所以这纯粹是缺测试,而且在实践中可以通过「对话框打开时缩放终端」触达。建议在 StatusLineDialog.test.tsx 里补两行。
总结
| 是否修复 #9037 | ✅ 在 8/10/12/14/16/18/20/24 行及 46 列下端到端验证通过 |
| 审阅者测试计划 | ✅ 四个步骤全部可复现 |
| 发现的回归 | matchedCount 少算(发现 2) |
| 测试强度 | 70/70 通过;14 个变异体杀死 12 个;/statusline 紧凑模式的两个守卫未被测试覆盖 |
| 建议 | 合并前修掉发现 1(只需改一个条件);发现 2–3 可随之一并解决 |
|
@qwen-code /resolve |
|
Qwen Code attempted to resolve merge conflicts but the run did not complete successfully. Check the workflow run for full logs. |
|
@qwen-code /resolve |
|
Qwen Code resolved the merge conflicts and pushed the branch update. Root causemain's #10600 ( Textual or semanticSemantic, but orthogonal. main removed The only textual conflict was the state block. Resolution keeps the PR's const [query, setQuery] = useState('');
// Capture the higher-scope disabled lists once at mount.
const higher = useMemo(() => buildHigherDisabled(settings), [settings]);Verified: merged file vs main = PR's diff + exactly the #10600 adoptions; vs PR head = #10600's diff. Only 11 files differ from both parents; all locales load; the deleted locale key has no remaining references. What is load-bearing
What I could not verifyNo build/tests run. Known fallout in a NON-conflicted file: 中文说明根因:main 的 #10600( 冲突性质:语义冲突但改动正交。main 删除 解决:保留 PR 对 关键依赖:Enter 选中依赖 未能验证:本流程不跑构建测试。已知非冲突文件受影响: |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
7 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- D13-4 persistChanges refresh-ordering unpinned — already reported in the round-12 deferred list (review 5058926694), same location
- D13-5 compact-mode escape with a retained hidden query unpinned — already reported as R4-12 (comment 3777951848)
- D13-6 j/k append-to-query under an active query unpinned — already reported in the round-12 deferred list (review 5058926694, disableVimNav wiring)
- D13-7 Enter on a just-toggled-off row unpinned — already reported in the round-12 deferred list (review 5058926694, handlePick disabled-row Enter branch)
- D13-8 compact-mode printable-input gate unpinned — already reported as R7-9 (comment 3782616721)
- D13-9 Esc-during-loading guard unpinned — already reported in the round-12 deferred list (review 5058926694)
- D13-10 untrusted-workspace persist guard untested — already reported in the round-12 deferred list (review 5058926694)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Deferred under the convergence posture (round 13, not a blocker) — recorded, not requested in this round:
packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:172 — [probe] no test presses Enter while a search filter is active in full/compact layout (the deleted activeValue guard's scenario)packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:276 — [probe] re-enable persistence direction never exercised (workspace skills.enabled / value-undefined branches unpinned)packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:212 — [probe] Escape-with-active-query test never asserts the dialog stays open (the branch's return is unpinned)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 7 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
收敛姿态下延后(第 13 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.3)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
5 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R14-3 persistChanges refresh-ordering unpinned (SkillsManagerDialog.test.tsx:282) — already reported in the round-12 deferred list (review 5058926694), same location
- R14-4 untrusted-workspace branch untested (SkillsManagerDialog.test.tsx:69) — already reported in the round-12 deferred list (review 5058926694), same location
- R14-6 Esc-during-loading guard unpinned (SkillsManagerDialog.test.tsx:333) — already reported in the round-12 deferred list (review 5058926694), same location
- R14-7 handlePick toggled-off Enter branch untested (SkillsManagerDialog.test.tsx:173) — already reported in the round-12 deferred list (review 5058926694), same location
- R14-8 persistChanges failure branches untested (SkillsManagerDialog.test.tsx:284) — already reported in the round-12 deferred list (review 5058926694), same location
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 10.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent 2": none — no check was cut short..
12 Suggestion(s) were drafted inline past the resolved critical posting floor; the CLI moved them into the deferral list below (floor enforcement).
Deferred under the convergence posture (round 14, not a blocker) — recorded, not requested in this round:
packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:25 — [review] Every fixture skill uses level: 'user' , so the LEVEL_ORDER term of sortSkills (project < user < extension < bundled) is pinned by none of the 26 tests — …packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:27 — [review] No fixture sets userInvocable , so the dialog's list.filter((skill) => skill.userInvocable !== false) (SkillsManagerDialog.tsx:169) is pinned by none of the…packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:35 — [review] No test renders MultiSelect with more than 15 unlocked skills, so the 15-row list cap ( maxItemsToShow = Math.min(15, Math.max(1, residual)) , SkillsManagerDia…packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:48 — [review] No test resolves listSkills to an empty array, so the allSkills.length === 0 → 'No skills are currently available.' branch is pinned by no test (the load…packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:54 — [review] The restored createConfig mock hardcodes isSkillEnabled to true , so the dialog's config-disabled-skill path has no test: neither the initial unchecked re…packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:58 — [review] The Array.isArray guard on user-editable skills.disabled (SkillsManagerDialog.tsx:94-95 in namesFromScope ) is pinned by no test — every settings fixture …packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:59 — [review] Every fixture uses lowercase names that exactly match skill names, so the dialog's case-insensitive lock matching ( lower() / normalizeNames in buildHigherDi…packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:60 — [review] Every createSettings fixture seeds only the User scope; the Workspace scope (4th LoadedSettings argument) is empty in all 26 tests, so persistChanges 's w…packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:66 — [review] Every fixture disables skills at User scope only, so scopeOf 's System > User > SystemDefaults precedence and the rendered [locked: {{scope}}] label (Skills…packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:245 — [review] The + filteredLocked.length term of matchedCount (SkillsManagerDialog.tsx:543) is pinned by no test — the only count assertions ('1 / 10 skills', '0 / 10 …packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:256 — [review] The no-change early return in persistChanges ( if (!disabledChanged && !enabledChanged) return 'ok'; , SkillsManagerDialog.tsx:308) is pinned by no test — …packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:282 — [review] The persist test asserts every side effect of a successful save except the user-facing confirmation toast — addItem is only ever supplied as an uninspected …
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 5 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:reverse audit — did not converge within the reverse-audit round cap of 10。
未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
未探索到全部深度(达到工具调用预算):"agent 2":none — no check was cut short.。
12 条 Suggestion 在已解析的 critical 发布下限之外被起草为行内评论;CLI 已将其移入下方延后清单(下限强制执行)。
收敛姿态下延后(第 14 轮,非阻断)——已记录,本轮不要求修改:共 12 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.3)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
4 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- DUP-1 unwitnessed compact-mode key-suppression guards (StatusLineDialog.tsx:200) — already reported as R4-12 (comment 3777951848) and R7-9 (comment 3782616721)
- DUP-2 createConfig hardcodes isSkillEnabled true, config-disabled path untested (SkillsManagerDialog.test.tsx:54) — already reported in the round-14 deferred list (review 5085698077)
- DUP-3 handlePick toggled-off early-return branch untested (SkillsManagerDialog.test.tsx:173) — already reported in the round-12 deferred list (review 5058926694) and re-confirmed as R14-7
- DUP-4 disableVimNav query-active side unwitnessed (SkillsManagerDialog.test.tsx:211) — already reported in the round-12 deferred list (review 5058926694)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent invariant-b (packages/cli/src/ui/components/skills/Sk…": none — no check was cut short..
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
Deferred under the convergence posture (round 15, not a blocker) — recorded, not requested in this round:
packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:171 — [probe] onConfirm pick contract only exercised at list index 0 — no test navigates before pressing Enter; mutant picking items[0]?.value survives all 44 testspackages/cli/src/ui/components/skills/SkillsManagerDialog.tsx:613 — [probe] (+N locked) renders twice when constrained and all skills locked (subtitle slot + list-area row), spending one budgeted row on repeated information
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 4 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent invariant-b (packages/cli/src/ui/components/skills/Sk…":none — no check was cut short.。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
收敛姿态下延后(第 15 轮,非阻断)——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.0)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
5 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- SkillsManagerDialog.test.tsx:171 Enter/pick contract only exercised at list index 0 - already reported (round-13 and round-15 deferral entries)
- shared/MultiSelect.tsx:84 onConfirm active-value argument indistinguishable from items[0] - already reported (round-15 deferral entry)
- SkillsManagerDialog.test.tsx:291 ca-locale title assertion resolves on the loading frame - already reported (comment 3782616715)
- SkillsManagerDialog.tsx:613 locked-count badge rendered twice when all skills are locked and constrained - already reported (round-15 deferral entry)
- SkillsManagerDialog.test.tsx:220 matched/total readout asymmetry under a height budget - already reported (comments 3785816314 and 3789720271, plus a round-14 deferral entry)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": I did not run packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx to confirm the error/loading matrix is green at the reviewed commit; the row …; "agent reverse-audit (round 2)": bisecting which preceding test / which post-commit pass adds the 21st row (candidate list narrowed to emitLayoutListeners in node_modules/ink/build/reconcile…; "agent reverse-audit (round 3)": npm run typecheck was not run — the changed onConfirm arity and the two wrap literals were checked against styles.d.ts / useSelectionList.ts types by re…; "agent reverse-audit (round 3)": I did not determine *why* items[0].value equals the picked row in SkillsManagerDialog.test.tsx:163-177 (fixture ordering vs. scrollOffset vs. INITIALIZE ….
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Deferred under the convergence posture (round 16, not a blocker) — recorded, not requested in this round:
docs/users/features/status-line.md:42 — [review] Documented 16-row threshold is the budget, not terminal rowspackages/cli/src/ui/components/StatusLineDialog.test.tsx:369 — [review] Compact layout's only save path has no Enter testpackages/cli/src/ui/components/StatusLineDialog.tsx:147 — [review] Non-interactive separator consumes a scarce compact-tier rowpackages/cli/src/ui/components/StatusLineDialog.tsx:150 — [review] The search text's only gained token has no test witnesspackages/cli/src/ui/components/StatusLineDialog.tsx:227 (+2 locations) — [review] Tier-keyed query-input gates are unwitnessed in both dialogspackages/cli/src/ui/components/StatusLineDialog.tsx:291 (+3 locations) — [review] Four narrow-width truncation guards have no witnesspackages/cli/src/ui/components/shared/MultiSelect.tsx:122 — [review] Scroll window never re-expands after a shrink-then-grow…packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:116 — [review] The j/k guard's !query half has no witnesspackages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx:344 — [review] oneLine()'s six non-newline break characters are unpinnedpackages/cli/src/ui/components/skills/SkillsManagerDialog.tsx:135 (+1 locations) — [review] TAB exempt from both new one-row normalizationspackages/cli/src/ui/components/skills/SkillsManagerDialog.tsx:223 (+1 locations) — [review] Compact/bare tier contents unpinned by upper-bound-only…packages/cli/src/ui/components/skills/SkillsManagerDialog.tsx:245 — [review] Locked-skill rows vanish at every height, not only scarce…packages/cli/src/ui/components/skills/SkillsManagerDialog.tsx:263 — [review] Truncated labels eat the skill's scope tagpackages/cli/src/ui/components/skills/SkillsManagerDialog.tsx:530 — [review] Error-state dismissal hint suppressed where it would fitpackages/cli/src/ui/components/skills/SkillsManagerDialog.tsx:569 — [review] Locked-skill count renders nowhere in the bare tierpackages/cli/src/ui/components/skills/SkillsManagerDialog.tsx:579 (+1 locations) — [review] Search row truncation hides the live edit point
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 5 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)":I did not run packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx to confirm the error/loading matrix is green at the reviewed commit; the row …;"agent reverse-audit (round 2)":bisecting which preceding test / which post-commit pass adds the 21st row (candidate list narrowed to emitLayoutListeners in node_modules/ink/build/reconcile…;"agent reverse-audit (round 3)":npm run typecheck was not run — the changed onConfirm arity and the two wrap literals were checked against styles.d.ts / useSelectionList.ts types by re…;"agent reverse-audit (round 3)":I did not determine *why* items[0].value equals the picked row in SkillsManagerDialog.test.tsx:163-177 (fixture ordering vs. scrollOffset vs. INITIALIZE …。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
收敛姿态下延后(第 16 轮,非阻断)——已记录,本轮不要求修改:共 16 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.0)
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 412 passed · 0 failed · 412 total Flakiness gate: ✅ 4 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:412 通过 · 0 失败 · 412 总计 抖动门:✅ 4 changed test file(s) x 5 identical rounds, no divergence Verification reportPR 9040 deep verification —
|
| dialog | width | cells | HEAD rows > budget |
BASE rows > budget |
|---|---|---|---|---|
| statusline | 20 | 14 | 0 / 14 | 14 / 14 |
| statusline | 26 | 14 | 0 / 14 | 14 / 14 |
| statusline | 40 | 14 | 0 / 14 | 14 / 14 |
| statusline | 100 | 14 | 0 / 14 | 14 / 14 |
| skills | 20 | 14 | 0 / 14 | 14 / 14 |
| skills | 26 | 14 | 0 / 14 | 14 / 14 |
| skills | 40 | 14 | 0 / 14 | 14 / 14 |
| skills | 100 | 14 | 0 / 14 | 13 / 14 |
| total | 112 | 0 / 112 | 111 / 112 |
The single base cell that fits is /skills at width 100, budget 24, where base's fixed 23 rows
happen to land under 24. Every expectation above is pinned per arm in the harness, so the base
arm's overflow is an assertion that passes when base overflows as predicted — fail: 0 on
both arms is the A/B working, not the A/B being skipped.
Worked cell, /skills, width 100, budget 11:
HEAD RESULT|head|skills|w=100|budget=11|rows=10|over=-1|widest=99|title+search+badge|selectable=5|lockedRows=0
BASE RESULT|base|skills|w=100|budget=11|rows=23|over=12|widest=100|title+search+footer+lockedrows|selectable=5|lockedRows=5
The clipping consequence is real, not hypothetical. DefaultAppLayout.tsx:94-95 wraps
DialogManager in <Box height={dialogHeight} overflow={uiState.constrainHeight ? 'hidden' : undefined}>,
and constrainedDialogHeight / listDialogHeight are both getDialogMaxHeight(...) — the same
number as the clip box height. So base's 23 rows inside an 11-row overflow='hidden' box loses 12
rows, including interactive ones. Head renders 10.
Secondary claim (a) — an A/A parity control. With availableTerminalHeight omitted, head and
base render identically: UNCONSTRAINED|head|rows=23|lockedRows=5 and
UNCONSTRAINED|base|rows=23|lockedRows=5. The unconstrained locked section is preserved exactly,
as the description claims.
Secondary claim (b) — content that tries to break rows. Witness: the sweep below, at
/skills budget 11, one hostile character injected into every unlocked description
(SWEEP|<arm>|<payload>|... in logs/*-behavior-assert.txt):
| payload | HEAD rows | BASE rows |
|---|---|---|
| benign space | 10 | 23 |
| LF | 10 | 33 |
| CRLF | 10 | 33 |
| CR / VT / FF / NEL / U+2028 / U+2029 / TAB | 10 | 23 |
| SGR colour escape | 10 | 23 |
| OSC-8 hyperlink escape | 10 | 23 |
Head renders 10 rows for every payload — identical to benign, so oneLine() costs nothing on
clean input (zero collateral). Base inflates by exactly 10 rows on LF/CRLF (5 skills × 2 breaks),
which is the sanitizer being load-bearing. Scaling ladder (descriptions of 2 k / 5 k / 20 k
characters containing line breaks): head 10 / 10 / 10 rows in 28 / 28 / 30 ms — flat, no
superlinear behaviour; base 116 rows at every size.
Test Plan step 3 (navigation) measured, not assumed. At /statusline budget 8, driving real
keypresses through the real KeypressProvider:
NAV|head|budget=8|distinctActiveRows=17|maxRowsDuringNav=8|jMoved=true|onClose=1|onSaved=1|savedKeys=["type","useThemeColors","items"].
All 17 selectable rows (theme-colours + 16 presets; navigation skips the disabled separator)
became active, j moved the marker, height never exceeded 8, and Enter saved and closed. Base
reaches the same 17 rows but at maxRowsDuringNav=20 — over its budget of 8.
Corrections
The PR description's "Not validated / out of scope" line is stale. It states that
"package-wide TypeScript checking remains blocked by pre-existing ACP, channel, and doctor
declaration mismatches outside this change". At this merge head:
cd packages/cli && npx tsc --noEmit -p tsconfig.json # -> 0 errors
Zero errors across the whole packages/cli project, including all four touched test files. The
branch merged main on 2026-09-06, which is the likely reason. This is a correction to the
description, not a request to change code — but a reviewer reading it would skip a gate that
now passes, and the same 0-error run is what proves the widened MultiSelect.onConfirm signature
((selectedValues, activeValue)) is consistent at every call site, not only at the one this PR
changed.
Findings
F1 — Suggestion: at budgets ≤ 5, higher-scope-locked skills leave no trace at all
docs/design/short-skills-dialog.md, added by this PR, states that when /skills receives a
height constraint "Higher-scope-locked skills are represented by a count". Measured against the
real component with 5 locked + 5 unlocked skills, the count is present from budget 6 upward and
absent below it, because the bare tier removes the subtitle that carries the badge:
LOCKED-probe (mixed fixture, width 100) badge present?
budget 1 -> rows=1 title/search/footer none NO
budget 5 -> rows=5 title/search/footer none NO
budget 6 -> rows=6 title+search+badge yes
budget 11 -> rows=10 title+search+badge yes
Reproduce — the scratch test file is removed from the tree after each run, so copy the canonical
harness back in first (logs/head-height-budget-assert.txt holds the cells already measured):
cp tmp/pr9040-verify-20260907-000421/harness/height-budget.test.tsx \
packages/cli/src/ui/components/zz-verify-height-budget.test.tsx
cd packages/cli && VERIFY_ARM=head VERIFY_MODE=report \
VERIFY_OUT=/tmp/head.txt npx vitest run \
src/ui/components/zz-verify-height-budget.test.tsx --silent=false
grep '^RESULT|head|skills|w=100|budget=[1235]|' /tmp/head.txt # flags=none, no badge
rm packages/cli/src/ui/components/zz-verify-height-budget.test.tsxBounded, and this is the part that keeps it a Suggestion rather than a blocker:
- The all-locked case is unaffected — there the badge becomes the single list row, so it
renders at every budget including 1 (LOCKED|head|budget=1|rows=1|badge=true). The loss needs
at least one workspace-toggleable skill to exist. - The budget invariant itself still holds; nothing is clipped and no row is unreachable. What is
lost is one piece of information, silently. - Commit
ba35a4cadded the badge precisely to note "hidden locked skills … when no locked row
fits (round-5 review)"; the round-7baretier then removed the surface it renders on. So this
reads as an interaction between two review rounds, not an oversight in either. - The doc sentence and the behaviour disagree, and the doc is the artifact a future editor will
trust. Either narrow the doc to "when the compact tier applies" or surface the count in bare
mode; both are one-line changes.
Not measured: the real terminal height at which budget ≤ 5 occurs. getDialogMaxHeight is
Math.max(1, terminalHeight - staticExtraHeight - 2), and I did not measure
staticExtraHeight in a running app, so I cannot state how rare this is — only that it needs a
very short terminal.
F2 — Nice to have: two Math.max(1, …) floors are live but unpinned
The mutation matrix (witness 02-mutation-matrix-9-of-11-killed.png) reverted one guard per row
and re-ran all four affected test files. Baseline unmutated control: Tests 70 passed (70).
| mutation (guard reverted) | suite result | verdict |
|---|---|---|
STATUS_LINE_DIALOG_FIXED_ROWS 15 → 8 |
5 failed | 65 passed | KILLED |
statusline hasFullLayout → always true |
4 failed | 66 passed | KILLED |
statusline Math.max(1, …) → Math.max(0, …) |
70 passed | SURVIVED |
skills compact → always false |
12 failed | 58 passed | KILLED |
skills bare → always false |
8 failed | 62 passed | KILLED |
skills if (constrained) return [] → if (false) |
11 failed | 59 passed | KILLED |
skills Math.max(1, residual) → residual |
70 passed | SURVIVED |
skills oneLine() → passthrough |
1 failed | 69 passed | KILLED |
| skills bare-mode backspace gate removed | 1 failed | 69 passed | KILLED |
MultiSelect truncateLabels ignored |
3 failed | 67 passed | KILLED |
statusLinePresets line-break collapse removed |
7 failed | 63 passed | KILLED |
9/11 killed. Each survivor has a positive control in the same file: reverting
hasFullLayout turns 4 tests red in StatusLineDialog.test.tsx, and reverting compact turns 12
red in SkillsManagerDialog.test.tsx — so the runner demonstrably collects tests that exercise
both mutated files, and the two survivals are real.
Classification, by measurement rather than by reading the code. Both survivors are the same shape
(a floor of 1 on the item count). I drove budgets the production caller cannot produce:
FLOOR|statusline|budget=0|rows=1 FLOOR|skills|budget=0|rows=1
FLOOR|statusline|budget=-5|rows=1 FLOOR|skills|budget=-5|rows=1
The floors bind: without them these budgets would render a 0-row list, i.e. an open dialog
with nothing in it. So they are live defence against out-of-contract props, not dead code.
They survive because DialogManager is the only caller and it passes getDialogMaxHeight(...),
which already clamps to >= 1 (layoutUtils.ts) — the guard is correct exactly as it stands and
is unreachable through the shipped path.
This is completeness reporting, not a merge condition. The fixture that would pin it is a render
at availableTerminalHeight={0} asserting one row.
F3 — Informational, pre-existing: terminal escapes in skill descriptions are forwarded verbatim
An OSC-8 hyperlink payload placed in a skill description reaches the rendered frame unchanged:
SWEEP|head|OSC8_LINK|…|oscPassthrough=true — and identically on the control,
SWEEP|base|OSC8_LINK|…|oscPassthrough=true. oneLine() collapses line breaks only; it does not
strip escapes, and does not claim to.
Reported for completeness because the PR touches this exact expression, and explicitly not
attributed to it: base and head agree, so nothing regressed. A project-level SKILL.md in a
cloned repository is untrusted content, and this is a path from that content to the user's
terminal. No exploit was demonstrated and none is claimed — the observable is that the bytes
survive. strip-ansi is already a packages/cli dependency and packages/cli/src/ui/utils/textUtils.ts
already uses it, so if a maintainer wants this closed it is cheap; it is out of scope for a
height-budget fix and should not hold this PR.
Not covered
- Per-commit attribution. The checkout is depth 2:
git rev-list HEAD^1..HEAD^2returns 1
commit while the snapshot'scommitsarray lists 17.git rev-parse --is-shallow-repository
istrue. Only the aggregateHEAD^1..HEADdiff was verified; no per-commit table is claimed. - No real TTY. This job has no terminal, so all rendering evidence is
ink-testing-library
frames of the real components — not a live tmux session and not the reporter's screenshot from
bug(cli): /statusline dialog is clipped in short terminals #9037. I reproduced the shape (row counts against the same budget the clip box uses), not
the reporter's end-to-end visual. Test Plan step 1 ("start Qwen Code with a short terminal
height") was therefore performed at the component seam, and step 4's "excluded from search" was
verified through the code path (filteredLockedreturns[]when constrained) plus the badge
measurement, not by typing a query that matched only a locked skill. - Non-English locales. The nine locale files each add
(+{{count}} locked). I rendered only
the default locale, so a long translation wrapping the subtitle at narrow widths is unmeasured —
the subtitle does carrywrap="truncate", which is why I expect it to hold, but that is
reading, not measurement. - Production
staticExtraHeight. Not measured, so the real-world reachability of budgets ≤ 5
(F1) is unquantified. - Sibling dialogs.
ThemeDialog,ModelDialog,SettingsDialog,ConsentPromptand
ShellConfirmationDialogalso consumeavailableTerminalHeightand were not swept for the same
bug class.SettingsDialogalready usesclampDialogHeight; the others were not examined. - Gates run vs skipped. Run: the four affected test files (70 passed / 70),
tsc --noEmitfor
packages/cli(0 errors),eslinton the eight touched files (0 problems, with a planted
unused-variable violation confirmed reported and then reverted, so the gate is proven live).
Skipped: the repo-wide unit suite,npm run lint/format/preflight, integration tests, and a
trial merge into currentmain(no network or token in this job; the head already mergedmain
on 2026-09-06, andHEAD^1is the checkout's base tip). screen.diffin the context directory lists the same 19 files asgit diff HEAD^1..HEADbut
is not byte-identical to it. I treated the live git diff as authoritative and did not investigate
the discrepancy.
Methodology
Container job on the merge-ref checkout (HEAD = merge commit, HEAD^1 = base tip,
HEAD^2 = PR head), Node v22.23.2, with npm ci and npm run build already completed at head.
The base arm is a scratch worktree at HEAD^1 under tmp/base-tree. Control cleanliness was
established before trusting any cell: git diff --name-only HEAD^1..HEAD over packages/core,
package.json, package-lock.json and every packages/*/package.json is empty, and
git diff --stat HEAD^1..HEAD -- packages/core is empty, so the dependency tree and core are
byte-identical between arms and reusing the root node_modules is a pure code A/B. The base
worktree needed two mechanical fixes that change nothing it loads: npm run generate for
src/generated/git-commit.ts, and symlinks for untouched packages' dist/ and package-local
node_modules/ (the base packages/cli vitest config aliases @qwen-code/qwen-code-core to the
base tree's own ../core/src, and root ajv is 6.15.0 while core's local ajv is 8.20.0, which
is what the missing link actually broke). Both harnesses are byte-identical on the two arms
(diff verified) and use only props whose interfaces were compared across arms first; canonical
copies are in harness/, raw per-cell output in logs/, and the mutation driver is
harness/mutation-matrix.sh (it re-runs the four test files per mutation, records the suite
counts, verifies by sha256 that each mutation actually applied, and restores via git checkout --).
Every number above comes from a scripted check that ran: 412 assertions across four arm/harness
runs (head 232 + 30, base 120 + 30), tallied by the harnesses themselves into the TALLY lines in
logs/.
Flakiness gate log
rounds=5 files=4 skipped=0
file packages/cli/src/ui/components/StatusLineDialog.test.tsx: (cd packages/cli) npx --no-install vitest run ./src/ui/components/StatusLineDialog.test.tsx
file packages/cli/src/ui/components/shared/MultiSelect.test.tsx: (cd packages/cli) npx --no-install vitest run ./src/ui/components/shared/MultiSelect.test.tsx
file packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx: (cd packages/cli) npx --no-install vitest run ./src/ui/components/skills/SkillsManagerDialog.test.tsx
file packages/cli/src/ui/statusLinePresets.test.ts: (cd packages/cli) npx --no-install vitest run ./src/ui/statusLinePresets.test.ts
per-file results (P=pass F=fail I=infra-exit, one letter per run):
packages/cli/src/ui/components/StatusLineDialog.test.tsx: PPPPP
packages/cli/src/ui/components/shared/MultiSelect.test.tsx: PPPPP
packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx: PPPPP
packages/cli/src/ui/statusLinePresets.test.ts: PPPPP
verdict: pass
summary: 4 changed test file(s) x 5 identical rounds, no divergence
--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/cli/src/ui/components/StatusLineDialog.test.tsx: P (exit 0)
round 1 · packages/cli/src/ui/components/shared/MultiSelect.test.tsx: P (exit 0)
round 1 · packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx: P (exit 0)
round 1 · packages/cli/src/ui/statusLinePresets.test.ts: P (exit 0)
round 2 · packages/cli/src/ui/components/StatusLineDialog.test.tsx: P (exit 0)
round 2 · packages/cli/src/ui/components/shared/MultiSelect.test.tsx: P (exit 0)
round 2 · packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx: P (exit 0)
round 2 · packages/cli/src/ui/statusLinePresets.test.ts: P (exit 0)
round 3 · packages/cli/src/ui/components/StatusLineDialog.test.tsx: P (exit 0)
round 3 · packages/cli/src/ui/components/shared/MultiSelect.test.tsx: P (exit 0)
round 3 · packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx: P (exit 0)
round 3 · packages/cli/src/ui/statusLinePresets.test.ts: P (exit 0)
round 4 · packages/cli/src/ui/components/StatusLineDialog.test.tsx: P (exit 0)
round 4 · packages/cli/src/ui/components/shared/MultiSelect.test.tsx: P (exit 0)
round 4 · packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx: P (exit 0)
round 4 · packages/cli/src/ui/statusLinePresets.test.ts: P (exit 0)
round 5 · packages/cli/src/ui/components/StatusLineDialog.test.tsx: P (exit 0)
round 5 · packages/cli/src/ui/components/shared/MultiSelect.test.tsx: P (exit 0)
round 5 · packages/cli/src/ui/components/skills/SkillsManagerDialog.test.tsx: P (exit 0)
round 5 · packages/cli/src/ui/statusLinePresets.test.ts: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
Runtime verification — round 2, at head
|
| terminal rows | /skills BEFORE |
/skills AFTER |
/statusline BEFORE |
/statusline AFTER |
|---|---|---|---|---|
| 8 | 0 | 3 | 0 | 2 |
| 10 | 0 | 5 | 0 | 4 |
| 12 | 0 | 2 | 0 | 6 |
| 14 | 1, 1 artifact | 4 | 1 | 8 |
| 16 | 2, 2 artifacts | 6 | 1 | 9 |
| 18 | 2, 2 artifacts | 2 | 2 | 9 |
| 20 | 3, 1 artifact | 4 | 4 | 9 |
| 24 | 5, 2 artifacts | 8 | 7 | 3 |
| 30 | 11, 1 artifact | 14 | 9 | 9 |
| 40 | 14, 1 artifact | 15 | 9 | 9 |
Zero overpaint artifacts on the PR arm at every size; the BEFORE arm produces them from 14 rows up. The Manage Skills heading is clipped away on the BEFORE arm at every height tested including 40 (wrapped description rows push the top of the dialog out of the overflow="hidden" box); the PR shows it from 12 rows up, which is where the bare tier ends.
The /statusline 24-row cell is worth reading correctly: 7 on BEFORE is seven rows painted, several of them corrupt; 3 on AFTER is three rows that are actually right. At a 24-row terminal the dialog budget is 19 and the full layout's chrome is exactly 15, so 4 items is the honest number.
Width-awareness at 46 columns still holds — the pre-fix dialog paints its footer into its own border and drops the list entirely:
Reviewer test plan, re-run at this head: all four steps reproduce. Every preset stays reachable in the compact layout (16 × Down walks from Use theme colors to session-id); Enter still saves (Down×6 → Space → Enter at 100×12 wrote ui.statusLine.items including total-input-tokens and the live status line picked it up in the same frame); the /skills bare tier at 100×10 does not filter on the hidden query, and Down → Space → Esc persisted {"skills":{"disabled":["proj-beta"]}} to workspace settings.
2. The #10600 merge reconciliation is behaviourally sound
The /resolve merge replaced initialResolved = resolveSkillSettings(settings) with config.isSkillEnabled(skill) and dropped lockedNames / defaultDisabled from the computeWorkspaceSkillListUpdates call, matching main's new signature. I checked the observable consequence rather than the diff: with skills.disabled: ["proj-beta","proj-gamma"] pre-seeded at workspace scope, the PR build opens /skills with both rows unchecked. Correct. (The BEFORE arm can't even be read at that size — the rows are overpainted.)
I also compared the two arms with zero higher-scope locks at 40 rows: the only difference is that the PR collapses multi-line descriptions to one truncated row instead of wrapping them to two. For users with no skills.disabled at User/System/SystemDefaults scope, this PR is a strict improvement with no information loss.
3. Finding 1 (carried over, still live) — the locked-skills section is dropped at every terminal size
SkillsManagerDialog.tsx:229 still gates on the existence of a budget rather than its size:
const constrained = availableTerminalHeight !== undefined; // :229
// …
const filteredLocked = useMemo(() => {
if (constrained) return []; // :245This round I have a sharper demonstration than last time. At 100×40 — 40 rows, dialog budget 35, no height pressure whatsoever — searching for a locked skill:
main answers 4 / 27 skills and names each locked skill with the scope that locked it. This PR answers 0 / 27 skills + No skills match the search. while the same subtitle says (+4 locked). There is no longer any in-product way to learn which settings scope is holding a skill down.
I also found the one escape hatch, and it does not help. availableTerminalHeight is undefined for this dialog only when uiState.constrainHeight === false (DialogManager.tsx:84/504), and Ctrl+S with the dialog already open does clear it — the locked rows appear. But AppContainer.tsx:4663-4665 sets constrainHeight back to true on the next keypress, so the first character you type to search for a locked skill takes them away again:
So the accurate statement is: you can glimpse the locked list, but you cannot search it, and the [locked: <scope>] labels are only ever visible in a state that any keystroke destroys. The All available skills are locked at a higher scope (see below). string this PR removes from all nine locales is gone rather than relocated.
Blast radius is bounded — lockedSkills is non-empty only when something appears in skills.disabled at SystemDefaults/User/System scope (buildHigherDisabled, :98-118), which the dialog itself never writes (it only writes Workspace scope). So this hits hand-edited user settings and admin/system policy, not the default install. But for exactly those users it is a regression relative to main, at every terminal height.
The suggested shape is unchanged and small: decide the collapse from the budget rather than from "a budget exists" — keep the locked rows while residual can hold them, and fall back to the (+N locked) badge (which is a genuine improvement) only when it cannot.
4. Finding 2 (same root cause) — the count contract is inconsistent
SkillsManagerDialog.tsx:542-543: totalCount = allSkills.length includes locked skills, while matchedCount = filteredUnlocked.length + filteredLocked.length cannot, because filteredLocked is unconditionally [] on the constrained path. That is the 0 / 27 above. Separately, (+4 locked) is rendered as an addendum to a total that already contains those 4, so it reads as 31. Both fall out of the Finding 1 fix.
5. Finding 3 (new this round) — a tier-boundary cliff, PR-only
Growing the terminal by one row can shrink the list, because crossing the tier boundary reinstates a frame that costs more than the row you gained. Measured on the PR arm, unfiltered:
| dialog | terminal rows | selectable rows, PR | selectable rows, main |
|---|---|---|---|
/statusline |
19 | 9 | — |
/statusline |
20 | 9 | 4 |
/statusline |
21 | 1 | 5 |
/statusline |
22 | 1 | 6 |
/skills |
16 | 6 | 2 |
/skills |
17 | 1 | — |
/skills |
18 | 2 | 2 |
Nothing is clipped — the 21-row frame fits its budget exactly — so this is not the bug the PR set out to fix, and main is monotonic here only because it overflows instead. But at 21 rows the dialog spends 15 rows of chrome to show a single option, where its own compact layout one row earlier shows nine, and 21–26 rows is squarely inside the "short terminal" range this PR targets. A floor on the switch (stay compact until the full layout can show at least N items) would remove it. I'd call this a follow-up rather than a blocker.
6. Test strength — 18 mutants, 12 killed
70/70 tests in the four touched files pass, as do 461 tests across DialogManager.test.tsx, shared/, layouts/, layoutUtils.test.ts and config/skill-settings.test.ts. tsc --noEmit on packages/cli is clean and so is eslint on all eight touched files — the PR description's "package-wide TypeScript checking remains blocked" caveat no longer applies to packages/cli at this head.
| # | mutation | result |
|---|---|---|
| M1 | constrained → false |
killed (11) |
| M2 | bare threshold -6 → -7 |
killed (4) |
| M3 | compact <= → < |
killed (1) |
| M4 | /statusline full-layout threshold +1 → +0 |
killed (2) |
| M5 | /skills maxItemsToShow +1 |
killed (4) |
| M6 | MultiSelect ignores truncateLabels |
killed (1) |
| M7 | oneLine() → identity |
killed (1) |
| M8 | bare no longer bypasses a retained query |
killed (2) |
| M9 | backspace not suppressed in bare mode | killed (1) |
| M10 | /statusline Esc drops the hasFullLayout guard |
survived |
| M11 | /statusline printable chars drop the hasFullLayout guard |
survived |
| M12 | separator no longer excluded from search | killed (1) |
| M13 | j/k early return drops bare |
survived — equivalent |
| M14 | /statusline full-layout maxItemsToShow +1 |
killed (3) |
| M15 | buildStatusLinePresetLines newline scrub removed |
killed (7) |
| M16 | initial checked state ignores config.isSkillEnabled |
survived |
| M17 | compact tier's frameRows reverts to the full frame |
survived |
| M18 | /statusline maxItemsToShow cap 10 → 20 |
survived — benign |
Three of these are worth acting on:
-
M16 —
SkillsManagerDialog.test.tsx:52mocksisSkillEnabled: vi.fn().mockReturnValue(true), so the production dependency the feat: manage workspace-scoped extension skill state #10600 merge introduced is inert in the suite: replacing the whole predicate with() => trueleaves all 26 tests green. I verified the real behaviour by hand (§2), but nothing in CI would catch it regressing. A single case with a workspace-disabled skill would close it. -
M17 — the budget tests are all
toBeLessThanOrEqual, never a lower bound, so nothing pins that the compact tier actually reclaims the 6 rows it sheds. RevertingframeRowsfor the compact tier turns/skillsat 100×16 from 6 selectable rows into 1, wastes 5 blank rows, and the entire suite stays green:The
/statuslinesuite already has the counterpart (uses every available row in an intermediate compact layout); the/skillssuite is missing it. -
M10 / M11 — carried over from round 1 and unchanged.
closes on escape in the compact layoutruns with an empty query, andbypasses a hidden query in compact mode and restores itnever types while compact, so neither guard is witnessed. The shipped code is correct on both (I re-checked with a live resize round trip); it is purely a missing test, reachable in practice by resizing the terminal while the dialog is open.
M13 is an equivalent mutant and I would not ask for a test: dropping bare from the j/k early return lets those keys fall through to the printable-character branch, which is itself !bare-gated, and MultiSelect receives the keypress from its own subscription regardless — so the behaviour is identical. The guard is redundant, not load-bearing. M18 is benign: the 10 cap is a product choice, and raising it overflows nothing at any budget.
Summary
| Fixes #9037 at this head | ✅ verified end-to-end at 8/10/12/14/16/18/20/24/30/40 rows and 46 columns |
| Reviewer test plan | ✅ all four steps reproduce |
| #10600 merge reconciliation | ✅ workspace-scoped disables render correctly |
| No-locked-skills case | ✅ strict improvement over main, no information loss |
Regressions vs main |
matchedCount / (+N locked) inconsistent (F2) |
| New this round | |
| Tests | 70/70 + 461 neighbours pass; tsc and eslint clean; 12 of 18 mutants killed |
| Recommendation | Fix F1 (one condition, using a value the file already computes) before merge; F2 falls out of it; F3, M16 and M17 are good follow-ups |
Harness details
- Worktree at
68ea6ff2b2,npm installreconciled against the branch's own lockfile. Both arms vianpm run build && npm run bundle;dist-pr= PR sources,dist-base= the five paths above reverted toorigin/main. Arm identity checked by probing the bundles forSTATUS_LINE_DIALOG_FIXED_ROWS,(+{{count}} locked)andAll available skills are locked at a higher scope— 1/19/0 on the PR arm, 0/0/19 on the base arm. origin/mainhas no commits touching any file this PR changes since the merge base, andgit merge-treereports a clean merge.- Isolated
HOMEper run, so no real user settings were touched. Fixture regenerated per session so persistence probes start from a known state. - Driven with
tmux new-session -x <cols> -y <rows>+send-keys, observed withcapture-paneonly (nevertee, which flips the render mode). Screenshots arecapture-pane -eANSI rendered through xterm.js under Playwright. - Mutants applied to the TypeScript sources and run against the PR's own four test files; M17's blast radius measured by re-bundling the mutant and driving the real CLI.
- Artifact-count metric = a rendered list line with text after its trailing
(Project)/(User)/(Bundled)scope tag, box borders stripped first.
中文说明
PR #9040 运行时验证报告(第 2 轮,head 68ea6ff2b2)
我的第 1 轮报告是在 d1590f1a 上做的。此后分支合入了三次 main 加一次测试修复,其中包括针对 #10600(工作区作用域技能状态)的 /resolve 冲突解决——因此我在当前 head 上从零重建了整套装置并重跑了全部验证。
方法与上轮相同:两臂都从同一个 worktree、同一份 node_modules 构建;BEFORE 臂只把 StatusLineDialog.tsx、MultiSelect.tsx、SkillsManagerDialog.tsx、statusLinePresets.ts 和 src/i18n/locales/*.js 回退到 origin/main(1feb3804cf,也是本 PR 所有改动文件的 merge base)。其余完全一致,因此唯一变量就是这份 diff。在隔离 HOME 下用固定尺寸的 tmux PTY 驱动真实 TUI。
结论:在当前 head 上修复依然成立,合并冲突的解决在行为上也是正确的。第 1 轮的发现 1 仍然存在,仍然是我建议合并前修掉的唯一一点。 本轮新增一个发现(层级边界断崖)和两个新的存活变异体。
1. 问题依然可复现,PR 依然修好了
/statusline 100×12、/skills 100×18 的对比见上文英文部分的前两张图:修复前标题丢失、行与行互相覆写((Project)t)、(Project)oject)、[locked: User]]),修复后是干净的对话框。
高度扫描表见上文(单元格 = 可见可选行数 / 检测到的覆写残影数)。PR 臂在所有尺寸下零残影;BEFORE 臂从 14 行起就开始出现。Manage Skills 标题在 BEFORE 臂上于我测试的所有高度(含 40 行)都被裁掉——换行的描述行把对话框顶部挤出了 overflow="hidden" 盒子;PR 之后从 12 行起标题都在(12 行以下是设计上的 bare 层)。
/statusline 24 行那一格需要正确解读:BEFORE 的 7 是「画了七行、其中几行是坏的」,AFTER 的 3 是三行真正正确的内容。24 行终端的对话框预算是 19,完整布局的固定开销恰好是 15,所以 4 项是诚实的数字。
46 列的宽度感知同样成立——修复前的对话框把页脚画进自己的边框并且整个列表消失(见上文第三张图)。
审阅者测试计划在本 head 上四步全部复现:紧凑布局下 16 次 Down 可从 Use theme colors 走到 session-id;100×12 下 Down×6 → Space → Enter 把含 total-input-tokens 的 ui.statusLine.items 写入用户设置且状态栏同帧生效;100×10 的 /skills bare 层不会用隐藏查询过滤,Down → Space → Esc 把 {"skills":{"disabled":["proj-beta"]}} 持久化到工作区设置。
2. 与 #10600 的合并解决在行为上是正确的
/resolve 把 initialResolved = resolveSkillSettings(settings) 换成了 config.isSkillEnabled(skill),并从 computeWorkspaceSkillListUpdates 调用里去掉了 lockedNames / defaultDisabled,与 main 的新签名一致。我没有只看 diff,而是看可观察后果:在工作区作用域预置 skills.disabled: ["proj-beta","proj-gamma"] 后打开 /skills,PR 构建把这两行渲染为未勾选。正确。(BEFORE 臂在该尺寸下根本读不出来——行被覆写了。)
我还在 40 行、没有任何高作用域锁定的情况下对比了两臂:唯一差别是 PR 把多行描述折叠成一行并截断,而不是换行成两行。对于 ~/.qwen/settings.json 等处没有 skills.disabled 的用户,本 PR 是纯粹的改进,没有信息损失。
3. 发现 1(沿用上轮,仍然存在)——锁定技能区块在任何终端尺寸下都被丢弃
SkillsManagerDialog.tsx:229 判断的仍然是「预算是否存在」而不是「预算够不够」(const constrained = availableTerminalHeight !== undefined;,随后 :245 处 if (constrained) return [];)。
本轮我拿到了比上轮更锋利的证据。在 100×40(40 行终端、对话框预算 35、完全没有高度压力)下搜索一个被锁定的技能:main 回答 4 / 27 skills 并逐条指出是哪个作用域锁住了它;本 PR 回答 0 / 27 skills + No skills match the search.,而同一行副标题却写着 (+4 locked)。用户在产品内再也无法得知究竟是哪个设置作用域压住了某个技能。(见上文第四张图。)
我也找到了唯一的逃生口,但它没有用。对这个对话框来说 availableTerminalHeight 只有在 uiState.constrainHeight === false 时才是 undefined(DialogManager.tsx:84/504),而在对话框已打开时按 Ctrl+S 确实会清除它——锁定行会出现。但 AppContainer.tsx:4663-4665 会在下一次按键时把它设回 true,所以你为了搜索锁定技能而敲下的第一个字符就会让它们再次消失(见上文第五张图)。
准确的说法是:你可以瞥见锁定列表,但无法搜索它,而 [locked: <scope>] 标签只在一个任何按键都会摧毁的状态里可见。本 PR 从九个语言包中删掉的 All available skills are locked at a higher scope (see below). 是被彻底移除而非搬到别处。
影响面是有界的——只有当 SystemDefaults/User/System 作用域的 skills.disabled 里有内容时 lockedSkills 才非空(buildHigherDisabled,:98-118),而对话框自己只写 Workspace 作用域。所以受影响的是手工编辑过用户设置的人和受管理员/系统策略约束的人,不是默认安装。但对这部分用户来说,这在所有终端高度上都是相对 main 的回归。
建议的修法不变且很小:让折叠由预算大小决定,而不是由「存在预算」决定——只要 residual 还放得下就保留锁定行,放不下时再退回 (+N locked) 徽标(这个徽标本身是真正的改进)。
4. 发现 2(同源)——计数契约不自洽
SkillsManagerDialog.tsx:542-543:totalCount = allSkills.length 包含锁定技能,而 matchedCount = filteredUnlocked.length + filteredLocked.length 不可能包含,因为受限路径上 filteredLocked 恒为 [],于是就有了上面的 0 / 27。另外 (+4 locked) 是作为「额外」附加在一个已经包含这 4 个的总数后面,读起来像 31。两者都会随发现 1 的修复一并解决。
5. 发现 3(本轮新增)——层级边界断崖,仅 PR 存在
把终端高度增加一行反而可能让列表变短,因为跨过层级边界会重新装上一个比你新得到的那一行更贵的外框。PR 臂实测(未过滤):/statusline 20 行 → 9 项,21 行 → 1 项,22 行 → 1 项;main 对应是 4 / 5 / 6,单调。/skills 16 行 → 6 项,17 行 → 1 项,18 行 → 2 项。(见上文第六张图。)
没有任何内容被裁剪——21 行的外框恰好落在预算内——所以这不是本 PR 要修的那个 bug,main 在这里单调也只是因为它直接溢出了。但 21 行时对话框花 15 行外框只为显示一个选项,而它自己的紧凑布局在低一行时能显示九个;而且 21–26 行正落在本 PR 所针对的「短终端」区间里。给层级切换加一个下限(在完整布局至少能显示 N 项之前保持紧凑)就能消除它。我认为这属于后续项而非阻塞项。
6. 测试强度——18 个变异体,杀死 12 个
四个被改动文件的 70 个测试全部通过,DialogManager.test.tsx、shared/、layouts/、layoutUtils.test.ts、config/skill-settings.test.ts 的 461 个测试也全部通过。packages/cli 的 tsc --noEmit 干净,八个改动文件的 eslint 也干净——PR 描述里「包级 TypeScript 检查仍被阻断」的说明在本 head 上对 packages/cli 已不再成立。完整变异体表格见上文英文部分。
其中三项值得处理:
- M16 ——
SkillsManagerDialog.test.tsx:52把isSkillEnabledmock 成vi.fn().mockReturnValue(true),于是 feat: manage workspace-scoped extension skill state #10600 合并引入的这个生产依赖在测试里是惰性的:把整个判定换成() => true,26 个测试全绿。我已手工验证了真实行为(见第 2 节),但 CI 抓不到它退化。补一个「工作区禁用了某技能」的用例即可闭合。 - M17 —— 预算类测试全部是
toBeLessThanOrEqual,从来没有下界,所以没有任何测试钉住紧凑层真的把它省下的 6 行用起来了。把紧凑层的frameRows改回完整外框,/skills在 100×16 下从 6 行可选变成 1 行、浪费 5 行空白,而整套测试依然全绿(见上文第七张图)。/statusline套件已经有对应用例(uses every available row in an intermediate compact layout),/skills套件缺这一条。 - M10 / M11 —— 沿用上轮且未变化。
closes on escape in the compact layout在空查询下运行,bypasses a hidden query in compact mode and restores it从未在紧凑状态下真的输入过,因此这两个守卫都没有被见证。发布代码这两点都是对的(我用实时缩放往返复核过),纯属缺测试,而且实践中「对话框打开时缩放终端」就能触达。
M13 是等价变异体,我不会为它要求补测试:把 bare 从 j/k 的提前返回里去掉后,这两个键会落到可打印字符分支,而该分支本身就被 !bare 挡住;同时 MultiSelect 通过自己的订阅照样收到这次按键。行为完全相同,这个守卫是冗余而非承重。M18 是良性的:10 这个上限是产品取舍,调高它在任何预算下都不会溢出。
总结
| 在本 head 上是否修复 #9037 | ✅ 在 8/10/12/14/16/18/20/24/30/40 行及 46 列下端到端验证通过 |
| 审阅者测试计划 | ✅ 四步全部复现 |
| #10600 合并冲突解决 | ✅ 工作区作用域禁用渲染正确 |
| 无锁定技能的场景 | ✅ 相对 main 是纯粹改进,无信息损失 |
相对 main 的回归 |
matchedCount / (+N locked) 不自洽(F2) |
| 本轮新增 | |
| 测试 | 70/70 + 461 邻近用例通过;tsc、eslint 干净;18 个变异体杀死 12 个 |
| 建议 | 合并前修掉 F1(一个条件,用的还是文件里已经算好的值);F2 随之解决;F3、M16、M17 作为后续项 |















What this PR does
Prevents the
/statuslineand/skillsconfiguration dialogs from rendering beyond a constrained terminal-height budget./statuslineuses a selectable compact layout below 16 rows, while/skillslimits locked-skill rows only when a height budget is present and preserves every locked skill otherwise.Why it's needed
The dialogs reserved a fixed amount of chrome while always rendering at least one list row. On short terminals, the total height exceeded the parent budget, clipping content and making options unreachable. This restores keyboard access to the complete selectable list without changing the normal-height layout.
Reviewer Test Plan
How to verify
/statusline.jto move through the compact/statuslinelist; every preset remains reachable and Enter still saves the selection./skillswith several skills disabled at a higher scope. Confirm that a constrained terminal never exceeds its available rows, locked skills appear only as the(+N locked)count and are excluded from search, and an unconstrained terminal shows every locked row.Evidence (Before & After)
Before: the clipped
/statuslinedialog is documented in #9037 with the reporter's screenshots.After: targeted Ink rendering tests assert the one-row
/statuslinelayout, its keyboard navigation, the 18-row/skillsbudget, and unconstrained locked-skill visibility.Tested on
Environment (optional)
Linux; Node.js 24.18.0; targeted CLI Vitest rendering tests.
Risk & Scope
/statuslinelayouts omit nonessential title, search, preview, and help text to reserve all rows for the selectable list.Linked Issues
Closes #9037
中文说明
此 PR 的内容
防止
/statusline和/skills配置对话框超出受限终端高度预算。/statusline在低于 16 行时使用可选择的紧凑布局;/skills只在存在高度预算时限制锁定技能行数,并在没有高度约束时保留全部锁定技能。为什么需要它
这些对话框会预留固定的界面行,同时始终渲染至少一行列表。在短终端中,总高度会超过父级预算,导致内容被裁剪且选项无法访问。此修复在不改变正常高度布局的前提下恢复了通过键盘访问完整可选列表的能力。
审阅者测试计划
如何验证
/statusline。j在紧凑/statusline列表中移动;每个预设均可访问,Enter 仍会保存选择。/skills。确认受限终端不会超过可用行数,锁定技能仅以(+N locked)计数显示且不参与搜索;未受限终端会显示全部锁定行。证据(修复前与修复后)
修复前:#9037 包含报告者提供的被裁剪
/statusline对话框截图。修复后:针对 Ink 渲染的测试断言了一行
/statusline布局及其键盘导航、18 行/skills预算,以及无约束时锁定技能的可见性。测试环境
环境(可选)
Linux;Node.js 24.18.0;目标 CLI Vitest 渲染测试。
风险与范围
/statusline布局会省略非必要的标题、搜索、预览和帮助文本,以便将所有行留给可选择列表。关联问题
Closes #9037