Skip to content

fix(web-shell): Select worktree mode without extra confirmation - #13607

Open
wenshao wants to merge 2 commits into
mainfrom
codex/web-shell-automatic-worktree-session
Open

wenshao wants to merge 2 commits into
mainfrom
codex/web-shell-automatic-worktree-session

Conversation

@wenshao

@wenshao wenshao commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Selecting Worktree for a new Web Shell session now selects that mode and closes the menu immediately. Sending the first message uses the existing session creation flow to automatically name and create the isolated worktree. The description explains this timing, and the reset control remains available before sending.

Why it's needed

Previously, choosing Worktree required another click on “Create worktree”, although that button only recorded the pending mode and did not create anything. Removing the redundant confirmation makes the UI match the existing automatic creation behavior.

Reviewer Test Plan

How to verify

  1. Start a new session in a trusted Git workspace and choose Worktree from the composer’s Git mode menu. The menu should close immediately and the chip should show Worktree, without a separate confirmation or name input. Reopen the menu and verify that Worktree remains checked, the worktree command preview is visible, and no confirmation button appears.
  2. Before sending a message, no session creation request should occur. Send the first message and verify that exactly one session creation request carries worktree: {} and no branch.
  3. In another new session, select Worktree and then reset to the current branch. Sending a message should create an ordinary session without a worktree request.
  4. Check that New branch still requires a valid name and confirmation, the default mode still uses the current branch, and the selector stays hidden for a non-Git workspace.

Evidence (Before & After)

Initialization after the first message — real daemon

First message submitted: worktree session initialization is still pending

Captured after sending the first message from the changed local Web Shell to an isolated test repository served by global Qwen Code 0.25.0. At capture time, the single real POST /session request carrying worktree: {} was still in flight, the input remained visible, and the bottom-right send button showed its existing loading spinner and was disabled. The response was not artificially delayed; this UI currently has no separate “Initializing worktree” message or progress bar.

The test passed (1/1, 8.2 seconds). The daemon subsequently returned HTTP 200 with persisted worktree ownership. The actual directory, Git worktree registration and branch, session marker, and daemon session association were verified. No prompt/generate/recap request was sent; model execution was not exercised.

Mode selection — before and after

These are screenshots captured from real browser test runs on macOS with a test workspace and intercepted daemon requests. Before uses the globally installed Qwen Code 0.25.0; After uses the changed local Web Shell source. They demonstrate the UI and request contract, not real Git worktree creation.

Before: selecting Worktree still requires confirmation After: one click selects Worktree and closes the menu
Before: Create worktree confirmation After: Worktree selected with reset control

Updated explanation before selection

All images are hosted on the wenshao/qwen-code fork’s assets/web-shell-automatic-worktree-session-20261007 branch.

Validation passed: six Chromium browser scenarios, a baseline screenshot check, a post-change screenshot check, 18 unit tests (16 existing branch-name cases and two real popover interaction cases), Web Shell build and typecheck, changed-file ESLint and Prettier, and whitespace checks. The browser scenarios cover one-click selection, deferred creation, exactly one outgoing creation request, both reset paths, branch selection, and non-Git visibility.

Two consecutive pre-commit audit rounds completed with no Critical or Suggestion findings: an open-ended audit followed by an adversarial audit. The adversarial audit also passed four additional browser scenarios covering keyboard selection with a prefilled prompt, branch/Worktree transitions in both directions, and escaping an unconfirmed invalid branch selection.

Review follow-up adds two real-component tests to the existing PR unit-test gate and links the superseded confirmation behavior in both design languages. Both requested mutations were rejected by the new tests: the old pending-only click fails the intent callback assertion, and resetting the selection on reopen fails the checked-state assertion. Unchanged controls passed 18/18 before and after. Two further pre-commit audits (round 3 open-ended and round 4 adversarial) were clean; the adversarial rerun also passed 18/18 with unhandled-error ignoring explicitly disabled.

Tested on

OS Status
🍏 macOS ✅ Tested
🪟 Windows ⚠️ Not tested
🐧 Linux ⚠️ Not tested

Environment (optional)

macOS, Node.js 22.22.2, Chromium Playwright, local Vite Web Shell, and mocked daemon responses. Baseline UI verification used an isolated loopback server from global Qwen Code 0.25.0.

Risk & Scope

  • Main risk or tradeoff: Worktree selection takes effect immediately, but remains reversible through the reset control before the first message. The daemon protocol and worktree lifecycle are unchanged.
  • Not validated / out of scope: Model execution was not tested. Real Git worktree creation and session ownership were verified separately using global Qwen Code 0.25.0 with the changed local Web Shell. Root build, typecheck, and bundle are blocked by missing email/A2A dependencies and stale bridge build output in the existing installation; no new CLI bundle was produced.
  • Breaking changes / migration notes: None.

Design: English · 简体中文. Both versions have matching decisions, constraints, and acceptance criteria.

Linked Issues

None.

中文说明

本 PR 的改动

在 Web Shell 新会话中选择 Worktree 后,立即选中该模式并关闭菜单。发送首条消息时,沿用现有会话创建流程自动命名并创建隔离 Worktree。说明文案明确了创建时机,发送前仍可通过重置控件取消选择。

为什么需要

此前选择 Worktree 后,还要再点击“创建 Worktree”,但该按钮实际上只记录待使用的模式,并不创建任何内容。移除这次多余确认,让界面与已有的自动创建行为一致。

