Skip to content

fix(cli): switch @ completion category tabs with bare arrow keys - #8576

Merged
LaZzyMan merged 1 commit into
QwenLM:mainfrom
LaZzyMan:lazzy/8069-bare-arrow-completion-tabs
Aug 11, 2026
Merged

fix(cli): switch @ completion category tabs with bare arrow keys#8576
LaZzyMan merged 1 commit into
QwenLM:mainfrom
LaZzyMan:lazzy/8069-bare-arrow-completion-tabs

Conversation

@LaZzyMan

@LaZzyMan LaZzyMan commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Makes bare Left and Right switch the visible category tabs in @ completion, replacing the Ctrl+arrow and Ctrl+Tab bindings. The arrows are consumed only when the tab bar is actually shown. Vim mode follows the same visible-tab contract, while command search, reverse search, export suggestions, attachment selection, and completion menus with at most two categories retain their existing arrow behavior.

While the category tabs are visible, Ctrl+B and Ctrl+F remain available for character-wise caret movement. Esc dismisses the menu and restores normal bare-arrow caret movement. Alt/Option+arrow word movement and terminal-owned Ctrl+arrow behavior are unaffected.

The on-screen hint and keyboard shortcut reference are updated to match.

Why it's needed

The previous Ctrl+arrow gesture was commonly intercepted for word movement or by macOS Mission Control, and Ctrl+Tab is also intercepted by many terminals. Bare arrows restore the interaction originally promised by the category-tab UI while limiting the ownership change to the state where users can see those tabs.

Reviewer Test Plan

How to verify

  1. Open @ completion with at least three available categories and confirm the hint reads (←/→ to switch).
  2. Press Left and Right and confirm the active category changes, including with Vim mode enabled.
  3. Confirm Ctrl+Left, Ctrl+Right, Ctrl+Tab, and Ctrl+Shift+Tab do not switch categories; Ctrl+B and Ctrl+F continue to move the caret.
  4. Enter command search or reverse search and confirm Left and Right do not change a hidden completion category.
  5. Enter attachment selection and confirm Left and Right select attachments rather than changing completion categories.
  6. Confirm the tabs are hidden and arrows retain their prior behavior for export suggestions and menus with at most two categories.

Evidence (Before & After)

Before: the tab bar advertised Ctrl+arrow and Ctrl+Tab gestures that were commonly intercepted before reaching the application. After: it advertises (←/→ to switch), and bare arrows switch only while the category tabs are visible. Automated interaction tests cover Vim precedence, hidden search tabs, attachment ownership, two-category behavior, removed modified-arrow bindings, and the visible hint. No screenshot or recording was captured.

Tested on

OS Status
🍏 macOS ✅ focused tests (266), build, typecheck, lint
🪟 Windows ⚠️ not tested locally
🐧 Linux ⚠️ not tested locally

Environment (optional)

Node.js 22 in a local macOS worktree.

Risk & Scope

  • Main risk or tradeoff: while visible category tabs own bare Left and Right, those keys do not move the caret; Ctrl+B, Ctrl+F, or dismissing the menu remain available.
  • Not validated / out of scope: manual Windows and Linux interaction testing and mouse-based category switching.
  • Breaking changes / migration notes: Ctrl+arrow and Ctrl+Tab no longer switch categories.

Linked Issues

Related to #8069 and #8330. Follow-up to #7302 and complementary to #8395.

中文说明

本 PR 做了什么

@ 补全在分类标签可见时使用裸 Left 和 Right 切换分类,替换 Ctrl+方向键与 Ctrl+Tab 绑定。只有标签栏真实显示时才会消费方向键。Vim 模式遵循同一可见标签合同;命令搜索、反向搜索、导出补全、附件选择,以及最多只有两个分类的补全菜单都保留原有方向键行为。

分类标签可见期间,Ctrl+B 和 Ctrl+F 仍可逐字符移动光标;按 Esc 关闭菜单后,裸方向键恢复常规光标移动。Alt/Option+方向键的按词移动以及终端自身处理的 Ctrl+方向键不受影响。

界面提示与键盘快捷键文档同步更新。

为什么需要

此前的 Ctrl+方向键通常会被终端用于按词移动,或被 macOS 调度中心拦截;Ctrl+Tab 也经常被终端占用。裸方向键恢复分类标签界面最初承诺的交互,并把按键所有权变化严格限制在用户能看到这些标签的状态。

Reviewer 测试计划

如何验证

  1. 打开至少包含三个可用分类的 @ 补全,确认提示为 (←/→ to switch)
  2. 按 Left 和 Right,确认激活分类切换;Vim 模式下也应如此。
  3. 确认 Ctrl+Left、Ctrl+Right、Ctrl+Tab 和 Ctrl+Shift+Tab 不会切换分类;Ctrl+B 和 Ctrl+F 仍可移动光标。
  4. 进入命令搜索或反向搜索,确认 Left 和 Right 不会切换隐藏的补全分类。
  5. 进入附件选择,确认 Left 和 Right 选择附件,而不是切换补全分类。
  6. 确认导出补全和最多只有两个分类的菜单不显示标签,方向键保持原行为。

证据(Before & After)

Before:标签栏提示 Ctrl+方向键与 Ctrl+Tab,但这些按键通常在抵达应用前就被拦截。After:提示为 (←/→ to switch),且裸方向键只在分类标签可见时切换分类。自动交互测试覆盖 Vim 优先级、搜索状态的隐藏标签、附件按键所有权、双分类行为、已移除的修饰键绑定和可见提示。本次没有录制截图或视频。

测试平台

OS 状态
🍏 macOS ✅ 定向测试(266)、构建、类型检查、lint
🪟 Windows ⚠️ 未在本地测试
🐧 Linux ⚠️ 未在本地测试

环境(可选)

Node.js 22,本地 macOS worktree。

风险与范围

  • 主要风险或取舍:分类标签可见时,裸 Left 和 Right 归标签所有,不能移动光标;仍可使用 Ctrl+B、Ctrl+F,或先关闭菜单。
  • 未验证或不在范围内:Windows 与 Linux 的手动交互验证,以及鼠标切换分类。
  • 破坏性改动或迁移说明:Ctrl+方向键与 Ctrl+Tab 不再切换分类。

关联 Issue

关联 #8069#8330;为 #7302 的后续修复,并与 #8395 互补。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finished — CI landed green on 33398d7 and the deferred approval was posted. finalize run

Qwen Triage 已完成 —— 33398d7 的 CI 全绿,延迟审批已提交。查看 finalize 运行

@LaZzyMan

LaZzyMan commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /tmux

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed bug with solid evidence. #8069 documents that terminals (iTerm2, Windows Terminal, Terminal.app, GNOME Terminal) intercept Ctrl+←/→ for word-jump, so the documented tab-switching gesture never reaches qwen-code — confirmed by user testing in the issue, and the earlier partial fix #8074 (Ctrl+Tab alternative) itself acknowledged the original binding was unreachable. The PR's claim that Ctrl+Tab is also commonly intercepted is plausible, and bare arrows are the gesture the original #7302 feature description promised users.

