feat(web-shell): show workspace overview and a workspace menu in the sidebar - #10407
Conversation
…sidebar Workspace rows gain session counts, the full path and facet chips (MCP, skills, extensions, channels, context files) while expanded, plus a workspace menu with rename, copy path, new worktree task, management entries, reload runtime and remove. Facets are fetched per expanded trusted workspace through the existing workspace-qualified routes and an uninitialized runtime is never rendered as zero. Layer A of #10399.
🖼️ 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 |
|
Thanks for the PR! Re-running the gate on the current head ( Template looks good ✓ — the earlier note is resolved: the Before/After table now carries real screenshots from the mock-daemon visual harness instead of the unfilled placeholders. Problem: planned feature, not speculation — this is layer A of #10399 (filed by the author), which documents the gap: the daemon already exposes all of this per workspace and the SDK wraps it, but the sidebar never consumed it. The design doc ships in the PR. No reproduction bar applies to a feature with a tracked design issue. Direction: aligned. Consuming workspace data the daemon already serves is squarely inside the #10399 roadmap (A → B1 → B2 → C), frontend-only, with no backend changes and only additive sidebar props/types. Not a Claude Code parity item — this is qwen-code's own Web Shell surface. Size: no core-module paths — everything sits in Approach: the shape remains right — a pure model module, one fetching hook gated on expanded + trusted + visible, a chip row, and the single-entry Risk: no elevated risk signals (no high-risk path matches). Moving on to code review. 🔍 中文说明感谢贡献!在第十轮评审后对当前 head( 模板完整 ✓ —— 此前提出的问题已解决:改动前后对比表现在附上了 mock daemon 视觉工具的真实截图,不再是未填写的占位符。 问题:规划中的功能,不是凭空设想——它是 #10399(作者本人提出)的 A 层,issue 中记录了「daemon 已按工作区暴露全部数据、SDK 也已封装,但侧栏从未消费」这一差距,设计文档随 PR 一起提交。有跟踪设计 issue 的功能类 PR 不适用复现门槛。 方向:对齐。消费 daemon 已有的工作区数据完全在 #10399 路线图内(A → B1 → B2 → C),纯前端改动,无后端变更,侧栏 props/类型均为增量。这不是 Claude Code 对齐项,而是 qwen-code 自己的 Web Shell 界面。 规模:未触及核心模块路径——全部改动位于 方案:结构依然合理——纯模型模块、一个以「展开 + 已信任 + 可见」为门控的拉取 hook、一行 chip,以及把原本只有「移除」单项的 风险:无升级风险信号(未命中高风险路径)。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-read the diff at the commit in the footer — this replaces my earlier pass on the original submission, which ten review rounds have since reworked. The shape survived intact: a pure model, one gated fetching hook, chip row, and a state-gated menu over the existing workspace-qualified daemon routes, with the integration points ( No critical blockers. What this pass confirmed:
Non-blocking, carried on the record:
sequenceDiagram
participant P1 as WorkspaceSection
participant P2 as useWorkspaceOverview
participant P3 as Daemon workspace routes
participant P4 as Chips and menu counts
Note over P1: expanded and trusted
P1->>P2: enabled, items, reloadToken
P2->>P3: one GET per facet, in parallel
P3-->>P2: answers (each facet fails alone)
P2->>P2: summarize, book misses per epoch, merge over last known
P2-->>P4: snapshot for chips and live counts
loop every 30s while visible, plus window focus
P2->>P3: refetch round
end
Files changed (27 of 27 shown)
Test evidence (the PR's own CI via API — no PR code executed in this run)Everything landed green on the reviewed commit — notably
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The behavioural claims — chip values and warning tone, header counts, request gating per expanded/trusted/visible state, menu contents per workspace kind — are pinned by this PR's own suites, and the earlier sandboxed verification (previous head, one commit before this one) proved the central claim load-bearing with an A/B against base: exactly one request per facet on trusted expanded rows, zero on base, gating flips with collapse/re-expand. That run's two advisory findings: the speed-correlated 30 s-poll e2e test above (test-only; the epoch commit since did not touch it) and a coverage gap the epoch tests have now closed. A fresh 中文说明代码审查在页脚所列提交上重新通读 diff——本次替代我早先对初始版本的审查,此后十轮评审已对代码做了大量打磨。整体形态保持完好:纯模型、一个门控拉取 hook、chip 行、基于既有按工作区限定路由的状态门控菜单;各接入点( 无关键阻塞项。本次确认:
非阻塞、留档:
(流程图与文件清单见英文部分。) 测试证据(经 API 读取本 PR 自己的 CI——本次运行未执行任何 PR 代码)受审提交上全部落绿——尤其值得注意:此前连续四次运行撞上 60 分钟任务超时的 CI 明细表见英文部分(机器可读区域由 finalize 任务维护)。 行为性结论——chip 数值与警告色、标题计数、按展开/信任/可见状态的请求门控、各类工作区的菜单内容——由本 PR 自带套件钉住;更早一次沙箱验证(上一个 head,距此一个提交)以 A/B 对照证明了中心声明是关键载荷:已信任展开行每个 facet 恰好一次请求、base 侧为零、折叠/重新展开时门控随之翻转。该次验证的两条参考级发现:上文提到的速度相关 30 秒轮询用例(仅测试;其后的纪元提交未触及该文件),以及一个覆盖缺口——现已被纪元测试闭合。针对本 head 的新一轮 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — ten rounds have converged this to a clean, coherent layer; the reservations are non-blocking (one speed-correlated e2e test, a few deferred ledger nits). My independent proposal for "let a sidebar row tell you what a workspace contains and let you manage it" was exactly this shape — a pure model, one gated polling hook, a chip row and a state-gated menu over the existing workspace-qualified routes — and re-reading the final diff I did not find a simpler path it missed. Waiting for the single overview endpoint (layer C) was the only real alternative, and it stays correctly deferred. What tips this from "reviewed" to "ready": the hard parts are the ones that usually regress silently, and they are the ones pinned here — request gating is asserted against recorded traffic, a placeholder can never masquerade as zero, miss budgets are scoped per bookkeeping epoch with the round-trip and disable windows test-pinned after the last critical, and facets fail independently without blanking their siblings. The review arc also behaved the way it should: rounds six through ten landed only criticals, and the last round found no blockers on this commit. CI is fully green on it, including the previously timing-out unit job and the e2e smoke that runs this PR's spec. The 4 rather than 5: the 30 s-poll e2e test is still speed-correlated on slow machines (the sandboxed verification measured it failing deterministically on a loaded runner; the one-line Approving now, pinned to the reviewed commit — CI has already landed green, so no deferred approval is needed. 中文说明Confidence: 4/5 —— 十轮评审已使这一层收敛到干净、内聚的状态;保留意见均为非阻塞项(一个与机器速度相关的 e2e 用例、少量延后的遗留小项)。 我对「让侧栏行能展示工作区内容并就地管理」的独立方案正是这个形态——纯模型、一个门控轮询 hook、一行 chip、基于既有按工作区限定路由的状态门控菜单——重读最终 diff 后,我仍未发现它遗漏的更简路径。等待单一概览端点(C 层)是唯一真正的替代方案,且仍被正确延后。让本 PR 从「已审」变为「可合」的关键在于:最难的部分恰恰是最容易悄悄回归的部分,而它们在这里都被钉住了——请求门控基于录制流量断言、占位绝不伪装成零、未答预算按记账纪元划分且往返/禁用窗口在上一个 critical 之后已有测试钉住、各 facet 独立失败而不连累其他。评审轨迹也符合预期:第六到第十轮只落 critical,最后一轮在本提交上未发现阻塞项。CI 在该提交上全绿,包括此前超时的单测任务与运行本 PR 用例的 e2e 冒烟。 给 4 而不是 5 的原因:30 秒轮询 e2e 用例在慢速机器上仍与速度相关(沙箱验证实测在负载运行器上确定性失败;报告给出的一行 现在批准,批准固定在受审提交上——CI 已落绿,无需延迟批准。 — Qwen Code · qwen3.8-max Reviewed at |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Test Plan (not a blocker): client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory; npm run build — exit 2.
中文说明
Test Plan(非阻断):client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory; npm run build — exit 2。
— qwen3.8-max via Qwen Code /review (v0.22.2)
- e2e: call the mock daemon's replay helper with its object form - stop fetching facets behind a custom (locked) header renderer and for the synthetic fallback workspace, which has nothing to display them - keep a row's last session counts while it is collapsed - rename: accept the daemon's 256-character display-name cap and treat a failed capabilities refresh after a successful rename as converged - chips: use the sidebar's warning token and say "unavailable" rather than "not initialized" for daemon-side facets; drop the unread loading state - record the Trust… deferral and the measured fan-out cost in the design doc; pin the reviewed behaviours with tests (hooks facet, user-question attention, poll default, in-flight invalidation, copy path, reload feedback, overview opt-out, worktree task)
|
Addressed the review round at 7af1806 — all 25 threads replied to and resolved. Summary: fixed the three Criticals (e2e replay helper called with its object form; no facet fetch behind a custom locked header renderer, nor for the synthetic fallback workspace; rename cap raised to the daemon's 256), kept a collapsed row's last session counts, split the rename mutation from the capabilities refresh, switched the chip warning tone to |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): chunk 4: executing WorkspaceMenu.test.tsx under vitest (no node_modules in the shared review worktree; a full monorepo install exceeds this chunk's budget — verification…; chunk 8: could not execute WorkspaceSection.test.tsx — the worktree has no node_modules , and I did not run a full monorepo install in the shared review tree; correct….
Test Plan (not a blocker): client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory; npm run build — exit 2.
Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:
packages/web-shell/client/components/sidebar/WebShellSidebar.tsx:1374 — [probe] public workspaceOverview: { items } option never populated by any test; ternary-collapse mutation survivespackages/web-shell/client/components/sidebar/WebShellSidebar.tsx:5395 — [probe] Projects workspace-count badge has only negative coverage; threshold-flip mutation survivespackages/web-shell/client/components/sidebar/WorkspaceSection.tsx:483 — [probe] primary sessionStats branch unwitnessed by unit tests; the e2e witness is post-merge-onlypackages/web-shell/client/components/sidebar/WorkspaceSection.tsx:611 — [probe] header attention-count branch has no test witness; branch-off mutation left 256 tests greenpackages/web-shell/client/components/sidebar/WorkspaceSection.tsx:653 — [probe] nextCursor '+' truncation render wiring unreachable by any testpackages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx:156 — [probe] menu newSession action has no dispatch witness; rewiring mutant survived 169 testspackages/web-shell/client/components/sidebar/WorkspaceOverview.tsx:48 — [probe] mcp zero-denominator guard in formatOverviewValue has no test witness (channels half covered by post-merge e2e)packages/web-shell/client/components/sidebar/WorkspaceRenameDialog.tsx:43 — [probe] busy dismiss-guard and submit-handler busy || term have no test witness; mutants survivepackages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx:156 — [probe] requested.size > 0 term of active never exercised; guard-removal mutant survivespackages/web-shell/client/components/sidebar/workspaceOverviewModel.ts:213 — [review] WorkspaceOverviewSnapshot.fetchedAt has zero read sites; merge stamps false freshness after fully failed rounds
中文说明
未探索到全部深度(达到工具调用预算):chunk 4:executing WorkspaceMenu.test.tsx under vitest (no node_modules in the shared review worktree; a full monorepo install exceeds this chunk's budget — verification…;chunk 8:could not execute WorkspaceSection.test.tsx — the worktree has no node_modules , and I did not run a full monorepo install in the shared review tree; correct…。
Test Plan(非阻断):client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory; npm run build — exit 2。
收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 10 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.2)
- context files are answered by the daemon from disk, so a workspace without a QWEN.md renders a known 0 instead of "not initialized" - offer "New worktree task" only when the git poll reports a branch - keep retained session counts only while the catalog query is disabled, not across a source switch on an expanded row - tests: daemon-rejected rename keeps the dialog open, clipboard stub is restored, the display-name cap is pinned exactly, worktree gating
|
Round 2 addressed at fd3939b — 6 threads replied to and resolved. Both Criticals: context files are now a daemon-side facet (a workspace without a QWEN.md shows 0, not 'not initialized'), and 'New worktree task' is offered only on rows whose git poll reports a branch. Also: retained collapsed-row counts no longer leak across a source switch on an expanded row, the rename-rejected path and the exact 256 cap are pinned by tests, and the clipboard stub is restored after the copy-path test. Locally: sidebar suites 18 files / 260 green, full web-shell unit suite 4479 green, full e2e 66 green, tsc/ESLint/Prettier clean. The earlier CI red on 7af1806 was runner-load flakiness in files this PR does not touch (core shellAstParser timing, web-shell MessageList/SkillsManagerPage timeouts); the failed job was re-run and passed. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
Test Plan (not a blocker): client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory.
Convergence: round 3 posted 9 inline comment(s), 9 of them reported for the first time; the previous round posted 6 (6 new). Findings keep coming back to the same files: packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx (findings in round 2; 2 more now); packages/web-shell/client/components/sidebar/WorkspaceSection.tsx (findings in round 1; 2 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. No Critical finding is open on this round, so merging and moving the remaining Suggestion threads to a follow-up issue is available as an ending — a merged pull request cannot diverge further. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
已审查——无阻断问题。 建议见行内评论。
Test Plan(非阻断):client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory。
收敛情况:第 3 轮发布了 9 条行内评论,其中 9 条是首次提出;上一轮发布了 6 条(其中 6 条首次提出)。发现反复回到同一批文件:packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx(第 2 轮已出过发现,本轮又有 2 条);packages/web-shell/client/components/sidebar/WorkspaceSection.tsx(第 1 轮已出过发现,本轮又有 2 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。本轮没有未决的 Critical,因此"合入后把剩余 Suggestion 线程转到后续 issue"是一个可选的结束方式——已合入的 PR 不会继续发散。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.2)
- primary row shows no counts while its session source is switching, instead of the previous source's numbers - poll git whenever header actions are wired, so the worktree entry no longer depends on the diff handler being present - render no chips until the first overview round lands - tests: primary-removal guard, branch-gated worktree entry on a non-git secondary workspace, untrusted rows never fetch, MCP disabled-server arithmetic, context file count, in-progress discovery is not a failure, and a deterministic wait for Radix's outside-press listener
|
Round 3 addressed at dfece46 — 9 threads replied to and resolved. Behaviour: the primary row shows no counts while its session source is switching (explicit null from the sidebar, no retained fallback); the git poll runs whenever header actions are wired so the worktree entry no longer depends on the diff handler; the chip row renders only once the first overview round has landed, so an in-flight fetch never reads as 'unavailable'. Tests: every named mutant was run against the new/strengthened cases and turns them red (primary-removal guard, branch-gated worktree entry on a non-git secondary, untrusted rows never fetch, MCP disabled-server arithmetic, context file count, in-progress discovery is not a failure); the outside-dismissal menu test now waits for Radix's macrotask listener registration and passes 8/8 locally. Full web-shell unit suite 4484 green, e2e 66 green, tsc/ESLint/Prettier clean. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not explored to full depth (tool budget reached): chunk 4: executed the test file ( npx vitest run client/components/sidebar/WorkspaceMenu.test.tsx ) — could not run because no node_modules exists in the review worktr….
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Test Plan (not a blocker): client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory.
Deferred under the convergence posture (round 4, not a blocker) — recorded, not requested in this round:
packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx:69 — [probe] channels zero-guard has no test; guard deletion ships green and chip/menu render 0/0 for no-channels workspacespackages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx:3266 — [probe] overview-off headerCounts assertion vacuous (zero sessions); the overviewEnabled && half of the stats gate is unpinnedpackages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx:203 — [probe] hooks tooltip disabled:false branch unpinned; a constant-disabled mutant ships green ('N hooks (disabled)' on active hooks)packages/web-shell/client/components/sidebar/WorkspaceSection.tsx:490 — [probe] overviewItems pass-through (hook items + WorkspaceOverview items) untested; the embedding items knob can be silently severedpackages/web-shell/client/components/sidebar/WorkspaceSection.tsx:146 — [probe] compact prop consumers (pathCompact class, compact pass-through) untested; tight-width label-less chips unpinnedpackages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx:2982 — [probe] primary-menu not.toContain('Rename…') vacuous (fixture lacks the feature); a !ws.primary mutant in canRename ships greenpackages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx:138 — [probe] triggerClassName/contentStyle pass-throughs untested; trigger styling and portal z-index unpinnedpackages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx:196 — [probe] chipUnknown class branch untested; unknown facets would lose the dimmed tone and look identical to real datapackages/web-shell/client/components/sidebar/workspaceOverviewModel.test.ts:278 — [probe] channel 'starting' runtime state unexercised; a classification refactor folding it into connected ships greenpackages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx:800 — [probe] dead workspaceGit fixture: beforeEach re-installs the handle without it, 175 poll TypeErrors per full-suite run
Convergence: round 4 posted 6 inline comment(s), 6 of them reported for the first time; the previous round posted 9 (9 new). Findings keep coming back to the same files: packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx (findings in round 3; 3 more now); packages/web-shell/client/components/sidebar/WorkspaceSection.tsx (findings in round 3; 2 more now); packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx (findings in round 3; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
未探索到全部深度(达到工具调用预算):chunk 4:executed the test file ( npx vitest run client/components/sidebar/WorkspaceMenu.test.tsx ) — could not run because no node_modules exists in the review worktr…。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
Test Plan(非阻断):client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory。
收敛姿态下延后(第 4 轮,非阻断)——已记录,本轮不要求修改:共 10 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 4 轮发布了 6 条行内评论,其中 6 条是首次提出;上一轮发布了 9 条(其中 9 条首次提出)。发现反复回到同一批文件:packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx(第 3 轮已出过发现,本轮又有 3 条);packages/web-shell/client/components/sidebar/WorkspaceSection.tsx(第 3 轮已出过发现,本轮又有 2 条);packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx(第 3 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.3)
- retained session counts are tagged with the source they were computed for, so a collapsed row drops them after a global source switch - a locked sidebar with a custom header passes no header actions, so the section skips the git poll that only feeds them - tests: primary-row wiring (no session list) for the loading guard, attention badge and truncation marker, overview reaches header actions, skills/extensions/channels tooltips, locked rows never poll git
|
Round 4 addressed at 05b8389 — 6 threads replied to and resolved. The Critical (a collapsed secondary row keeping the previous source's counts after a global source switch) is fixed by tagging the retained counts with their source; the locked-sidebar git poll is closed off caller-side by passing no header actions under a custom header renderer; the remaining suggestions are pinned by tests (primary-row wiring for the loading guard, attention badge and '+' marker, overview reaching the header actions, skills/extensions/channels tooltips, locked rows never polling git), each verified red against the named mutant. Locally: sidebar suites 269 green, full web-shell unit suite 4488 green, e2e 66 green, tsc/ESLint/Prettier clean. |
…ce-overview # Conflicts: # packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx # packages/web-shell/client/components/sidebar/WorkspaceSection.tsx
|
Merged origin/main (c1f8a42) into the branch at 76f1208 to clear the conflict with #10416 in WorkspaceSection: main's search/pinned split ( |
- an armed branch/worktree intent no longer resets on a transient git-status gap; only a session, an untrusted workspace, a no-branch answer or a draft workspace switch clears it - the workspace header action wrapper stays visible while its menu is open, so Escape returns focus to the trigger - the git poll runs only when a consumer of the branch is wired (diff chip or worktree entry outside a locked sidebar) - the worktree entry shares the new-task re-entrancy guard, busy state and catalog refresh - the menu's management section is one flat group; facet summaries are isolated per facet so a malformed body cannot blank the whole round - tests for each of the above plus the items/cwd/re-enable transitions, placeholder overwrite on merge, skills placeholder, and action order
|
Round 5 addressed at a910a03 — 14 threads replied to and resolved. The Critical (an armed worktree intent silently cleared by a transient git-status gap) is fixed by resetting the intent only on definitive answers — session exists, workspace untrusted, git answered no branch, or the draft's workspace was switched — via a small pure helper with unit tests plus an App-level test that rejects one refetch round and asserts the intent survives (red under the previous behaviour). Also: the header action wrapper stays visible while its menu is open; the git poll runs only when the diff chip or an unlocked worktree entry consumes the branch; the worktree entry shares the new-task re-entrancy guard and catalog refresh; the manage section is one flat group; facet summaries are isolated per facet. Every named mutant was run against the new tests and turns them red. Locally: sidebar suites 19 files green, full web-shell unit suite 4693 green, e2e 66 green, tsc/ESLint/Prettier clean. On CI: the earlier reds on this branch were all 60-minute job timeouts / cascaded cancels on the self-hosted pool (Test ubuntu ×4, E2E Smoke's 20-minute step), not test failures. |
wenshao
left a comment
There was a problem hiding this comment.
Not reviewed: the executable-script lint — the report is stale or its diff could not be verified; re-run qwen review script-lint.
Not reviewed: reverse audit — an auditor ran and opened its brief, but no agent was launched with the prompt the CLI built — the launch was written by hand, and what the agent was actually asked is not what this skill certifies.
— [unverified] tag when the loop ended — the verifier never ruled on them, and they are not confirmed.
Deferred under the convergence posture (round 5, not a blocker) — recorded, not requested in this round:
packages/web-shell/client/components/sidebar/workspaceOverviewModel.ts:75 — [review] D5-1 doc comment lists context among ACP-discovered facets — five agreeing authorities contradict it; a maintainer trusting it 'fixes' the known-gate and e…packages/web-shell/client/components/sidebar/WebShellSidebar.module.css:1871 — [review] D5-2 Projects badge pairs --muted-foreground on --sidebar-accent: ~2.27:1 in the light theme; six sibling precedents use --sidebar-accent-foreground (~1…packages/web-shell/client/components/sidebar/WorkspaceOverview.module.css:28 — [review] D5-3 the overview chips replicate the same 2.27:1 light-theme pair in a different new file — a badge-only fix leaves the chips unfixed; dark theme passe…packages/web-shell/client/e2e/web-shell.workspace-overview.spec.ts:186 — [review] R5-27 'nothing keeps polling' settle-check is vacuous for ≥1.5s intervals and the final re-check flakes against the by-design 30s poll — pin the cadence with …packages/web-shell/client/e2e/web-shell.workspace-overview.spec.ts:233 — [review] R5-28 the e2e worktree comment claims the draft opens in THAT workspace but no assertion checks the target — a wrong-cwd regression ships green (anchor unreso…
Convergence: round 5 posted 29 inline comment(s), 29 of them reported for the first time. Findings keep coming back to the same files: packages/web-shell/client/components/sidebar/WorkspaceSection.tsx (findings in rounds 3, 4; 6 more now); packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx (findings in round 4; 4 more now). (Evidence: the previous round was recovered from a marker this account did not post, so those rounds may not be this account's own.) A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
未审查:the executable-script lint — the report is stale or its diff could not be verified; re-run qwen review script-lint。
未审查:反向审计——有审计 agent 运行并打开了自己的 brief,但没有 agent 是用 CLI 构建的 prompt 启动的——启动 prompt 是手写的,agent 实际被要求做的并不是本 skill 所认证的内容。
— [unverified] 标记——验证者从未对它们作出裁决,它们不算已确认。
收敛姿态下延后(第 5 轮,非阻断)——已记录,本轮不要求修改:共 5 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 5 轮发布了 29 条行内评论,其中 29 条是首次提出。发现反复回到同一批文件:packages/web-shell/client/components/sidebar/WorkspaceSection.tsx(第 3、4 轮已出过发现,本轮又有 6 条);packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx(第 4 轮已出过发现,本轮又有 4 条)。(证据说明:上一轮的数据来自并非本账号发布的标记,上述轮次可能不属于本账号。)一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— glm-5.3 via Qwen Code /review (v0.22.2)
- the sidebar's worktree arm is dropped when another session start ran while the creation was in flight - header totals honour the daemon's page-level truncated flag as well as a next cursor, on both the primary and secondary derivations - rename is offered on registration-backed rows only; the rename dialog refuses control characters before the request is sent - the header menu keeps the row's last overview snapshot while collapsed and its live counts are part of the items' accessible names - a custom header still fetches the overview when header actions consume it; a facet reads as unavailable after three unanswered rounds and a cwd change never carries the previous workspace's facets over - mock daemon: per-workspace overview facets; e2e asserts the secondary row's own counts and pins the 30 s poll with a fake clock - main-boot.test mocks the provider module main.tsx imports (also opened separately against main)
|
Round 6 addressed at acef9f3 — 29 threads replied to and resolved (several were already covered by a910a03 and are marked as such). Judged and kept: the stale-arm race is closed with a composer-source-version token, header totals honour the daemon's truncated flag, rename is offered on registration-backed rows only (persistence, not trust), control characters are refused in the dialog, the menu keeps the row's last snapshot while collapsed and exposes counts to assistive tech, a custom header with header actions still fetches, facets expire after three unanswered rounds, and a cwd change clears the snapshot; the mock daemon serves per-workspace facets and the e2e pins the secondary row's own counts plus the 30 s cadence with a fake clock. One deliberate limitation is stated in-thread (the sidebar-side truncated flag cannot be driven through the mocked legacy sessions hook). Separately, main's web-shell unit suite fails at collection in |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
2 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- context-listed-among-ACP-discovered-facets doc comment (workspaceOverviewModel.ts:75) — already reported (round 5 deferred list, D5-1, review 5058918480)
- write-only WorkspaceOverviewSnapshot.fetchedAt (workspaceOverviewModel.ts:218) — already reported (round 2 deferred list, review 5054597661; named again in round 5's duplicate list)
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
Test Plan (not a blocker): client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory.
Deferred under the convergence posture (round 6, not a blocker) — recorded, not requested in this round:
packages/web-shell/client/components/sidebar/WorkspaceOverview.tsx:123 — [review] compact-mode chip aria-label unexposed on role-less span (Chromium AX probe)packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx:1410 — [review] zero-parameter headerActions mocks: TS2493/TS18048 at five sites, invisible to CI typecheckpackages/web-shell/client/App.tsx:8752 — [review] draft-workspace-switch intent resets untested; deleting both keeps 583/583 greenpackages/web-shell/client/components/sidebar/WorkspaceOverview.tsx:109 — [review] expired runtime-discovered facet shows 'not initialized yet' forever instead of 'unavailable'packages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx:448 — [review] visibilityState redefine never restored; a mid-window failure leaks 'hidden' into later tests
Convergence: round 6 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 14 (14 new). Findings keep coming back to the same files: packages/web-shell/client/components/sidebar/useWorkspaceOverview.ts (findings in round 5; 1 more now). (Evidence: the previous round was recovered from a marker this account did not post and merged over this account's own entries, so some of those rounds and its counts may not be this account's own.) A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)
Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 2 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
Test Plan(非阻断):client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory。
收敛姿态下延后(第 6 轮,非阻断)——已记录,本轮不要求修改:共 5 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 6 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 14 条(其中 14 条首次提出)。发现反复回到同一批文件:packages/web-shell/client/components/sidebar/useWorkspaceOverview.ts(第 5 轮已出过发现,本轮又有 1 条)。(证据说明:上一轮的数据来自并非本账号发布的标记,并与本账号自己的条目合并,上述轮次与其计数中的部分可能不属于本账号。)一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)
机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)
— qwen3.8-max via Qwen Code /review (v0.22.3)
The SDK's request deadline equals the overview poll cadence, so while a daemon hangs every round times out just after the next tick has replaced it and never reached the miss bookkeeping; facets could freeze on their pre-hang counts for the whole hang. Misses are now recorded for every round that lands, and a superseded round that observes an expiry drops those facets without merging its stale data. A round from a previous cwd is ignored entirely.
|
Round 7 addressed at a4aaed4 — the one Critical (superseded timeout rounds never reaching the miss bookkeeping, so a hung daemon froze the chips) is fixed by counting misses on every landed round and letting a superseded round drop expired facets without merging its stale data; verified red under the old ordering. Locally on the merged tree: web-shell unit suite 5333 green, e2e overview spec green, tsc/ESLint/Prettier clean. CI: the branch's |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
3 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- compact-mode chip aria-label unexposed on role-less span (WorkspaceOverview.tsx:123) — already reported (round 6 deferred list, review 5059570999)
- doc comment lists context among ACP-discovered facets (workspaceOverviewModel.ts:75) — already reported (round 5 deferred D5-1, review 5058918480; named again in round 6)
- draft-workspace-switch intent resets untested (App.tsx:8752) — already reported (round 6 deferred list, review 5059570999)
Not reviewed: reverse audit — stopped before round 5 by the review time budget.
Test Plan (not a blocker): client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory.
Deferred under the convergence posture (round 7, not a blocker) — recorded, not requested in this round:
packages/web-shell/client/App.tsx:1356 — [review] workspaceOverview: false App-level forwarding untested — dropping the forwarding line keeps all 583 App tests greenpackages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx:3249 — [review] worktree-gate test confounds branch gate with primary flag — adding !ws.primary keeps 103/103 greenpackages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx:294 — [review] cwd-isolation guard (useWorkspaceOverview.ts:145) load-bearing but unpinned — guard-deletion mutant 20/20 green, probe flipspackages/web-shell/client/components/sidebar/WorkspaceOverview.tsx:101 — [review] unknown items in the public workspaceOverview.items option crash the whole Web Shell via ICONS[item] and the root ErrorBoundarypackages/web-shell/client/App.test.tsx:1139 — [review] createNewSession(workspaceCwd) seam unpinned — createNewSession() mutant keeps 583 tests green, flip probe redspackages/web-shell/client/components/sidebar/WebShellSidebar.tsx:454 — [review] onNewWorktreeSession void-return contract skips bumpWorkspaceReload/refreshWorkspace, diverging from the sibling onNewSessionpackages/web-shell/client/components/sidebar/WorkspaceSection.tsx:554 — [review] sessionsActive ? undefined : retainedStats guard unpinned — mutant survives 46+103 tests, flip probe redspackages/web-shell/client/components/sidebar/WorkspaceSection.tsx:513 — [review] retainedOverview never cleared when the overview is disabled — the menu keeps stale counts, contradicting the prop JSDocpackages/web-shell/client/components/sidebar/WorkspaceSection.tsx:681 — [review] headerCounts stats.total > 0 gate unpinned — empty workspaces would render a bare '0' badge; mutant 149/149 greenpackages/web-shell/client/e2e/web-shell.workspace-overview.spec.ts:45 — [review] e2e worktree assertions hang on the global gitStatus mock — wrong-cwd polling mutant stays greenpackages/web-shell/client/e2e/web-shell.workspace-overview.spec.ts:325 — [review] clock-driven cadence e2e fails deterministically on loaded runners — Playwright's installed clock syncs real time during startup waits; product exonerated by …
Convergence: round 7 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 1 (1 new). The rate of new findings is not falling. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)
Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (1 Critical(s)), the rate of first-time findings is not falling (this round 1, previous 1), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):
| standing Critical | attack surface | attacker-dependency | blast radius |
|---|---|---|---|
| (each standing Critical) | … | … | … |
Advisory only — it does not block this review.
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 3 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查:反向审计——评审时间预算不足,未能开始第 5 轮。
Test Plan(非阻断):client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory。
收敛姿态下延后(第 7 轮,非阻断)——已记录,本轮不要求修改:共 11 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 7 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 1 条(其中 1 条首次提出)。新发现的产出速度没有下降。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)
残余风险:本循环处于 persistently-critical 形态——上一轮工作清单中的 Critical 本轮依然存在(本轮 1 条 Critical),首次发现的速率没有下降(本轮 1,上一轮 1),且未决 Critical 积压没有减少。severity floor 无法使其收敛。建议:land-with-residual-risk——出口是 maintainer 的风险接受决定(合入并承担残余风险),而非再开一轮评审。供该决定使用的残余风险清单(maintainer 填写):按每条未决 Critical 列出「攻击面 · 攻击者依赖性 · 影响范围」三栏。仅为建议——不阻断本次评审。
— qwen3.8-max via Qwen Code /review (v0.22.3)
The sidebar's worktree entry armed the intent after createNewSession settled, guarded by a composer-version token. Two gaps remained: a prompt submitted while the previous session was still clearing read the reset intent and got the current branch, and a draft re-targeted through the sidebar during the await kept the token so the arm still landed. createNewSession now takes the git intent and writes both the state and the ref in the same synchronous step that resets the previous one, so the intent belongs to that draft from the start; the sidebar entry passes worktree and no post-await arm exists. The invalidation effect also re-runs on intent changes so an intent set while a session already exists is cleared at once.
|
Round 8 (1 Critical) addressed in 8e4ebb2. The worktree intent is no longer armed after Verification: two new App tests (prompt during in-flight clear gets 中文说明第 8 轮(1 条 Critical)已在 8e4ebb2 处理。 worktree 意图不再在 验证:新增两个 App 测试(清理进行中提交提示拿到 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): "agent 6c": local execution of the four new App tests — blocked because @qwen-code/sdk (packages/sdk-typescript) has no built dist/ in this worktree and its build scrip….
Test Plan (not a blocker): client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory.
Deferred under the convergence posture (round 8, not a blocker) — recorded, not requested in this round:
packages/web-shell/client/App.tsx:8773 — [review] composer-seam draft reset untestedpackages/web-shell/client/App.tsx:12690 — [review] management/overview App seams untestedpackages/web-shell/client/App.tsx:8692 — [review] same-tick ref-write window untestedpackages/web-shell/client/App.test.tsx:11203 — [review] hand-rolled deferred instead of file's helper
Convergence: round 8 posted 2 inline comment(s), 2 of them reported for the first time; the previous round posted 1 (1 new). Findings keep coming back to the same files: packages/web-shell/client/App.tsx (findings in round 7; 2 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)
Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (2 Critical(s)), the rate of first-time findings is not falling (this round 2, previous 1), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):
| standing Critical | attack surface | attacker-dependency | blast radius |
|---|---|---|---|
| (each standing Critical) | … | … | … |
Advisory only — it does not block this review.
中文说明
未探索到全部深度(达到工具调用预算):"agent 6c":local execution of the four new App tests — blocked because @qwen-code/sdk (packages/sdk-typescript) has no built dist/ in this worktree and its build scrip…。
Test Plan(非阻断):client/e2e/web-shell.workspace-overview.spec.ts — no such file or directory。
收敛姿态下延后(第 8 轮,非阻断)——已记录,本轮不要求修改:共 4 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 8 轮发布了 2 条行内评论,其中 2 条是首次提出;上一轮发布了 1 条(其中 1 条首次提出)。发现反复回到同一批文件:packages/web-shell/client/App.tsx(第 7 轮已出过发现,本轮又有 2 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)
残余风险:本循环处于 persistently-critical 形态——上一轮工作清单中的 Critical 本轮依然存在(本轮 2 条 Critical),首次发现的速率没有下降(本轮 2,上一轮 1),且未决 Critical 积压没有减少。severity floor 无法使其收敛。建议:land-with-residual-risk——出口是 maintainer 的风险接受决定(合入并承担残余风险),而非再开一轮评审。供该决定使用的残余风险清单(maintainer 填写):按每条未决 Critical 列出「攻击面 · 攻击者依赖性 · 影响范围」三栏。仅为建议——不阻断本次评审。
— qwen3.8-max via Qwen Code /review (v0.22.3)
…pace draft moves Two paths still dropped a worktree intent that nothing had invalidated. Re-selecting the draft's own workspace from the composer picker (Radix fires onValueChange for the checked row too) went through the draft branch of switchWorkspace and hit the reset added for real switches. The branch now compares the target with the selected workspace before it overwrites the ref and skips only the reset when they match; the cwd re-set and composer version bump stay as before. Arming a worktree intent from another workspace's row while the draft workspace had a definitive no-branch answer (a non-repo primary) was undone in the same commit: activeWorkspaceCwd already pointed at the new workspace but selectedWorkspaceGitStatus still held the old answer, which the git-status effect clears only after the commit. The reset check now keys the status by workspaceCwd, the same rule the git_status_changed mirror effect uses, so a status for a workspace being left never decides for the new one. A definitive no-branch answer for the draft's own workspace still clears the intent.
|
Round 9 (2 Critical) addressed in 254fdc0. Both were real: re-selecting the draft's own workspace from the composer picker hit the draft-branch reset added for real switches (now guarded by a same-target compare taken before the ref is overwritten; only the reset is skipped), and a worktree intent armed from another workspace's row was undone by the previous draft workspace's stale no-branch status in the same commit (the reset check now keys the status by Verification: three new App tests, each red under its named mutant (guard removed / raw status / status clause dropped); 中文说明第 9 轮(2 条 Critical)已在 254fdc0 处理。 两条都成立:从作曲器选择器重选草稿自身的工作区会撞上为真实切换加的草稿分支重置(现在在覆写 ref 之前做同目标比较,相同则只跳过重置);从另一工作区行置位的 worktree 意图会在同一次提交里被上一个草稿工作区的陈旧无分支状态清掉(重置检查现在按 验证:新增三个 App 测试,各自在对应变异体(去守卫 / 原始状态 / 去状态子句)下变红; |
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 50 passed · 2 failed · 52 total Flakiness gate: 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:50 通过 · 2 失败 · 52 总计 抖动门: Verification reportPR 10407 deep verification — feat(web-shell): show workspace overview and a workspace menu in the sidebarVerdict: Two reviewer-worthy findings, neither a product defect: the PR's new 30 s-poll e2e test is speed-correlated (fails deterministically on machines where the page settles slower than ~1 s; measured failing on this runner, measured fix validated), and one of the hook's two cwd-isolation guards (the in-flight stale-round check) is not pinned by any test. The central behavior is proven load-bearing by an A/B against base. 中文摘要结论:
Central claim and A/BCentral claim: expanded, trusted workspaces fetch one request per overview facet from the workspace-qualified daemon routes, render chips, and stop fetching when collapsed/untrusted; uninitialized runtimes never render as A byte-identical harness (
Scripted expectations over both arms: 35/35 passed ( Secondary claims verified:
FindingsF1 (Suggestion) — the new 30 s-poll e2e test is speed-correlated: it fails on machines where the page settles slower than ~1 s
Attribution, measured with an instrumented probe (
The product behavior is correct: the same probe shows the interval fires exactly 30 fake-seconds after registration (registered ≈ fake −4 s → tick at baseline+26 s, next would be +56 s), and the validated fix reproduces the spec's intent deterministically on this same machine: Suggested fix (measured)Freeze the fake clock while the page settles, so the baseline and the interval registration share one fake instant: await page.clock.install();
await page.clock.pauseAt(Date.now());
await gotoSession(page, scenario, daemon);Measured on this runner: hostile case (slow settle) goes clean — no tick at 29 s, exactly +5 at 31 s, +10 at 61 s; the assertions the spec already contains are unchanged. The suite count for the spec file is unchanged (1 test, now deterministic). The probe validated this exact patch; the first two tests of the spec are unaffected (they do not install the clock). F2 (Suggestion) — the in-flight stale-round cwd guard survives deletion: its window is pinned by no testMutation matrix (control green at 53/53 first;
The settled-state handoff is pinned only by the reset effect; the reload-time guard is the sole defence for the window where a slow round from the old cwd lands after the switch ( Targeted gates
Full web-shell unit suite — adjudicated environmental via a base A/A (
Every head-only failure in a PR-touched file (20 tests across Not covered
MethodologyEnvironment: CI verify container ( Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not explored to full depth (tool budget reached): chunk 15: none (I skipped executing the new tests because the worktree has no installed dependencies; that is a disclosure, not a ceiling-stopped check — the static trace…; chunk 2: executed the new tests in packages/web-shell/client/App.test.tsx to confirm they pass (no node_modules in the worktree or parent checkout; a full npm ci + build….
Deferred under the convergence posture (round 9, not a blocker) — recorded, not requested in this round:
packages/web-shell/client/components/sidebar/WebShellSidebar.tsx:1117 (+2 locations) — [probe] N+ lower-bound nextCursor arm unwitnessed at both summarizeSessions call sitespackages/web-shell/client/components/sidebar/WebShellSidebar.tsx:2686 — [probe] handleNewWorktreeSession's failure branch has no test witnesspackages/web-shell/client/components/sidebar/WorkspaceMenu.tsx:116 — [probe] empty-actions transition dismantles an open menu without the close signalpackages/web-shell/client/components/sidebar/workspaceOverviewModel.ts:218 — [review] WorkspaceOverviewSnapshot.fetchedAt has zero production read sitespackages/web-shell/client/App.test.tsx:11135 — [probe] transient-failure intent test never asserts the failing refetch round ranpackages/web-shell/client/components/sidebar/WorkspaceOverview.tsx:123 — [probe] overview chip accessible name sits in aria-label on a role-less span (generic naming prohibited)packages/web-shell/client/components/sidebar/WorkspaceSection.tsx:511 — [probe] retainedOverview is add-only — defeats the hook's clear-on-disablepackages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx:249 — [probe] stale-cwd discard test cannot detect removal of the cwd guardpackages/web-shell/client/App.test.tsx:11234 — [probe] in-flight-clear prompt test resolves the clear before submitting — sync ref write unwitnessedpackages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx:3392 — [probe] worktree double-submission guard's release arm is unwitnessedpackages/web-shell/client/e2e/utils/mockDaemon.ts:131 — [review] WorkspaceOverviewOverrides lacks a channels key — per-workspace channels isolation untestablepackages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx:235 — [probe] chipUnknown class has zero test witnessespackages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx:352 — [probe] reset-on-success branch has no witness for the never-reset mutant
中文说明
未探索到全部深度(达到工具调用预算):chunk 15:none (I skipped executing the new tests because the worktree has no installed dependencies; that is a disclosure, not a ceiling-stopped check — the static trace…;chunk 2:executed the new tests in packages/web-shell/client/App.test.tsx to confirm they pass (no node_modules in the worktree or parent checkout; a full npm ci + build…。
收敛姿态下延后(第 9 轮,非阻断)——已记录,本轮不要求修改:共 13 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.3)
The miss bookkeeping in reload() runs for every landed round on purpose — during a daemon hang every round is superseded before it lands, and only their observed misses can expire a facet. But it also ran for rounds launched before a reset boundary: a round held in flight across a cwd round trip (A→B→A passes the cwd guard) or across a collapse/expand cycle landed after missesRef was reset and booked into the fresh session, shortening the three-round grace, or — landing successfully — refilled the budget so a frozen count outlived it. Budgets are now scoped by an epoch that advances at the two reset boundaries (cwd change, disable); a round captures the epoch at launch and books nothing if a reset happened while it was in flight. Rounds inside one unbroken session still all count, and the hang-expiry test pins that a per-round epoch bump would break.
|
Round 10 (1 Critical) addressed in 1717b76. The overview miss budget is now scoped to one bookkeeping session: an epoch advances at the two reset boundaries (cwd change, section disable), and a round launched before a reset can neither book misses into the fresh session nor refill it with a stale success. Superseded rounds inside one unbroken session still count — the hang-expiry behaviour is unchanged and its test now also pins that a per-round epoch would break it. Verification: three new hook regressions (A→B→A stale failure, stale success past the grace, collapse/expand twin), each red under its named mutant and one mutant per reset site; hook suite 23 green; sidebar suites 319 green; full web-shell unit suite 5355 green; overview e2e 3 green; tsc and ESLint clean. All 94 review threads resolved. 中文说明第 10 轮(1 条 Critical)已在 1717b76 处理。 概览 miss 预算现在以单个记账会话为作用域:纪元在两个重置边界(cwd 切换、区块禁用)自增,重置前启动的轮次既不能把 miss 记进新会话,也不能用陈旧成功重填预算。同一不间断会话内被取代的轮次仍然计数——卡死过期行为不变,其测试现在同时钉住「逐轮自增会破坏它」。 验证:新增三个 hook 回归(A→B→A 陈旧失败、宽限期后的陈旧成功、折叠/展开孪生),各自在对应变异体下变红,两个重置位各有一个变异体覆盖;hook 套件 23 绿;sidebar 套件 319 绿;web-shell 全量单测 5355 绿;overview e2e 3 绿;tsc 与 ESLint 干净。94 条评审线程全部已 resolve。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers.
1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- retainedOverview is add-only — defeats the hook's clear-on-disable (WorkspaceSection.tsx:760-763) — already reported (round 9 deferred list, review 5061621856)
Deferred under the convergence posture (round 10, not a blocker) — recorded, not requested in this round:
docs/design/web-shell/web-shell-workspace-overview.md:99 — [probe] doc fetch-gate sentence contradicts the code gate — a custom header with wired headerActions does fetchpackages/web-shell/client/components/sidebar/useWorkspaceOverview.ts:163 — [probe] per-facet miss-budget independence unpinned — coupled-counter mutant ships 23/23 green
中文说明
已审查——无阻断问题。
本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
收敛姿态下延后(第 10 轮,非阻断)——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.3)
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 63 passed · 0 failed · 63 total Flakiness gate: ✅ 9 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:63 通过 · 0 失败 · 63 总计 抖动门:✅ 9 changed test file(s) x 5 identical rounds, no divergence Verification reportPR 10407 deep verification (follow-up round) — feat(web-shell): show workspace overview and a workspace menu in the sidebarVerdict: One commit landed since the previous round ( 中文 — 判定:findings · 63 通过 / 0 失败 / 63 总计结论:
Previous-finding status (round 1 verified head
|
| # | Finding (round 1) | Severity | Status at the new head |
|---|---|---|---|
| F1 | The new 30 s-poll e2e test is speed-correlated: fails on machines where the page settles slower than ~1 s (measured failing 2/2 on the loaded runner; page.clock.install() without pauseAt lets fake time run with real time) |
Suggestion | Stands. The spec file is byte-identical (the delta touches only useWorkspaceOverview.ts + its tests). Re-measured at the new head: the spec passed in both of this round's runs (clean window), and the margin probe now measures the deciding quantity model-independently — in-page fake-time from interval registration to baseline: 128 / 145 / 128 ms over 3 samples against the 1000 ms threshold. Round 1 measured ~4 s under contention on this same runner class (failing 2/2). The distribution crosses the threshold with load, which is the speed-correlated class; a green run here proves only this window was fast enough. The suggested fix (pauseAt while the page settles) was re-validated at the new head: exact intended cadence, no early tick (see 04-f1-margin-and-fix.png). |
| F2 | The in-flight stale-round cwd guard (if (cwd !== cwdRef.current) return;, mutation M5) survived deletion: its window was pinned by no test — classified coverage gap, guard itself correct |
Suggestion | Fixed. The delta adds an epoch mechanism that closes the same window: a round captures epochRef at launch and returns if a reset (cwd change or disable) advanced it while in flight. Mutation D1 (epoch check disabled) is killed by exactly the three new tests; D2/D3 (either bump removed) are killed by the matching round-trip / disable-cycle tests; the combination row (cwd guard + epoch check both off) is killed — while M5 alone still survives, i.e. the cwd guard is now redundant defence, not a coverage gap: every window the tests can reach is guarded twice and one of the two is pinned. The only residual the cwd guard uniquely covers is the render-to-effect-flush sliver, which jsdom's synchronous act() flushing cannot produce. No action needed; the guard is harmless and clarifies intent. |
Central claim and A/B (re-measured at the new head)
Central claim: expanded, trusted workspaces fetch one request per overview facet from the workspace-qualified daemon routes, render chips, and stop fetching when collapsed/untrusted; uninitialized runtimes never render as 0.
A byte-identical harness (verify-ab.test.tsx in this artifact dir) renders WorkspaceSection directly in jsdom in both trees with a per-cwd recording client; expectations branch on AB_ARM. Scenario: trusted primary /tmp/project, trusted secondary /tmp/other, untrusted /tmp/danger, all expanded; phases P1 mounted → P2 collapse other → P3 re-expand other.
| Cell | Environment | Oracle | Result |
|---|---|---|---|
| head P1 | PR build 1717b763, 3 rows expanded |
facet spy counts + [data-web-shell-workspace-overview] chips |
trusted rows: 1 call per facet (mcp/skills/extensions/channels/memory; hooks 0 = opt-in); untrusted row never reaches the client; 5 chips per trusted row with texts MCP2/3 Skills2 Extensions1/2 Channels1/1 Context2 (01-ab-head-gating.png) |
| head P2 | other collapsed |
counts unchanged; chips 10→5 | no fetch on collapse; chips dropped |
| head P3 | other re-expanded |
counts +1 per facet on other; chips back to 10 |
gating flips back on |
| base P1–P3 | base worktree 40889bad, identical harness |
same oracles | 0 facet calls, 0 chips in every phase (02-ab-base-silent.png) |
Scripted expectations: 26/26 head + 8/8 base = 34/34 passed. Base-control purity: tmp/base-tree has no local node_modules (resolution walks up to the shared root); the PR touches nothing outside packages/web-shell (+2 docs), so the root-installed @qwen-code/sdk (realpath packages/sdk-typescript, dist built at HEAD) is byte-equivalent for both arms; the daemon-react-sdk module the harness mocks via importOriginal is also untouched by the PR (verified in the aggregate diff). Note the base tip moved since round 1 (168a88c0 → 40889bad, the PR merged main again after round 1 verified it); the metadata snapshot's baseRefOid (168a88c0…) has drifted from the live merge ref — the local HEAD^1 was used throughout.
Delta verification — 1717b763 "scope overview miss budgets to one bookkeeping session"
Delta since the round-1 verified head (git diff 2a83a40d..1717b763, the object is present locally): 3 files — useWorkspaceOverview.ts +12, useWorkspaceOverview.test.tsx +113, design doc ±5. The mechanism: epochRef advances at both reset boundaries (cwd-change effect, disable branch) and reload() drops any round whose captured epoch is stale — so a round held in flight across a cwd round trip (A→B→A passes the cwd guard) or a collapse/expand cycle can neither book misses into the fresh session nor zero them with a stale success.
Mutation matrix, scratch worktree at head, suites = workspaceOverviewModel.test + useWorkspaceOverview.test + WorkspaceOverview.test (56 tests, baseline green 56/56; 03-mutation-matrix.png, per-row logs in logs/mut-*.log):
| Row | Mutation | Result | Failed tests (attribution) |
|---|---|---|---|
| C0 | unmutated baseline | GREEN 56/56 | — |
| C1 | control, model file: summarizeSkills total +1 |
KILLED 10 | harness live |
| C2 | control, hook file: miss increment +2 | KILLED 5 | expiry + hang + all 3 new tests (control lands in the mutated file) |
| M1 | uninitialized MCP counts as known | KILLED 3 | placeholder-never-zero |
| M2 | MCP warning tone off (both branches) | KILLED 4 | warning tone |
| M3 | merge carry-over removed | KILLED 7 | transient-failure retention |
| M4 | miss expiry >= → > |
KILLED 4 | expiry |
| M5 | in-flight cwd guard removed (round-1 survivor) | SURVIVED 56/56 | see combination rows |
| M6 | enabled gating removed | KILLED 6 | gating |
| M5B | cwd reset effect emptied | KILLED 3 | cwd carry-over |
| M5C | both cwd guards removed | KILLED 3 | same |
| D1 | DELTA: epoch check disabled | KILLED 3 | all three new tests |
| D2 | DELTA: no epoch bump on cwd change | KILLED 2 | the two round-trip tests |
| D3 | DELTA: no epoch bump on disable | KILLED 1 | the disable/enable-cycle test |
| D23 | DELTA: both bumps removed (check live but dead) | KILLED 3 | the bumps are the load-bearing part |
| D4 | DELTA: per-round epoch bump | KILLED 1 | expires facets during a hang even though every round is superseded — exactly the pin the commit message claims |
| D-PRE | DELTA: full revert (D1+D2+D3) | KILLED 3 | vacuity check: the new tests fail against the un-fixed code, on their intended assertions (miss-budget booking) |
| M5D1 | COMBINATION: cwd guard + epoch check both off | KILLED 3 | same 3 tests — epoch check alone accounts for every observable kill |
Read: every hunk of the delta is load-bearing and individually pinned; the new tests are non-vacuous (D-PRE) with correct kill attribution (D1/D2/D3 map 1:1 onto the scenarios they were written for); the design invariant "superseded rounds must still count during a daemon hang" is pinned (D4). F2's former gap is closed by construction: the in-flight window is now guarded by the epoch check, which three tests pin.
Findings
F1 (Suggestion, carried) — the 30 s-poll e2e test remains speed-correlated; margin now measured directly
polls an expanded workspace once per 30 s tick and not faster (client/e2e/web-shell.workspace-overview.spec.ts:303) is unchanged by the delta. At the new head:
-
Spec run pre-release: fix ci #1 (this round): 3/3 passed (
06-e2e-overview-pass.png), and the capture re-run agreed. -
Margin probe (3 samples, model-independent:
Date.now()read inside the page under the fake clock, so no clock-flow model is assumed):sample settle (registration → baseline) spec-would 1 145 ms PASS 2 128 ms PASS 3 128 ms PASS The test fails iff settle ≥ 1000 ms (first tick at registration+30 s lands inside the spec's
runFor(29_000)window). Round 1 measured settle ≈ 4 s on this runner class under contention and the spec failed 2/2 (Expected length: 11 / Received length: 16). The margin therefore crosses its threshold with runner load — the speed-correlated class;retriesdoes not absorb a load-correlated failure. An earlier probe iteration that reported settle ≈ 3 s was a measurement artifact (it assumed each clock step advanced exactly 1 s while the flowing clock also advanced during inter-step waits) — superseded by the fake-timestamp probe above. -
The product behavior is correct: the interval fires exactly 30 fake-seconds after registration, and the validated fix reproduces the spec's intent deterministically at the new head:
PROBE-FIX settled=11 at29=11 at31=16 at61=21.
Suggested fix (re-validated at the new head)
Freeze the fake clock while the page settles, so baseline and interval registration share one fake instant:
await page.clock.install();
await page.clock.pauseAt(Date.now());
await gotoSession(page, scenario, daemon);Re-measured at 1717b763: no tick at 29 s, exactly +5 at 31 s, +10 at 61 s — the spec's own assertions unchanged, now deterministic regardless of settle time.
No new findings this round. In particular: no regressions surfaced by the full suite (green), no chip/gating behavior change beyond the delta's intent, and the epoch mechanism introduces no observable cost (same request counts as round 1's A/B).
Targeted gates
- Typecheck (
tsc -p tsconfig.json --noEmitinpackages/web-shell): clean, exit 0. Liveness proven: a plantedconst __gate_probe: number = "not a number"producederror TS2322at the exact location; file restored clean afterwards. - Sidebar directory suite (the PR's own review command): 18 files, 319 tests, 0 failed at head (
05-sidebar-gate-head.png) — 316 from round 1 plus the 3 delta tests. - Full web-shell unit suite at head: 239 files, 5355 tests, 0 failed (exit 0, isolated, no contention). Round 1's environmental failures (246 failed at head under a contended container, adjudicated via a base A/A) did not reproduce in this quieter container — the full-suite green the PR description claims is reproducible at the new head; no A/A was needed this round because there was nothing to attribute.
- e2e overview spec (wire oracle, chromium headless shell 149 against the repo's mock daemon): 3/3 passed this run; the poll test's caveat is F1 above.
Not covered
- Per-commit attribution: depth-2 checkout —
git rev-list HEAD^1..HEAD^2returns 1 at the shallow boundary while the metadata lists 15 commits. The aggregateHEAD^1..HEADdiff is what was verified. (The round-1→round-2 delta, in contrast, was verified exactly:2a83a40dexists locally.) - Real daemon: all wire evidence is against the Playwright mock daemon (route interception) — the wire shape and the UI's handling of it, not that a real
qwen serveanswers these routes identically. The delta's motivating premise (SDK deadline == poll cadence during a daemon hang) is exercised only through the hook's fake timers, not a hanging daemon. - The PR's full e2e suite (66 specs) — only
web-shell.workspace-overview.spec.tsran. - Rename flow is unit-tested only; Windows/Linux rendering; layer B1/B2/C follow-ups (out of scope by design).
- A repo-wide gate was not run; the diff touches only
packages/web-shell(+docs), and only that workspace was gated. - The F1 margin probe measured 3 samples in one load window; the load-side of the distribution (>1 s settle) is carried from round 1's measurements rather than re-driven this round (reproducing a loaded-runner state on demand is not controllable from inside the container).
Methodology
Environment: CI verify container (node:22-bookworm, user node, node v22.23.2), working tree at the merge ref 060466ba (HEAD^1 = base tip 40889bad, HEAD^2 = verified head 1717b763), npm ci + npm run build pre-run; Playwright 1.61.1 with chromium headless shell 149 installed this round at $PLAYWRIGHT_BROWSERS_PATH (/__w/_temp/pw-browsers). A/B: git worktree add tmp/base-tree HEAD^1; a byte-identical jsdom harness (verify-ab.test.tsx) copied into both trees rendered WorkspaceSection through one I18nProvider with a per-cwd recording client whose facet methods resolve daemon-shaped fixtures; measurements emitted as ABRESULT JSON and judged by arm-specific expectations inside the test. Mutations: exact-string patches applied to a scratch worktree (tmp/mut-tree) by run-mutation.mjs, each restored via git checkout with the tree verified clean; suites run via npx --no-install vitest run (summary lines re-parsed from logs/mut-*.log with ANSI stripped). e2e: the PR's own spec plus probe files (deleted after running) that measured, in the page's fake time, the interval-registration-to-baseline distance and the pauseAt-fixed cadence; raw logs in logs/. Gates: tsc --noEmit (with a planted-violation liveness check), vitest sidebar directory and full suite in packages/web-shell. Both scratch worktrees were removed after the A/B cells and matrix were captured; the working tree was restored to its arrival state (the pre-existing unstaged deletion of .qwen/e2e-tests/2026-08-28-web-shell-workspace-overview.md was left untouched). Evidence images were produced with scripts/verify-capture.mjs.
Flakiness gate log
e2e suite, out of gate scope: packages/web-shell/client/e2e/web-shell.workspace-overview.spec.ts
rounds=5 files=9 skipped=1
file packages/web-shell/client/App.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/App.test.tsx
file packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx
file packages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/WorkspaceMenu.test.tsx
file packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/WorkspaceOverview.test.tsx
file packages/web-shell/client/components/sidebar/WorkspaceRenameDialog.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/WorkspaceRenameDialog.test.tsx
file packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/WorkspaceSection.test.tsx
file packages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/useWorkspaceOverview.test.tsx
file packages/web-shell/client/components/sidebar/workspaceOverviewModel.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/workspaceOverviewModel.test.ts
file packages/web-shell/client/utils/gitModeIntent.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/utils/gitModeIntent.test.ts
per-file results (P=pass F=fail I=infra-exit, one letter per run):
packages/web-shell/client/App.test.tsx: PPPPP
packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: PPPPP
packages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx: PPPPP
packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx: PPPPP
packages/web-shell/client/components/sidebar/WorkspaceRenameDialog.test.tsx: PPPPP
packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx: PPPPP
packages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx: PPPPP
packages/web-shell/client/components/sidebar/workspaceOverviewModel.test.ts: PPPPP
packages/web-shell/client/utils/gitModeIntent.test.ts: PPPPP
verdict: pass
summary: 9 changed test file(s) x 5 identical rounds, no divergence
--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/WorkspaceRenameDialog.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/workspaceOverviewModel.test.ts: P (exit 0)
round 1 · packages/web-shell/client/utils/gitModeIntent.test.ts: P (exit 0)
round 2 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WorkspaceRenameDialog.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/workspaceOverviewModel.test.ts: P (exit 0)
round 2 · packages/web-shell/client/utils/gitModeIntent.test.ts: P (exit 0)
round 3 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WorkspaceRenameDialog.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/workspaceOverviewModel.test.ts: P (exit 0)
round 3 · packages/web-shell/client/utils/gitModeIntent.test.ts: P (exit 0)
round 4 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WorkspaceRenameDialog.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/workspaceOverviewModel.test.ts: P (exit 0)
round 4 · packages/web-shell/client/utils/gitModeIntent.test.ts: P (exit 0)
round 5 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WorkspaceMenu.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WorkspaceOverview.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WorkspaceRenameDialog.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/useWorkspaceOverview.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/workspaceOverviewModel.test.ts: P (exit 0)
round 5 · packages/web-shell/client/utils/gitModeIntent.test.ts: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI green on the reviewed commit and ten review rounds have converged. ✅
ytahdn
left a comment
There was a problem hiding this comment.
Independent static review @ head 1717b76 / 独立静态审查 @ head 1717b76
Static review only — no tests executed. Four module batches (data layer: polling hook + pure model + git-intent rules / presentation components / integration wiring / e2e + mock daemon + docs) walked all 27 files against the head tree. This PR has been through ten review rounds; this pass re-verified the final state independently rather than trusting that ledger.
纯静态审查(未运行测试)。4 个模块批次(数据层:轮询 hook + 纯模型 + git 意图规则 / 展示组件 / 集成接线 / e2e+mock daemon+文档)走查全部 27 个文件并对照 head tree。此 PR 已经历十轮评审,本轮为独立复核而非采信既有结论。
What I verified / 核验内容
All six load-bearing claims hold against the code, not the description:
六项关键声明均经代码证实:
- Placeholders never masquerade as zero / 占位绝不伪装成零 —
formatOverviewValueconsultsisOverviewFacetKnownfirst (runtime facets gate oninitialized === true; context is daemon-side and always known); unknown renders—with a tooltip distinguishing "not initialized yet" from "daemon does not support this route" (WorkspaceOverview.tsx:100-133). - Triple-gated fetching / 三重门控拉取 — expanded ∧ trusted (WorkspaceSection) ∧
document.visibilityState === 'visible'inside the interval, plus focus refetch; on collapse/untrust the in-flight round is invalidated viarequestIdRef/epochRefbumps so it cannot land after the clear (useWorkspaceOverview.ts:188-207). - Independent facet failure with carry-over / facet 独立失败且保留已知值 — the
facet()wrapper isolates rejections and malformed 200 bodies per facet;mergeOverviewSnapshotskeeps last-known values for unanswered facets. - Epoch-scoped miss budgets / 纪元隔离的未答预算 — verified the double guard (cwd comparison + epoch comparison) after the await: a pre-reset round can neither book misses into the fresh session nor zero them with a stale success; superseded rounds may only drop facets already at 3+ misses, never merge stale data (useWorkspaceOverview.ts:146-180). Booking superseded rounds is deliberate and correct given the SDK 30 s deadline equals the poll cadence (daemon-hang expiry depends on it).
- Git-intent reset only on definitive answers / git 意图只在确定性答案时重置 —
gitModeIntentMustResetfires on session-exists / untrusted / answered-without-branch for the current workspace; the production producer only ever passesundefinedon transient poll failures, so an armed worktree intent survives transient gaps; the intent is written in the synchronous step of session creation, not armed after an await (App.tsx wiring). - Menu gating matches the deferred-binding design / 菜单门控与延后绑定设计一致 — Rename requires
dynamic_workspace_registration+ absolute path + non-primary; the Manage group requires primary + trusted + a wired callback; untrusted non-removable rows render nothing (not even the container); locked sidebars keep their existing action area. The rename dialog mirrors the daemon store's trim → 256-char → control-char → empty-means-clear rules exactly.
Deletion audit: every invariant of the old single-item ⋮ menu (untrusted+non-removable → null, removal gating, destructive aria-label carrying the cwd, submit-in-flight trigger disable, focus suppression on menu close) is rebuilt in the new WorkspaceMenu wiring. WorkspaceSection.headerActions gaining a second argument is purely additive — the single production call site is updated and a single-parameter callback stays assignable. Cross-contracts checked against the head tree: the mock daemon's new /workspaces/:w/{mcp,skills,extensions,memory,hooks} shapes match the real daemon routes and SDK types (including initialized: false placeholder semantics and the extensions route returning a projection, not a manager status); all 36 new i18n keys exist in EN and ZH and are referenced; every CSS variable the chips consume is defined in both themes.
删除审计:旧单项 ⋮ 菜单的全部不变量(未信任且不可移除 → null、移除门控、带 cwd 的 destructive aria-label、提交中禁用触发器、菜单关闭时的焦点抑制)均在新 WorkspaceMenu 接线中重建;headerActions 第二参数纯增量,唯一生产调用点已更新且单参回调仍可赋值。跨契约对照 head tree 核实:mock daemon 新增路由形状与真实路由及 SDK 类型一致(含 initialized: false 占位语义、extensions 返回投影而非管理器状态);36 个新 i18n 键中英文齐备且全部被引用;chip 使用的 CSS 变量在两套主题均有定义。
Findings — no Critical, no Important / 发现——无 Critical、无 Important
-
🟢 Nit —
WorkspaceOverview.tsx:117-126: the chip'stitle/aria-labelsit on a plain<span>(generic elements do not support accessible naming), whilerole="listitem"is on the wrapper div. In full mode the visible label compensates; in compact mode a screen reader announces bare numbers like "3/4" with no context. Movetitle/aria-labelonto therole="listitem"div. chip 的title/aria-label挂在无 role 的span上(generic 元素不支持可访问命名),role="listitem"却在外层 div;compact 模式下读屏只会听到无上下文的 "3/4"。建议把两属性移到role="listitem"的 div 上。 -
🟢 Nit —
WebShellSidebar.tsx:2640-2712:handleNewWorktreeSessionis a line-for-line copy ofhandleNewSession(re-entrancy guard, busy state,bumpWorkspaceReload, catalog refresh, abort-error routing); only the creation callback differs, as the comment itself notes. Both sharecreatingSessionRef, so a future fix to one path silently diverges from the other. Extract a shared runner taking the creation callback as a parameter.handleNewWorktreeSession与handleNewSession逐行同构(防重入、忙碌态、刷新逻辑全同),仅创建回调不同且共享creatingSessionRef,未来单侧修复会产生静默漂移;建议抽出接受创建回调的共享执行器。
Carried on the record from prior rounds, not re-reported: the 30 s-poll e2e's page.clock.install() without pauseAt (speed-correlated on slow runners, test-only), retainedOverview being add-only by design, and the doc sentence slightly overstating the fetch gate. I also re-probed the e2e's hard request-count assertions and found them safe as written: the assertion windows are ~1.5 s, far too short for the 30 s interval to intrude.
历轮留档项不重复上报:30 秒轮询用例缺 pauseAt 的速度相关问题(仅测试)、retainedOverview 有意只增不清、设计文档一句门控表述出入。另复核了 e2e 的硬请求数断言:断言窗口约 1.5 秒,30 秒轮询不可能闯入,无需改动。
CI
Green at this head across the board, including Test (ubuntu-latest, Node 22.x) (24 m, previously timing out) and the web-shell E2E smoke that runs this PR's spec against the mock daemon.
head 上 CI 全绿,包括此前连续超时的 Test (ubuntu)(24 分钟)与运行本 PR 用例的 web-shell E2E 冒烟。
Verdict / 结论
✅ Approve — an independent four-batch pass over the final head found no Critical and no Important issues; the two nits above are worth folding in opportunistically but do not block. The hard parts of this feature — request gating, placeholder semantics, epoch-scoped expiry, facet isolation, and the git-intent lifecycle — are exactly the parts that usually regress silently, and each one checks out against the code with tests pinning it.
✅ 批准——四个批次对最终 head 的独立走查未发现 Critical 与 Important;上述两条 Nit 值得顺手收掉但不阻塞。本特性最容易静默回归的部分(请求门控、占位语义、纪元过期、facet 隔离、git 意图生命周期)逐一在代码层面核验成立且有测试钉住。

































What this PR does
Workspace rows in the Web Shell sidebar now tell you what a workspace contains and let you manage it from the row itself. The folder header shows session counts (waiting on you / running / total) and carries the full path as a tooltip; while a trusted workspace is expanded, the path is printed under the header followed by a chip row summarizing MCP servers (connected/enabled), skills, extensions, channels and context files. The MCP chip turns warning-colored when a server errored or discovery finished with an enabled server still not connected, and every runtime-discovered facet shows
—with a "not initialized yet" tooltip until the workspace's ACP child has reported in — a placeholder is never rendered as0. The Projects label shows how many workspaces are registered once there is more than one.The hover
⋮menu on a workspace row, which previously offered only Remove workspace on removable secondary workspaces, becomes a full workspace menu: Rename… (on daemons that advertise dynamic workspace registration; an empty name falls back to the folder name), Copy path, New task, New worktree task, a Manage group (MCP servers, Skills, Extensions, Channels, Settings — with the live counts next to the first four), Reload runtime and Remove workspace. Each entry appears only when the workspace's trust, lock and registration state allows it; untrusted rows that cannot be removed still show nothing, and locked (embedded) sidebars keep their current action area.Facets are fetched through the existing workspace-qualified daemon routes, one request per facet, only while a workspace is expanded and trusted, polling every 30 s while the tab is visible plus a refetch on focus. Each facet fails independently, so an older daemon without one of the routes leaves that chip unknown and the others intact, and a facet keeps its last known value across a transient failure. Embedders can pass
sidebar.workspaceOverview: falseto keep plain folder headers, or{ items: [...] }to pick the chips; two new sidebar callbacks (onOpenWorkspaceManagement,onNewWorktreeSession) let hosts wire the management entries and the worktree action.This is layer A of #10399. The Manage group is offered on the daemon's primary workspace only, because the management pages still read the connection's bound workspace; binding them to an arbitrary workspace (layer B1) and a single daemon-side overview endpoint (layer C) are tracked in the issue.
Why it's needed
On
maina workspace is a folder heading and nothing more: the sidebar cannot tell you how many workspaces are registered, whether anything is running in one, or whether its MCP servers are connected, and the only management action is removal. The daemon already exposes all of this per workspace and the SDK already wraps it; the sidebar simply never consumed it. See #10399 for the full diagnosis.Reviewer Test Plan
How to verify
npm run devinpackages/web-shellagainst aqwen servedaemon with at least two--workspaceregistrations, or the served bundle) with one MCP server that cannot start.MCP a/bin the warning tone with a tooltip naming the failed server count. Before any session has started in that workspace the MCP / Skills / Context chips show—, not0.⋮: on a secondary workspace expect Copy path, New task, New worktree task, Reload runtime, Remove workspace (Rename… appears when the daemon advertisesdynamic_workspace_registration); on the primary workspace additionally the Manage group with counts. Rename updates the row label without a reload; Copy path puts the absolute path on the clipboard; Reload runtime issuesPOST /workspaces/<cwd>/reload; New worktree task opens a draft with the composer's git mode set to worktree; the Manage entries open the corresponding pages./workspaces/: no facet requests while collapsed; on expand exactly one request per facet, then nothing until the 30 s tick or a window focus.cd packages/web-shell && npx vitest run client/components/sidebar(new suites for the model, hook, chips, menu and rename dialog plus the extended workspace-removal suite) andPLAYWRIGHT_PORT=5199 npx playwright test packages/web-shell/client/e2e/web-shell.workspace-overview.spec.ts(frompackages/web-shell, drop thepackages/web-shell/prefix) (chips, counts, request gating and menu contents against the mock daemon). The full web-shell unit suite (211 files) and e2e suite (66 specs) pass locally; typecheck, ESLint and Prettier are clean.Evidence (Before & After)
Screenshots come from the repository's mock-daemon visual harness: two trusted workspaces, an MCP status with 2 connected / 1 failed / 1 disabled server and 12 skills.
Tested on
Environment (optional)
npm run dev(Vite) with the Playwright mock daemon;npm run buildfor the package succeeds.Risk & Scope
npm run dev, React StrictMode, 5 trusted workspaces all expanded, tab visible): 25 facet requests per 30 s tick — 50 over 60 s — next to the 33 session-catalog and git-status requests the same rows already made in that window (the initial round is doubled to 50 by StrictMode in dev; 25 in production). Layer C collapses that to 5 per tick. The MCP / Skills / Extensions / Channels management entries on the primary workspace open the existing pages, which are bound to the connection's workspace — that is why secondary rows do not get the group yet.GET /workspaces/:w/overview(C) are follow-ups in web-shell: sidebar shows nothing about a workspace beyond its name — add inline overview, a full workspace menu, and a per-workspace overview endpoint #10399. The Trust… action from the proposal was left out and is recorded as a follow-up in the design doc: the trust-change route only records a request that needs operator action and a daemon restart. Not tested on Windows/Linux locally; the change is frontend-only.WorkspaceSection.headerActionsgains a second argument (the overview snapshot); existing single-argument callers keep working. New optional sidebar props and types are additive; the default sidebar shows the overview, embedders can opt out withworkspaceOverview: false.Linked Issues
Part of #10399 (layer A).
中文说明
本 PR 的改动
Web Shell 侧栏的 workspace 行现在能直接告诉你这个工作区里有什么,并且能在行内管理它。文件夹标题显示会话计数(等待处理 / 运行中 / 总数),名称带完整路径 tooltip;已信任的工作区展开后,标题下方打印完整路径,随后是一行 chip:MCP 服务器(已连接/已启用)、技能、扩展、频道、上下文文件。当有服务器出错、或发现完成后仍有已启用的服务器未连接时,MCP chip 变为警告色;所有由运行时发现的 facet 在 ACP 子进程上报之前都显示
—并提示「尚未初始化」——占位数据绝不会渲染成0。注册了多个工作区时,Projects 标题显示数量。workspace 行的悬停
⋮菜单原来只在可移除的次级工作区上提供「移除工作区」一项,现在变成完整的工作区菜单:重命名…(daemon 声明动态注册能力时;留空则回退到文件夹名)、复制路径、新建任务、新建 Worktree 任务、「管理」分组(MCP 服务器、技能、扩展、频道、设置,前四项旁边显示实时计数)、重新加载运行时、移除工作区。每一项只在工作区的信任、锁定、注册状态允许时出现;不可移除的未信任行仍然什么都不显示,锁定(嵌入)模式的侧栏保持现有的操作区。facet 数据通过现有的按工作区限定的 daemon 路由获取,每个 facet 一个请求,只在工作区处于展开且已信任时发起,标签页可见时每 30 秒轮询一次并在窗口获得焦点时刷新。各 facet 独立失败:旧 daemon 缺某条路由时只有对应 chip 显示未知,其余不受影响;瞬时失败时保留上一次的已知值。嵌入方可传
sidebar.workspaceOverview: false保留原来的纯文件夹标题,或用{ items: [...] }选择要显示的 chip;新增两个侧栏回调(onOpenWorkspaceManagement、onNewWorktreeSession)供宿主接入管理入口和 worktree 动作。本 PR 是 #10399 的 A 层。「管理」分组只在 daemon 的主工作区上提供,因为管理页面目前仍读取连接绑定的工作区;把页面绑定到任意工作区(B1 层)以及 daemon 端的单一概览端点(C 层)在 issue 中跟踪。
为什么需要
main上的工作区只是一个文件夹标题:侧栏无法告诉你注册了几个工作区、某个工作区里是否有任务在跑、它的 MCP 服务器是否连上,唯一的管理动作是移除。daemon 早已按工作区暴露了这些信息,SDK 也已封装,只是侧栏从未消费。完整诊断见 #10399。审查测试计划
如何验证
packages/web-shell里npm run dev,对接一个至少注册两个--workspace的qwen serve,或直接用打包产物),配置一个无法启动的 MCP 服务器。MCP a/b呈警告色,tooltip 标明失败数量。该工作区还没启动过任何会话时,MCP / 技能 / 上下文 chip 显示—而不是0。⋮:次级工作区应看到 复制路径、新建任务、新建 Worktree 任务、重新加载运行时、移除工作区(daemon 声明dynamic_workspace_registration时会有 重命名…);主工作区额外有带计数的「管理」分组。重命名后行标签无需刷新即更新;复制路径把绝对路径放入剪贴板;重新加载运行时发出POST /workspaces/<cwd>/reload;新建 Worktree 任务打开一个 git 模式已设为 worktree 的草稿;管理项打开对应页面。/workspaces/):折叠时没有 facet 请求;展开时每个 facet 恰好一个请求,之后直到 30 秒周期或窗口获得焦点前没有新请求。cd packages/web-shell && npx vitest run client/components/sidebar(模型、hook、chip、菜单、重命名对话框的新套件,以及扩展后的 workspace-removal 套件)和PLAYWRIGHT_PORT=5199 npx playwright test packages/web-shell/client/e2e/web-shell.workspace-overview.spec.ts(frompackages/web-shell, drop thepackages/web-shell/prefix)(对 mock daemon 验证 chip、计数、请求门控和菜单内容)。web-shell 全量单测(211 个文件)和 e2e(66 个用例)本地通过;typecheck、ESLint、Prettier 干净。证据(改动前后)
截图来自仓库自带的 mock daemon 视觉工具:两个已信任工作区,MCP 状态为 2 个已连接 / 1 个失败 / 1 个已禁用,12 个技能。
测试平台
环境(可选)
npm run dev(Vite)+ Playwright mock daemon;npm run build打包成功。风险与范围
npm run dev,React StrictMode,5 个已信任工作区全部展开,标签页可见):每 30 秒一轮 25 个 facet 请求——60 秒内 50 个——同一窗口内这些行既有的会话目录与 git 状态请求为 33 个(dev 下首轮被 StrictMode 翻倍为 50,生产为 25)。C 层会把它收敛到每轮 5 个。主工作区上的 MCP / 技能 / 扩展 / 频道管理项打开的是现有页面,这些页面绑定在连接的工作区上——这也是次级行暂不提供该分组的原因。GET /workspaces/:w/overview(C 层)作为 web-shell: sidebar shows nothing about a workspace beyond its name — add inline overview, a full workspace menu, and a per-workspace overview endpoint #10399 的后续。方案里的「信任…」动作没有做,已在设计文档的后续项中记录:trust 变更路由只是记录一个需要操作员处理并重启 daemon 的请求。本地未在 Windows/Linux 测试;改动仅限前端。WorkspaceSection.headerActions新增第二个参数(概览快照),现有单参数调用方不受影响。新增的侧栏 props 与类型都是增量的;默认侧栏显示概览,嵌入方可通过workspaceOverview: false关闭。关联 Issue
属于 #10399(A 层)。