审阅者测试计划

如何验证

  1. 在已信任的 Git 工作区中新建会话,从输入框的 Git 模式菜单选择 Worktree。菜单应立即关闭,标签显示 Worktree,不再需要单独确认或输入名称。重新打开菜单,确认 Worktree 仍处于选中状态,显示 Worktree 命令预览,且没有确认按钮。
  2. 发送消息前,不应出现创建会话请求。发送首条消息后,应恰好发出一次创建会话请求,携带 worktree: {},且不含 branch。
  3. 在另一个新会话中选择 Worktree,再重置为当前分支。发送消息后应创建普通会话,请求不包含 Worktree。
  4. 确认新建分支仍需合法名称及确认,默认模式仍使用当前分支,非 Git 工作区仍隐藏该选择器。

证据(改动前后)

首次发送后的初始化 — 真实 daemon

首条消息已提交:Worktree 会话仍在初始化

当前修改后的本地 Web Shell 向全局 Qwen Code 0.25.0 管理的隔离测试仓库发送首条消息后拍摄。截图时,唯一的真实 POST /session 请求携带 worktree: {} 且仍在处理中,输入内容仍可见,右下角发送按钮显示现有加载动画并处于禁用状态。没有人为延迟响应;当前界面尚无单独的“正在初始化 Worktree”文案或进度条。

该测试通过(1/1,8.2 秒)。随后 daemon 返回 HTTP 200,Worktree 所有权已持久化。实际目录、Git Worktree 注册信息及分支、Session marker 和 daemon 会话关联均已验证。未发送 prompt/generate/recap 请求,未测试模型执行。

模式选择 — 改动前后

以下截图来自 macOS 上实际运行的浏览器测试,使用测试工作区并拦截 daemon 请求。改动前使用全局安装的 Qwen Code 0.25.0,改动后使用修改后的本地 Web Shell 源码。截图展示界面和请求契约,不代表真实 Git Worktree 创建验证。

改动前:选择 Worktree 后仍需确认 改动后:单击即可选中 Worktree 并关闭菜单
改动前:创建 Worktree 确认按钮 改动后:Worktree 已选中且可重置

选择前的新说明文案

所有图片均存放于 wenshao/qwen-code fork 的 assets/web-shell-automatic-worktree-session-20261007 分支。

已通过的验证包括:六项 Chromium 浏览器场景、一次基线截图检查、一次改动后截图检查、18 项单元测试(16 项现有分支名校验及 2 项真实弹层交互)、Web Shell 构建和类型检查、改动文件的 ESLint 和 Prettier,以及空白检查。浏览器场景覆盖单击选择、延迟创建、仅发出一次创建请求、两种重置路径、分支选择和非 Git 工作区可见性。

提交前连续两轮审计均无 Critical 或 Suggestion:先进行无方向审计,再进行对抗审计。对抗审计还通过了四项额外浏览器场景,覆盖已输入消息时用键盘选择模式、分支与 Worktree 双向切换,以及退出未确认的无效分支选择。

评论处理补充了两项进入现有 PR 单元测试门禁的真实组件测试,并在双语设计中明确旧确认流程的替代关系。两种指定回退均被新测试拦截:旧的仅待选点击在意图回调断言处失败,重新打开时重置选择在选中态断言处失败。变异前后正常对照均为 18/18 通过。随后提交前第 3 轮无方向审计及第 4 轮对抗审计均无发现;对抗复跑显式禁用忽略未处理异常后,仍为 18/18 通过。

已测试系统

操作系统 状态
🍏 macOS ✅ 已测试
🪟 Windows ⚠️ 未测试
🐧 Linux ⚠️ 未测试

环境(可选)

macOS、Node.js 22.22.2、Chromium Playwright、本地 Vite Web Shell 和模拟 daemon 响应。基线界面验证使用全局 Qwen Code 0.25.0 启动的隔离本地回环服务。

风险与范围

  • 主要风险或取舍:Worktree 模式选择立即生效,但发送首条消息前仍可通过重置控件撤销。daemon 协议和 Worktree 生命周期保持不变。
  • 未验证或范围之外:未测试模型执行。已使用全局 Qwen Code 0.25.0 配合修改后的本地 Web Shell,单独验证真实 Git Worktree 创建及会话所有权。现有安装缺失邮件通道及 A2A 依赖,bridge 构建产物也已过期,导致根目录 build、typecheck 和 bundle 未通过;未产出新的 CLI bundle。
  • 破坏性变更或迁移说明:无。

设计文档:English · 简体中文。两份文档的决策、约束和验收标准一致。

关联 Issue

无。

@wenshao

wenshao commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

E2E verification report

Verified on macOS with Node.js 22.22.2 and Chromium. Mode-selection screenshots in the PR body come from actual browser runs with mocked daemon requests. The additional first-message initialization screenshot uses the changed local Web Shell with a real isolated Qwen Code 0.25.0 daemon.