Direction: aligned — this repairs an unreachable gesture in a shipped feature (the category tabs from #7302), which is exactly what the issue's own triage concluded ("Next: rebind tab switching to a non-conflicting key combo"). Claude Code's CHANGELOG has no direct reference to this interaction, but the area is clearly relevant — it's qwen-code's own completion UI.

Size: packages/cli/src/config/keyBindings.ts matches a core-path pattern, but this is maintainer-authored and tiny regardless — 43 production lines (26 in keyBindings.ts, 9 in InputPrompt.tsx, 8 in SuggestionsDisplay.tsx), 111 test lines, 92 docs lines. No split needed.

Approach: scope feels right. Independently I'd have done exactly this: rebind COMPLETION_TAB_LEFT/RIGHT to bare arrows with all modifiers pinned off, keep the consuming branch gated on the tab bar actually being visible (availableCategories.length > 2), and update the hint + shortcut reference. The accepted tradeoff — arrows don't move the caret while the tab bar is up — is documented with Esc / Ctrl+B / Ctrl+F as escape hatches, which is honest and defensible. The alternatives from the issue (Tab cycling at the edges, number keys, mouse) either conflict with existing bindings or add new machinery. Every edit in the diff serves the goal; the large docs table churn is just column re-alignment around two changed cells. No drive-by changes spotted.

Risk: no elevated risk signals — none of the changed files match the high-risk path patterns.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:已观测到的 bug,证据充分。#8069 记录了终端(iTerm2、Windows Terminal、Terminal.app、GNOME Terminal)会拦截 Ctrl+←/→ 用于按词跳转,导致文档中承诺的切换手势根本到不了 qwen-code——issue 中已经过用户实测确认,此前的部分修复 #8074(Ctrl+Tab 备选)也等于承认原绑定不可达。PR 中"Ctrl+Tab 同样常被拦截"的说法可信,而裸方向键正是 #7302 原始功能描述向用户承诺的手势。

方向:对齐——这是修复已上线功能(#7302 的分类标签)中不可达的操作方式,也正是该 issue 自身 triage 的结论("下一步:将标签切换重绑定到不冲突的按键")。Claude Code 的 CHANGELOG 中没有对此交互的直接引用,但该领域显然相关——这是 qwen-code 自己的补全 UI。

规模:packages/cli/src/config/keyBindings.ts 匹配核心路径模式,但本 PR 为维护者提交,且改动本身很小——生产代码 43 行(keyBindings.ts 26 行、InputPrompt.tsx 9 行、SuggestionsDisplay.tsx 8 行),测试 111 行,文档 92 行。无需拆分。

方案:范围合理。我独立推导的方案与此完全一致:把 COMPLETION_TAB_LEFT/RIGHT 重绑定为裸方向键并显式关闭所有修饰键,消费分支继续以标签栏实际可见(availableCategories.length > 2)为前提,并同步更新提示文案与快捷键文档。已接受的取舍——标签栏显示期间方向键不移动光标——有明确说明,且提供 Esc / Ctrl+B / Ctrl+F 作为替代路径,坦诚且站得住脚。issue 中的其他备选方案(边界处 Tab 循环、数字键、鼠标)要么与现有绑定冲突,要么需要新增机制。diff 中每处改动都服务于目标;文档表格的大量变化只是两个单元格变长后的整列重新对齐,无顺手改动。

风险:无升级风险信号——改动文件均未命中高风险路径模式。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 33398d762ab3502672a5f81404516b3f77431942 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Code review

Read the diff against an independent proposal I wrote first from the title and motivation — I'd have done it exactly this way. The integration points that matter for this change all check out:

  • Matcher semantics. matchKeyBinding in keyMatchers.ts treats an omitted modifier as "don't care", so pinning shift/ctrl/command/meta all false on the new bare-arrow bindings is precisely what keeps Ctrl+←/→ (terminal word-jump) and Alt/Option+←/→ (word movement) falling through untouched. (command and meta both check key.meta, so the two fields are redundant here — harmless and consistent with neighboring bindings.)
  • Consumption gate matches rendering. The consuming branch in InputPrompt.tsx gates on availableCategories.length > 2, the exact condition SuggestionsDisplay uses for showTabBar — arrows are consumed if and only if the tab bar is actually on screen. This also resolves the secondary issue in @ completion tab switching uses Ctrl+←/→ which conflicts with terminal word-jump #8069 (hint shown while switching was disabled).
  • Branch ordering is safe. The right-arrow followup-suggestion accept sits above the completion branch but requires an empty buffer, which can't coexist with an open @ menu. Caret movement is the fall-through in BaseTextInput (a key onKeypress doesn't consume reaches the buffer), so Ctrl+B/Ctrl+F still move the caret while the tab bar is up, and bare arrows keep their normal caret behaviour in every other context.
  • Tests pin the right things. The keyMatchers matrix asserts bare arrows match and every modifier variant is rejected, including Ctrl+Tab now being unbound; the InputPrompt tests assert the >2-category switch, the exactly-2 no-switch, and no Ctrl+arrow consumption.

One non-blocking observation: attachment chips are also navigated with bare ←/→, and that branch runs after the completion branch — so while the tab bar is up, arrows switch categories rather than move between chips. It's bounded (Esc restores chip navigation) and consistent with the documented tradeoff, just worth knowing.

Testing

CI evidence read from the GitHub API for the reviewed commit (per policy, the PR's code is never executed by triage):

  • Test (ubuntu-latest, Node 22.x) — the PR's unit signal — is still in progress. Not polling; the table below is updated in place by the finalize job once CI settles.
  • Test (macos-latest / windows-latest) show skipped by design: ci.yml runs those jobs only in the merge queue ("ubuntu is the fast PR signal").
  • Integration Tests (CLI, No Sandbox) is skipped by design for fork PRs (gated on head.repo.full_name == github.repository).
  • Already green: precheck-pr / precheck (×2), Classify PR, label, Desktop Shell (ubuntu-22.04).
  • Not verified: Windows/Linux behaviour — the author reports unit tests + typecheck + lint on macOS only (author's claim, not independently re-run).

Final CI results for 33398d7 (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
route ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

Sandboxed verification would settle the remaining gap: @qwen-code /tmux — the PR's central claim is TUI behaviour (bare arrows reach the app and switch categories while the tab bar is up; Ctrl+arrows are intercepted and never arrive), and this PR's unit tests prove the handler wiring against mock stdin but not what a real terminal actually delivers. A tmux pass would confirm the switch, the updated hint text, and that Esc / Ctrl+B / Ctrl+F restore caret movement.

中文说明

代码审查

先根据标题和动机独立推导方案,再对照 diff——我的独立方案与本 PR 完全一致。关键集成点均已核实:

  • 匹配器语义keyMatchers.tsmatchKeyBinding 对未声明的修饰键视为"不关心",因此新绑定显式关闭 shift/ctrl/command/meta 正是让 Ctrl+←/→(终端按词跳转)和 Alt/Option+←/→(按词移动)原样穿透的关键。(commandmeta 都检查 key.meta,此处两者重复但无害,且与相邻绑定风格一致。)
  • 消费门控与渲染一致InputPrompt.tsx 的消费分支以 availableCategories.length > 2 为条件,与 SuggestionsDisplayshowTabBar 完全相同——方向键仅在标签栏确实可见时被消费。这同时解决了 @ completion tab switching uses Ctrl+←/→ which conflicts with terminal word-jump #8069 的附加问题(切换不可用时仍显示提示)。
  • 分支顺序安全:右方向键接受 followup 建议的分支在前,但要求输入缓冲区为空,而 @ 菜单打开时缓冲区不可能为空。光标移动是 BaseTextInput 的兜底路径(onKeypress 未消费的按键会交给缓冲区处理),因此标签栏显示期间 Ctrl+B/Ctrl+F 仍可移动光标,其他场景下裸方向键保持正常光标行为。
  • 测试钉住了正确行为:keyMatchers 矩阵断言裸方向键匹配、各修饰键变体(包括不再绑定的 Ctrl+Tab)均不匹配;InputPrompt 测试断言超过 2 个分类时切换、恰好 2 个分类时不切换、Ctrl+方向键不触发切换。

一个非阻塞观察:附件 chip 也用裸 ←/→ 导航,且该分支在补全分支之后——因此标签栏显示期间方向键切换分类而非移动 chip。影响有限(Esc 可恢复 chip 导航),与已记录的取舍一致,知晓即可。

测试

CI 证据通过 GitHub API 读取(triage 按规约不执行 PR 代码):

  • Test (ubuntu-latest, Node 22.x)(本 PR 的单测信号)仍在运行,不轮询;下方表格由 finalize 任务在 CI 落定后就地更新。
  • macOS/Windows 单测任务按 ci.yml 设计仅在 merge queue 运行,PR 阶段跳过属预期。
  • 集成测试对 fork PR 按设计跳过(以 head.repo.full_name == github.repository 为门控)。
  • 已通过:precheck、分类、打标签、Desktop Shell。
  • 未验证:Windows/Linux 行为——作者声明仅在 macOS 上运行了单测、typecheck 与 lint(作者自述,非独立复核)。

沙箱验证可补齐剩余缺口:@qwen-code /tmux——本 PR 的核心主张是 TUI 行为(裸方向键能到达应用并切换分类;Ctrl+方向键被终端拦截根本到不了),而单测只对 mock stdin 证明了处理逻辑接线,无法证明真实终端实际送达的按键序列。跑一次 tmux 即可确认切换、更新后的提示文案,以及 Esc / Ctrl+B / Ctrl+F 恢复光标移动。

Qwen Code · qwen3.8-max

Reviewed at 33398d762ab3502672a5f81404516b3f77431942 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, focused fix that matches my independent proposal exactly; one point withheld only for the still-running unit suite and a minor edge case named below.

Stepping back: this is the second attempt at #8069, and it lands on the right answer. The first fix (#8074) bolted on Ctrl+Tab as an alternative after terminals swallowed Ctrl+←/→; this PR correctly recognizes that anything Ctrl-based in this slot is fighting terminal emulators, and moves to the one gesture nothing intercepts — the bare arrows the original #7302 feature promised in the first place. The problem was observed and confirmed (issue triage, user testing), not theoretical, and the fix is the minimal version of itself: one rebind with modifiers pinned off, the same > 2 categories visibility gate, hint and docs updated to match, and a test matrix that pins exactly the new contract.

The tradeoff — arrows switch categories instead of moving the caret while the tab bar is up — is stated plainly in the PR, gated so it can only fire when the tab bar is actually rendered, and always escapable via Esc / Ctrl+B / Ctrl+F. If I'm maintaining this in six months, the comment in keyBindings.ts tells me the whole history (#8069, why not Ctrl, why bare arrows). That's the kind of diff I'd thank the author for.

Reservations, none blocking: the ubuntu unit job is still in flight (the deferred approval below is pinned to it landing green); the attachment-chip navigation edge from my Stage 2 note; and real-terminal behaviour rests on the author's local macOS run plus mock-stdin unit tests — the @qwen-code /tmux line in Stage 2 names what a live pass would settle.

Approval deferred until CI lands green on 33398d762ab3502672a5f81404516b3f77431942 — the finalize job submits the commit-pinned approval once every check completes, and withholds it if anything lands red.

中文说明

信心:4/5 —— 干净、聚焦的修复,与我独立推导的方案完全一致;仅因单测仍在运行及下文提到的一个小边界情况扣一分。

整体来看:这是 #8069 的第二次修复尝试,且落在了正确的答案上。第一次修复(#8074)在终端吞掉 Ctrl+←/→ 后补上了 Ctrl+Tab 备选;本 PR 正确认识到这个键位上任何带 Ctrl 的组合都在与终端模拟器对抗,转而采用不会被任何一方拦截的手势——也正是 #7302 原始功能当初承诺的裸方向键。问题是观测到并确认过的(issue triage、用户实测),不是理论推测;修复本身也是最简形态:一次重绑定并显式关闭所有修饰键、沿用原有的"超过 2 个分类"可见性门控、同步更新提示与文档、测试矩阵精确钉住新契约。

取舍——标签栏显示期间方向键切换分类而非移动光标——在 PR 中坦诚说明,门控保证它只在标签栏真实渲染时生效,且随时可通过 Esc / Ctrl+B / Ctrl+F 脱身。六个月后维护这段代码时,keyBindings.ts 里的注释交代了全部历史(#8069、为何不用 Ctrl、为何用裸方向键)。这是让人感谢作者的 diff。

保留意见(均不阻塞):ubuntu 单测任务仍在运行(下面的延迟批准以其全绿为前提);Stage 2 中提到的附件 chip 导航边界情况;真实终端行为目前只有作者的 macOS 本地运行加 mock stdin 单测支撑——Stage 2 中的 @qwen-code /tmux 一行说明了实机验证可以补齐什么。

批准延迟至 CI 在 33398d762ab3502672a5f81404516b3f77431942 上全绿后执行——finalize 任务会在所有检查完成后提交绑定到该 commit 的批准;若有检查变红则不会批准。

Qwen Code · qwen3.8-max

Reviewed at 33398d762ab3502672a5f81404516b3f77431942 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship — CI landed green after the review. ✅

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

tmux real-user testing: timeout - workflow run

The tmux test did not complete before the time limit. This is not a pass/fail result for the affected flow; see the workflow run and artifacts for details.

Full tmux transcript


===== 01 initial screen - prompt ready =====

   ▄▄▄▄▄▄  ▄▄     ▄▄ ▄▄▄▄▄▄▄ ▄▄▄    ▄▄   ┌──────────────────────────────────────────────────────────┐
  ██╔═══██╗██║    ██║██╔════╝████╗  ██║  │ >_ Qwen Code (v0.21.5)                                   │
  ██║   ██║██║ █╗ ██║█████╗  ██╔██╗ ██║  │                                                          │
  ██║▄▄ ██║██║███╗██║██╔══╝  ██║╚██╗██║  │ API Key | fake-model (/model to change)                  │
  ╚██████╔╝╚███╔███╔╝███████╗██║ ╚████║  │ /__w/.../at-tabs-pr8576-tmux-20260805-094204/workspace   │
   ╚══▀▀═╝  ╚══╝╚══╝ ╚══════╝╚═╝  ╚═══╝  └──────────────────────────────────────────────────────────┘

  Tips: Type / to open the command popup; Tab autocompletes slash commands and saved prompts.
  ●︎ Extensions changed on disk. Run /reload-plugins to apply updates.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
*   Type your message or @path/to/file
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ➜ workspace · git:(054247a) · fake-model
  YOLO mode (shift + tab to cycle)



































===== 02 @ menu open - tab bar with hint =====

   ▄▄▄▄▄▄  ▄▄     ▄▄ ▄▄▄▄▄▄▄ ▄▄▄    ▄▄   ┌──────────────────────────────────────────────────────────┐
  ██╔═══██╗██║    ██║██╔════╝████╗  ██║  │ >_ Qwen Code (v0.21.5)                                   │
  ██║   ██║██║ █╗ ██║█████╗  ██╔██╗ ██║  │                                                          │
  ██║▄▄ ██║██║███╗██║██╔══╝  ██║╚██╗██║  │ API Key | fake-model (/model to change)                  │
  ╚██████╔╝╚███╔███╔╝███████╗██║ ╚████║  │ /__w/.../at-tabs-pr8576-tmux-20260805-094204/workspace   │
   ╚══▀▀═╝  ╚══╝╚══╝ ╚══════╝╚═╝  ╚═══╝  └──────────────────────────────────────────────────────────┘

  Tips: Type / to open the command popup; Tab autocompletes slash commands and saved prompts.
  ●︎ Extensions changed on disk. Run /reload-plugins to apply updates.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
* @ ​
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  > Seeded prior session for PR 8576 tab test Session

Qwen Code · tmux real-user testing

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.21.5)

// more than two tabs (at least 3 entries including 'all'). While this
// menu is open the arrows therefore do NOT move the caret — Esc first to
// dismiss it. Outside this branch ←/→ keep their normal caret behaviour.
if ((completion.availableCategories?.length ?? 0) > 2) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] This branch consumes bare ←/→ for category switching but is gated only on showCompletionSuggestions && availableCategories > 2 — it does not exclude command-search mode or attachment mode, states in which the tab bar is not rendered (SuggestionsDisplay receives availableCategories={undefined} during search). The comment's "gated on the menu being visible" claim does not hold there. — Failure scenario: (1) type @ses (>2 categories), press Ctrl+R: the completion reset effect only checks reverseSearchActive, so showSuggestions stays true while no tab bar renders; bare ←/→ fall through the search block and call switchCategory(±1) on a hidden menu — the caret cannot move inside the search query (zero feedback), and after Esc the menu silently reopens on a different tab. (2) With attachments present and the menu open, switch to a tab holding exactly one suggestion (one bare → now suffices) and press Up at row 0: the attachment-mode entry has no !showCompletionSuggestions gate, so ←/→ then hit this branch before the attachment chip-navigation branch and chips can no longer be arrow-moved until Esc.

Suggested change
if ((completion.availableCategories?.length ?? 0) > 2) {
if (!commandSearchActive && !reverseSearchActive && !isAttachmentMode && (completion.availableCategories?.length ?? 0) > 2) {

A deeper fix is one shared "tab bar visible" predicate consumed by both this branch and showTabBar in SuggestionsDisplay, so key capture and rendering cannot diverge again.

中文说明

[Critical] 该分支会消费裸 ←/→ 来切换分类,但仅以 showCompletionSuggestions && availableCategories > 2 为门控——没有排除命令搜索模式和附件模式,而这两种状态下标签栏并不渲染(搜索期间 SuggestionsDisplay 收到的是 availableCategories={undefined})。注释中"以菜单可见为前提"的说法在这些状态下不成立。— 故障场景:(1) 输入 @ses(>2 个分类)后按 Ctrl+R:补全重置副作用只检查 reverseSearchActive,因此 showSuggestions 保持为 true 而标签栏并未渲染;裸 ←/→ 穿过搜索分支后落到这里,对一个不可见的菜单调用 switchCategory(±1)——搜索框中的光标无法移动(毫无反馈),按 Esc 后重新打开的菜单会悄悄落在另一个分类上。(2) 存在附件且菜单打开时,切换到只有一个建议的分类(现在只需按一下裸 →),在第 0 行按 Up:进入附件模式的分支没有 !showCompletionSuggestions 门控,于是 ←/→ 会先命中本分支而不是附件选择分支,在按 Esc 之前无法再用方向键选择附件。

更彻底的修复是让本分支与 SuggestionsDisplay 的 showTabBar 共用同一个"标签栏可见"谓词,使按键捕获与渲染不再脱节。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment thread packages/cli/src/config/keyBindings.ts Outdated
[Command.COMPLETION_TAB_RIGHT]: [
{ key: 'right', shift: false, ctrl: true, command: false },
{ key: 'tab', shift: false, ctrl: true, command: false },
{ key: 'right', shift: false, ctrl: false, command: false, meta: false },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] With Vim keybindings enabled the new bare-arrow bindings are unreachable: vimHandleInput runs before the completion branch, and Vim INSERT mode consumes bare left/right as caret movement (NORMAL mode consumes everything). This PR simultaneously deletes the Ctrl+Tab / Ctrl+Shift+Tab alternatives that DID pass through Vim insert mode, and Ctrl+Tab now matches the unpinned ACCEPT_SUGGESTION tab entry — so Vim users lose every keyboard path to switch categories while SuggestionsDisplay still renders (←/→ to switch). — Failure scenario: a Vim user in INSERT mode types @ with >2 categories; ←/→ never reach COMPLETION_TAB_*, the old Ctrl+Tab path is gone, and Ctrl+Tab now inserts the highlighted suggestion instead of switching — no keyboard path remains while the hint claims one works.

Suggested change
{ key: 'right', shift: false, ctrl: false, command: false, meta: false },
{ key: 'right', shift: false, ctrl: false, command: false, meta: false },
// Ctrl+Tab passes through Vim insert mode; bare arrows do not.
{ key: 'tab', shift: false, ctrl: true, command: false },

(Add the COMPLETION_TAB_LEFT counterpart { key: 'tab', shift: true, ctrl: true, command: false } to restore the pre-PR behaviour; alternatives are suppressing the tab-switch hint when Vim mode is active, or documenting the limitation.)

中文说明

[Critical] 启用 Vim 键位时,新的裸方向键绑定无法触达:vimHandleInput 在补全分支之前运行,且 Vim INSERT 模式会把裸 left/right 作为光标移动消费掉(NORMAL 模式消费所有按键)。本 PR 同时删除了能够穿透 Vim insert 模式的 Ctrl+Tab / Ctrl+Shift+Tab 备选绑定,而 Ctrl+Tab 现在会命中未固定修饰键的 ACCEPT_SUGGESTION tab 条目——于是 Vim 用户失去了所有切换分类的键盘路径,而 SuggestionsDisplay 仍然显示 (←/→ to switch)。— 故障场景:Vim 用户在 INSERT 模式输入 @(>2 个分类);←/→ 到不了 COMPLETION_TAB_*,旧的 Ctrl+Tab 路径已被删除,Ctrl+Tab 现在会插入高亮建议而不是切换分类——提示文字声称可用的路径已不复存在。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +190 to +191
// for Mission Control, so the documented gesture was unreachable for most
// users (#8069).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The PR says "Closes #8069", but #8069 was already closed on 2026-07-31 by merged PR #8074 (the Ctrl+Tab alternative this PR removes). The live gap this PR actually fixes — Ctrl+Tab being intercepted by Warp — is tracked in still-OPEN issue #8330, and the complementary mouse-click fix is open PR #8395; neither is referenced. — Concrete cost: after merge, #8330 stays open with no cross-reference, "Closes #8069" is a no-op link, and two open PRs touching the same picker files (#8395, #8576) are not connected anywhere, risking conflicting review/merge decisions. Suggested fix: update the PR description, e.g. "Addresses the remaining gap from #8330; complements the mouse-click fix in #8395; #8069 was already closed by #8074".

中文说明

[Suggestion] 本 PR 声明 "Closes #8069",但 #8069 已于 2026-07-31 被合并的 PR #8074(即本 PR 所移除的 Ctrl+Tab 备选方案)关闭。本 PR 实际修复的活跃缺口——Ctrl+Tab 被 Warp 拦截——记录在仍开放的 issue #8330 中;互补的鼠标点击修复是开放 PR #8395;二者均未被引用。— 具体代价:合并后 #8330 仍然开放且没有交叉引用;"Closes #8069" 成为无效链接;两个触及同一补全选择器文件的开放 PR(#8395#8576)互不关联,可能导致冲突的评审/合并决定。建议更新 PR 描述,例如 "Addresses the remaining gap from #8330; complements the mouse-click fix in #8395; #8069 was already closed by #8074"。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accurate — #8069 was already closed by merged PR #8074, and the live gap this PR actually closes is #8330 (Ctrl+Tab intercepted by Warp), with the complementary mouse-click fix in #8395. Updating the PR description is a GitHub write that this automated round cannot perform (it has no GitHub credentials; the workflow owns all GitHub writes), so it must be applied by a maintainer rather than in code. Suggested description wording: "Addresses the remaining gap from #8330; complements the mouse-click fix in #8395; #8069 was already closed by #8074." No code change is required for this finding.

中文说明

确实如此—— #8069 已被合并的 PR #8074 关闭,本 PR 实际修复的活跃缺口是 #8330(Ctrl+Tab 被 Warp 拦截),互补的鼠标点击修复是 #8395。更新 PR 描述属于 GitHub 写操作,本轮自动化无法执行(没有 GitHub 凭据,所有 GitHub 写操作由工作流负责),因此需要由维护者应用而非通过代码完成。建议的描述措辞为:"Addresses the remaining gap from #8330; complements the mouse-click fix in #8395; #8069 was already closed by #8074"。该发现无需代码改动。

Comment thread packages/cli/src/ui/keyMatchers.test.ts Outdated
// gestures, leaving the documented shortcut unreachable (#8069). The caret
// tradeoff while the menu is open is gated in InputPrompt, not here.
describe('Completion tab-switching bare arrow bindings (#8069)', () => {
it('should match bare ← / → as COMPLETION_TAB_LEFT / RIGHT', () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The standalone #8069 describe block re-asserts the bare-arrow positives and ctrl/meta negatives that the parameterized comparison block in this same file already makes for COMPLETION_TAB_LEFT/COMPLETION_TAB_RIGHT (asserted against both the keyMatchers and the originalMatchers oracle, lines ~243-265). This PR itself had to perform the double edit. Only the Ctrl+Tab / Ctrl+Shift+Tab / bare-Tab negatives in the fourth it are unique. — Concrete cost: every future binding change must be edited in two places in the same file, and this PR is proof the drift is easy. Suggested fix: delete the three duplicated its ('should match bare ← / →…', 'should not consume Ctrl+arrows…', 'should not consume Alt/Option+arrows…') and keep only 'should no longer bind Ctrl+Tab / Ctrl+Shift+Tab', or fold the tab negatives into the parameterized negative lists and drop the whole describe.

中文说明

[Suggestion] 独立的 #8069 describe 块重复断言了同文件参数化比较块(约 243-265 行,同时对 keyMatchersoriginalMatchers 预言机断言)已覆盖的裸方向键正向用例和 ctrl/meta 反向用例。本 PR 自己就不得不进行双份编辑。只有第四个 it 中的 Ctrl+Tab / Ctrl+Shift+Tab / 裸 Tab 反向用例是独有的。— 具体代价:以后每次绑定变更都要在同一文件里编辑两处,本 PR 正是这种漂移易发生的证明。建议删除三个重复的 it('should match bare ← / →…'、'should not consume Ctrl+arrows…'、'should not consume Alt/Option+arrows…'),只保留 'should no longer bind Ctrl+Tab / Ctrl+Shift+Tab';或把 tab 反向用例并入参数化反向列表后整体删除该 describe。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines 201 to 203
[Command.COMPLETION_TAB_LEFT]: [
{ key: 'left', shift: false, ctrl: true, command: false },
{ key: 'tab', shift: true, ctrl: true, command: false },
{ key: 'left', shift: false, ctrl: false, command: false, meta: false },
],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Removing the Ctrl+Tab / Ctrl+Shift+Tab bindings also deleted the only thing shadowing the modifier-agnostic ACCEPT_SUGGESTION { key: 'tab' } binding. On terminals that deliver Ctrl+Tab distinctly (kitty CSI-u ESC[9;5u, xterm modifyOtherKeys ESC[27;5;9~ — both decoded by KeypressContext into {name:'tab', ctrl:true}), pressing Ctrl+Tab with the tabbed menu open now inserts the highlighted suggestion instead of switching the tab — an unexpected buffer mutation on exactly the UI where the previous release's hint advertised that gesture as navigation. Pre-PR this fallthrough only occurred with ≤2 categories; the diff extends it to the tabbed UI. — Suggested fix: if "no longer bind" is meant literally, pin ctrl: false on ACCEPT_SUGGESTION's tab entry ({ key: 'tab', ctrl: false }), keeping bare Tab and Shift+Tab accepting; alternatively state in the PR/tests that Ctrl+Tab ≡ Tab is intentional.

中文说明

[Suggestion] 删除 Ctrl+Tab / Ctrl+Shift+Tab 绑定的同时,也移除了唯一遮蔽不区分修饰键的 ACCEPT_SUGGESTION { key: 'tab' } 绑定的东西。在能区分递送 Ctrl+Tab 的终端上(kitty CSI-u ESC[9;5u、xterm modifyOtherKeys ESC[27;5;9~——KeypressContext 均解码为 {name:'tab', ctrl:true}),在显示分类标签的菜单打开时按 Ctrl+Tab,现在会插入高亮建议而不是切换分类——恰恰在上一版本提示文字把该手势标注为导航的界面上,产生了意外的输入框变更。PR 之前该穿透只发生在 ≤2 个分类时;本 diff 把它扩展到了带标签栏的界面。— 建议:若"不再绑定"是字面意图,给 ACCEPT_SUGGESTION 的 tab 条目固定 ctrl: false{ key: 'tab', ctrl: false }),保留裸 Tab 与 Shift+Tab 的接受行为;或在 PR/测试中明确 Ctrl+Tab ≡ Tab 是有意为之。

— qwen3.8-max via Qwen Code /review (v0.21.5)

<Text color={theme.text.secondary}>
{t('(Ctrl+Tab / Ctrl+Shift+Tab or Ctrl+←/→ to switch)')}
</Text>
<Text color={theme.text.secondary}>{t('(←/→ to switch)')}</Text>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] [test] The test-efficacy probe (harness validated) reverted this hunk alone and every affected test stayed green — no test asserts the rendered (←/→ to switch) hint; the PR's test changes exercise only key escape sequences. — Failure scenario: if a future change reverts or mangles the hint — e.g. restores the old (Ctrl+Tab / Ctrl+Shift+Tab or Ctrl+←/→ to switch) — CI stays green and users again see shortcut hints pointing at bindings that terminals / macOS Mission Control intercept: exactly the unreachable-binding confusion (#8069) this PR removes. Suggested fix: add a render assertion (SuggestionsDisplay or InputPrompt test) that with availableCategories > 2 the rendered output contains (←/→ to switch).

中文说明

[Suggestion] [test] 测试有效性探针(harness 已验证)单独回退该 hunk 后,所有受影响测试仍为绿色——没有任何测试断言渲染出的 (←/→ to switch) 提示;本 PR 的测试改动只覆盖了按键转义序列。— 故障场景:若未来某个改动回退或篡改该提示——例如恢复旧的 (Ctrl+Tab / Ctrl+Shift+Tab or Ctrl+←/→ to switch)——CI 依然全绿,而用户会再次看到指向被终端 / macOS 调度中心拦截的绑定的快捷键提示:正是本 PR 要消除的"绑定不可达"困惑(#8069)。建议:增加渲染断言(SuggestionsDisplay 或 InputPrompt 测试),验证 availableCategories > 2 时渲染输出包含 (←/→ to switch)

— qwen3.8-max via Qwen Code /review (v0.21.5)

@LaZzyMan

LaZzyMan commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 5, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes). Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

AutoFix round 7 finishedview run. See this round's report below.

中文说明

AutoFix 第 7 轮已完成 —— 查看运行。本轮报告见下方。

qwen-code-dev-bot added a commit to LaZzyMan/qwen-code that referenced this pull request Aug 5, 2026
…enLM#8576)

The category tab-switching branch consumed bare ←/→ whenever more than two
categories existed, even in states where the tab bar is not rendered. During
command/reverse search the menu shows without categories (availableCategories
is passed as undefined to SuggestionsDisplay), and in attachment mode the
arrows navigate the attachment chips — in both states the arrows switched a
hidden menu instead of keeping their normal behaviour. Gate the branch on the
tab bar actually being visible: not in command search, not in reverse search,
and not in attachment mode.

This PR had also removed the Ctrl+Tab / Ctrl+Shift+Tab alternatives. With Vim
keybindings enabled, vimHandleInput consumes bare arrows before completion
handling runs, so Vim users lost every keyboard path to switch categories
while the hint still advertised ←/→. Restore the Ctrl+Tab / Ctrl+Shift+Tab
alternatives (tab keys pass through Vim insert mode) and re-document them.

Tests cover the two gated states, assert the Ctrl+Tab fallback end-to-end via
its kitty CSI-u encoding, pin the rendered "(←/→ to switch)" hint, fold the
former standalone QwenLM#8069 matcher block into the parameterized comparison, and
pin Ctrl+Tab falling through to ACCEPT_SUGGESTION when the tab bar is hidden.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Autofix review round — PR #8576

Addressed both Critical findings and three Suggestions from the automated
review; one Suggestion (PR-description references) requires a GitHub write and
is answered in its thread instead. Committed as
fix(cli): switch completion tabs only when the tab bar is visible (#8576)
(89086e8b5) on the PR head branch. No conflicts (--conflict false, no
merge performed).

Feedback points

  • [Critical] rc:3720100868 — bare ←/→ switch categories while the tab bar is hidden (search / attachment modes). FIXED.
    Verified against the code: the completion-reset effect only reacts to
    reverseSearchActive, so under command search (Ctrl+R) the menu stays
    "open" while rendering receives availableCategories={undefined} (hidden tab
    bar); and the tab-switch branch ran before the attachment chip-navigation
    block. The branch in InputPrompt.tsx is now gated on
    !commandSearchActive && !reverseSearchActive && !isAttachmentMode && availableCategories > 2, i.e. keys are only captured while the tab bar is
    actually rendered (plus the attachment-mode behaviour gate). The reviewer's
    "shared predicate" deeper fix was considered: the render-side exclusions are
    exactly suggestionsFromExport || commandSearchActive || reverseSearchActive,
    and multiple categories only exist in @ mode while export suggestions only
    exist for /export text, so the export case cannot co-occur with a tab bar —
    the explicit gate already mirrors the render condition without a new
    abstraction. Added two regression tests (command search, attachment mode);
    both fail on the pre-fix code and pass with the fix.

  • [Critical] rc:3720100885 — Vim users lose every keyboard path to switch categories. FIXED.
    Verified: vimHandleInput runs before the completion branch
    (InputPrompt.tsx:1083), and Vim insert mode consumes bare left/right
    (buffer.handleInput(...); return true in vim.ts) while tab keys return
    false and pass through. Restored the pre-PR Ctrl+Tab /
    Ctrl+Shift+Tab alternative bindings in keyBindings.ts (bare arrows remain
    primary), re-documented them in keyboard-shortcuts.md, and added coverage:
    matcher positives/negatives plus an end-to-end InputPrompt test driving the
    kitty CSI-u encodings (ESC[9;5u / ESC[9;6u). This also restores the
    pre-PR fallthrough semantics: with the tab bar hidden, Ctrl+Tab accepts the
    suggestion exactly as before.

  • [Suggestion] rc:3720100891 — PR description references the wrong issues. NOT ACTIONABLE IN CODE.
    Accurate: @ completion tab switching uses Ctrl+←/→ which conflicts with terminal word-jump #8069 was already closed by merged fix(cli): add Ctrl+Tab alternative for @ completion tab switching #8074; the live gap is @ completion tab switching is inaccessible in Warp because Ctrl+Tab switches terminal tabs #8330
    (complemented by fix(cli): make @ completion category tabs clickable #8395). Updating the PR description is a GitHub write this
    automation cannot perform; replied in the thread with suggested wording for a
    maintainer to apply.

  • [Suggestion] rc:3720100896 — duplicated #8069 describe block in keyMatchers.test.ts. FIXED.
    With the tab alternatives restored, the standalone describe's assertions were
    folded into the parameterized comparison block (positives and negatives for
    both matchers and the originalMatchers oracle) and the whole describe block
    was deleted — no assertion is duplicated any more.

  • [Suggestion] rc:3720100903 — removing Ctrl+Tab unshadowed ACCEPT_SUGGESTION's modifier-agnostic { key: 'tab' }. FIXED (by the rc:3720100885 fix).
    Restoring the Ctrl+Tab binding re-shadows it while the tab bar is rendered
    (COMPLETION_TAB_* is checked before ACCEPT_SUGGESTION in the completion
    branch). The remaining fallthrough when the tab bar is hidden (Ctrl+Tab ≡ Tab = accept) is the pre-PR behaviour and is now pinned by an explicit
    ACCEPT_SUGGESTION positive case, so no change to the accept binding itself
    was needed.

  • [Suggestion] rc:3720100910 — no test asserts the rendered (←/→ to switch) hint. FIXED.
    SuggestionsDisplay.test.tsx now asserts the hint renders with the tab bar
    (availableCategories > 2) and is absent when the tab bar is hidden.

Also noted: the issue-level tmux real-user testing comment reports a timeout —
explicitly not a pass/fail result, so no action was taken on it.

Verification

Commands actually run (working tree = PR head + this round's commit):

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check on all changed files — passed (after one --write fix during development)
  • npx vitest run src/ui/components/InputPrompt.test.tsx (packages/cli) — 200 passed (incl. 3 new tests; the two gate tests were re-verified to FAIL on the pre-fix code by temporarily reverting the gate)
  • npx vitest run src/ui/keyMatchers.test.ts src/ui/components/SuggestionsDisplay.test.tsx (packages/cli) — 57 passed
  • npx vitest run src/config/keyBindings.test.ts (packages/cli) — 4 passed
  • npx vitest run src/ui/hooks/useCompletion.test.ts src/ui/hooks/useCommandCompletion.test.ts (packages/cli) — 61 passed
  • npx vitest run src/ui/hooks/useAutoAcceptIndicator.test.ts src/ui/components/agent-view/AgentComposer.test.tsx (packages/cli) — 24 passed (Ctrl+Shift+Tab consumers)
  • Full packages/cli unit suite — only failures are 9 pre-existing environment-dependent files (settings/QWEN_HOME, sandbox image, AuthDialog timeouts, Windows-path and Footer golden tests); reproduced identically with this round's changes stashed, so none are caused by this commit
  • npm run bundle — passed
  • npm run test:integration:cli:sandbox:none (the suite the review flagged as not run) — 181 passed, 2 failed, 18 skipped; both failures (QWEN_HOME … 1d: CLI functions normally when QWEN_HOME is not set and daemon Todo Stop Guard replay) reproduce identically on the base commit with changes stashed — pre-existing/environment (this runner has QWEN_HOME set), not caused by this commit
  • npm run test:integration:interactive:sandbox:none — 10 passed, 2 failed, 2 skipped; both failures (cron interactive > loop fires inline in conversation, > user input takes priority over cron, ~96 s timeouts) reproduce identically on the base commit — pre-existing, unrelated to key handling
中文说明

Autofix 评审轮次 — PR #8576

本轮处理了自动评审的两个 Critical 发现和三个 Suggestion;另一个 Suggestion(PR 描述引用问题)需要 GitHub 写操作,已在其评论线程中回复。提交为
fix(cli): switch completion tabs only when the tab bar is visible (#8576)
89086e8b5),位于 PR head 分支。无冲突(--conflict false,未执行合并)。

反馈要点

  • [Critical] rc:3720100868 — 标签栏隐藏时(搜索/附件模式)裸 ←/→ 仍会切换分类。已修复。
    经代码核实:补全重置副作用只响应 reverseSearchActive,因此命令搜索(Ctrl+R)期间菜单仍处于"打开"状态,而渲染端收到的是 availableCategories={undefined}(标签栏已隐藏);且分类切换分支先于附件选择分支执行。现将 InputPrompt.tsx 中该分支的门控改为
    !commandSearchActive && !reverseSearchActive && !isAttachmentMode && availableCategories > 2,即只有在标签栏确实渲染时才捕获按键(外加附件模式的行为门控)。评审建议的"共享谓词"深层修复也已评估:渲染端的排除条件恰为 suggestionsFromExport || commandSearchActive || reverseSearchActive,而多分类只存在于 @ 模式、导出建议只存在于 /export 文本,二者不可能与标签栏同时出现——显式门控已与渲染条件一致,无需引入新的抽象。新增两个回归测试(命令搜索、附件模式);二者在修复前代码上失败、修复后通过。

  • [Critical] rc:3720100885 — Vim 用户失去所有切换分类的键盘路径。已修复。
    已核实:vimHandleInput 先于补全分支执行(InputPrompt.tsx:1083),Vim INSERT 模式会消费裸 left/right(vim.tsbuffer.handleInput(...); return true),而 tab 键返回 false 直接穿透。在 keyBindings.ts 中恢复了 PR 之前的 Ctrl+Tab /
    Ctrl+Shift+Tab 备选绑定(裸方向键仍为主绑定),在 keyboard-shortcuts.md 中重新补充文档,并增加覆盖:匹配器正/负用例,以及一个通过 kitty CSI-u 编码(ESC[9;5u / ESC[9;6u)驱动的端到端 InputPrompt 测试。同时恢复了 PR 之前的穿透语义:标签栏隐藏时,Ctrl+Tab 与之前一致地接受建议。

  • [Suggestion] rc:3720100891 — PR 描述引用了错误的 issue。无法在代码中处理。
    意见准确:@ completion tab switching uses Ctrl+←/→ which conflicts with terminal word-jump #8069 已被合并的 fix(cli): add Ctrl+Tab alternative for @ completion tab switching #8074 关闭;活跃缺口是 @ completion tab switching is inaccessible in Warp because Ctrl+Tab switches terminal tabs #8330(与 fix(cli): make @ completion category tabs clickable #8395 互补)。更新 PR 描述属于 GitHub 写操作,本自动化无法执行;已在该线程回复建议措辞,待维护者应用。

  • [Suggestion] rc:3720100896 — keyMatchers.test.ts 中重复的 #8069 describe 块。已修复。
    在恢复 tab 备选绑定后,原独立 describe 的断言已并入参数化比较块(同时对 keyMatchersoriginalMatchers 预言机断言正/负用例),并整体删除该 describe——不再有重复断言。

  • [Suggestion] rc:3720100903 — 删除 Ctrl+Tab 使 ACCEPT_SUGGESTION 不区分修饰键的 { key: 'tab' } 失去遮蔽。已修复(随 rc:3720100885 的修复一并解决)。
    恢复 Ctrl+Tab 绑定后,标签栏渲染时重新遮蔽该条目(补全分支中 COMPLETION_TAB_* 先于 ACCEPT_SUGGESTION 检查)。标签栏隐藏时的穿透行为(Ctrl+Tab ≡ Tab = 接受建议)即 PR 之前的行为,现已用显式的 ACCEPT_SUGGESTION 正向用例固定,因此无需改动接受绑定本身。

  • [Suggestion] rc:3720100910 — 没有测试断言渲染出的 (←/→ to switch) 提示。已修复。
    SuggestionsDisplay.test.tsx 现在断言:标签栏显示(availableCategories > 2)时渲染该提示,标签栏隐藏时不渲染。

另外:issue 级别的 tmux 真实用户测试评论报告的是超时——明确说明这不算通过/失败结果,故未对其采取行动。

验证

实际执行的命令(工作树 = PR head + 本轮提交):

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • 对全部改动文件执行 npx prettier --check — 通过(开发过程中曾用 --write 修正一处)
  • npx vitest run src/ui/components/InputPrompt.test.tsx(packages/cli)— 200 通过(含 3 个新测试;两个门控测试已通过临时回退门控重新验证其在修复前会失败)
  • npx vitest run src/ui/keyMatchers.test.ts src/ui/components/SuggestionsDisplay.test.tsx(packages/cli)— 57 通过
  • npx vitest run src/config/keyBindings.test.ts(packages/cli)— 4 通过
  • npx vitest run src/ui/hooks/useCompletion.test.ts src/ui/hooks/useCommandCompletion.test.ts(packages/cli)— 61 通过
  • npx vitest run src/ui/hooks/useAutoAcceptIndicator.test.ts src/ui/components/agent-view/AgentComposer.test.tsx(packages/cli)— 24 通过(Ctrl+Shift+Tab 的相关消费方)
  • packages/cli 完整单测套件 — 仅有的失败是 9 个预先存在的环境相关文件(settings/QWEN_HOME、sandbox 镜像、AuthDialog 超时、Windows 路径与 Footer 黄金快照测试);在本轮改动被 stash 的情况下完全一致地复现,因此均非本提交引入
  • npm run bundle — 通过
  • npm run test:integration:cli:sandbox:none(评审指出未运行的套件)— 181 通过、2 失败、18 跳过;两个失败(QWEN_HOME … 1d: CLI functions normally when QWEN_HOME is not setdaemon Todo Stop Guard replay)在 stash 改动后的基础提交上完全一致地复现——属预先存在/环境问题(本 runner 设置了 QWEN_HOME),非本提交引入
  • npm run test:integration:interactive:sandbox:none — 10 通过、2 失败、2 跳过;两个失败(cron interactive > loop fires inline in conversation> user input takes priority over cron,约 96 秒超时)在基础提交上完全一致地复现——预先存在,与按键处理无关

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

No changes needed

The only feedback item this round is a failed check; there are no review comments, inline comments, or issue-level comments to address.

Failed check: Test (ubuntu-latest, Node 22.x) — diagnosed as an early-step CI failure, not a code defect

Evidence from the check run itself (checks.json):

  • The job started at 2026-08-05T14:41:51Z and failed at 2026-08-05T14:42:06Z — a total lifetime of 15 seconds.
  • A full-profile run of this job performs, in order: workspace restore, stale-.qwen cleanup, checkout, head-commit verification, CI-profile classification, Node setup, npm cache/config, npm ci (minutes even warm), seven lint/format/i18n/schema gates, the serve-bundle check, the full unit suite (npm run test:ci — ~26 minutes on this tree), and the no-AK integration gate. Nothing that executes this PR's code can be reached within 15 seconds; the job died in a pre-npm ci step (checkout/verification/environment bring-up).
  • This PR changes no dependency manifests (package.json / package-lock.json are untouched), so even the earliest dependency-related step consumed byte-identical input to the previous, green run.
  • The parent commit 33398d7 — the same production code minus only the tab-bar-visibility gating, the restored Ctrl+Tab alternatives, and their tests — landed with all checks green (triage: "CI landed green on 33398d7").
  • All dependent jobs (Test (macos/windows), Integration Tests (CLI, No Sandbox), web-shell E2E Smoke, coverage) were SKIPPED purely because this one job failed early; none ran and failed on their own.
  • The loop's own infrastructure auto-rerun keys on annotation signatures (INFRA_FAILURE_SIGNATURES); this failure's annotations did not match any, which is why it was routed here instead of being rerun automatically.

Local reproduction on the exact failing head (89086e8b5) — everything CI would have run on the PR's code is green: see the Verification section below. In particular, npm run test:ci — the exact command behind the job's "Run tests and generate reports" step — exits 0 across all workspaces.

Disposition: there is no code-level hypothesis to implement — no step that can fail within 15 seconds consumes anything this PR changed, and every code-level gate passes locally on this exact tree. The red check clears when CI runs again on this tree (a maintainer job re-run, or the loop's stale-base update, since main has advanced 10 commits past this branch's base). No commit is made deliberately: fabricating a change solely to retrigger CI would grow the diff without fixing anything, and an empty commit is treated as "no new commit" by this workflow's verification gate and is never pushed.

Verification

Commands actually run at HEAD 89086e8b5 (Node v22.23.2, matching the failing check's Node 22.x):

  • npx vitest run src/ui/keyMatchers.test.ts src/ui/components/SuggestionsDisplay.test.tsx src/ui/components/InputPrompt.test.tsx (packages/cli) — passed, 257/257 tests
  • npm run test:ci (full workspace unit suite + script tests, same as CI's test step) — passed, exit 0
  • npm run lint:ci (ESLint, --max-warnings 0) — passed
  • npm run check-i18n — passed (warnings only, pre-existing unused-key notices)
  • npm run build — passed
  • npm run typecheck — passed across all workspaces (run after build; package exports resolve from dist/)
中文说明

无需改动

本轮唯一的反馈项是一个失败的检查(check);没有任何评审意见、行内评论或 issue 级评论需要处理。

失败检查:Test (ubuntu-latest, Node 22.x) —— 诊断为 CI 早期步骤失败,而非代码缺陷

来自该检查运行本身的证据(checks.json):

  • 该 job 于 2026-08-05T14:41:51Z 开始,2026-08-05T14:42:06Z 失败 —— 总耗时仅 15 秒
  • 该 job 的 full profile 依次执行:工作区权限恢复、清理残留 .qwen、checkout、校验 head commit、CI profile 分类、Node 安装、npm 缓存/配置、npm ci(即使有缓存也需数分钟)、七项 lint/format/i18n/schema 门禁、serve bundle 检查、完整单元测试(npm run test:ci —— 在本代码树上约 26 分钟),以及 no-AK 集成门禁。15 秒之内不可能到达任何执行本 PR 代码的步骤;job 死在了 npm ci 之前的步骤(checkout / 校验 / 环境准备)。
  • 本 PR 未改动任何依赖清单(package.json / package-lock.json 均未触碰),因此即便是最早的依赖相关步骤,其输入也与上一次绿色运行逐字节相同。
  • 父提交 33398d7 —— 与当前生产代码完全相同,仅缺少标签栏可见性门控、恢复的 Ctrl+Tab 备选键及其测试 —— 落地时所有检查全绿(triage 机器人:“CI landed green on 33398d7”)。
  • 所有下游 job(Test (macos/windows)Integration Tests (CLI, No Sandbox)web-shell E2E Smoke、coverage)均为 SKIPPED,仅仅因为这一个 job 过早失败;它们没有一个是自行运行后失败的。
  • 循环自身的基础设施自动重跑机制依据检查注解中的特征签名(INFRA_FAILURE_SIGNATURES)触发;本次失败的注解未匹配任何签名,因此它被路由到这里,而不是被自动重跑。

在出错的同一个 head(89086e8b5)上本地复现 —— CI 会针对本 PR 代码执行的每一项在本地均为绿色: 见下方 Verification 一节。其中 npm run test:ci —— 即该 job 中 “Run tests and generate reports” 步骤所执行的同一条命令 —— 在所有 workspace 上以退出码 0 通过。

处置结论: 不存在可以落地的代码层面假设 —— 15 秒内可能失败的步骤没有一个会消费本 PR 改动的任何内容,且在本树精确状态下所有代码级门禁全部通过。该红色检查会在 CI 重新运行本树时消除(维护者手动重跑 job,或循环的 stale-base 自动更新 —— main 已领先本分支 base 10 个提交)。此处刻意不提交任何 commit:为了重触发 CI 而凭空制造改动只会无谓增大 diff,而空提交在本工作流的验证门禁中等同于“无新提交”,且永远不会被推送。

Verification(验证记录)

在 HEAD 89086e8b5 上实际执行的命令(Node v22.23.2,与失败检查所用的 Node 22.x 一致):

  • npx vitest run src/ui/keyMatchers.test.ts src/ui/components/SuggestionsDisplay.test.tsx src/ui/components/InputPrompt.test.tsx(packages/cli)—— 通过,257/257 个测试
  • npm run test:ci(全部 workspace 的完整单元测试 + 脚本测试,与 CI 的测试步骤相同)—— 通过,退出码 0
  • npm run lint:ci(ESLint,--max-warnings 0)—— 通过
  • npm run check-i18n —— 通过(仅有警告,为既有的未使用键提示)
  • npm run build —— 通过
  • npm run typecheck —— 所有 workspace 通过(在 build 之后运行;包的 exports 从 dist/ 解析)

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.21.5)

unmount();
});

it('should switch category on Ctrl+Tab / Ctrl+Shift+Tab when availableCategories > 2', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The PR description contradicts the code under review. The Evidence section still claims the tests assert "that Ctrl+Tab is no longer bound" ("Ctrl+Tab 已解除绑定"), and Risk & Scope says "Breaking changes / migration notes: Ctrl+arrow and Ctrl+Tab no longer switch categories" — but this commit keeps the Ctrl+Tab / Ctrl+Shift+Tab alternative bindings (keyBindings.ts COMPLETION_TAB_*) and adds the test anchored here asserting they DO switch categories. The docs (keyboard-shortcuts.md) already advertise them as alternatives.

Failure scenario: the round-1 autofix restored the Ctrl+Tab alternatives without updating the PR body, so a maintainer assessing breaking-change risk, a reviewer checking the claimed coverage, or anyone writing release notes from the description concludes Ctrl+Tab does nothing after merge — the opposite of what ships (it switches categories while the tab bar is rendered, and accepts the suggestion when it is hidden).

Suggested fix: update the PR description — Evidence: "Ctrl+Tab / Ctrl+Shift+Tab still switch as alternatives"; Risk & Scope: "Ctrl+arrow no longer switches categories; Ctrl+Tab / Ctrl+Shift+Tab remain as alternatives" (maintainer/author description edit, same channel as the issue-reference wording fix).

中文说明

PR 描述与被审查的代码相矛盾。Evidence 一节仍声称测试断言"Ctrl+Tab 已解除绑定",Risk & Scope 仍写着"破坏性改动或迁移说明:Ctrl+方向键与 Ctrl+Tab 不再切换分类"——但本提交保留了 Ctrl+Tab / Ctrl+Shift+Tab 备选绑定(keyBindings.tsCOMPLETION_TAB_*),并新增了此处锚定的测试来断言它们确实可以切换分类。文档(keyboard-shortcuts.md)也已将其宣传为备选方式。

故障场景:第 1 轮 autofix 恢复了 Ctrl+Tab 备选绑定却没有更新 PR 描述,于是评估破坏性改动风险的维护者、核对所声称测试覆盖的评审者、或任何依据描述撰写发布说明的人,都会得出"合并后 Ctrl+Tab 不再有任何作用"的结论——与实际交付的行为恰恰相反(标签栏显示时它切换分类,标签栏隐藏时它接受建议)。

建议修复:更新 PR 描述——Evidence 改为"Ctrl+Tab / Ctrl+Shift+Tab 仍作为备选方式切换分类";Risk & Scope 改为"Ctrl+方向键不再切换分类;Ctrl+Tab / Ctrl+Shift+Tab 保留为备选"(与修正 issue 引用的措辞一样,属于维护者/作者的描述编辑)。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not resolved in code this round: this finding targets the PR description on GitHub, which the autofix fix job cannot edit (it has no GitHub write access; the workflow owns network writes). The code already matches the finding's account — COMPLETION_TAB_* keeps the Ctrl+Tab / Ctrl+Shift+Tab alternatives and the tests anchored here assert they DO switch categories — so only the description needs to catch up.

Proposed description edits for the maintainer/author channel:

  • Evidence: replace the "Ctrl+Tab is no longer bound" claim with: "Ctrl+Tab / Ctrl+Shift+Tab still switch categories as alternatives; they accept the suggestion only while the tab bar is hidden."
  • Risk & Scope (breaking changes / migration notes): replace "Ctrl+arrow and Ctrl+Tab no longer switch categories" with: "Ctrl+arrow no longer switches categories; Ctrl+Tab / Ctrl+Shift+Tab remain as alternatives."
中文说明

本轮未在代码中解决:该发现针对的是 GitHub 上的 PR 描述,而 autofix 修复任务无法编辑它(修复任务没有 GitHub 写权限,网络写操作由工作流负责)。代码本身已与该发现所述一致——COMPLETION_TAB_* 保留了 Ctrl+Tab / Ctrl+Shift+Tab 备选绑定,且此处锚定的测试断言它们确实可以切换分类——因此只需要让 PR 描述跟上代码。

给维护者/作者渠道的描述修改建议:

  • Evidence:将"Ctrl+Tab 已解除绑定"的说法替换为:"Ctrl+Tab / Ctrl+Shift+Tab 仍作为备选方式切换分类;仅在标签栏隐藏时接受建议。"
  • Risk & Scope(破坏性改动或迁移说明):将"Ctrl+方向键与 Ctrl+Tab 不再切换分类"替换为:"Ctrl+方向键不再切换分类;Ctrl+Tab / Ctrl+Shift+Tab 保留为备选。"

Comment on lines +1421 to +1424
!commandSearchActive &&
!reverseSearchActive &&
!isAttachmentMode &&
(completion.availableCategories?.length ?? 0) > 2

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] "Is the category tab bar visible/interactive" is now hand-maintained in three places that already diverge: this key gate (lacks suggestionsFromExport), the render-side prop suppression ~lines 2280-2291 (availableCategories passed as undefined when suggestionsFromExport || commandSearchActive || reverseSearchActive, no isAttachmentMode check), and showTabBar in SuggestionsDisplay.tsx. The autofix round declined the shared-predicate fix claiming "the explicit gate already mirrors the render condition" — but that is false for this commit's final state (the render side also omits isAttachmentMode, which this gate has).

Failure scenario: the suggestionsFromExport gap is unreachable today only because export suggestions never carry >2 categories — an implicit cross-module invariant stated nowhere near this code. Any future render-side suppression (a settings flag, compact mode, slash suggestions gaining categories) that misses this gate silently re-creates the round-1 Critical: bare ←/→ consumed while the tab bar is hidden, freezing caret movement with no visible UI to explain it.

Suggested fix: derive one boolean next to the render-prop computation, e.g. const categoryTabsVisible = !suggestionsFromExport && !commandSearchActive && !reverseSearchActive && (completion.availableCategories?.length ?? 0) > 2;, pass availableCategories={categoryTabsVisible ? completion.availableCategories : undefined}, and gate this branch on categoryTabsVisible && !isAttachmentMode (the attachment exclusion stays explicit — it is arrow-ownership, not visibility).

中文说明

"分类标签栏是否可见/可交互"目前由三处手工维护且已经出现分歧:本按键门控(缺少 suggestionsFromExport)、渲染端约 2280-2291 行的属性抑制(在 suggestionsFromExport || commandSearchActive || reverseSearchActive 时把 availableCategories 传为 undefined,但不检查 isAttachmentMode)、以及 SuggestionsDisplay.tsx 中的 showTabBar。autofix 轮次曾以"显式门控已与渲染条件一致"为由拒绝了共享谓词的深层修复——但就本提交的最终状态而言该说法不成立(渲染端同样缺少本门控所拥有的 isAttachmentMode)。

故障场景:suggestionsFromExport 缺口目前不可达,仅仅因为导出建议永远不会携带超过 2 个分类——这是一个未在此代码附近任何地方说明的跨模块隐式不变量。未来任何渲染端的抑制条件(设置开关、紧凑模式、斜杠建议获得分类)若遗漏本门控,都会悄悄复现第 1 轮的 Critical:标签栏隐藏时裸 ←/→ 被消费,光标移动被冻结且没有任何可见界面解释原因。

建议修复:在渲染属性计算旁边派生一个布尔量,例如 const categoryTabsVisible = !suggestionsFromExport && !commandSearchActive && !reverseSearchActive && (completion.availableCategories?.length ?? 0) > 2;,传 availableCategories={categoryTabsVisible ? completion.availableCategories : undefined},并把本分支的门控改为 categoryTabsVisible && !isAttachmentMode(附件模式排除保持显式——那是方向键归属问题,不是可见性问题)。

— qwen3.8-max via Qwen Code /review (v0.21.5)

// the tab bar is actually rendered: more than two tabs (at least 3
// entries including 'all') and no history search active (search shows
// the menu without categories). In attachment mode the arrows belong
// to chip navigation below. While this menu is open the arrows

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] In attachment mode the !isAttachmentMode gate blocks this whole branch — including the Ctrl+Tab / Ctrl+Shift+Tab fallback it was added alongside. With the menu still open (single-suggestion tab, Up at row 0 — the exact state the new attachment-mode test constructs), the tab bar keeps advertising "(←/→ to switch)" while ←/→ navigate chips, and Ctrl+Tab falls through to ACCEPT_SUGGESTION's modifier-agnostic { key: 'tab' } and silently accepts the single suggestion instead of switching categories. Pre-PR, Ctrl+Tab switched categories in this state.

Failure scenario (probe-confirmed at this commit): image attachment + @ menu with 3 categories + one suggestion on the active tab; Up at row 0 enters attachment mode with the menu open; Kitty Ctrl+Tab (ESC[9;5u) then produced handleAutocomplete(0) (buffer mutated) with zero switchCategory calls. No keyboard path switches categories until Esc. Reachability is narrow (needs the single-suggestion tab), hence Suggestion.

Suggested fix: gate only the bare-arrow matchers on !isAttachmentMode (e.g. apply the attachment exclusion only when key.name === 'left' || key.name === 'right'), keeping Ctrl+Tab / Ctrl+Shift+Tab active whenever the tab bar is rendered.

中文说明

附件模式下 !isAttachmentMode 门控阻断了整个分支——连同与之一起加入的 Ctrl+Tab / Ctrl+Shift+Tab 备选绑定。菜单仍然打开时(只有一个建议的分类、在第 0 行按 Up——正是新增附件模式测试所构造的状态),标签栏继续显示"(←/→ to switch)",而 ←/→ 实际在导航附件芯片,Ctrl+Tab 则穿透到不区分修饰键的 ACCEPT_SUGGESTION { key: 'tab' } 绑定,悄悄接受那唯一的建议而不是切换分类。PR 之前,Ctrl+Tab 在此状态下可以切换分类。

故障场景(已在本提交上以探针确认):图片附件 + 3 个分类的 @ 菜单 + 激活分类只有一个建议;在第 0 行按 Up 进入附件模式且菜单保持打开;此时按 Kitty Ctrl+Tab(ESC[9;5u)触发了 handleAutocomplete(0)(输入缓冲区被改写),switchCategory 调用为零。在按 Esc 之前没有任何键盘路径可以切换分类。可达性较窄(需要单建议分类),故定为 Suggestion。

建议修复:仅对裸方向键匹配器施加 !isAttachmentMode 门控(例如仅在 key.name === 'left' || key.name === 'right' 时应用附件模式排除),让 Ctrl+Tab / Ctrl+Shift+Tab 在标签栏渲染时始终可用。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment thread packages/cli/src/config/keyBindings.ts Outdated
// a keyboard path to switch categories for Vim users.
[Command.COMPLETION_TAB_LEFT]: [
{ key: 'left', shift: false, ctrl: true, command: false },
{ key: 'left', shift: false, ctrl: false, command: false, meta: false },

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] command: false and meta: false compile to the identical check: in matchKeyBinding (keyMatchers.ts) both fields test key.meta — the Key type has no command field, and KeypressContext documents that a binding's command maps to key.meta. So one of the two fields here is a no-op duplicate that reads as two distinct physical modifiers being pinned. Siblings pin once each (SUBMIT/QUEUE_MESSAGE use command: false, VOICE_PUSH_TO_TALK uses meta: false).

Concrete cost: the comment above says "Modifiers are pinned false", which reads as Cmd and Alt independently pinned when only the single key.meta state exists (pinned twice); a future change that ever distinguishes command from meta in the compiler silently changes this binding's meaning.

Suggested fix (both COMPLETION_TAB_LEFT and COMPLETION_TAB_RIGHT bare-arrow entries):

{ key: 'left', shift: false, ctrl: false, meta: false },
{ key: 'right', shift: false, ctrl: false, meta: false },
中文说明

command: falsemeta: false 编译出完全相同的检查:matchKeyBinding(keyMatchers.ts)中两个字段都测试 key.meta——Key 类型没有 command 字段,KeypressContext 中也注明绑定的 command 映射到 key.meta。因此两者之一是无效重复,读起来却像分别固定了两个不同的物理修饰键。同级的绑定各只固定一次(SUBMIT/QUEUE_MESSAGEcommand: falseVOICE_PUSH_TO_TALKmeta: false)。

具体代价:上方注释写着"Modifiers are pinned false",读起来像 Cmd 与 Alt 被各自独立固定,而实际只存在单一的 key.meta 状态(被固定了两次);未来若有改动在编译器中区分 commandmeta,此绑定的含义会被悄悄改变。

建议修复(COMPLETION_TAB_LEFTCOMPLETION_TAB_RIGHT 的裸方向键条目):

{ key: 'left', shift: false, ctrl: false, meta: false },
{ key: 'right', shift: false, ctrl: false, meta: false },

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +2822 to +2824
// Kitty CSI-u encodings of Ctrl+Tab / Ctrl+Shift+Tab — the fallback that
// stays reachable with Vim keybindings, where bare arrows are consumed
// before completion handling runs (#8069).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This test's comment claims to pin "the fallback that stays reachable with Vim keybindings", but the chain is never exercised: the test renders with vimHandleInput undefined (the gate at InputPrompt.tsx:1083 is a no-op in every category-switching test), and no test anywhere covers vim.ts's insert-mode tab pass-through (vim.test.ts has no such case). The split at vim.ts:912 (tab keys return false / pass through; bare arrows are consumed) plus the gate ordering are the exact mechanism the round-1 Vim Critical's fix rests on — pinned by comments alone.

Failure scenario: if a future change makes Vim insert mode consume tab (e.g. indent-on-tab) or reorders the vim gate, Vim users silently lose the only remaining keyboard path to switch categories — the exact regression this commit claims to fix — and the whole suite stays green.

Suggested fix: add a case with props.vimHandleInput = vi.fn((key) => key.name === 'left' || key.name === 'right') (arrows consumed, tab passes) and assert bare arrows do NOT switch while ESC[9;5u / ESC[9;6u DO switch; optionally pin the pass-through in vim.test.ts too.

中文说明

该测试的注释声称固定了"在 Vim 键位下仍然可达的备选路径",但这条链路从未被真正执行:测试渲染时 vimHandleInput 为 undefined(InputPrompt.tsx:1083 的门控在所有分类切换测试中都是空操作),且任何测试都没有覆盖 vim.ts 插入模式的 tab 穿透(vim.test.ts 中没有此类用例)。vim.ts:912 处的分叉(tab 键返回 false / 穿透,裸方向键被消费)加上门控顺序,正是第 1 轮 Vim Critical 修复所依赖的确切机制——目前只靠注释固定。

故障场景:若未来改动让 Vim 插入模式消费 tab(例如 tab 缩进)或调整 vim 门控顺序,Vim 用户会悄悄失去切换分类的唯一键盘路径——正是本提交声称修复的回归——而整个测试套件仍然全绿。

建议修复:新增一个用例,设 props.vimHandleInput = vi.fn((key) => key.name === 'left' || key.name === 'right')(方向键被消费、tab 穿透),断言裸方向键不切换分类而 ESC[9;5u / ESC[9;6u 可以切换;也可在 vim.test.ts 中一并固定该穿透行为。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +2764 to +2767
stdin.write('\x1b[D'); // plain left arrow
await wait();

expect(switchCategory).toHaveBeenCalledWith(-1);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] These positive tests assert the category switched but not that the arrow was consumed — half of the PR's stated behaviour ("the arrows no longer move the caret") is unverified.

Failure scenario (mutation proven at this commit): deleting both return true; after completion.switchCategory(±1) (InputPrompt.tsx:1428/1433) keeps every PR test green while the arrow also reaches BaseTextInput — the caret drifts one character on every category switch. The probe assertion below fails under the mutation and passes on the original code, so it distinguishes correct from buggy.

Suggested fix: after each arrow write in the > 2 test, also assert the key did not reach the buffer:

expect(mockBuffer.move).not.toHaveBeenCalled();
expect(mockBuffer.handleInput).not.toHaveBeenCalled();
中文说明

这些正向测试断言了分类发生切换,却没有断言方向键被消费——PR 所声明行为的一半("方向键不再移动光标")未被验证。

故障场景(已在本提交上做变异验证):删除 completion.switchCategory(±1) 之后的两处 return true;(InputPrompt.tsx:1428/1433),PR 的所有测试仍然全绿,而方向键同时会到达 BaseTextInput——每次切换分类光标都会漂移一个字符。下面的探针断言在该变异下失败、在原始代码上通过,因此能区分正确与错误实现。

建议修复:在 > 2 测试中每次写入方向键后,追加断言按键没有到达输入缓冲区:

expect(mockBuffer.move).not.toHaveBeenCalled();
expect(mockBuffer.handleInput).not.toHaveBeenCalled();

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +1416 to +1417
// entries including 'all') and no history search active (search shows
// the menu without categories). In attachment mode the arrows belong

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] While command search (Ctrl+R, non-shell) is active, this gate rejects Ctrl+Tab / Ctrl+Shift+Tab from COMPLETION_TAB_* — and the keystroke then falls through to ACCEPT_SUGGESTION, whose { key: 'tab' } binding pins no modifiers, silently accepting the still-open @ completion into the search-query buffer. Completion stays "open" under command search because the reset effect only reacts to reverseSearchActive, and with a no-match query the search block renders nothing and consumes no Tab.

Failure scenario (probe-confirmed at this commit): buffer @ with ≥3 categories → Ctrl+R → no-match query → Kitty Ctrl+Tab (ESC[9;5u) fired handleAutocomplete(0) and mutated the search query with no completion UI visible; flipping the gate to pre-PR makes the same keystroke switch categories without touching the buffer. Pre-PR the old gate had no search condition, so COMPLETION_TAB_RIGHT consumed the keystroke. (Shell reverse search is unaffected — completion resets there.)

Suggested fix: skip this completion branch while commandSearchActive (probe-verified with the suite green). Do not globally exclude Ctrl+Tab from ACCEPT_SUGGESTION — this PR documents that it accepts when the tab bar is hidden.

中文说明

命令搜索(Ctrl+R,非 shell)激活时,本门控拒绝了 COMPLETION_TAB_*Ctrl+Tab / Ctrl+Shift+Tab——按键随后落入 ACCEPT_SUGGESTION(其 { key: 'tab' } 绑定不固定任何修饰键),把仍然"打开"的 @ 补全悄悄接受进搜索查询缓冲区。补全在命令搜索期间保持"打开",因为重置副作用只响应 reverseSearchActive;而当查询无匹配时,搜索分支不渲染任何内容也不消费 Tab。

故障场景(已在本提交上以探针确认):缓冲区输入 @(≥3 个分类)→ 按 Ctrl+R → 输入无匹配的查询 → 按 Kitty Ctrl+Tab(ESC[9;5u)触发了 handleAutocomplete(0),在没有任何补全界面可见的情况下改写了搜索查询;把门控翻回 PR 之前的形态,同一按键则切换分类且不触碰缓冲区。PR 之前旧门控没有搜索条件,COMPLETION_TAB_RIGHT 会消费该按键。(shell 反向搜索不受影响——补全会在那里被重置。)

建议修复:commandSearchActive 期间跳过本补全分支(已用探针验证,套件全绿)。不要全局地把 Ctrl+Tab 从 ACCEPT_SUGGESTION 中排除——本 PR 明确记录了标签栏隐藏时 Ctrl+Tab 接受建议的行为。

— qwen3.8-max via Qwen Code /review (v0.21.5)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blockers. Suggestions are inline.

中文说明

已审查——无阻断问题。 建议见行内评论。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +1420 to +1422
if (
!commandSearchActive &&
!reverseSearchActive &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R2-2: "Is the category tab bar visible/interactive" is still hand-maintained in three places that already diverge: this key gate (lacks suggestionsFromExport), the render-side prop suppression (~lines 2280-2292, which nulls availableCategories for suggestionsFromExport || commandSearchActive || reverseSearchActive), and showTabBar inside SuggestionsDisplay.tsx (length > 2). The suggestionsFromExport omission is unreachable today (export suggestions are single-category), but nothing encodes that invariant. — Failure scenario: a future change to tab-bar visibility (a new hide condition or threshold in SuggestionsDisplay, or a new category producer during export cycling) must be manually mirrored into this keypress gate; forgetting it silently consumes bare ←/→ to switch an invisible tab bar — the exact regression class this PR's own new command-search/attachment-mode tests exist to catch — turning caret movement into a no-op.

Fix (spans multiple locations, so no one-click suggestion): compute the predicate once in InputPrompt and share it:

const showCategoryTabs =
  !suggestionsFromExport &&
  !commandSearchActive &&
  !reverseSearchActive &&
  (completion.availableCategories?.length ?? 0) > 2;
// gate the key branch on: showCategoryTabs && !isAttachmentMode
// and pass showCategoryTabs / the same inputs to SuggestionsDisplay
// instead of letting it re-derive showTabBar independently.
中文说明

[Suggestion] R2-2:“分类标签栏是否可见/可交互”目前仍由三处手工维护且已经出现分歧:本按键门控(缺少 suggestionsFromExport)、渲染侧的 prop 抑制(约 2280-2292 行,在 suggestionsFromExport || commandSearchActive || reverseSearchActive 时把 availableCategories 置为 undefined)、以及 SuggestionsDisplay.tsx 内的 showTabBarlength > 2)。缺少 suggestionsFromExport 目前不可达(导出建议是单分类),但这一不变式没有任何代码保证。— 故障场景:未来任何对标签栏可见性的修改(SuggestionsDisplay 新增隐藏条件或阈值变化、导出循环中新增分类来源)都必须手工同步到这个按键门控;一旦遗漏,裸 ←/→ 就会悄悄切换一个不可见的标签栏——这正是本 PR 新增的命令搜索/附件模式测试所要防范的回归类型——方向键移动光标变成无响应。

修复建议(跨多处,无法一键应用):在 InputPrompt 中只计算一次谓词并共享,见上方代码块;按键分支以 showCategoryTabs && !isAttachmentMode 为门控,并让 SuggestionsDisplay 使用同一来源而不是自行推导 showTabBar

— qwen3.8-max via Qwen Code /review (v0.21.6)

if (
!commandSearchActive &&
!reverseSearchActive &&
!isAttachmentMode &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R2-3: The !isAttachmentMode gate term also disables the Ctrl+Tab / Ctrl+Shift+Tab alternatives in attachment mode. Probe-confirmed at this commit: with the menu open in attachment mode (image attachment, ≥3 categories, Up at row 0 — the exact state this PR's new test constructs), Ctrl+Tab falls through to ACCEPT_SUGGESTION (the { key: 'tab' } entry pins no modifiers, ~line 1457) and silently autocompletes the highlighted suggestion into the buffer, while the tab bar and its (←/→ to switch) hint remain rendered. Pre-PR (gate only > 2) Ctrl+Tab switched categories here — a probe flip reproduced the old behaviour. — Failure scenario: paste a clipboard image, type @ with ≥3 categories, press Up at row 0 to enter attachment mode, then press Ctrl+Tab — the binding the keyBindings comment and docs advertise as the category-switch alternative: the highlighted suggestion is autocompleted into the buffer instead of the category switching, while the hint still shows (←/→ to switch).

Fix idea: exclude only the bare arrows in attachment mode — e.g. check !isAttachmentMode inside the two arrow-matcher arms and keep the tab entries live, or accept the behaviour and say so in the keyBindings comment.

中文说明

[Suggestion] R2-3:!isAttachmentMode 门控项同时禁用了附件模式下的 Ctrl+Tab / Ctrl+Shift+Tab 备选绑定。已在当前提交上用探针证实:附件模式下菜单打开时(粘贴图片附件、≥3 个分类、在第 0 行按 Up——正是本 PR 新测试构造的状态),Ctrl+Tab 会落到 ACCEPT_SUGGESTION{ key: 'tab' } 条目未固定任何修饰键,约 1457 行),把高亮建议静默自动补全进输入缓冲区,而此时标签栏及其 (←/→ to switch) 提示仍在渲染。PR 之前(门控仅为 > 2Ctrl+Tab 在此状态下可以切换分类——探针翻转后复现了旧行为。— 故障场景:粘贴图片附件、输入 @(≥3 个分类)、在第 0 行按 Up 进入附件模式,然后按 Ctrl+Tab——即 keyBindings 注释和文档所宣传的分类切换备选键:结果高亮建议被自动补全进缓冲区而不是切换分类,提示文字却仍显示 (←/→ to switch)

修复思路:仅在附件模式下排除裸方向键——例如把 !isAttachmentMode 放进两个方向键匹配分支内部、保留 tab 条目生效;或接受该行为并在 keyBindings 注释中说明。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +2759 to 2760
stdin.write('\x1b[C'); // plain right arrow
await wait();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R2-6: The positive category-switching tests assert switchCategory was called but never assert the arrow was consumed — half of the PR's stated behaviour ("the arrows no longer move the caret") is unpinned. Probe-confirmed at this commit: removing the return true after switchCategory(1) makes the arrow both switch the category AND fall through to caret movement, and this test still passes. — Failure scenario: a future dispatch mutation that switches and then falls through leaves every test in this PR green while the headline tradeoff ("arrows no longer move the caret while the tab bar is visible") silently regresses.

Fix idea: after the arrow, also assert the buffer state — e.g. cursor position / buffer.text unchanged, or spy that buffer.handleInput was not called with the arrow key.

中文说明

[Suggestion] R2-6:正向的分类切换测试只断言了 switchCategory 被调用,从未断言方向键被消费——PR 声明行为的一半(“方向键不再移动光标”)没有被测试固定。已在当前提交上用探针证实:删除 switchCategory(1) 后的 return true,方向键会既切换分类又继续移动光标,而本测试仍然通过。— 故障场景:未来某次派发修改让方向键先切换再穿透,本 PR 的所有测试依旧全绿,而核心取舍(“标签栏可见时方向键不再移动光标”)已悄悄回归。

修复建议:在方向键之后追加缓冲区状态断言——例如光标位置/buffer.text 不变,或用 spy 断言 buffer.handleInput 没有收到该方向键。

— qwen3.8-max via Qwen Code /review (v0.21.6)

// to chip navigation below. While this menu is open the arrows
// therefore do NOT move the caret — Esc first to dismiss it.
if (
!commandSearchActive &&

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R2-7: Probe-confirmed at this commit: while command search (Ctrl+R, non-shell) is active, completion stays open (the reset effect only reacts to reverseSearchActive), this gate rejects Ctrl+Tab / Ctrl+Shift+Tab from COMPLETION_TAB_*, and the keystroke then falls through to ACCEPT_SUGGESTION ({ key: 'tab' } pins no modifiers) — handleAutocomplete fires on the invisible @ completion, mutating the search query. Pre-PR the same keystroke switched categories on the hidden tab bar (a probe on the base gate reproduced it), so this diff changes the outcome rather than merely removing a dead path. — Failure scenario: type @ses (menu open, >2 categories), press Ctrl+R to search command history, press Ctrl+Tab expecting nothing (tab bar hidden) — the highlighted @ suggestion is inserted into the search query, mutating it unexpectedly.

Fix idea: inside the showCompletionSuggestions branch, swallow COMPLETION_TAB_* matches while commandSearchActive || reverseSearchActive (return true without switching), or pin the chosen fallthrough behaviour with an explicit test.

中文说明

[Suggestion] R2-7:已在当前提交上用探针证实:命令搜索(Ctrl+R,非 shell)激活期间,补全保持打开(重置副作用只响应 reverseSearchActive),本门控拒绝 Ctrl+Tab / Ctrl+Shift+Tab 进入 COMPLETION_TAB_*,按键随后落到 ACCEPT_SUGGESTION{ key: 'tab' } 未固定修饰键)——handleAutocomplete 作用在不可见的 @ 补全上,把搜索词改掉。PR 之前同一按键会在隐藏的标签栏上切换分类(对基线门控的探针复现了该行为),因此本 diff 改变了结果,而不仅仅是移除了一条死路径。— 故障场景:输入 @ses(菜单打开、>2 个分类),按 Ctrl+R 进入命令搜索,按 Ctrl+Tab(标签栏已隐藏,预期无反应)——高亮的 @ 建议被插入搜索词,查询被意外修改。

修复建议:在 showCompletionSuggestions 分支内,当 commandSearchActive || reverseSearchActive 时吞掉 COMPLETION_TAB_* 匹配(直接 return true 不切换);或用显式测试固定所选的穿透行为。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +82 to +83
> alternatives when an editor mode (for example Vim keybindings) consumes the
> bare arrows.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-2: This note is false for Vim NORMAL mode. Verified at the reviewed commit: (1) Esc from Vim insert mode is consumed by vimHandleInput (vim.ts ~904-908 returns true) before the menu-dismissing Esc branch runs, so the menu stays open while Vim enters normal mode; (2) in normal mode bare arrows are consumed as vim movement (vim.ts ~1826-1861) AND Ctrl+Tab falls into the default catch-all (~1863: CLEAR_PENDING_STATES; return true) — so while the menu is open there is NO keyboard path to switch categories; only a second Esc dismisses it. — Failure scenario: with Vim keybindings on and the @ menu open (insert mode), the user presses Esc; Vim consumes it to enter normal mode without dismissing the menu — afterwards arrows move the vim cursor and Ctrl+Tab is silently swallowed, so no keyboard path switches categories while both this note and the in-app hint advertise reachable gestures.

Fix idea: qualify the note to Vim insert mode (and mention Esc dismissing the menu), or make Vim normal mode's default branch pass tab keys through the way insert mode does.

中文说明

[Suggestion] R3-2:该注释对 Vim NORMAL(普通)模式不成立。已在被审提交上核实:(1) Vim INSERT 模式下按 Esc 会被 vimHandleInput 消费(vim.ts 约 904-908 行返回 true),先于关闭菜单的 Esc 分支执行,因此 Vim 进入普通模式时菜单仍然打开;(2) 普通模式下裸方向键被作为 vim 光标移动消费(vim.ts 约 1826-1861 行),且 Ctrl+Tab 落入默认兜底分支(约 1863 行:CLEAR_PENDING_STATESreturn true)——于是菜单打开期间没有任何键盘路径可以切换分类;只有再按一次 Esc 才能关闭菜单。— 故障场景:启用 Vim 键位且 @ 菜单打开(INSERT 模式)时按 Esc,Vim 消费该键进入普通模式但不关闭菜单——之后方向键移动 vim 光标、Ctrl+Tab 被静默吞掉,注释和界面提示所宣传的手势全部不可达。

修复建议:把注释限定为 Vim INSERT 模式(并提及 Esc 可关闭菜单),或让 Vim 普通模式的默认分支像 INSERT 模式一样放行 tab 键。

— qwen3.8-max via Qwen Code /review (v0.21.6)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下:

Autofix round-2 review response (PR #8576)

Addressed 6 of the 7 round-2 findings from the automated reviewer (all **[Suggestion]**) in one commit: fix(cli): keep completion tab keys aligned with the rendered tab bar (#8576). No base conflict was reported (--conflict false), and no merge was performed.

Findings and dispositions

  1. [Suggestion] PR description contradicts the code (rc:3722691115, InputPrompt.test.tsx:2802) — Not resolved in code; answered on the thread. The contradiction is real, but the fix is an edit to the GitHub PR description, which this job cannot make (no GitHub write access — the workflow owns network writes). The code already matches the finding's account: COMPLETION_TAB_* keeps the Ctrl+Tab / Ctrl+Shift+Tab alternatives and the tests assert they switch categories. Posted the exact replacement wording for the Evidence and Risk & Scope sections as a thread reply so the maintainer/author channel can apply it.
  2. [Suggestion] Tab-bar visibility hand-maintained in three diverging places (rc:3722691125, InputPrompt.tsx:1424) — Resolved. Introduced one shared categoryTabsVisible boolean and used it in both the key gate and the availableCategories render prop (the existing shouldUseExportSuggestions/suggestionsFromExport derivations were hoisted so the boolean is visible to handleInput). The gate now also covers suggestionsFromExport, which it previously lacked, so the key side and the render side can no longer diverge silently.
  3. [Suggestion] Attachment mode blocks the Ctrl+Tab fallback (rc:3722691134, InputPrompt.tsx:1418) — Resolved. The attachment exclusion now applies only to the bare arrows (!isAttachmentMode || key.name === 'tab'), so Ctrl+Tab / Ctrl+Shift+Tab keep switching categories whenever the tab bar is rendered, matching pre-PR behavior in that state. Extended the attachment-mode test with the positive Ctrl+Tab / Ctrl+Shift+Tab assertions.
  4. [Suggestion] Duplicate command: false / meta: false (rc:3722691139, keyBindings.ts:208) — Resolved. Verified in matchKeyBinding (keyMatchers.ts:49/57) and in KeypressContext's own comment that both fields test key.meta. Removed the redundant command: false from both bare-arrow entries and kept meta: false, matching the suggested fix; the pre-existing Ctrl+Tab entries were left untouched.
  5. [Suggestion] Vim fallback chain never exercised (rc:3722691144, InputPrompt.test.tsx:2824) — Resolved. Added an InputPrompt test with vimHandleInput consuming the bare arrows but passing tab keys: bare arrows do NOT switch, ESC[9;5u / ESC[9;6u DO switch. Also pinned the other half of the chain in vim.test.ts: INSERT mode passes Tab and Ctrl+Tab through (false) and consumes bare left/right (true).
  6. [Suggestion] Positive tests don't assert the arrow was consumed (rc:3722691148, InputPrompt.test.tsx:2767) — Resolved. After each arrow write in the > 2 test, asserts buffer.move and buffer.handleInput were not called.
  7. [Suggestion] Ctrl+Tab during command search silently accepted into the query (rc:3722691151, InputPrompt.tsx:1417) — Resolved. The completion branch is now skipped entirely while commandSearchActive, so the modifier-agnostic ACCEPT_SUGGESTION tab binding can no longer accept the hidden completion into the search query; the keystroke becomes a clean no-op (BaseTextInput never inserts literal tabs). Per the finding's guidance, ACCEPT_SUGGESTION itself was not changed, so Ctrl+Tab still accepts when the tab bar is hidden in the documented cases. Added a regression test.

Mutation verification

Each behavioral fix was proven load-bearing by temporarily reintroducing the defect and confirming the new/extended tests fail:

  • Removing the !commandSearchActive outer gate → the command-search Ctrl+Tab test fails (handleAutocomplete called).
  • Blocking all tab keys in attachment mode → the extended attachment test fails on switchCategory(1).
  • Removing the return true after switchCategory(±1) → the caret-drift assertions fail (arrow reaches the buffer).

Changed files

  • packages/cli/src/ui/components/InputPrompt.tsx
  • packages/cli/src/ui/components/InputPrompt.test.tsx
  • packages/cli/src/config/keyBindings.ts
  • packages/cli/src/ui/hooks/vim.test.ts

Verification

  • npm run build — passed (exit 0)
  • npm run typecheck — passed (exit 0)
  • npm run lint — passed (exit 0)
  • npx prettier --check on the 4 changed files — passed
  • Focused Vitest (packages/cli): InputPrompt.test.tsx + keyMatchers.test.ts + SuggestionsDisplay.test.tsx + vim.test.ts — 393 passed, 0 failed
  • Mutation probes — 3/3 reintroduced defects caught (see above)
  • Full packages/cli Vitest suite (extra, not gate-required) — 17754 passed; 35 failures in 9 files, all A/B-proven pre-existing and environment-related: the identical 28 tests fail with my changes reverted (no docker/podman/sandbox binaries on this runner breaks the sandbox-config tests), and the remaining 7 AuthDialog failures occur only under the full parallel run in both states. None touch code in this PR's diff.
  • Integration tests after npm run bundle — not run: the touched behavior is fully exercised by the component-level tests above, which drive real stdin sequences (Kitty CSI-u included) through the live keypress pipeline.
中文说明

Autofix 第 2 轮审查响应(PR #8576

在一次提交中处理了自动评审者第 2 轮 7 条发现中的 6 条(均为 **[Suggestion]**):fix(cli): keep completion tab keys aligned with the rendered tab bar (#8576)。未报告基线冲突(--conflict false),也未执行任何合并。

发现与处置

  1. [Suggestion] PR 描述与代码矛盾rc:3722691115,InputPrompt.test.tsx:2802)——**未在代码中解决;已在该线索回复。**矛盾确实存在,但修复方式是编辑 GitHub 上的 PR 描述,本任务无法执行(没有 GitHub 写权限——网络写操作由工作流负责)。代码本身已与该发现所述一致:COMPLETION_TAB_* 保留了 Ctrl+Tab / Ctrl+Shift+Tab 备选绑定,且测试断言它们确实切换分类。已在线程回复中给出 Evidence 与 Risk & Scope 两节的准确替换措辞,供维护者/作者渠道应用。
  2. [Suggestion] 标签栏可见性由三处手工维护且已分歧rc:3722691125,InputPrompt.tsx:1424)——**已解决。**引入单一共享布尔量 categoryTabsVisible,同时用于按键门控与 availableCategories 渲染属性(将既有的 shouldUseExportSuggestions/suggestionsFromExport 派生上移,使 handleInput 可见该布尔量)。门控现在也覆盖了此前缺失的 suggestionsFromExport,按键侧与渲染侧不再可能悄悄分歧。
  3. [Suggestion] 附件模式阻断了 Ctrl+Tab 备选路径rc:3722691134,InputPrompt.tsx:1418)——**已解决。**附件模式排除现在仅作用于裸方向键(!isAttachmentMode || key.name === 'tab'),因此只要标签栏在渲染,Ctrl+Tab / Ctrl+Shift+Tab 就能继续切换分类,与 PR 之前该状态下的行为一致。附件模式测试已扩展,加入 Ctrl+Tab / Ctrl+Shift+Tab 的正向断言。
  4. [Suggestion] command: false / meta: false 重复rc:3722691139,keyBindings.ts:208)——**已解决。**已在 matchKeyBinding(keyMatchers.ts:49/57)及 KeypressContext 自身的注释中确认两个字段都测试 key.meta。按建议修复,从两个裸方向键条目中移除冗余的 command: false、保留 meta: false;既有的 Ctrl+Tab 条目未改动。
  5. [Suggestion] Vim 备选链路从未被真正执行rc:3722691144,InputPrompt.test.tsx:2824)——**已解决。**新增 InputPrompt 测试,令 vimHandleInput 消费裸方向键但放行 tab 键:裸方向键不切换,ESC[9;5u / ESC[9;6u 可以切换。同时在 vim.test.ts 中固定链路的另一半:INSERT 模式下 Tab 与 Ctrl+Tab 穿透(返回 false),裸 ←/→ 被消费(返回 true)。
  6. [Suggestion] 正向测试未断言方向键被消费rc:3722691148,InputPrompt.test.tsx:2767)——**已解决。**在 > 2 测试中每次写入方向键后,断言 buffer.movebuffer.handleInput 未被调用。
  7. [Suggestion] 命令搜索期间 Ctrl+Tab 悄悄接受建议进查询rc:3722691151,InputPrompt.tsx:1417)——已解决。commandSearchActive 期间现在整体跳过补全分支,不区分修饰键的 ACCEPT_SUGGESTION tab 绑定不再能把隐藏的补全接受进搜索查询;该按键成为干净的无操作(BaseTextInput 从不插入字面 tab)。按该发现的指引,未改动 ACCEPT_SUGGESTION 本身,因此在已记录的场景中标签栏隐藏时 Ctrl+Tab 仍然接受建议。已添加回归测试。

变异验证

通过临时重新引入每个缺陷、确认新增/扩展的测试会失败,证明各行为修复确实是承重的:

  • 移除外层 !commandSearchActive 门控 → 命令搜索 Ctrl+Tab 测试失败(handleAutocomplete 被调用)。
  • 在附件模式下阻断所有 tab 键 → 扩展后的附件测试在 switchCategory(1) 处失败。
  • 移除 switchCategory(±1) 之后的 return true → 光标漂移断言失败(方向键到达了输入缓冲区)。

变更文件

  • packages/cli/src/ui/components/InputPrompt.tsx
  • packages/cli/src/ui/components/InputPrompt.test.tsx
  • packages/cli/src/config/keyBindings.ts
  • packages/cli/src/ui/hooks/vim.test.ts

验证

  • npm run build — 通过(exit 0)
  • npm run typecheck — 通过(exit 0)
  • npm run lint — 通过(exit 0)
  • 对 4 个变更文件执行 npx prettier --check — 通过
  • 聚焦 Vitest(packages/cli):InputPrompt.test.tsx + keyMatchers.test.ts + SuggestionsDisplay.test.tsx + vim.test.ts — 393 通过,0 失败
  • 变异探针 — 3/3 重新引入的缺陷均被捕获(见上)
  • packages/cli 完整 Vitest 套件(额外执行,非门槛要求)— 17754 通过;9 个文件 35 个失败,均经 A/B 证明为预先存在且与环境相关:回退我的改动后同样这 28 个测试失败(本 runner 上没有 docker/podman/sandbox 可执行文件,导致 sandbox 配置类测试失败),其余 7 个 AuthDialog 失败只在两种状态下的完整并行运行中出现。均不涉及本 PR diff 中的代码。
  • npm run bundle 之后的集成测试 — 未运行:上述组件级测试已完整覆盖所触碰的行为,它们通过真实的按键管道驱动真实 stdin 序列(包括 Kitty CSI-u)。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 3/100 轮)。改动内容与我反驳保留之处如下:

Autofix round-3 review response (PR #8576)

The round-3 review was submitted against the pre-fix merge commit
(edc206fe1), while the previous round's fix commit (41a11ef8f) was pushed
mid-review. Four of the five findings therefore re-report issues that
41a11ef8f had already resolved; this round re-verified each of them against
HEAD with evidence (including a mutation probe), and fixed the one genuinely
new finding (R3-2, Vim NORMAL mode) in code. One additive commit:
fix(cli): pass completion tab keys through Vim NORMAL mode (#8576)
(e0c1fc64b). No base conflict was reported (--conflict false), and no
merge was performed.

Feedback points

  1. [Suggestion] rc:3723855254 (R2-2) — tab-bar visibility hand-maintained in three diverging places. RESOLVED IN CODE by the previous commit 41a11ef8f; re-verified this round.
    The reviewed commit (edc206fe1) still had the inline gate lacking
    suggestionsFromExport. At HEAD the predicate is computed once as
    categoryTabsVisible (InputPrompt.tsx, just above line 390) and drives BOTH
    the keypress gate (categoryTabsVisible && (!isAttachmentMode || key.name === 'tab')) and the render prop
    (availableCategories={categoryTabsVisible ? completion.availableCategories : undefined}).
    SuggestionsDisplay's internal length > 2 threshold is its documented
    render contract; the parent — the only production caller — now only ever
    passes undefined or a >2-entry list, so the key side and the render side
    can no longer diverge.

  2. [Suggestion] rc:3723855257 (R2-3) — !isAttachmentMode gate disables the Ctrl+Tab alternatives in attachment mode. RESOLVED IN CODE by 41a11ef8f; re-verified this round.
    At HEAD the attachment exclusion applies to the bare arrows only:
    categoryTabsVisible && (!isAttachmentMode || key.name === 'tab'). The
    attachment-mode test ("should keep Ctrl+Tab switching but not bare arrows
    while in attachment mode") constructs the exact state from the finding
    (image attachment, ≥3 categories, Up at row 0) and asserts bare arrows do
    NOT switch while Ctrl+Tab / Ctrl+Shift+Tab DO switch, so the documented
    alternatives no longer fall through to ACCEPT_SUGGESTION in that state.

  3. [Suggestion] rc:3723855262 (R2-6) — positive category-switching tests never assert the arrow was consumed. RESOLVED IN CODE by 41a11ef8f; re-verified this round with a mutation probe.
    The > 2 test asserts after each arrow write that mockBuffer.move and
    mockBuffer.handleInput were not called (InputPrompt.test.tsx:2766-2767,
    2773-2774), pinning the "arrows no longer move the caret" half of the
    behaviour. Mutation probe this round: removing the return true after
    switchCategory(1) makes the arrow fall through to caret movement and the
    test now FAILS at line 2767 (verified, mutation reverted).

  4. [Suggestion] rc:3723855269 (R2-7) — Ctrl+Tab during command search fell through to ACCEPT_SUGGESTION and mutated the search query. RESOLVED IN CODE by 41a11ef8f; re-verified this round.
    At HEAD the whole completion branch is gated on
    showCompletionSuggestions && !commandSearchActive, so the
    modifier-agnostic { key: 'tab' } accept binding is unreachable while
    command search is active; the history-search key handler (which runs
    earlier in handleInput) owns the keystroke instead. The regression test
    ("should NOT accept the suggestion on Ctrl+Tab while command search is
    active") asserts handleAutocomplete is not called.

  5. [Suggestion] rc:3723855278 (R3-2) — the keyboard-shortcuts note is false for Vim NORMAL mode. FIXED THIS ROUND in code (e0c1fc64b), which makes the note true as written.
    Both claims verified in vim.ts at HEAD: (1) Esc from INSERT mode is
    consumed by vimHandleInput (returns true after ESCAPE_TO_NORMAL),
    before the menu-dismissing branch in InputPrompt runs, so the menu stays
    open while Vim enters NORMAL mode; (2) in NORMAL mode bare arrows are
    consumed as vim movement and Ctrl+Tab fell into the default catch-all
    (CLEAR_PENDING_STATES; return true) — leaving NO keyboard path to switch
    categories while the note advertised one. Implemented the finding's second
    fix idea: NORMAL mode now passes Ctrl-modified tab keys through
    (if (normalizedKey.name === 'tab' && normalizedKey.ctrl) return false;),
    mirroring insert mode's tab pass-through, so Ctrl+Tab /
    Ctrl+Shift+Tab reach InputPrompt's completion branch in every Vim mode.
    Bare Tab and the bare arrows keep their existing NORMAL-mode behaviour
    (pending-state clearing / vim movement), and pendingCharRead still
    consumes everything while awaiting a target character. A docs-only fix was
    rejected because it would have documented the very hole this PR exists to
    close (the Ctrl+Tab alternatives were restored in round 1 precisely so Vim
    users keep a keyboard path). Added two vim.test.ts tests; mutation probe:
    removing the pass-through fails the pass-through test while the
    consumption test stays green (verified, mutation reverted).

Verification

Commands actually run this round (Node v22, on the working tree that became commit e0c1fc64b):

  • npx vitest run src/ui/hooks/vim.test.ts src/ui/keyMatchers.test.ts src/ui/components/SuggestionsDisplay.test.tsx (packages/cli) — 193 passed (3 files)
  • npx vitest run src/ui/hooks/vim.test.ts src/ui/keyMatchers.test.ts src/ui/components/SuggestionsDisplay.test.tsx src/ui/components/SuggestionsDisplay.mouse.test.tsx src/ui/components/InputPrompt.test.tsx (packages/cli) — 398 passed (5 files)
  • npx vitest run src/ui/hooks/vim.test.ts (packages/cli, re-run on the committed state) — 136 passed
  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check packages/cli/src/ui/hooks/vim.ts packages/cli/src/ui/hooks/vim.test.ts — passed
  • Mutation probes (both reverted afterwards): removing return true after switchCategory(1) fails the R2-6 consumption test; removing the new NORMAL-mode pass-through fails the new pass-through test.
  • No settings source changed, so npm run generate:settings-schema was not applicable. Integration tests were not run: the change is unit-level key handling in the vim hook, fully covered by the Vitest suites above.
中文说明

Autofix 第 3 轮评审响应(PR #8576

第 3 轮评审提交时针对的是修复前的合并提交(edc206fe1),而上一轮的修复提交
41a11ef8f)是在评审进行期间推送的。因此五条发现中有四条重复报告了
41a11ef8f 已经解决的问题;本轮逐条在 HEAD 上用证据重新核实(包括一次变异探针),
并修复了唯一真正的新发现(R3-2,Vim NORMAL 模式)。一个增量提交:
fix(cli): pass completion tab keys through Vim NORMAL mode (#8576)
e0c1fc64b)。未报告 base 冲突(--conflict false),未执行任何合并。

反馈要点

  1. [Suggestion] rc:3723855254(R2-2)—— 标签栏可见性在三处手工维护且已分歧。已由前一提交 41a11ef8f 在代码中解决;本轮重新核实。
    被评审的提交(edc206fe1)中门控仍是缺少 suggestionsFromExport 的内联写法。
    在 HEAD 上,谓词只计算一次,即 categoryTabsVisible(InputPrompt.tsx,390 行上方),
    同时驱动按键门控(categoryTabsVisible && (!isAttachmentMode || key.name === 'tab')
    和渲染 prop(availableCategories={categoryTabsVisible ? completion.availableCategories : undefined})。
    SuggestionsDisplay 内部的 length > 2 阈值是其文档化的渲染契约;父组件——唯一的生产调用方——
    现在只会传入 undefined 或超过 2 项的列表,因此按键侧与渲染侧不再可能分歧。

  2. [Suggestion] rc:3723855257(R2-3)—— !isAttachmentMode 门控在附件模式下禁用了 Ctrl+Tab 备选键。已由 41a11ef8f 在代码中解决;本轮重新核实。
    HEAD 上附件模式排除只作用于裸方向键:
    categoryTabsVisible && (!isAttachmentMode || key.name === 'tab')
    附件模式测试(“should keep Ctrl+Tab switching but not bare arrows while in attachment mode”)
    构造了与发现完全相同的状态(图片附件、≥3 个分类、在第 0 行按 Up),并断言裸方向键不切换、
    而 Ctrl+Tab / Ctrl+Shift+Tab 可以切换,因此该状态下文档所宣传的备选键不再落到 ACCEPT_SUGGESTION

  3. [Suggestion] rc:3723855262(R2-6)—— 正向分类切换测试从未断言方向键被消费。已由 41a11ef8f 在代码中解决;本轮用变异探针重新核实。
    > 2 测试在每次方向键写入后断言 mockBuffer.movemockBuffer.handleInput
    均未被调用(InputPrompt.test.tsx:2766-2767、2773-2774),固定了“方向键不再移动光标”
    这一半行为。本轮变异探针:删除 switchCategory(1) 后的 return true,方向键会穿透到
    光标移动,测试在第 2767 行失败(已验证,变异已还原)。

  4. [Suggestion] rc:3723855269(R2-7)—— 命令搜索期间 Ctrl+Tab 落到 ACCEPT_SUGGESTION,把搜索词改掉。已由 41a11ef8f 在代码中解决;本轮重新核实。
    HEAD 上整个补全分支以 showCompletionSuggestions && !commandSearchActive 为门控,
    因此命令搜索激活期间不区分修饰键的 { key: 'tab' } 接受绑定不可达;
    按键由更早运行于 handleInput 的历史搜索按键处理器接管。回归测试
    (“should NOT accept the suggestion on Ctrl+Tab while command search is active”)
    断言 handleAutocomplete 未被调用。

  5. [Suggestion] rc:3723855278(R3-2)—— 键盘快捷键注释对 Vim NORMAL 模式不成立。本轮在代码中修复(e0c1fc64b),使该注释按原文成立。
    两个论断均已在 HEAD 的 vim.ts 中核实:(1) INSERT 模式下的 EscvimHandleInput
    消费(执行 ESCAPE_TO_NORMAL 后返回 true),先于 InputPrompt 中关闭菜单的分支运行,
    因此 Vim 进入 NORMAL 模式时菜单仍然打开;(2) NORMAL 模式下裸方向键被作为 vim 移动消费,
    Ctrl+Tab 落入默认兜底分支(CLEAR_PENDING_STATES; return true)——注释所宣传的
    键盘路径在菜单打开期间完全不存在。实现了该发现给出的第二个修复思路:NORMAL 模式现在
    放行带 Ctrl 的 tab 键(if (normalizedKey.name === 'tab' && normalizedKey.ctrl) return false;),
    与 INSERT 模式的 tab 放行保持一致,使 Ctrl+Tab / Ctrl+Shift+Tab 在所有 Vim 模式下
    都能到达 InputPrompt 的补全分支。裸 Tab 与裸方向键保持既有 NORMAL 模式行为
    (清除待定状态 / vim 移动),等待目标字符的 pendingCharRead 状态仍会消费所有按键。
    未采用仅改文档的方案,因为那等于把本 PR 要填的坑写进文档(Ctrl+Tab 备选键正是在第 1 轮
    为让 Vim 用户保留键盘路径而恢复的)。新增两个 vim.test.ts 测试;变异探针:删除该放行逻辑
    会使放行测试失败、消费测试保持绿色(已验证,变异已还原)。

验证

本轮实际执行的命令(Node v22,工作树即提交 e0c1fc64b):

  • npx vitest run src/ui/hooks/vim.test.ts src/ui/keyMatchers.test.ts src/ui/components/SuggestionsDisplay.test.tsx(packages/cli)—— 193 通过(3 个文件)
  • npx vitest run src/ui/hooks/vim.test.ts src/ui/keyMatchers.test.ts src/ui/components/SuggestionsDisplay.test.tsx src/ui/components/SuggestionsDisplay.mouse.test.tsx src/ui/components/InputPrompt.test.tsx(packages/cli)—— 398 通过(5 个文件)
  • npx vitest run src/ui/hooks/vim.test.ts(packages/cli,在已提交状态上复跑)—— 136 通过
  • npm run build —— 通过
  • npm run typecheck —— 通过
  • npm run lint —— 通过
  • npx prettier --check packages/cli/src/ui/hooks/vim.ts packages/cli/src/ui/hooks/vim.test.ts —— 通过
  • 变异探针(事后均已还原):删除 switchCategory(1) 后的 return true 会使 R2-6 消费断言测试失败;删除新的 NORMAL 模式放行逻辑会使新的放行测试失败。
  • 未改动任何 settings 源文件,故无需 npm run generate:settings-schema。未运行集成测试:该改动是 vim hook 层的按键处理,上述 Vitest 套件已完整覆盖。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. 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: verification and reverse audit — each ran and opened its brief, but neither was launched with the prompt the CLI built — the launches were written by hand, so the posted findings cannot be counted as verified, and what the agents were actually asked is not what this skill certifies.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 未审查:验证与反向审计——两者都运行并打开了各自的 brief,但都不是用 CLI 构建的 prompt 启动的——启动 prompt 是手写的,发布的发现不能算作已验证,agent 实际被要求做的也不是本 skill 所认证的内容。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment thread packages/cli/src/ui/hooks/vim.ts Outdated
Comment on lines +1165 to +1167
if (normalizedKey.name === 'tab' && normalizedKey.ctrl) {
return false;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The new NORMAL-mode Ctrl+Tab pass-through returns before the switch's default: catch-all, which pre-PR dispatched CLEAR_PENDING_STATES (zeroes count, pendingOperator, pendingCharRead) and consumed the key — so a pending Vim operator/count now silently survives a category switch. The adjacent ? pass-through in this same function gates on s.pendingOperator === null && s.count === 0, i.e. the house pattern passes keys through only from a clean state. — Failure scenario: with the @ menu open in NORMAL mode (Esc from INSERT is consumed by vim, so the menu stays open), press d then Ctrl+Tab: pre-PR the pending state was cleared; now d stays armed and the next motion (w) deletes a word the user's Ctrl+Tab gesture indicated they had abandoned. A partially typed count (3, Ctrl+Tab, motion) leaks the same way. Probe-confirmed at this commit: d → Ctrl+Tab → h executes the pending delete, and dispatching CLEAR_PENDING_STATES before return false flips the probe.

Suggested change
if (normalizedKey.name === 'tab' && normalizedKey.ctrl) {
return false;
}
if (normalizedKey.name === 'tab' && normalizedKey.ctrl) {
dispatch({ type: 'CLEAR_PENDING_STATES' });
return false;
}
中文说明

[Suggestion] 新增的 NORMAL 模式 Ctrl+Tab 放行分支在 switch 的 default: 兜底之前直接返回,而 PR 之前按键会落入该兜底并触发 CLEAR_PENDING_STATES(清零 countpendingOperatorpendingCharRead)——于是待定的 Vim 操作符/计数会在切换分类后悄悄保留。同一函数中相邻的 ? 放行分支显式以 s.pendingOperator === null && s.count === 0 为门控,即此处的惯例是仅在干净状态下放行按键。— 故障场景:NORMAL 模式下 @ 菜单打开时(INSERT 下按 Esc 被 vim 消费、菜单仍打开),先按 d 再按 Ctrl+Tab:PR 之前待定状态会被清除;现在 d 仍处于待发状态,下一个移动键(w)会删除用户已用 Ctrl+Tab 手势放弃的那个词。半截计数(3、Ctrl+Tab、移动键)同样泄漏。已在当前提交上用探针确认:d → Ctrl+Tab → h 会执行待定删除;在 return false 前派发 CLEAR_PENDING_STATES 后探针结果翻转。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +394 to +398
const categoryTabsVisible =
!suggestionsFromExport &&
!commandSearchActive &&
!reverseSearchActive &&
(completion.availableCategories?.length ?? 0) > 2;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The > 2 tab-bar threshold is now duplicated across two files that must agree by convention: categoryTabsVisible re-implements the exact render-side rule const showTabBar = (availableCategories?.length ?? 0) > 2; in SuggestionsDisplay.tsx (~line 121) — the component whose rendering the added comment documents this boolean as tracking. This PR promoted the previously-inline check into a named single sync point while leaving the render-side original in place, and raised the cost of a desync: it now eats the bare arrows (every ←/→ during @ completion) rather than the rarely-pressed Ctrl+arrows. Distinct from the already-fixed R2-2 gate/prop divergence — this is the threshold constant's second home. — Failure scenario: a future change to the render threshold in SuggestionsDisplay (show tabs with 2 entries, or require 4) without a matching edit here leaves categoryTabsVisible true while the bar is hidden; handleInput then consumes every bare ←/→ with return true — caret movement silently freezes with no visible tab bar to explain it, the exact failure the added comment warns about. — Suggested fix: export the rule from SuggestionsDisplay.tsx (e.g. export function hasCategoryTabs(categories) { return (categories?.length ?? 0) > 2; }), use it for showTabBar there, and compose categoryTabsVisible from it here.

中文说明

[Suggestion] > 2 的标签栏阈值现在重复存在于两个必须靠约定保持一致的文件中:categoryTabsVisible 重新实现了 SuggestionsDisplay.tsx(约 121 行)里渲染侧的规则 const showTabBar = (availableCategories?.length ?? 0) > 2;——而新增注释明确声称该布尔量就是用来跟踪这个组件的渲染结果的。本 PR 把原先内联的检查提升成了命名的单一同步点,却保留了渲染侧的原实现,且提高了失同步的代价:现在失同步会吞掉裸方向键(@ 补全期间的每次 ←/→),而不是过去很少按下的 Ctrl+方向键。这与已修复的 R2-2(门控/prop 分歧)不同——这里指的是阈值常量的第二处宿主。— 故障场景:未来修改 SuggestionsDisplay 的渲染阈值(2 项也显示标签,或要求 4 项)而忘了同步这里时,categoryTabsVisible 为 true 而标签栏实际隐藏;handleInput 会以 return true 吞掉所有裸 ←/→——光标移动在无可见标签栏可解释的情况下静默失效,正是新增注释所警告的失败。— 修复建议:从 SuggestionsDisplay.tsx 导出该规则(如 export function hasCategoryTabs(categories) { return (categories?.length ?? 0) > 2; }),在那里用于 showTabBar,并在此处用它组合出 categoryTabsVisible

— qwen3.8-max via Qwen Code /review (v0.21.6)

// more than two tabs (at least 3 entries including 'all'). Plain ←/→ are
// never consumed here, so they always move the caret in the editable buffer.
if ((completion.availableCategories?.length ?? 0) > 2) {
if (showCompletionSuggestions && !commandSearchActive) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Command-search suppression is bolted onto consumers even though the producer (useCommandCompletion) already owns a generalized suppression mechanism and is simply never told about command search: its reset effect keys off reverseSearchActive || !active (useCommandCompletion.tsx ~276-284), and the call site (~line 371) passes the shell reverse-search state but not commandSearchActive, so showSuggestions stays true for the whole command-search session — a fact this PR's own test comment records. Six compensation sites verified: getActiveCompletion(), the activeCategory render gate, the mode render gate, shouldUseExportSuggestions, plus this PR's handleInput gate and categoryTabsVisible. InputPrompt is the hook's only production caller, so closing this at the producer is contained; the current code is correct — this is root-cause ownership. — Failure scenario: the next consumer of completion.showSuggestions / completion.suggestions must rediscover the guard or it acts on the stale still-open completion during search — the exact class of bug this hunk fixes (Tab/Ctrl+Tab silently accepting a suggestion into the search query). — Suggested fix: pass reverseSearchActive || commandSearchActive at the useCommandCompletion(...) call so completion resets on entering command search exactly as it already does for shell reverse search; the consumer-side !commandSearchActive gates then become redundant. Tradeoff: category/scroll state would be recomputed rather than preserved when search exits — the same behavior shell reverse search already has.

中文说明

[Suggestion] 命令搜索期间的补全抑制被拼接在各个消费方,而生产方(useCommandCompletion)本就有一个通用的抑制机制,只是从未被告知命令搜索的存在:其重置副作用以 reverseSearchActive || !active 为条件(useCommandCompletion.tsx 约 276-284 行),调用处(约 371 行)传入了 shell 反向搜索状态却没有传入 commandSearchActive,因此整个命令搜索期间 showSuggestions 一直保持 true——本 PR 自己的测试注释也记录了这一点。已核实六处补偿点:getActiveCompletion()activeCategory 渲染门控、mode 渲染门控、shouldUseExportSuggestions,外加本 PR 新增的 handleInput 门控与 categoryTabsVisible。InputPrompt 是该 hook 唯一的生产调用方,在生产方收口是可控的;当前代码是正确的——这是根因归属问题。— 故障场景:下一个读取 completion.showSuggestions / completion.suggestions 的消费方必须重新发现该守卫,否则会在搜索期间对仍然“打开”的陈旧补全做出反应——正是本 hunk 所修复的 bug 类别(Tab/Ctrl+Tab 悄悄把建议接受进搜索词)。— 修复建议:在 useCommandCompletion(...) 调用处传入 reverseSearchActive || commandSearchActive,使进入命令搜索时补全像 shell 反向搜索一样被重置;消费侧的 !commandSearchActive 门控随之变为冗余。权衡:退出搜索时分类/滚动状态会被重算而非保留——与 shell 反向搜索的既有行为一致。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined for this PR — recording the reason here so it isn't silently dropped. The finding itself notes the current code is correct; closing this at the producer is a root-cause refactor of the pre-existing command-search suppression rather than a defect fix in this keybinding change. Two concrete problems: (1) passing reverseSearchActive || commandSearchActive into useCommandCompletion changes pre-existing command-search behavior — completion state (category, scroll, active index, dismissed flag) is currently preserved across a command-search session and restored on exit, while the producer reset would zero it on entry and recompute on exit (the tradeoff the finding itself acknowledges); that behavior call shouldn't ride along in this PR. (2) It would not consolidate all six compensation sites anyway: shouldUseExportSuggestions also gates the separate useExportCompletion hook, and getActiveCompletion() selects between completion and commandSearchCompletion for rendering, so those consumer gates must stay regardless; only the two small gates this PR added (both commented and test-covered) could be removed. Happy to revisit in a follow-up focused on command-search suppression ownership if maintainers want that behavior change.

中文说明

本 PR 拒绝此项——在此记录理由,避免被静默丢弃。该发现本身承认当前代码是正确的;在生产方收口属于对既有命令搜索抑制机制的根因重构,而非本键位改动中的缺陷修复。具体有两点:(1) 向 useCommandCompletion 传入 reverseSearchActive || commandSearchActive 会改变命令搜索的既有行为——补全状态(分类、滚动、选中项、dismissed 标志)目前在命令搜索期间被保留、退出后恢复,而生产方重置会在进入时清零、退出时重算(即该发现自己承认的权衡);这个行为决策不应顺带塞进本 PR。(2) 即便如此也无法把六处补偿点全部收口:shouldUseExportSuggestions 同时还门控着独立的 useExportCompletion hook,getActiveCompletion() 也要在 completioncommandSearchCompletion 之间做渲染选择,这些消费方门控无论如何都必须保留;能移除的只有本 PR 新增的两处小门控(均有注释且被测试覆盖)。如果维护者希望做出该行为变更,欢迎在专注于命令搜索抑制归属的后续 PR 中重新讨论。

Comment on lines +82 to +83
> alternatives when an editor mode (for example Vim keybindings) consumes the
> bare arrows.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This note promises Ctrl+Tab / Ctrl+Shift+Tab as the Vim-user fallback unqualified, but in terminals without the Kitty keyboard protocol Ctrl+Tab arrives as an indistinguishable bare Tab: the app negotiates the protocol itself (detectAndEnableKittyProtocol, with a kittyProtocolEnabled=false fallback), and the bare Tab fails the ctrl: true COMPLETION_TAB_* matchers while matching the modifier-agnostic ACCEPT_SUGGESTION { key: 'tab' } binding. Ctrl+Shift+Tab fares no better (arrives as ESC[Z{tab, shift, no ctrl}). — Failure scenario: Vim keybindings on, tab bar showing, terminal without Kitty support: the user presses Ctrl+Tab expecting a category switch; the highlighted suggestion is inserted into the prompt instead — and since Vim modes also consume the bare arrows, there is no working keyboard path while this note asserts one. — Suggested fix: qualify the note (and the keyBindings.ts comment) that the alternatives require a terminal that reports modifiers on Tab (Kitty keyboard protocol), otherwise Ctrl+Tab behaves as plain Tab.

Suggested change
> alternatives when an editor mode (for example Vim keybindings) consumes the
> bare arrows.
> alternatives when an editor mode (for example Vim keybindings) consumes the
> bare arrows. (On terminals without the Kitty keyboard protocol, `Ctrl+Tab`
> arrives as a plain `Tab` and accepts the highlighted suggestion instead.)
中文说明

[Suggestion] 该注释无条件地承诺 Ctrl+Tab / Ctrl+Shift+Tab 是 Vim 用户的备选路径,但在不支持 Kitty 键盘协议的终端里,Ctrl+Tab 到达时与裸 Tab 无法区分:应用自身会协商该协议(detectAndEnableKittyProtocol,回退为 kittyProtocolEnabled=false),而裸 Tab 不满足 COMPLETION_TAB_*ctrl: true 匹配条件,却会命中不区分修饰键的 ACCEPT_SUGGESTION{ key: 'tab' } 绑定。Ctrl+Shift+Tab 同样不行(以 ESC[Z 到达 → {tab, shift, 无 ctrl})。— 故障场景:启用 Vim 键位、标签栏可见、终端不支持 Kitty:用户按 Ctrl+Tab 期望切换分类,结果高亮建议被插入输入框——而且 Vim 各模式同时消费裸方向键,该注释声称可用的键盘路径实际完全不存在。— 修复建议:在注释(以及 keyBindings.ts 的注释)中加上限定——备选键需要能报告 Tab 修饰键的终端(Kitty 键盘协议),否则 Ctrl+Tab 等同于普通 Tab。

— qwen3.8-max via Qwen Code /review (v0.21.6)

qwen-code-dev-bot pushed a commit to LaZzyMan/qwen-code that referenced this pull request Aug 6, 2026
Clear the pending Vim operator/count when Ctrl+Tab passes through
NORMAL mode (the pre-existing default catch-all cleared them), share
the tab-bar visibility threshold between SuggestionsDisplay and
InputPrompt via hasCategoryTabs, and document that the Ctrl+Tab
alternatives need a terminal with Kitty keyboard protocol support.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下:

Autofix review round — PR #8576

Commit: 959a79b15 on lazzy/8069-bare-arrow-completion-tabs (no base merge; --conflict false).

Feedback dispositions

  • R4-1 — Vim NORMAL-mode Ctrl+Tab pass-through leaks pending operator/count (vim.ts) — Resolved. Before this PR the key fell into the switch's default: catch-all, which dispatches CLEAR_PENDING_STATES; the pass-through added earlier in this PR skipped that clear, so a pending d/partial count survived the category-switch gesture and fired on the next motion. The pass-through now dispatches CLEAR_PENDING_STATES before returning false, exactly the suggested fix. Added two regression tests: d → Ctrl+Tab → w must move (not delete) a word, and 3 → Ctrl+Shift+Tab → l must move one character.
  • R4-2 — > 2 tab-bar threshold duplicated between InputPrompt and SuggestionsDisplayResolved. SuggestionsDisplay.tsx now exports hasCategoryTabs(categories) and uses it for its own showTabBar; InputPrompt's categoryTabsVisible composes the same predicate, so the threshold has a single home. Added a boundary unit test (undefined / [] / 1 / 2 tabs → false, 3 → true).
  • R4-3 — move command-search suppression into useCommandCompletionDeclined with evidence. The finding itself states the current code is correct and this is root-cause ownership, not a defect. Passing reverseSearchActive || commandSearchActive into the hook would change pre-existing command-search behavior (completion category/scroll/active-index state is currently preserved across command search; the producer reset would zero it on entry and recompute on exit — the tradeoff the finding acknowledges), and it would not consolidate all six compensation sites anyway: shouldUseExportSuggestions also gates the separate useExportCompletion hook and getActiveCompletion() selects between completion and commandSearchCompletion, so those consumer gates must stay. Only the two small, commented, test-covered gates this PR added could be removed. Out of scope for a keybinding fix; a reply with this reasoning is posted on the thread (left open).
  • R4-4 — docs promise Ctrl+Tab alternatives unqualifiedResolved. The note in keyboard-shortcuts.md now states that on terminals without the Kitty keyboard protocol these keys arrive as plain Tab / Shift+Tab and accept the highlighted suggestion instead (verified against the code: ACCEPT_SUGGESTION matches { key: 'tab' } modifier-agnostic, COMPLETION_TAB_* requires ctrl: true, and legacy reverse-tab ESC[Z parses as {tab, shift} without ctrl). The keyBindings.ts comment is qualified the same way.

Verification

Commands actually run this round (all from a clean npm ci + build runner state):

  • npm run typecheck — passed
  • npm run lint — passed
  • npm run build — passed
  • npx vitest run src/ui/hooks/vim.test.ts src/ui/components/SuggestionsDisplay.test.tsx src/ui/keyMatchers.test.ts (in packages/cli) — 3 files, 196 tests passed
  • npx vitest run src/ui/components/InputPrompt.test.tsx (in packages/cli) — 202 tests passed
  • npx prettier --check on all seven changed files — passed
  • Integration tests after npm run bundle — not run: the touched behavior is fully exercised by the unit/component tests above, not only through the bundled CLI or integration harness
  • npm run generate:settings-schema — not applicable: no settings source changed
中文说明

Autofix 审查轮次 — PR #8576

提交:lazzy/8069-bare-arrow-completion-tabs 分支上的 959a79b15(未合并 base;--conflict false)。

反馈处理结论

  • R4-1 — Vim NORMAL 模式下 Ctrl+Tab 放行导致待定操作符/计数泄漏vim.ts)— 已解决。 本 PR 之前该按键会落入 switch 的 default: 兜底分支并派发 CLEAR_PENDING_STATES;本 PR 早先新增的放行分支跳过了这次清零,于是待定的 d/半截计数会在切换分类的手势后保留,并在下一个移动键上执行。现在放行分支在 return false 之前先派发 CLEAR_PENDING_STATES,与建议的修复完全一致。新增两个回归测试:d → Ctrl+Tab → w 必须只移动(不删除)一个词;3 → Ctrl+Shift+Tab → l 必须只移动一个字符。
  • R4-2 — > 2 标签栏阈值在 InputPrompt 与 SuggestionsDisplay 之间重复已解决。 SuggestionsDisplay.tsx 现在导出 hasCategoryTabs(categories) 并在自身的 showTabBar 中使用;InputPrompt 的 categoryTabsVisible 复用同一谓词,阈值从此只有一处宿主。新增边界单元测试(undefined / [] / 1 / 2 个标签 → false,3 个 → true)。
  • R4-3 — 把命令搜索抑制收口到 useCommandCompletion 生产方附证据拒绝(Declined)。 该发现本身承认当前代码是正确的,属于根因归属问题而非缺陷。向 hook 传入 reverseSearchActive || commandSearchActive 会改变命令搜索的既有行为(补全的分类/滚动/选中项状态目前在命令搜索期间被保留、退出后恢复;生产方重置会在进入时清零、退出时重算——即该发现自己承认的权衡),而且也无法把六处补偿点全部收口:shouldUseExportSuggestions 同时还门控着独立的 useExportCompletion hook,getActiveCompletion() 也要在 completioncommandSearchCompletion 之间做选择,这些消费方门控无论如何都要保留。能移除的只有本 PR 新增的两处小门控(均有注释且被测试覆盖)。对键位修复 PR 而言超出范围;已在该线程回复上述理由(线程保持打开)。
  • R4-4 — 文档无条件承诺 Ctrl+Tab 备选路径已解决。 keyboard-shortcuts.md 中的注释现在说明:在不支持 Kitty 键盘协议的终端上,这些按键会以普通 Tab / Shift+Tab 到达,并接受当前高亮的建议(已对照代码核实:ACCEPT_SUGGESTION 以不区分修饰键的 { key: 'tab' } 匹配,COMPLETION_TAB_* 要求 ctrl: true,且 legacy 反向 Tab ESC[Z 解析为不带 ctrl 的 {tab, shift})。keyBindings.ts 的注释也做了同样限定。

验证

本轮实际执行的命令(runner 为干净的 npm ci + build 状态):

  • npm run typecheck — 通过
  • npm run lint — 通过
  • npm run build — 通过
  • npx vitest run src/ui/hooks/vim.test.ts src/ui/components/SuggestionsDisplay.test.tsx src/ui/keyMatchers.test.ts(位于 packages/cli)— 3 个文件,196 个测试通过
  • npx vitest run src/ui/components/InputPrompt.test.tsx(位于 packages/cli)— 202 个测试通过
  • npx prettier --check(全部七个改动文件)— 通过
  • npm run bundle 后的集成测试 — 未执行:改动的行为已由上述单元/组件测试完整覆盖,并非只能通过打包后的 CLI 或集成测试框架验证
  • npm run generate:settings-schema — 不适用:未改动任何 settings 源文件

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.21.6)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix review round — no action required (PR #8576)

No actionable feedback arrived this round:

  • The automated reviewer (round 5) reported zero anchored findings: its only Suggestion-level finding could not be anchored to a changed line and was dropped, and the review explicitly stated "nothing further to act on here".
  • No inline comments, no issue-level comments, no failed checks, and no still-red checks were included in the feedback.
  • No base-conflict resolution was requested (--conflict false), so origin/main was not merged.

No code changes were made and no commit was created. The PR head remains at 959a79b15 ("fix(cli): tighten completion tab switching per review (#8576)"), matching origin/lazzy/8069-bare-arrow-completion-tabs.

Note: the reviewer disclosed that the "build-and-test — Integration Tests (CLI, No Sandbox)" suite was skipped in CI and not run locally by the reviewer. That is a disclosure of the reviewer's own coverage, not a change request, and the workflow's independent CI remains the final verification gate.

中文说明

Autofix 审查轮次 — 无需处理(PR #8576

本轮没有收到可操作的反馈:

  • 自动审查器(第 5 轮)报告零条可锚定的发现:其唯一一条建议级发现无法锚定到改动行,已被丢弃,且审查明确说明"此处无需进一步处理"。
  • 反馈中没有包含行内评论、issue 级评论、失败的检查,也没有持续红色的检查。
  • 没有要求解决与基础分支的冲突(--conflict false),因此未合并 origin/main

未做任何代码改动,也未创建提交。PR head 仍为 959a79b15("fix(cli): tighten completion tab switching per review (#8576)"),与 origin/lazzy/8069-bare-arrow-completion-tabs 一致。

备注:审查器披露 "build-and-test — Integration Tests (CLI, No Sandbox)" 套件在 CI 中被跳过、审查器本地也未运行。这是审查器自身覆盖范围的披露,不是变更请求,工作流的独立 CI 仍是最终验证关卡。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下:

Autofix review round summary — PR #8576

Feedback triage

No actionable feedback this round: feedback.md listed no reviews, inline
comments, issue-level comments, failed checks, or still-red checks newer than
the last evaluation. Nothing to classify or address.

Base-conflict resolution (--conflict true)

Merged origin/main (tip 6cae50c7ea) into
lazzy/8069-bare-arrow-completion-tabs and resolved the conflicts by
understanding both sides.

Conflict: docs/users/reference/keyboard-shortcuts.md

The only content conflict was the Input Prompt table, which both sides
re-padded after editing different rows:

  • origin/main (via fd76d4ddde, fix(cli): let ESC cancel ongoing work before popping queued messages #8353) expanded the Up Arrow (at the
    top) / Esc row to describe ESC cancelling an ongoing request before
    popping queued messages, and added a new Background tasks dialog
    section.
  • This PR added completion-tab notes to the Ctrl+B / Left Arrow and
    Ctrl+F / Right Arrow rows (bare arrows switch @ completion category
    tabs while the tab bar is rendered).

Resolution: took the union of both sides — main's expanded Up Arrow/Esc
row and Background tasks dialog section are kept, and the PR's Ctrl+B/
Ctrl+F notes are re-applied on top. The PR's rewritten Suggestions table
and tab-behavior note (which main never touched) came through the auto-merge
intact. Table padding was normalized with Prettier.

Auto-merged files (reviewed, no conflicts)

Merge-result audit: the tree diff against origin/main is exactly the PR's 9
files, and the diff against the pre-merge HEAD is exactly main's incoming
changes — nothing was dropped from either side.

Changes

  • One merge commit f419ae3503 (parents 959a79b159 + 6cae50c7ea),
    containing only the conflict resolution described above. No behavioral code
    changes were made in this round.

Verification

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • vitest run packages/cli (focused: InputPrompt.test.tsx,
    SuggestionsDisplay.test.tsx, vim.test.ts, keyMatchers.test.ts,
    keyBindings.test.ts) — 5 files, 408 tests passed
中文说明

Autofix 评审轮次总结 — PR #8576

反馈分类

本轮没有需要处理的反馈:feedback.md 中未列出任何新于上次评估的评审、行内评论、
Issue 级评论、失败检查或持续失败的检查。没有需要分类或处理的内容。

基础分支冲突解决(--conflict true

已将 origin/main(最新提交 6cae50c7ea)合并到
lazzy/8069-bare-arrow-completion-tabs,并在理解双方改动的基础上解决了冲突。

冲突:docs/users/reference/keyboard-shortcuts.md

唯一的内容冲突是 Input Prompt 表格——双方在修改了不同行之后都对表格重新做了
列对齐:

  • origin/main(通过 fd76d4dddefix(cli): let ESC cancel ongoing work before popping queued messages #8353)扩展了 Up Arrow(位于顶部)/ Esc
    行的描述,说明 ESC 会先取消正在进行的请求、再弹出排队消息,并新增了
    Background tasks dialog 章节。
  • 本 PR 在 Ctrl+B / Left ArrowCtrl+F / Right Arrow 行添加了补全分类
    标签页的说明(标签栏显示时,裸方向键用于切换 @ 补全分类标签页)。

解决方式:取双方的并集——保留 main 扩展后的 Up Arrow/Esc 行和 Background
tasks dialog 章节,并在其上重新应用本 PR 的 Ctrl+B/Ctrl+F 说明。本 PR 重写
Suggestions 表格和标签页行为注释(main 从未改动过该部分)通过自动合并
完整保留。表格列对齐已用 Prettier 规范化。

自动合并的文件(已审查,无冲突)

合并结果审计:工作树相对 origin/main 的差异恰好是本 PR 的 9 个文件,相对合并前
HEAD 的差异恰好是 main 引入的全部改动——双方的内容都没有丢失。

变更

  • 一个合并提交 f419ae3503(父提交为 959a79b159 + 6cae50c7ea),仅包含上述
    冲突解决。本轮未做任何行为上的代码改动。

验证

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • vitest run packages/cli(聚焦:InputPrompt.test.tsx
    SuggestionsDisplay.test.tsxvim.test.tskeyMatchers.test.ts
    keyBindings.test.ts)— 5 个文件,408 个测试全部通过

Base-conflict check · 基分支冲突检查: conflicted with main — resolved in this push. · 与 main 有冲突——已在本次推送中解决。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

已审查。 建议见行内评论。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.21.7)

Comment on lines 2735 to 2737
// With only 2 entries (all + one real category) the tab bar is hidden,
// so Ctrl+arrows must not trigger category switching.
// so the arrows must not trigger category switching.
expect(switchCategory).not.toHaveBeenCalled();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The hidden-tab-bar tests assert only that switching does not happen (switchCategory not called); the other half of the arrow contract — that the arrows still reach the input buffer when the tab bar is hidden — is never asserted. Probe-confirmed at this commit: inserting if (!categoryTabsVisible && (key.name === 'left' || key.name === 'right')) return true; into the completion branch survives the full suite (207/207 pass), silently freezing caret movement in the common ≤2-category case. Note the fall-through goes via BaseTextInput.tsx:305buffer.handleInput(key), and mockBuffer.move receives zero calls in this state, so the pinning assertion must target mockBuffer.handleInput. — Failure scenario: a future regression that eats bare arrows while the tab bar is hidden ships green — half of the PR's stated behaviour ("arrows keep their usual caret behaviour everywhere else") is unpinned.

Suggested change
// With only 2 entries (all + one real category) the tab bar is hidden,
// so Ctrl+arrows must not trigger category switching.
// so the arrows must not trigger category switching.
expect(switchCategory).not.toHaveBeenCalled();
// With only 2 entries (all + one real category) the tab bar is hidden,
// so the arrows must not trigger category switching.
expect(switchCategory).not.toHaveBeenCalled();
// The arrows must still reach the buffer so the caret stays movable.
expect(mockBuffer.handleInput).toHaveBeenCalledWith(
expect.objectContaining({ name: 'right' }),
);
expect(mockBuffer.handleInput).toHaveBeenCalledWith(
expect.objectContaining({ name: 'left' }),
);
中文说明

[Suggestion] 标签栏隐藏的测试只断言了切换不会发生(switchCategory 未被调用);方向键契约的另一半——标签栏隐藏时方向键仍能到达输入缓冲区——从未被断言。已在本提交通过变异探针确认:在补全分支中插入 if (!categoryTabsVisible && (key.name === 'left' || key.name === 'right')) return true; 后整个测试套件仍然全部通过(207/207),会在常见的 ≤2 分类场景中悄悄冻结光标移动。注意穿透路径经由 BaseTextInput.tsx:305buffer.handleInput(key),此状态下 mockBuffer.move 的调用次数为零,因此固定该行为的断言必须以 mockBuffer.handleInput 为目标。— 故障场景:未来某个在标签栏隐藏时吞掉裸方向键的回归将一路绿灯地合入——PR 所声明行为的一半("其他场景下方向键保持正常的光标行为")没有任何测试固定。

— qwen3.8-max via Qwen Code /review (v0.21.7)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 6/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 6/100 轮)。改动内容与我反驳保留之处如下:

Autofix review response — PR #8576

Feedback points and dispositions

1. Inline [Suggestion] by @qwen-code-ci-botpackages/cli/src/ui/components/InputPrompt.test.tsx:2737 (rc:3740641267)

Point: The hidden-tab-bar tests assert only that switching does not happen (switchCategory not called). The other half of the arrow contract — that bare arrows still reach the input buffer so the caret stays movable while the tab bar is hidden — is never asserted. A mutation probe showed an arrow-eating regression survives the full suite.

Disposition: Implemented. Verified against the current commit first: bare arrows fall through InputPrompt's completion branch into BaseTextInput.tsxbuffer.handleInput(key) (plain arrows never reach mockBuffer.move), so the pinning assertions target mockBuffer.handleInput, as the finding states. The suggested assertions were added to both hidden-tab-bar tests, since the finding is written about "the hidden-tab-bar tests" and both share the same gap:

  • should NOT switch category on left/right when availableCategories <= 2
  • should NOT switch category on left/right when availableCategories is exactly 2

Each now also asserts mockBuffer.handleInput was called with { name: 'right' } and with { name: 'left' }.

Pinning proof (mutation probe): inserted the exact regression described in the finding (if (!categoryTabsVisible && (key.name === 'left' || key.name === 'right')) return true;) into the completion branch — both hidden-tab-bar tests failed (handleInput received 0 calls), exactly the previously-green regression. After restoring the source, the suite returns to 207/207 passing. The mutation is now caught.

2. Review-level note by @qwen-code-ci-bot (COMMENTED review)

  • "1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here." — informational; no action available.
  • "Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally." — this round's change is test-only (assertions inside an existing unit test file), and the arrow-key path is fully exercised by the unit harness, so no integration-harness run applies to this change; CI remains the final gate.

Changes

  • Commit 45463b027atest(cli): pin bare-arrow fallthrough to buffer when tab bar is hidden (#8576): adds two handleInput assertions (plus one why-comment each) to each of the two hidden-tab-bar tests in packages/cli/src/ui/components/InputPrompt.test.tsx (+14 lines). No production code changed; nothing else touched.

Conflict notes

None — --conflict false; origin/main was not merged.

Verification

  • npx vitest run src/ui/components/InputPrompt.test.tsx (packages/cli, touched file) — passed, 207/207
  • Mutation probe: applied the finding's regression → 2 targeted tests failed as expected; restored source → passed, 207/207
  • npm run buildpassed
  • npm run typecheckpassed
  • npm run lintpassed
  • npx prettier --check packages/cli/src/ui/components/InputPrompt.test.tsxpassed
  • Integration tests (after npm run bundle) — not run: the change is test-only and the exercised behavior is covered by the unit harness, so the integration gate does not apply to this change
中文说明

Autofix 审查回应 — PR #8576

反馈点及处理

1. @qwen-code-ci-bot 的行内 [Suggestion]packages/cli/src/ui/components/InputPrompt.test.tsx:2737(rc:3740641267)

要点: 标签栏隐藏的测试只断言了切换不会发生(switchCategory 未被调用)。方向键契约的另一半——标签栏隐藏时裸方向键仍能到达输入缓冲区、光标保持可移动——从未被断言。变异探针表明,一个吞掉方向键的回归可以让整个测试套件全部通过。

处理:已实现。 先在当前提交上核实:裸方向键会穿透 InputPrompt 的补全分支,经 BaseTextInput.tsxbuffer.handleInput(key)(纯方向键永远不会到达 mockBuffer.move),因此固定行为的断言按该发现的说明以 mockBuffer.handleInput 为目标。建议中的断言被添加到两个标签栏隐藏测试中,因为该发现针对的是"标签栏隐藏的测试"(复数),且两个测试存在相同的缺口:

  • should NOT switch category on left/right when availableCategories <= 2
  • should NOT switch category on left/right when availableCategories is exactly 2

两个测试现在都额外断言 mockBuffer.handleInput{ name: 'right' }{ name: 'left' } 被调用过。

固定效果证明(变异探针): 在补全分支中插入该发现所描述的回归原句(if (!categoryTabsVisible && (key.name === 'left' || key.name === 'right')) return true;)后,两个标签栏隐藏测试失败handleInput 收到 0 次调用),正是此前一路绿灯的回归场景。恢复源码后,测试套件回到 207/207 全部通过。该变异现在会被捕获。

2. @qwen-code-ci-bot 的审查级备注(COMMENTED 审查)

  • "1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。"——信息性说明;无可执行操作。
  • "未审查:build-and-test — Integration Tests (CLI, No Sandbox) 在 CI 中被跳过,其套件也未在本地运行。"——本轮改动仅为测试(在既有单测文件中添加断言),方向键路径已由单测测试框架完整覆盖,因此该改动不适用集成测试;CI 仍是最终验证关卡。

变更内容

  • 提交 45463b027atest(cli): pin bare-arrow fallthrough to buffer when tab bar is hidden (#8576):在 packages/cli/src/ui/components/InputPrompt.test.tsx 的两个标签栏隐藏测试中各添加两条 handleInput 断言(各附一条说明"为什么"的注释)(+14 行)。未改动任何生产代码;未触及其他内容。

冲突说明

无 —— --conflict false;未合并 origin/main

验证

  • npx vitest run src/ui/components/InputPrompt.test.tsx(packages/cli,被改动文件)— 通过,207/207
  • 变异探针:应用该发现所述的回归 → 2 个目标测试按预期失败;恢复源码后 → 通过,207/207
  • npm run build通过
  • npm run typecheck通过
  • npm run lint通过
  • npx prettier --check packages/cli/src/ui/components/InputPrompt.test.tsx通过
  • 集成测试(npm run bundle 之后)— 未运行:该改动仅为测试,所涉行为已由单测框架覆盖,因此集成验证关卡不适用于本次改动

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blockers. 1 Suggestion-level finding(s) could not be anchored to a changed line and were dropped; nothing further to act on here.

中文说明

已审查——无阻断问题。 1 条建议级发现无法锚定到改动行,已丢弃;此处无需进一步处理。

— qwen3.8-max via Qwen Code /review (v0.21.7)

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix review round — no action needed (PR #8576)

The latest feedback on this PR contains no actionable items:

  • Automated review (round 7): no blockers. The single Suggestion-level finding could not be anchored to a changed line and was dropped by the review workflow; the review ledger records zero findings.
  • Inline comments: none.
  • Issue-level comments: none.
  • Failed / still-red checks: none.

No code changes were made this round. The branch remains at its current head commit; no merge of origin/main was performed since no conflicts were reported.

中文说明

Autofix 审查轮次 — 无需处理(PR #8576

本 PR 的最新反馈中没有任何可操作的事项:

  • 自动审查(第 7 轮): 无阻断问题。唯一的一条建议级(Suggestion)发现无法锚定到改动行,已被审查工作流丢弃;审查账本(ledger)中记录的发现数量为零。
  • 行内评论: 无。
  • Issue 级评论: 无。
  • 失败 / 仍然失败的检查: 无。

本轮未做任何代码改动。分支保持在当前头部提交;由于未报告冲突,也未执行 origin/main 的合并。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@LaZzyMan LaZzyMan added autofix/skip Not eligible for the scheduled autofix agent and removed autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) labels Aug 10, 2026
@LaZzyMan
LaZzyMan force-pushed the lazzy/8069-bare-arrow-completion-tabs branch from 45463b0 to e39832c Compare August 10, 2026 10:11
@github-actions

Copy link
Copy Markdown
Contributor

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 qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines +1090 to +1092
// The visible category tabs own the bare arrows, including in Vim mode.
// All other states fall through to their existing input owner.
if (showCompletionSuggestions && categoryTabsVisible) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Consuming bare Left/Right here — before vimHandleInput — bypasses vim's arrow-motion branch (vim.ts:1825-1860), which pre-PR applied/cancelled the pending operator and cleared the repeat count. With Vim mode on and the category tabs visible, a pending d/c/y/</> operator or a typed digit count survives a category switch and fires on the user's next motion key. This is distinct from the R3-2 thread (the missing Esc dismiss path): pre-PR the bare arrow always reached vim's arrow branch, which cleaned this state up; post-PR vim never sees the key at all. — Failure scenario: Vim mode enabled; type @ with ≥3 categories (tab bar visible); press Esc — vim consumes it (menu persists, NORMAL mode); press d (pendingOperator set); press Right Arrow (category switches, vim never sees the key); press l → the stale delete executes, removing text the user did not ask to delete. Probe-verified at this commit: with the PR's routing, d → intercepted Right → l performs the delete; with pre-PR routing the same sequence cancels the operator. Counts and c leak the same way.

Suggested fix direction: when this block consumes an arrow while vim is active, clear vim's pending operator/count first (expose a reset from useVim), or let vim observe the key for state purposes before consuming it.

中文说明

[Critical] 在此处——vimHandleInput 之前——消费裸 Left/Right 会绕过 vim 的方向键分支(vim.ts:1825-1860);该分支在本 PR 之前会应用/取消待命操作符并清空重复计数。启用 Vim 模式且分类标签可见时,待命的 d/c/y/</> 操作符或已输入的次数会越过分类切换存活下来,并在用户的下一个移动键上触发。这与 R3-2 线程(缺少 Esc 关闭菜单路径)是不同的问题:本 PR 之前裸方向键总会到达 vim 的方向键分支并清理这些状态;本 PR 之后 vim 完全看不到该按键。— 故障场景:启用 Vim 模式;输入 @ 且 ≥3 个分类(标签栏可见);按 Esc——被 vim 消费(菜单仍在,进入 NORMAL 模式);按 d(设置 pendingOperator);按 Right Arrow(切换分类,vim 看不到该键);按 l → 悬置的删除被执行,删掉用户并未要求删除的文本。已在本提交通过探针验证:按本 PR 的路由,d → 被拦截的 Right → l 会执行删除;按 PR 之前的路由,同样的按键序列会取消操作符。计数与 c 以同样方式泄漏。

修复方向建议:当此分支在 vim 激活时消费方向键时,先清除 vim 的待命操作符/计数(从 useVim 暴露一个重置方法),或在消费按键前先让 vim 观察该键以维护状态。

— qwen3.8-max via Qwen Code /review (v0.21.8)

Comment on lines 2315 to 2317
availableCategories={
suggestionsFromExport ||
commandSearchActive ||
reverseSearchActive
? undefined
: completion.availableCategories
categoryTabsVisible ? completion.availableCategories : undefined
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] availableCategories now derives from categoryTabsVisible (which includes !isAttachmentMode), but the sibling activeCategory prop on this same call is still gated only by suggestionsFromExport. useCompletion filters the suggestion list by activeCategory upstream (useCompletion.ts:91-99) and nothing resets the category when entering attachment mode — so hiding the tab bar leaves an invisible filter narrowing the list, and the arrows are now owned by attachment navigation, so the user cannot switch back to 'All' until leaving attachment mode. Pre-PR the tab bar stayed rendered in attachment mode, so the filter state was always visible. — Failure scenario: type @foo with ≥3 categories, switch to a category tab holding ≤1 match, paste an image and press Up at row 0 → attachment mode hides the tab bar while the list stays filtered to the now-invisible category; the user cannot see or change the filter until leaving attachment mode or dismissing the menu. Probe-confirmed at this commit (with flip check).

Suggested fix: reset the completion category to 'all' when entering attachment mode (e.g. near setIsAttachmentMode(true)); gating the activeCategory prop alone is not enough because the filtering happens upstream in useCompletion.

中文说明

[Suggestion] availableCategories 现在由 categoryTabsVisible(包含 !isAttachmentMode)派生,但同一处的 activeCategory 属性仍只以 suggestionsFromExport 为门控。useCompletion 在上游按 activeCategory 过滤建议列表(useCompletion.ts:91-99),且进入附件模式时没有任何逻辑重置分类——于是标签栏被隐藏后,一个不可见的过滤器仍在收窄列表,而方向键已归附件导航所有,用户在离开附件模式之前无法切回 'All'。本 PR 之前标签栏在附件模式下仍然渲染,过滤状态始终可见。— 故障场景:输入 @foo(≥3 个分类),切换到只有 ≤1 条匹配的分类标签,粘贴图片后在第 0 行按 Up → 附件模式隐藏标签栏,而列表仍被过滤到这个已不可见的分类;用户在离开附件模式或关闭菜单之前无法看到或更改该过滤。已在本提交通过探针验证(含翻转检查)。

修复建议:进入附件模式时将补全分类重置为 'all'(例如在 setIsAttachmentMode(true) 附近);只门控 activeCategory 属性是不够的,因为过滤发生在上游的 useCompletion

— qwen3.8-max via Qwen Code /review (v0.21.8)

@LaZzyMan
LaZzyMan added this pull request to the merge queue Aug 11, 2026
Merged via the queue into QwenLM:main with commit e20601d Aug 11, 2026
86 of 87 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.10.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/skip Not eligible for the scheduled autofix agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants