feat(web-shell): surface worktree isolation in the new-session empty state - #7365
Conversation
…state The worktree-isolated session entry was buried in the sidebar git-branch pill dropdown, making it hard to discover. Add a visible toggle to the chat empty state — the de-facto new-session page — that reuses the existing pending-worktree state machine and lazy session creation, so no SDK or daemon changes are needed. Enabling it shows the pending badge with a cancel affordance; the first prompt then creates the session in an isolated worktree. The toggle is offered only when the target workspace is trusted and is a git repository, mirroring the sidebar entry gating. Also simplify the sidebar git pill: drop the now-redundant "New worktree task" item and make the pill open the changes view directly instead of a single-item dropdown.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Thanks for the PR! Template looks good ✓ Problem: the worktree isolation entry point was buried in the sidebar git-pill dropdown — a real discoverability issue with an existing feature, not a theoretical concern. Before/after screenshots clearly show the old vs new placement. Direction: aligned. Surfacing an existing feature at the moment of session creation is a straightforward UX improvement. CHANGELOG shows worktree isolation is a well-established feature area with ongoing fixes upstream, so improving its discoverability is relevant. Size: not applicable — all changes are in Approach: the scope feels right. Reusing the existing Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:worktree 隔离入口藏在侧边栏 git 胶囊下拉菜单里——这是已有功能的真实可发现性问题,不是理论性担忧。前后截图清楚展示了新旧入口位置。 方向:对齐。在会话创建时暴露已有功能是直接的 UX 改进。CHANGELOG 显示 worktree 隔离是成熟的功能领域,上游有持续修复,改善其可发现性是相关的。 规模:不适用——所有改动在 方案:范围合理。复用现有 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
Code ReviewIndependent proposal: given "worktree entry point is buried in a sidebar dropdown", I would add a toggle in the chat empty state that sets the existing Comparison: the PR does exactly this. The implementation reuses the existing pending-worktree state machine ( No critical blockers found. Specific observations:
Typecheck passes cleanly ( Real-Scenario TestingThis is a browser UI change (web-shell), so the real-scenario evidence comes from Playwright E2E tests driving a real Chromium browser against the actual web-shell UI with a mock daemon: Unit tests (vitest): Dev server starts and serves correctly: 中文说明代码审查独立方案: 给定"worktree 入口藏在侧边栏下拉里"的问题,我会在聊天空状态添加一个开关来设置现有的 对比: PR 完全这样做了。实现复用现有 pending-worktree 状态机( 未发现关键阻塞项。具体观察:
类型检查通过( 真实场景测试这是浏览器 UI 变更(web-shell),真实场景证据来自 Playwright E2E 测试——驱动真实 Chromium 浏览器 against 实际 web-shell UI(mock daemon): 单元测试(vitest): 开发服务器正常启动和响应。 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 4/5 — clean, focused UI improvement with solid test coverage; only non-blocking nit is the elaborate CSS polish. This PR does one thing well: it takes an existing feature that was effectively hidden (worktree isolation, buried in a sidebar dropdown that only rendered under several conditions) and puts it where users actually start a session — the empty state. The implementation is the simplest possible path: the toggle just sets the existing The sidebar simplification (dropdown → direct button) is a natural consequence of removing the worktree menu item — leaving a single-item dropdown would be worse UX than a direct click. The Tests are comprehensive: 6 unit tests covering all gating conditions and the toggle lifecycle, 3 Playwright E2E tests driving a real browser, and the existing 156-test suite passes without regression. Typecheck is clean. The CSS is more elaborate than strictly necessary (entrance animations, hover glow, icon medallion), but for a user-facing discoverability feature, visual polish is part of the deliverable — not scope creep. Approving. ✅ 中文说明置信度:4/5 — 干净、聚焦的 UI 改进,测试覆盖扎实;唯一非阻塞的小点是 CSS 打磨较精致。 这个 PR 做好了一件事:把一个实际被隐藏的功能(worktree 隔离,藏在需要满足多个条件才渲染的侧边栏下拉里)放到用户真正开始会话的地方——空状态。实现是最简路径:开关只设置现有的 侧边栏简化(下拉→直接按钮)是移除 worktree 菜单项的自然结果——只剩一项的下拉不如直接点击。 测试全面:6 个单元测试覆盖所有门控条件和开关生命周期,3 个 Playwright E2E 测试驱动真实浏览器,现有 156 个测试套件无回归通过。类型检查干净。 CSS 比严格必要更精致(入场动画、悬停发光、图标徽章),但对面向用户的可发现性功能来说,视觉打磨是交付物的一部分——不是范围蔓延。 批准。✅ — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
|
|
||
| describe('WorkspaceSection git chip', () => { | ||
| it('renders a git chip inside a dropdown trigger for a trusted repo', async () => { | ||
| it('renders a clickable git chip for a trusted repo', async () => { |
There was a problem hiding this comment.
[Suggestion] The test name promises "clickable" but the body only checks DOM attributes (tagName, data-dirty, className, aria-label) — it never clicks the <button> or asserts onOpenGitDiff was called. A future miswire of the onClick handler would go undetected.
Failure scenario: a refactor wraps the button in a <div> that swallows click events, or accidentally deletes the onClick prop. The test still passes because it only verifies the chip rendered, not that it responded to interaction.
| it('renders a clickable git chip for a trusted repo', async () => { | |
| it('renders a clickable git chip for a trusted repo', async () => { | |
| render(<WorkspaceSection {...baseProps} />); | |
| const chip = screen.getByText('main'); | |
| expect(chip.closest('[data-dirty]')).toHaveAttribute('data-dirty', 'false'); | |
| const button = chip.closest('button'); | |
| expect(button).toHaveClass(styles.gitPill); | |
| expect(button).toHaveAttribute('aria-label', 'View changes'); | |
| fireEvent.click(button!); | |
| expect(baseProps.onOpenGitDiff).toHaveBeenCalledWith('/tmp/project'); | |
| }); |
— qwen3.7-max via Qwen Code /review
| onOpenGitDiff={onOpenGitDiff} | ||
| onNewWorktreeSession={(cwd) => | ||
| handleNewSession(ws.primary ? undefined : cwd, { | ||
| worktree: {}, | ||
| }) | ||
| } | ||
| formatTime={(iso) => formatRelativeTime(iso, t)} |
There was a problem hiding this comment.
[Suggestion] The worktree option on createNewSession / handleNewSession / onNewSession is now dead code — this diff removed the only caller that passed { worktree: {} }. The new toggle sets pendingWorktreeRef.current directly, bypassing this path entirely.
Failure scenario: a future developer reads the opts?: { worktree?: { slug?: string } } type, assumes it's a live API, and writes a caller through createNewSession. It compiles and type-checks, but the code path has zero integration test coverage since this PR removed the sole exerciser.
Suggested fix: remove worktree from the opts type on createNewSession, handleNewSession, and the onNewSession prop signature. Keep opts for keepView.
— qwen3.7-max via Qwen Code /review
|
@qwen-code /takeover |
|
🤝 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. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
…uite The worktree toggle lives in the new-session empty state, and every visuals scenario navigates to /session/:id via gotoSession — so the suite had never rendered the empty state at all, and the before/after preview reported "no screenshot changes" for this PR despite the new UI. Add a `gotoNewSession` harness helper (primes the theme, lands on `/`, asserts the theme took effect; no replay to settle) and a `worktree empty state` scenario using the git-ready workspace this PR already made mockable (`gitStatus` + the /workspaces/:cwd/git route). It captures both states — the offered toggle and, after clicking, the pending-worktree badge with its cancel affordance — and asserts the swap, so a regression fails an assertion rather than only differing in the screenshot. All four captures are byte-stable across runs (0% pixel diff). The helper also closes the structural gap: any future empty-state work (onboarding copy, first-run affordances) now has a way into the preview.
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: Autofix review response — PR #7365Addressed both inline suggestions from the automated reviewer. No conflict Feedback point 1 — "clickable" git chip test never clicksFile: The test name promised a "clickable" chip but only asserted DOM attributes Change: kept the existing assertions and added an interaction check in the Feedback point 2 — dead
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
Code review — surface worktree isolation in the new-session empty stateOverviewMoves the worktree-isolation entry point out of the sidebar git-pill dropdown and into the new-session empty state as a visible toggle. It reuses the existing pending-worktree state machine and lazy (first-prompt) session creation, so there are no SDK/daemon changes. The sidebar git pill is simplified from a dropdown to a single native button that opens the Changes view directly. Ships unit + Playwright E2E + visual-screenshot coverage, an i18n copy pass (en/zh), and a design doc. I traced the core path and it holds up:
Strengths
Findings (all minor — nothing blocking)
Nits
Verdict: Clean, well-tested, low-risk discoverability win. Recommend addressing the two a11y items (1, 2) and the test-robustness items (3, 4) as follow-up polish, none of which block merge. |
|
🤖 Could not address the latest feedback automatically (round 2/100). A human should take over this PR. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/29791533733 🧠 Handled by Qwen Code · model/模型 |
#7365 stranded at round 2/100 on this render: [API Error: terminated (cause: read ECONNRESET)] The connection to the model dropped mid-run. That is as transient as a 429, but the classifier never saw it that way: a transport failure never got far enough to have an HTTP status, so it fell through to the keyword arm, and the keyword arm only knew about rate limits and quotas. It was classified terminal, the watermark advanced, and a PR that needed nothing but a re-run was handed to a human. Verified against the shipped classifier before the fix — every transport render came back terminal: terminated (cause: read ECONNRESET) -> terminal fetch failed -> terminal socket hang up -> terminal connect ETIMEDOUT -> terminal Adds a transport arm to the code-less branch: ECONNRESET, ECONNREFUSED, ETIMEDOUT, EPIPE, EAI_AGAIN, socket hang up, fetch failed, terminated. ENOTFOUND is deliberately excluded. A hostname that does not resolve is a misconfigured endpoint, which repeats forever — the same reasoning that keeps a bad model name terminal. Coded errors are unaffected: the arm sits after the status-code branch, so the 400 short-circuit added in 719991a still runs first.
…wenLM#7375) An empty visual preview means one of two opposite things: the change genuinely moves no pixel, or no scenario renders the UI it touches. The bot printed the same green check for both, so the second — a coverage gap, where the preview literally cannot see the feature — read as a clean bill of health. That has now happened three times (QwenLM#7035 primary label, QwenLM#7221 worktree badge, QwenLM#7365 empty-state toggle), each caught only because a maintainer noticed the missing image and asked. The signal to tell them apart was already there and unused: the render workflow only runs when the web-shell client or webui source changed, so an empty preview is by construction "UI code changed, nothing rendered differently". When no view changed, look at which files the PR touched. If any are render-shaping (.tsx / .css / .svg under the rendered surface, excluding test and scenario code), list them and say the result is ambiguous, with a pointer to where a scenario goes. Otherwise keep the green check — a logic-only PR with no visual delta is expected, and prompting there would train everyone to ignore the prompt when it matters. The path list comes from the PR files API in the privileged publish job, which never checks out PR code; if that call fails the comment falls back to the current wording. Co-authored-by: wenshao <wenshao@example.com>
|
@qwen-code /retry |
|
🔄 AutoFix re-armed. The next scan re-reads this PR's feedback from the start and the round counter resets. Nothing was deleted — this marker supersedes the evaluation markers above it. 中文说明🔄 已重新武装 AutoFix。下一次扫描会从头重新读取本 PR 的反馈,轮次计数也已重置。未删除任何内容 —— 本标记使其上方的评估标记失效。 |
- Move focus to cancel button on toggle enable and back on cancel (a11y) - Include branch name in git-pill button aria-label (a11y) - Replace hardcoded flush() ticks with vi.waitFor() in test helper - Move git-repo mock default from afterEach to beforeEach - Add test: sidebar New chat clears pending worktree intent
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: Review feedback addressedInline comments[rc:3618264941] WorkspaceSection.test.tsx — test should click the button and assert [rc:3618264957] WebShellSidebar.tsx — dead [rc:3618626603] App.tsx — no test for sidebar "New chat" clearing pending worktree intent Issue-level review (@wenshao)1. Focus management on toggle↔badge swap (a11y) 2. Git-pill accessible name includes branch (a11y) 3. Test robustness — hardcoded promise ticks 4. Test setup — mock default in Nit: Nit: Verification
中文说明已处理的审查反馈行内评论[rc:3618264941] WorkspaceSection.test.tsx — 测试应点击按钮并断言 [rc:3618264957] WebShellSidebar.tsx — [rc:3618626603] App.tsx — 缺少侧边栏"新对话"清除待定 worktree 意图的测试 Issue 级别审查(@wenshao)1. 切换开关↔徽章交换时的焦点管理(无障碍) 2. Git 分支标签的无障碍名称包含分支名(无障碍) 3. 测试健壮性 — 硬编码的 Promise tick 4. 测试设置 — mock 默认值从 小建议: 小建议:切换开关使用 验证结果
Base-conflict check: no conflict with main. Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. 🧠 Handled by Qwen Code · model/模型 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
— qwen3.7-max via Qwen Code /review
…enLM#7247) * fix(autofix): retry a model API error instead of stranding the PR When the agent's qwen subprocess dies on a model-side [API Error] (403 access denied, a 429 quota, a 5xx), run-agent.mjs wrote a handoff/failure.md, so the handoff step treated it as an EVALUATED handoff — it advanced the watermark and the next scan saw 'nothing new', stranding the PR until a manual re-arm. But the agent never actually evaluated the feedback; the model was unreachable. QwenLM#7220 hit exactly this: fork-takeover engaged and ran the agent, the model returned '[API Error: 403 Model access denied]' (the autofix key lacks access to qwen3.8-max-preview), and the PR was left with an advanced watermark that will not retry. Fix, mirroring QwenLM#7229's no-output-crash handling: - run-agent.mjs extracts a [API Error: 4xx/5xx] from the captured output tail, includes it in failure.md, and drops an marker file. - The handoff step reads that marker and routes the failure to the sentinel-ts (retry) path — the watermark does NOT advance, so the next scan retries; the round still increments so a PERSISTENT model failure is bounded by MAX_ROUNDS. The headline names the model error and, on the final attempt, tells the maintainer to check the autofix model key/access and re-arm — instead of a generic crash message. Tests: run-agent.mjs flags a model [API Error] (marker + failure.md) and does NOT flag a generic failure; the handoff replay treats an API-error handoff as sentinel|retry (not a watermark advance) with a model-aware, cause-specific headline. 62/62 + 12/12. * fix(autofix): scope + broaden the retryable model-API detection (review) Addresses wenshao's review on QwenLM#7247: - Behavioral (1): the agent-api-error marker was written on ANY non-zero exit whose output tail contained an API-error string — so a loop guard, a timeout, or an agent-written failure.md (a real verdict) would wrongly retry and, worst case, silently discard a verdict. The write is now scoped to the bare-failure branch and guarded by !timedOut, so only an un-evaluated model failure retries. - Coverage (2): the old regex only matched a LEADING status digit, so it missed the canonical rate-limit render, the (Status: …) form, the bad-key 401, the Chinese quota text, and the unwrapped Qwen OAuth quota — i.e. most real errors this targets. Detection is now a whitelist of RECOVERABLE errors (401/402/403/429/5xx + rate-limit / quota / api-key / RESOURCE_EXHAUSTED / overloaded phrasings, plus the standalone OAuth-quota form); a 400/404 stays terminal. - Test gap (3): a writer↔reader contract test now runs the REAL run-agent.mjs to write the marker, then the extracted workflow reader block against that same workdir — a rename on either side (proven with the YAML-only mutation) now fails the suite. - Smaller: API_ERROR_DETAIL is comment-escaped (sed) and capped (cut -c1-200) since it derives from agent stdout; the marker match is single-line ([^]\n]) so a multi-line render can't smuggle a newline; agent-api-error is added to the run-artifacts list. Non-recoverable 4xx (400/404) deliberately stay terminal; the live 401/403 config cases retry and self-heal once the key/access is fixed. 79/79 across both suites. * test(autofix): cover the timeout guard and the OAuth-quota fallback (review) Two coverage gaps from the ci-bot review on QwenLM#7247: - The !result.timedOut guard was only asserted indirectly — no test emitted an [API Error] AND timed out. Added a case (spawnSync + QWEN_TIMEOUT_MS=100): qwen streams [API Error: 503] then hangs past the budget → killed → no marker. A refactor to !loopDetected now fails here. - The standalone Qwen-OAuth-quota fallback (unwrapped, no [API Error:]) had no test. Added a case emitting bare 'Qwen OAuth quota exceeded (limit: 100/min)' → marker written, wrapped as '[API Error: Qwen OAuth quota exceeded …]'. * fix(autofix): anchor the API-error code, split retry budget by cause, keep the headline UTF-8 Addresses the review on QwenLM#7247. Classifier (points 2 and 4): the status code is now read from its POSITION in the render (`[API Error: <code>`) instead of matched anywhere in the message. Matching anywhere retried permanent failures forever — `400 Invalid value for max_tokens: must be <= 512` matched a bare \b5\d\d\b and `400 context length exceeded` matched a bare `exceeded`. `exceeded` now only counts as part of `quota`. A 404 whose message says the model "does not exist or you do not have access to it" — the OpenAI-compatible render of what a 403 reports — is no longer terminal. Retry budget (point 3): the marker now carries the cause class. A transient 429/5xx self-heals and keeps the full round budget; an auth/access error that only a maintainer can fix is capped at API_AUTH_MAX_ROUNDS (3) and then goes terminal with the "check the autofix model key/access, then re-arm" headline — instead of ~100 agent runs and ~100 PR comments over ~17h on a takeover PR. The terminal round is stamped so the scan's round gate skips the PR while the sentinel ts keeps the feedback live for a re-arm. Headline (point 1): `cut -c` counts bytes under GNU coreutils and the classifier deliberately matches CJK renders, so the 200-byte cap could split a multi-byte character and emit invalid UTF-8. Guarded with `iconv -f utf-8 -t utf-8 -c || true`, matching the sibling publish site (the `|| true` is required — iconv -c exits 1 when it discards). Minor (point 5): documented that detection is best-effort because apiError is derived from the last 20 KB of output; `head -1` -> `head -n 1`; tests added for a permanent 400 carrying a 3-digit number >= 500 and for a >200-byte CJK render staying valid UTF-8. * test(autofix): cover the auth-capped retry budget and Chinese API-error patterns (QwenLM#7247) * fix(autofix): short-circuit 400 as terminal and classify only the last API error (QwenLM#7247) * fix(autofix): treat transport-level API failures as retryable QwenLM#7365 stranded at round 2/100 on this render: [API Error: terminated (cause: read ECONNRESET)] The connection to the model dropped mid-run. That is as transient as a 429, but the classifier never saw it that way: a transport failure never got far enough to have an HTTP status, so it fell through to the keyword arm, and the keyword arm only knew about rate limits and quotas. It was classified terminal, the watermark advanced, and a PR that needed nothing but a re-run was handed to a human. Verified against the shipped classifier before the fix — every transport render came back terminal: terminated (cause: read ECONNRESET) -> terminal fetch failed -> terminal socket hang up -> terminal connect ETIMEDOUT -> terminal Adds a transport arm to the code-less branch: ECONNRESET, ECONNREFUSED, ETIMEDOUT, EPIPE, EAI_AGAIN, socket hang up, fetch failed, terminated. ENOTFOUND is deliberately excluded. A hostname that does not resolve is a misconfigured endpoint, which repeats forever — the same reasoning that keeps a bad model name terminal. Coded errors are unaffected: the arm sits after the status-code branch, so the 400 short-circuit added in 719991a still runs first. * fix(autofix): address review — OAuth fallback override, comment accuracy, display clamp (QwenLM#7247) --------- Co-authored-by: wenshao <wenshao@example.com> Co-authored-by: 易良 <1204183885@qq.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
ytahdn
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: verification — the review posts findings, but no verifier was launched with a prompt this skill builds — they were ruled on, if at all, without the verdict bar its brief carries.
— qwen3.7-max via Qwen Code /review
| useEffect(() => { | ||
| if (!worktreeFocusTarget.current) return; | ||
| const target = worktreeFocusTarget.current; | ||
| worktreeFocusTarget.current = null; | ||
| if (target === 'cancel') { | ||
| worktreeCancelRef.current?.focus(); | ||
| } else { | ||
| worktreeToggleRef.current?.focus(); | ||
| } | ||
| }, [worktreePending]); |
There was a problem hiding this comment.
[Suggestion] This focus management useEffect has no test coverage.
Concrete cost: If this effect is accidentally removed or the worktreeFocusTarget ref assignment is missed in a handler (e.g., handleCancelWorktree forgets to set worktreeFocusTarget.current = 'toggle'), keyboard users lose focus to <body> after clicking cancel because the original button is removed from the DOM. This is a WCAG 2.4.3 Focus Order regression that won't be caught by visual or functional tests.
Suggested fix: Add focus assertions to the existing "toggles the pending badge on and off" test:
// After enabling toggle:
expect(document.activeElement).toBe(container.querySelector('[data-testid="worktree-cancel"]'));
// After clicking cancel:
expect(document.activeElement).toBe(container.querySelector('[data-testid="worktree-toggle"]'));— qwen3.7-max via Qwen Code /review
ytahdn
left a comment
There was a problem hiding this comment.
Reviewed. LGTM ✅
No Critical issues found. 1 Suggestion (posted inline): focus management useEffect in App.tsx lacks test coverage — a WCAG 2.4.3 Focus Order regression could go undetected. Non-blocking.
— qwen3.7-max via Qwen Code /review
Code Review SummaryVerdict: Approved ✅ Statistics
Findings
Suggestion
SummaryWell-structured feature PR that surfaces worktree isolation in the new-session empty state. Comprehensive test coverage across unit, E2E, and visual regression layers. Code quality is good with proper state management and cleanup paths. — qwen3.7-max via Qwen Code /review |
|
Released in v0.20.1. |
|
The comment is just the release bot announcing that PR #7365 shipped in v0.20.1 — no action needed. Acknowledged, no code changes or reply required. ✅ completed |




What this PR does
This PR moves the worktree-isolated session entry point out of the sidebar's git-branch pill dropdown — where it was hard to discover — and into the chat empty state, which is the de-facto "new session" page. A visible toggle now appears below the welcome header whenever the target workspace is trusted and is a git repository. Turning it on shows the existing pending badge (now with a cancel button); the first prompt then creates the session in an isolated worktree, reusing the existing pending-worktree state machine and lazy session creation, so there are no SDK or daemon changes. The toggle and badge also get a more polished visual treatment (icon medallion, hover micro-interactions, entrance animation) and concise copy that no longer wraps.
The sidebar git pill is simplified as well: the now-redundant "New worktree task" menu item is removed, and because that left a single-item dropdown, the pill now opens the changes view directly on click instead of opening a menu.
Why it's needed
The worktree isolation feature was effectively hidden — reaching it required knowing that the git-branch pill in the workspace header was clickable and then finding the item in a dropdown that only rendered under several conditions. Putting the entry point in the empty state makes it discoverable at the exact moment a user starts a new session, which is when the decision to isolate belongs. Consolidating on one entry point also lets the sidebar git pill return to a single, obvious action.
Reviewer Test Plan
How to verify
Evidence (Before & After)
Worktree entry point — before: buried in the sidebar git-pill dropdown. After: a visible toggle in the new-session empty state.
Close-ups:
Tested on
Environment (optional)
npm run dev:daemon(real daemon + vite web-shell) for manual verification; Playwright mock-daemon E2E and vitest for automated coverage (web-shell suite: 1948 passed).Risk & Scope
onNewSessionworktree option (a public sidebar prop) is preserved; only the internal sidebar menu entry was removed.Linked Issues
中文说明
这个 PR 做了什么
本 PR 把 worktree 隔离会话的入口从侧边栏 git 分支胶囊的下拉菜单(很难被发现)移到了聊天空状态——也就是事实上的"新建会话"页。只要目标 workspace 已信任且是 git 仓库,欢迎语下方就会出现一个可见的开关。开启后显示现有的 pending 徽标(现在带取消按钮);发送首条消息时会复用现有的 pending-worktree 状态机和懒创建链路,在隔离 worktree 中创建会话,因此没有 SDK 或 daemon 改动。开关和徽标也做了更精致的视觉处理(图标徽章、悬停微交互、入场动画),文案改短后不再换行。
侧边栏 git 胶囊也一并简化:删掉了现在已冗余的"新建 worktree 任务"菜单项;由于只剩一个选项,胶囊现在点击直接打开"查看变更",不再弹下拉菜单。
为什么需要
worktree 隔离功能实际上被藏起来了——要用它得先知道 workspace 头部的 git 分支胶囊可以点击,再在一个只有满足多个条件才渲染的下拉里找到那一项。把入口放到空状态,能让用户在开始新会话的那一刻(正是决定是否隔离的时机)就发现它。收敛到单一入口也让侧边栏 git 胶囊回归到一个明确的操作。
审阅测试计划
如何验证
证据(前后对比)
worktree 入口——之前:藏在侧边栏 git 胶囊下拉里;之后:新建会话空状态里的可见开关。截图见上方英文版。
测试环境
环境(可选)
手动验证用
npm run dev:daemon(真实 daemon + vite web-shell);自动化覆盖用 Playwright mock-daemon E2E 和 vitest(web-shell 套件 1948 通过)。风险与范围
onNewSession的 worktree 选项(侧边栏对外 prop)保留;只移除了内部侧边栏菜单项。关联 Issue