Check Result
Global Qwen Code 0.25.0 baseline Reproduced the extra Create worktree confirmation; selecting and confirming each produce zero session requests, and the first prompt produces exactly one request with worktree: {}.
Local Web Shell browser suite 6/6 passed in 16.8 seconds. Single-click selection closes the menu; creation is deferred until the first message; the outgoing request has worktree: {} and no branch. Worktree/branch reset, normal branch creation, default mode, and non-Git visibility pass.
Actual Before screenshot 1/1 passed in 4.8 seconds; the old confirmation is visible and POST /session remains at zero.
Actual After screenshots 1/1 passed in 6.9 seconds; updated explanation and single-click selection captured and visually inspected.
Component and branch-name unit tests 18/18 passed: 16 existing branch-name cases plus two real popover interaction cases covering one-click Worktree selection/close and the checked state/command on reopening. These run in the existing PR unit-test gate.
Regression mutation checks Both requested regressions fail at the intended assertions: restoring the old pending-only click gives zero intent callbacks; resetting to Current branch on reopening gives aria-checked="false". Unchanged controls pass 18/18 before and after.
Web Shell build and typecheck Passed.
Changed-file ESLint, Prettier, whitespace checks Passed.

Browser command, from packages/web-shell:

PLAYWRIGHT_PORT=5190 npx playwright test client/e2e/web-shell.git-mode.spec.ts --project=chromium --workers=1

The original six-scenario browser suite verifies UI state and the outgoing request contract. An additional real-daemon initialization test now verifies actual Git worktree creation and session ownership. Model execution was not tested. Root build, typecheck, and bundle did not pass because the existing installation is missing email/A2A dependencies and contains stale bridge build output. No new CLI bundle was produced.

The PR body includes the real Before/After screenshots hosted on wenshao/qwen-code under assets/web-shell-automatic-worktree-session-20261007.

Pre-commit review: two consecutive open-ended/adversarial audit rounds found zero Critical and zero Suggestion issues. Four additional adversarial browser cases passed: keyboard selection with prefilled text does not submit early, both branch/Worktree transitions clear the other mode, and escaping an invalid unconfirmed branch preserves the selected intent correctly. Each case rechecked exactly one session creation request after the prompt request.

Additional first-message initialization evidence

Real initialization screenshot is embedded at the start of the PR description’s English and Chinese evidence sections. The test passed 1/1 in 8.2 seconds: while the real creation request was still pending, the first input remained visible and the send button showed Loading and was disabled. There was no artificial response delay. The daemon then returned HTTP 200 with persisted ownership; the actual directory, Git registration and branch, owner marker, and daemon session association all matched. Zero prompt/generate/recap requests were sent. The isolated daemon was stopped afterward.

Review follow-up verification

Review follow-up adds two real-component tests to the existing PR unit-test gate and links the superseded confirmation behavior in both design languages. Both requested mutations were rejected by the new tests: the old pending-only click fails the intent callback assertion, and resetting the selection on reopen fails the checked-state assertion. Unchanged controls passed 18/18 before and after. Two further pre-commit audits (round 3 open-ended and round 4 adversarial) were clean; the adversarial rerun also passed 18/18 with unhandled-error ignoring explicitly disabled.

Only tests and the bilingual design documentation changed in this follow-up; the browser and real-daemon screenshot evidence above remains from the unchanged product implementation. Web Shell build/typecheck, changed-file ESLint/Prettier and whitespace checks passed again. Root build/typecheck remain blocked by the existing dependency/build-output problems described above.

@wenshao
wenshao marked this pull request as ready for review October 7, 2026 14:12
@qwen-code-review-bot

qwen-code-review-bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

⏳ Approval still deferred — 1 PR CI workflow run(s) still in progress for 8b2d319; the finalize job approves automatically once every run lands green. finalize run

⏳ 审批仍在延迟中 —— 8b2d319 还有 1 个 PR CI workflow 在运行,全部通过后 finalize 任务会自动提交审批。查看 finalize 运行

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — every required section filled in, both languages, before/after evidence attached.

Problem: real, and confirmable from the code rather than taking the description's word for it. handleConfirmWorktree did nothing but call onIntentChange({ mode: 'worktree' }) and close the popover — no checkout, no request. The worktree is actually created later by the pre-existing first-prompt path: App.tsx reads the intent when the first message is sent and passes worktree: { slug } into session creation, which lands as worktree: {} on POST /session. So the second click bought nothing, and the button's label ("Create worktree") actively misdescribed what it did. That is an observed defect, not theoretical hardening.

Direction: aligned. One click is already the norm on this surface — the Worktrees manager path creates a new session with a worktree intent directly and no confirmation, and the "Current branch" option in this same popover commits immediately. Worktree was the odd one out, and it was odd in the direction of extra friction. No direct reference in the upstream Claude Code CHANGELOG to a git-mode selector confirmation; worktree isolation is a heavily worked area there, so the surface itself is clearly relevant.

Size: not applicable — no core paths are touched (all six files are Web Shell client or docs/design/). For context: +165/−84 across 6 files, roughly 38 production lines (component, its CSS module, i18n copy), 111 test lines, 100 documentation lines. Well under any advisory threshold.

Approach: close to the minimal edit, which is what I'd have written from the title alone — drop the button, drop its now-orphaned styles and the two translation keys, rename the handler to say what it does, and fix the description copy so it states when the copy is created. Turning the clear-button test into a two-mode loop is assertion-preserving: every branch assertion survives, and the worktree path gains coverage plus a "no session request before send" check. No drive-by refactors or unrelated churn ride along. Design doc is present in both languages with matching sections and reciprocal links.

Risk: no elevated risk signals — none of the changed files match the revert-correlated high-risk paths. The component has exactly one consumer (the composer), and it only renders while the workspace is git-eligible and no session is loaded, so the blast radius is the empty-session composer.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ —— 各必填章节齐全,中英文对照,附有改动前后证据。

问题: 真实存在,并且可以直接从代码验证,而不必只信描述。原来的 handleConfirmWorktree 只做了两件事:调用 onIntentChange({ mode: 'worktree' })、关闭弹层——不创建任何检出,也不发任何请求。真正创建 worktree 的是既有的「首条消息」链路:发送首条消息时读取该意图,把 worktree: { slug } 传给会话创建,最终以 worktree: {} 出现在 POST /session。也就是说第二次点击毫无收益,而按钮文案「创建 Worktree」还误导了它的实际行为。这是已观测到的缺陷,不是理论性加固。

方向: 对齐。这个界面本来就是一次点击生效——Worktrees 管理入口新建会话时直接带上 worktree 意图、不需要确认;同一个弹层里的「当前分支」选项也是点击即生效。Worktree 是唯一的例外,而且例外的方向是增加多余操作。上游 Claude Code 的 CHANGELOG 没有关于 git 模式选择器二次确认的直接条目,但 worktree 隔离在上游是持续投入的领域,说明这个界面本身是相关的。

规模: 不适用——未触及核心路径(6 个文件全部属于 Web Shell client 或 docs/design/)。供参考:+165/−84,约 38 行生产代码(组件、CSS module、i18n 文案)、111 行测试、100 行文档,远低于任何提醒阈值。

方案: 基本就是最小改动,也正是只看标题时我会写的方案——删掉按钮、删掉随之失效的样式和两条翻译、把处理函数改成能说明行为的名字、并把说明文案改成明确「何时」创建副本。把清除按钮的测试改成两种模式的循环是保留断言的写法:分支相关断言一条没少,同时补上了 worktree 路径,还新增了「发送前不发创建请求」的检查。没有夹带无关重构或格式抖动。设计文档中英双语齐备,章节对应、互链完整。

风险: 无升级风险信号——改动文件均未命中与回滚相关的高风险路径。该组件只有一个使用方(输入区),且仅在工作区可用 Git、尚未加载会话时渲染,影响面就是空会话输入区。

进入代码审查 🔍

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 8b2d319a5ab6b665958ddb02ce57f2df3b683412 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Code review

No Critical blockers and no AGENTS.md violations. This is a removal, so most of the review is proving nothing was left behind and nothing else depended on what went away — I could settle both statically.

The premise checks out in the code, not just in the description. The deleted handler only recorded { mode: 'worktree' } and closed the popover; creation is done by the pre-existing first-prompt path, which reads the intent ref and passes worktree: { slug } into session creation (arriving as worktree: {} on POST /session). Nothing about that path changes here.

The removal is complete. A repo-wide search for the deleted translation key, the git-mode-confirm-worktree test id, and the .confirmWorktree rule turns up nothing outside the files this PR touches. Messages is Record<string, MessageValue>, so there is no key-list type for a dropped key to desynchronize, and EN and ZH dropped it together. The selected-state styling the worktree radio still relies on (.checkWorktree) is untouched — only the confirm-button rules went.

selectedMode can't go stale. The click no longer calls setSelectedMode('worktree'), but the open handler re-syncs it from intent.mode every time the popover opens, so reopening with a worktree intent still shows the checked radio, the ✓, and the git worktree add footer line. There's no reachable state where selectedMode says worktree while the intent doesn't.

Reversibility is real, and now tested. The ✕ reset renders for both branch and worktree intents, unchanged, and the rewritten spec drives it for worktree.

Consumers, named. The composer is the only renderer of this component, and App only wires the intent props while the workspace is git-eligible and no session is loaded; the intent is reset on session start and workspace switch and read exactly once, at first-prompt session creation. Nothing else reads the removed key or test id.

The tests come out stronger, not weaker. The worktree test trades a "the confirm button stayed visible for 300 ms" guard for assertions on the thing that actually matters: the chip reads Worktree, no POST /session was sent by selecting it, and after sending there is exactly one creation request carrying worktree: {} and no branch. That pins "deferred, and not duplicated" far better than the old guard did. The dropped focus-steal guard is genuinely moot for this option — the popover is supposed to close on click now — and a regression of that dismissal would fail the new chip assertion instead of passing silently. The clear-button test becoming a two-mode loop keeps every branch assertion and adds the worktree path.

Design docs meet docs/design/README.md: both languages, same section order (problem → proposal → scope and constraints → validation and acceptance → risks), reciprocal links under the title, and the technical identifiers (worktree: {}, POST /session, the .qwen/worktrees/<slug> path) match across the pair.

Two non-blocking notes, take them or leave them:

  1. The worktree option loses its "preview without committing" affordance — the footer's git worktree add line is now only visible once the intent is already worktree, i.e. on reopen. That's the same tradeoff "Current branch" already makes and reset covers it, so I'm fine with it; just naming it so it reads as a decision rather than a side effect.
  2. Nothing asserts the reopen state (select Worktree → chip shows Worktree → reopen → option still checked, worktree command in the footer, no confirm button). Two extra lines in the existing worktree spec would pin the selected-state styling too.

Testing

This was an unattended CI run, so nothing in the PR was built or executed here — the evidence below is the PR's own CI, read through the API for the reviewed commit, plus static reading of the diff against the surrounding code.

CI results for 8b2d319 — this table auto-updates as CI workflows complete:

Check Conclusion
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ⏳ running
Capture web-shell visuals (ubuntu-latest, Node 22.x) ✅ success
Classify PR ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Lint & Static (ubuntu-latest, Node 22.x) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success

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

No check is red, so there is no failure log to excerpt. Reading the green ones for what they actually cover: Lint & Static gives ESLint and Prettier over the changed files, and Test (ubuntu) runs the package vitest suites — which for this component means the branch-name validation cases and the i18n unit tests, plus the App-level intent tests. None of those drive the popover.

The changed browser specs do not run in this PR's CI. The CI browser job runs the smoke lane (playwright test --grep @smoke), and web-shell.git-mode.spec.ts carries no @smoke tag, so the rewritten tests are not executed by any check above. The Capture web-shell visuals job is the closest thing: its git-mode scenario renders the closed chip, the open three-mode popover, and the branch sub-state as a before/after composite against the merge base — which will show the new, longer description string in the layout, and it never clicks Worktree, so it asserts nothing about this change. Not verified here: the one-click interaction itself, and the "exactly one POST /session with worktree: {}" contract. The author reports six local Chromium scenarios plus a real-daemon run covering those; that is the author's claim, not evidence this review re-ran.

Sandboxed verification would settle it: @qwen-code /tmux on the Web Shell surface — that one click on Worktree closes the popover, shows the chip, and that sending the first message produces a single session creation with worktree: {} and no branch, which no check in the table above exercises. @qwen-code /verify is the lane if you'd rather have the request contract A/B-proved against the base build. The author has write access, so either can be triggered directly.

中文说明

代码审查

无 Critical 阻塞项,也没有违反 AGENTS.md 的地方。这是一次「删除」型改动,审查重点在于证明没有遗留、也没有别处依赖被删掉的东西——这两点都可以静态确认。

前提在代码里成立,不只是描述里的说法。 被删掉的处理函数只记录 { mode: 'worktree' } 并关闭弹层;真正创建 worktree 的是既有的首条消息链路,它读取意图 ref 并把 worktree: { slug } 传给会话创建(最终以 worktree: {} 出现在 POST /session)。本 PR 没有改动这条链路。

删除是干净的。 全仓搜索被删的翻译键、git-mode-confirm-worktree 测试 id 和 .confirmWorktree 样式,除本 PR 改动的文件外没有其他引用。Messages 是 Record<string, MessageValue>,不存在会因删键而失配的键列表类型,且中英文同时删除。worktree 选中态仍依赖的 .checkWorktree 样式未被触碰,只删了确认按钮相关规则。

selectedMode 不会失配。 点击不再调用 setSelectedMode('worktree'),但每次打开弹层时 open 处理函数都会用 intent.mode 重新同步,因此带着 worktree 意图重新打开仍会显示选中态、✓ 以及 footer 里的 git worktree add 命令。不存在 selectedMode 为 worktree 而意图不是的状态。

可撤销是真实的,并且现在有测试覆盖。 ✕ 重置按钮在 branch 和 worktree 两种意图下都会渲染,逻辑未变,改写后的用例对 worktree 走了这条路径。

下游使用方已点名。 该组件只有输入区一个渲染方,App 只在工作区可用 Git 且未加载会话时接入意图 props;意图会在会话开始和工作区切换时重置,并且只在首条消息创建会话时读取一次。没有其他地方读取被删的键或测试 id。

测试变得更强,而不是更弱。 worktree 用例把「确认按钮在 300ms 内仍可见」的守护换成了真正关键的断言:chip 显示 Worktree、仅选择模式时没有发出 POST /session、发送后只有一个创建请求且携带 worktree: {}、不含 branch。这比原来的守护更牢地钉住了「延后创建且不重复创建」。被删掉的抢焦点守护对这个选项已确实失去意义——现在点击本就应该关闭弹层——而该 dismiss 缺陷若回归,会在新加的 chip 断言上失败,不会静默通过。清除按钮用例改成两种模式的循环,保留了全部原有分支断言并新增 worktree 路径。

设计文档符合 docs/design/README.md:中英双语、章节顺序一致(问题 → 方案 → 范围与约束 → 验证与验收 → 风险)、标题下互链,技术标识(worktree: {}、POST /session、.qwen/worktrees/<slug>)两侧一致。

两点非阻塞建议,取舍随意:

  1. worktree 选项失去了「只预览不生效」的能力——footer 里的 git worktree add 命令行现在只有在意图已是 worktree(即重新打开)时才可见。这与「当前分支」选项已有的取舍一致,且有重置控件兜底,我认为可以接受;只是点明,让它读起来是一个决定而不是副作用。
  2. 没有用例覆盖「重新打开」的状态(选中 Worktree → chip 显示 Worktree → 重新打开 → 选项仍选中、footer 显示 worktree 命令、没有确认按钮)。在现有 worktree 用例里加两行就能把选中态样式也钉住。

测试

本次为无人值守的 CI 运行,因此没有构建或执行 PR 中的任何代码——下面的证据来自通过 API 读取的该提交自身 CI 结果,以及对 diff 与周边代码的静态阅读。

CI 表格见上方标记区域:审查时 0 个失败,Test (ubuntu-latest) 与 Capture web-shell visuals 仍在运行,macOS/Windows 单测与 CLI 集成测试为 skipped。没有红色检查,因此没有失败日志可摘录。就绿色检查的实际覆盖面而言:Lint & Static 覆盖改动文件的 ESLint 与 Prettier;Test (ubuntu) 跑各包 vitest——对这个组件而言即分支名校验用例、i18n 单测,以及 App 层的意图测试,但它们都不驱动这个弹层。

本 PR 改动的浏览器用例不会在 CI 中执行。 CI 的浏览器任务只跑 smoke 通道(playwright test --grep @smoke),而 web-shell.git-mode.spec.ts 没有 @smoke 标签,所以上表中没有任何检查会执行改写后的用例。最接近的是 Capture web-shell visuals:它的 git-mode 场景会渲染关闭态 chip、打开态三模式弹层和分支子状态,并与 merge base 合成前后对比图——因此能看到新的、更长的说明文案在布局中的效果,但它从不点击 Worktree,对本改动不做任何断言。本次未验证:一次点击的交互本身,以及「仅一个 POST /session 且携带 worktree: {}」的请求契约。作者报告本地跑过六个 Chromium 场景和一次真实 daemon 验证,这属于作者的说明,不是本审查重新执行得到的证据。

沙箱验证可以把这件事定下来:@qwen-code /tmux 走 Web Shell 界面——确认单击 Worktree 即关闭弹层并显示 chip,且发送首条消息只产生一次会话创建、携带 worktree: {}、不含 branch;上表中的检查都没有覆盖这一点。如果更想对请求契约做与 base 构建的 A/B 证明,则用 @qwen-code /verify。作者具备写权限,两条通道都可以直接触发。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 8b2d319a5ab6b665958ddb02ce57f2df3b683412 · re-run with @qwen-code /triage

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal removal whose premise I could verify in the code; the only reservations are about test reach, not about the change.

Stepping back: this is the rare UI PR where the diff is smaller than the justification. My own independent read of the title and the "why" was exactly what landed — make the Worktree option behave like the Current-branch option next to it, delete the button that never did what its label claimed, and correct the copy so it says when the isolated copy appears. There was no simpler version of this that I could find, and no scope to cut: 38 production lines, all of them deletions or a renamed handler, plus the copy and the tests that had to follow.

What convinces me it's right rather than just tidy: the second click was not a safety confirmation, it was a label lying. Nothing behind that button created a worktree; creation has always happened on the first message, and the Worktrees manager already starts a worktree draft in one click without anyone worrying about mis-selection. Reset before sending covers the accidental-click case and is now tested for worktree, which is the one real risk the design doc names. Six months from now this reads as "the selector has three options and all three behave the same way" — that's a maintenance win, not a debt.

Where I'd hold back a point: the tests that prove the behaviour are the ones CI doesn't run. The rewritten specs are genuinely better than what they replace — asserting one POST /session with worktree: {} and nothing sent before the first message beats the old button-visibility guard — but they sit outside the smoke lane, so no check on this commit executes them. The green suite above tells me lint, formatting, i18n and the App-level intent tests are fine; it does not tell me the click works. That's a gap in what CI reaches, not a defect I found, and the author's local browser runs plus the real-daemon capture are plausible — they're just not evidence I re-ran. If you want the claim settled rather than believed, @qwen-code /tmux on the Web Shell surface is one comment away. The two nits from the review (no assertion on the reopen state; the worktree footer command is now only visible on reopen) are worth a follow-up at most, not a round trip.

CI is still running on the reviewed commit — Test (ubuntu-latest) and the web-shell visuals capture were in flight at review time, with nothing red. So approval is deferred until CI lands green on 8b2d319a5ab6b665958ddb02ce57f2df3b683412; the finalize job posts the commit-pinned approval at that point, and withholds it if anything fails or the head moves. No approval is submitted by this run.

中文说明

Confidence: 4/5 —— 改动干净、范围最小,前提可以在代码里直接验证;保留的一点意见针对测试覆盖面,而不是改动本身。

退一步看整体:这是一个 diff 比论证还短的 UI PR。只看标题和「为什么需要」,我自己会写的方案与最终落地的完全一致——让 Worktree 选项和旁边的「当前分支」选项行为一致,删掉那个名不副实的按钮,并把文案改成说明独立副本何时创建。我找不到比这更简单的写法,也没有可裁的范围:38 行生产代码,全是删除或一个改名的处理函数,其余是文案和必须跟随的测试。

让我认为它是「对的」而不只是「整洁」的关键在于:那第二次点击并不是安全确认,而是一个说谎的按钮标签。按钮背后没有任何东西创建 worktree;创建一直发生在首条消息发送时,而 Worktrees 管理入口本来就是一次点击直接进入 worktree 草稿,也没人因此担心误选。发送前的重置控件覆盖了误点场景,并且现在对 worktree 有了测试——这正是设计文档点名的唯一实质风险。半年后再看,这段代码读起来就是「选择器有三个选项,三个行为一致」,是维护上的收益而不是负担。

扣掉一分的地方在于:真正证明行为的用例恰好是 CI 不跑的那些。改写后的用例确实比原来更强——断言只有一个携带 worktree: {} 的 POST /session、且发送首条消息前不发任何请求,胜过原来的按钮可见性守护——但它们不在 smoke 通道内,因此本提交上没有任何检查会执行它们。上面绿色的检查说明 lint、格式、i18n 和 App 层意图测试没问题,但并不能说明这次点击真的生效。这是 CI 覆盖范围的缺口,不是我发现的缺陷;作者本地的浏览器运行和真实 daemon 截图是可信的,只是不是本审查重新执行得到的证据。如果希望把这个结论「定下来」而不是「相信」,在 Web Shell 界面上触发一次 @qwen-code /tmux 即可。审查中的两点小意见(未覆盖重新打开后的状态;worktree 的 footer 命令行现在只在重新打开时可见)最多值得后续跟进,不值得为此返工。

审查时该提交的 CI 仍在运行——Test (ubuntu-latest) 与 web-shell 视觉截图任务在跑,没有红色检查。因此批准挂起,等 CI 在 8b2d319a5ab6b665958ddb02ce57f2df3b683412 上全绿后,由 finalize 任务发出绑定该提交的批准;若有检查失败或 head 变动则不会批准。本次运行不提交任何批准。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 8b2d319a5ab6b665958ddb02ce57f2df3b683412 · re-run with @qwen-code /triage

@qwen-code-review-bot

qwen-code-review-bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head aa4613e. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

git-mode-branch-dark before/after

git-mode-branch-light before/after

git-mode-popover-dark before/after

git-mode-popover-light before/after

Full-resolution recordings (.webm) are attached to the workflow run.

— Qwen Code · web-shell visuals

@wenshao

wenshao commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator Author

Maintainer verification — PR #13607: fix(web-shell): Select worktree mode without extra confirmation

Verdict: merge-ready — 10,991/10,991 scripted assertions passed, 0 unexpected failures.
Verified head: 8b2d319a5ab6 · base: 718ae1e6c6da (current main tip, so the A/B is also the merge preview).

中文摘要

结论:merge-ready —— 10,991/10,991 条脚本断言全部通过,无意外失败。

  • A/B 承重证明:同一份 PR 新 e2e 规格在 head 构建上 6/6 通过,在 base 构建上恰有 2 个 worktree 用例失败(弹层在单击后不关闭、等待已删除的确认按钮),其余 4 个用例在 base 上通过。失败签名正是被移除的旧行为,证明改动是承重的。反向对称单元:旧规格(点确认按钮)在 head 上失败,证明旧流程已移除。
  • 变异测试:单独把 onClick 一行回退为旧的 pending 行为(保留其余改动),2 个 worktree 用例变红,证明该行单独承重。
  • 独立 harness(非 PR 自带,覆盖 PR 描述中声称但未提交的对抗场景):worktree→branch、branch→worktree 双向切换、预填文本后键盘选择不提前提交、无效分支名 Escape 保留默认模式 —— 4 个场景 29 条断言全过。
  • 真实 daemon 臂:用 head 构建的 qwen serve + 真实 Chromium 走完整链路:单击选中 → 发首条消息 → 线上恰好一次 POST /session 且 body 为 worktree:{}、无 branch;磁盘上真实创建 .qwen/worktrees/bright-fox-41c1e6,git worktree list 注册,分支 worktree-bright-fox-41c1e6 存在。10/10 断言通过。
  • 门禁:web-shell 全套单测 10,907/10,907;head 全仓 npm run build、typecheck、改动文件 ESLint(已用植入违规证明门禁存活)与 Prettier 全部通过。设计文档中英双版结构一致、互链齐全。confirmWorktree 无任何残留引用。
  • 非阻塞建议 1 条:worktree 一键选择行为只有 e2e 覆盖,GitModePopover.test.tsx(16 个分支名校验单测)完全没有 worktree 用例 —— 建议后续补一个组件级单测,不阻塞合并。
  • 未覆盖:Windows/Linux 平台实机(仅 macOS);模型执行内容质量(真实 daemon 臂触发了真实模型调用但未评估输出);移动端 webkit 视图。

Central claim

Selecting Worktree in the composer git-mode popover now selects the mode and closes the popover in one click (previously a second click on a "Create worktree" button was required, which only recorded a draft intent). No session request is sent at selection time; the first message sends exactly one POST /session carrying worktree: {} and no branch, and the daemon then auto-names and creates the real git worktree.

A/B load-bearing proof

Same committed spec (client/e2e/web-shell.git-mode.spec.ts @ 8b2d319a5ab6) run against both builds; trees differ only in the PR's production code (each tree's @qwen-code/sdk realpath verified tree-local).

Cell Build Spec Result
Head 8b2d319a5ab6 PR version 6/6 passed (18.9s)
Base 718ae1e6c6da PR version 2 failed / 4 passed — the two worktree tests fail at expect(popover).not.toBeVisible() (the popover stays open awaiting the deleted confirm button); branch, default, clear-branch and non-git tests pass
Symmetry 8b2d319a5ab6 Base version Old worktree-confirm test fails (git-mode-confirm-worktree no longer exists) — the old flow is gone

Witnesses: 01-ab-head-spec-green.png, 02-ab-base-spec-red.png.

Mutation check (vacuity): reverting only the onClick={handleSelectWorktree} line back to setSelectedMode('worktree') at head — keeping everything else — turns exactly the two worktree tests red (the branch-clear test stays green, correctly). The onClick hunk alone is load-bearing, and the new spec is pinned by the behavior, not by construction.

Independent harness (maintainer-written, not from the PR)

The PR description cites four adversarial browser scenarios that are not committed to the repo. I re-implemented them as an independent spec driving the head build through the same mock-daemon seam (29 scripted assertions, all passing — 03-independent-harness.png):

  1. Worktree → Branch: select Worktree (chip flips, zero POSTs of any kind), switch to New branch, confirm — send carries branch, no worktree.
  2. Branch → Worktree: confirm branch first, switch to Worktree — send carries worktree: {}, no branch.
  3. Keyboard select with prefilled composer: Enter on the Worktree radio selects and closes without submitting; composer text preserved; explicit send then issues exactly one POST /session with worktree: {}.
  4. Escape unconfirmed invalid branch: invalid name disables confirm; Escape restores current-branch mode; send carries neither key.

Real-daemon arm (head qwen serve + real Chromium)

Drove the head-built web-shell served by the head-built daemon (packages/cli/dist/index.js serve --workspace /tmp/pr13607-wt-test, isolated git repo, scratch HOME seeded with auth) in real Chromium: one click on Worktree → type → send.

  • Wire: exactly one POST /session, body {"cwd":…,"sessionScope":"thread","approvalMode":"yolo","sourceType":"default","worktree":{}} — worktree: {} present, no branch key; zero POST requests before the first message.
  • Disk: /tmp/pr13607-wt-test/.qwen/worktrees/bright-fox-41c1e6 created, registered in git worktree list, branch worktree-bright-fox-41c1e6 checked out inside it. 10/10 assertions.

Witnesses (live browser shots against the real daemon): rd-2-popover.png (new copy "Creates an isolated copy when you send your first message"; no confirm button), rd-3-selected.png (one click → chip shows Worktree with reset control), rd-4-after-send.png (session created; real model turn started).

Environment note: with a scratch HOME lacking credentials, the same flow fails at POST /session with 500 Authentication required and leaves no worktree on disk — a clean, pre-existing daemon failure mode unrelated to this PR (the PR only changes what the UI sends; the wire body was verified correct in that run too).

Gates

Gate Result
npm run build (full monorepo, head) pass
tsc --noEmit (web-shell, head) pass
web-shell full unit suite (head) 10,907/10,907 passed (412 files)
git-mode targeted unit tests (head) 20/20 (16 branch-name validation + 4 intent)
ESLint on changed files pass — liveness proven: a planted unused var on the changed file was caught (exit 1), clean files exit 0
Prettier on all 6 changed files pass
Design docs EN/ZH structure, decisions, constraints and acceptance criteria match; reciprocal language links present
Dangling-reference sweep zero confirmWorktree references remain in web-shell

Findings

Suggestion (non-blocking): the one-click Worktree selection has no unit-level pin. GitModePopover.test.tsx (16 tests) exercises only branch-name validation — the word "worktree" appears in neither unit test file. All behavioral coverage is e2e-only; a component-level test (click Worktree → onIntentChange({mode:'worktree'}) + popover closes) would catch a regression faster than the browser suite. The e2e coverage itself is proven load-bearing by the A/B above, so this is a test-pyramid nicety, not a gap in protection.

Not covered

  • Windows / Linux (macOS only, Node 22.23.1, Chromium 1.61.1).
  • Model output quality: the real-daemon arm did start a real model turn, but only session/worktree creation was asserted.
  • Mobile webkit viewport of the popover.
  • The branch-mode flow is covered only by the committed spec (passes at both builds) — unchanged code path.

Methodology

Two git worktrees at the exact headRefOid/baseRefOid resolved from the PR, each installed with corepack pnpm install --frozen-lockfile --ignore-scripts, with only @qwen-code/acp-bridge + @qwen-code/sdk + (head) the full monorepo built. Workspace links verified tree-local via readlink -f (no cross-tree leak). Browser suites ran via Playwright against each tree's own Vite dev server (ports 5191/5192); the base arm ran the PR's spec copied verbatim into the base tree, changing no base production code. The real-daemon arm used the head-built CLI daemon with an isolated git workspace and a scratch HOME (auth block seeded from the maintainer's settings; no secret material copied into artifacts). Raw logs: tmp/pr13607-verify-20261007-221640/ (logs-head-spec.txt, logs-base-spec.txt, serve-5196.log, evidence/).


Local verification by @wenshao's maintainer harness; evidence images hosted on the verify-pr13607-assets branch of wenshao/qwen-code.

@qwen-code-review-bot qwen-code-review-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 explored to full depth (tool budget reached): "agent 4": none — I stopped at 8 of ~37 tool calls with every planned check completed..

中文说明

已审查。 建议见行内评论。

未探索到全部深度(达到工具调用预算):"agent 4":none — I stopped at 8 of ~37 tool calls with every planned check completed.。

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

Comment thread docs/design/2026-10-07-web-shell-automatic-worktree-session.md
Comment thread packages/web-shell/client/components/GitModePopover.tsx

@qwen-code-review-bot qwen-code-review-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.

No issues found. LGTM! ✅

Not explored to full depth (tool budget reached): "agent 1d": none — every check above completed (no Budget gap: items to disclose)..

中文说明

未发现问题。LGTM!✅

未探索到全部深度(达到工具调用预算):"agent 1d":none — every check above completed (no Budget gap: items to disclose).。

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

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants