Skip to content

refactor(web-shell): share sidebar test harness - #9797

Merged
wenshao merged 5 commits into
QwenLM:mainfrom
bluefateludi:codex/refactor-9701-web-shell-sidebar-harness
Sep 12, 2026
Merged

refactor(web-shell): share sidebar test harness#9797
wenshao merged 5 commits into
QwenLM:mainfrom
bluefateludi:codex/refactor-9701-web-shell-sidebar-harness

Conversation

@bluefateludi

Copy link
Copy Markdown
Contributor

What this PR does

Consolidates the stable test setup shared by three Web Shell sidebar suites into one collocated harness. Session-page resolution, DOM shims, session fixtures, async flushing, and click dispatch now have one implementation, while suite-specific catalog controllers and rendering options remain local.

Why it's needed

The copied setup had already diverged on the unloaded-page contract: an explicitly unloaded catalog page could expose stale fixture sessions in one suite while remaining empty in another. Sharing that contract prevents future catalog changes from being applied inconsistently and removes repeated test plumbing without changing production behavior.

Reviewer Test Plan

How to verify

Run the three Web Shell sidebar suites together and confirm all 128 tests pass. Confirm the Web Shell and repository type checks pass, the repository build succeeds, and no production file or test expectation changed.

Evidence (Before & After)

N/A — internal test refactor with no user-facing change.

Tested on

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

Environment (optional)

Node.js 22 on Windows. Targeted Vitest suites: 128 passed. Repository build and typecheck passed. Changed TypeScript files passed ESLint and all changed files passed Prettier.

Risk & Scope

  • Main risk or tradeoff: an overly broad harness could hide suite-specific behavior, so only stable shared behavior moved and specialized catalog controllers remain local.
  • Not validated / out of scope: the full unit-test suite and browser E2E; this change has no production behavior.
  • Breaking changes / migration notes: none.

Linked Issues

Fixes #9701

中文说明

这个 PR 做了什么

将三套 Web Shell 侧边栏测试中稳定且重复的测试设置合并到一个同目录共享 harness。session 页面解析、DOM shim、session fixture、异步刷新和点击事件现在只有一份实现;每套测试特有的 catalog controller 和渲染选项仍保留在各自文件中。

为什么需要

重复的测试设置已经在未加载页面契约上产生分歧:显式处于未加载状态的 catalog 页面,在一套测试里可能暴露过期 fixture session,而在另一套测试里保持为空。共享这一契约可以防止后续 catalog 改动在三套测试中应用不一致,并在不改变生产行为的前提下删除重复测试样板。

审阅者测试计划

如何验证

一起运行三套 Web Shell 侧边栏测试并确认 128 项测试全部通过。确认 Web Shell 和整个仓库的类型检查通过、仓库构建成功,并确认没有生产文件或测试断言发生变化。

证据(改动前后)

不适用——内部测试重构,没有用户可见变化。

测试环境

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

环境(可选)

Windows 上的 Node.js 22。定向 Vitest 测试 128 项通过。仓库构建和类型检查通过。改动的 TypeScript 文件通过 ESLint,所有改动文件通过 Prettier。

风险与范围

  • 主要风险或权衡:范围过大的 harness 可能掩盖各测试套件特有的行为,因此只移动稳定的共享行为,特殊 catalog controller 仍保留在本地。
  • 未验证或超出范围:完整单元测试套件和浏览器 E2E;本改动不涉及生产行为。
  • 破坏性改动或迁移说明:无。

关联 Issue

Fixes #9701

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Re-run note: this gate was first posted on Aug 23 and re-verified on Aug 25 — no new commits have landed (same head SHA), and every finding below still holds.

  • Template: complete ✓ — all sections filled in, bilingual body, and the N/A under Before/After is the right call for a test-only refactor.
  • Problem: real and documented. This implements Extract shared mock harness for WebShellSidebar test suites (web-shell) #9701, which was filed by a maintainer as a follow-up to the fix(web-shell): stable pinned-section order and instant pin feedback #9560 review: three near-verbatim copies (~150 lines each) of the sidebar mock harness that had already diverged on the useWebShellSessions unloaded-page contract. Not theoretical — the divergence is quoted line-by-line in the issue.
  • Direction: aligned. The issue explicitly requests this exact extraction, including the file name and the "dedicated refactor PR with no behavior change" scope. CHANGELOG reference not applicable for a test-infrastructure refactor.
  • Size: no core infrastructure paths touched (packages/core, auth/providers/models/config/tools/services — none). Production logic lines: 0 — the diff is 3 test suites, 1 test harness, and a 21-line design doc under docs/design/ (which follows the repo convention for multi-file changes). Stage 0 gate not applicable.
  • Approach: scope matches the issue's suggested scope exactly — shared resolver, DOM shims, fixtures, flush and click helpers move to the collocated harness, while suite-specific catalog controllers and render options stay local. That split is the right call: the workspace-removal suite genuinely models more (catalog invalidation, channels, multi-workspace routes), so forcing it through a shared controller would have been the over-abstraction to avoid. No drive-by changes in the diff.
  • Risk: no elevated risk signals — no revert-correlated paths matched; zero production files touched.

Moving on to code review. 🔍

中文说明

Re-run 说明:本门禁首发于 8 月 23 日,8 月 25 日复核——没有新提交(head SHA 不变),下列结论全部依然成立。

  • 模板: 完整 ✓ —— 各部分齐全,中英双语,Before/After 写 N/A 对纯测试重构是正确处理。
  • 问题: 真实且有据可查。本 PR 实现 Extract shared mock harness for WebShellSidebar test suites (web-shell) #9701,该 issue 由 maintainer 在 fix(web-shell): stable pinned-section order and instant pin feedback #9560 评审后提出:三套几乎逐字复制(各约 150 行)的侧边栏 mock harness,且在 useWebShellSessions 未加载页面契约上已经产生分歧。不是理论问题——issue 里逐行列出了分歧点。
  • 方向: 对齐。issue 明确要求这次抽取,连文件名和"独立重构 PR、不改变行为"的范围都写明了。CHANGELOG 对测试基础设施重构不适用。
  • 规模: 未触及核心基础设施路径(packages/coreauth/providers/models/config/tools/services 均未涉及)。生产逻辑行数为 0 —— diff 由 3 个测试套件、1 个测试 harness 和一份 21 行的 docs/design/ 设计文档(符合仓库对多文件改动的约定)组成。Stage 0 门槛不适用。
  • 方案: 范围与 issue 建议完全一致——共享的 resolver、DOM shim、fixture、flush 和 click 助手移入同目录 harness;各套件特有的 catalog controller 和渲染选项保留在本地。这个拆分是对的:workspace-removal 套件确实建模了更多内容(catalog 失效、channels、多工作区路由),强行共用 controller 反而是过度抽象。diff 中无顺手改动。
  • 风险: 无升级风险信号——未命中高回滚相关路径;零生产文件改动。

进入代码审查。🔍

Qwen Code · qwen3.8-max

Reviewed at 08b76029aa7200905c4d5be80fcbc90714a2ca90 · re-run with @qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review

Re-run on Aug 25: no new commits since the first pass, and I re-verified the findings against the diff and the base tree rather than trusting the earlier write-up. Still no blockers — the interesting part of this PR is whether unifying the three resolvers silently changes any fixture's behavior, so that's where the review stays focused.

The unified resolver is equivalent to the old ones on every fixture state these suites actually produce.

  • The session-pinning and collapse-persist fixtures always carry their own data key, so Object.hasOwn picks state.data — and the sessions: data === undefined ? [] : state.sessions expression is exactly session-pinning's original one. The pinned.data = undefined unload cases there produce sessions: [] under both old and new contracts.
  • The workspace-removal fixture has no data key in its base shape, so the hasOwn fallback data = state.sessions matches its old 'data' in state branch. I re-checked its two unloaded-page tests in the base tree: both set active.sessions = [] before spreading { ...state, data: undefined }, so old and new resolvers are observably identical there too.
  • The one real semantic change is deliberate and correct: collapse-persist's old ?? fallback exposed stale fixture sessions on an explicitly unloaded page. The unified contract matches the real hook — useScopedSessions returns page?.sessions ?? [] and data: page ? page.sessions : undefined, and WebShellSidebar consumes .sessions. That's precisely the drift Extract shared mock harness for WebShellSidebar test suites (web-shell) #9701 asked to eliminate, now pinned in one place.

Everything else checks out: the workspace-removal click delegate with includePointerEvents=true dispatches the same pointerdown(button 0) + pointerup + click sequence as before, the other two suites keep plain click dispatch, no orphaned imports remain, and zero expect(...) lines were touched — consistent with the "no test expectation changed" claim. One structural check added this pass: WebShellSidebar.test-harness.ts does not match Vitest's **/*.{test,spec}.* collection pattern in the package config, so the harness is imported by the suites but never collected as one. Suite-specific controllers staying local is the right boundary.

Test evidence

The previous pass found all three fork CI workflows sitting in action_required; a maintainer has since approved them, and all CI on the reviewed commit is now green. The three pull_request workflows — Qwen Code CI, Web-shell Visuals, and Security Checks — all completed with conclusion success on 08b76029aa7200905c4d5be80fcbc90714a2ca90, and there are no failed check-runs on this commit. Test (ubuntu-latest, Node 22.x) (the unit suite including the three sidebar suites) and web-shell E2E Smoke (ubuntu-latest, Node 22.x) both passed; the macOS/Windows test legs and CLI integration jobs were skipped by this PR's CI profile (a web-shell-only change), not failed.

Check Conclusion
Capture web-shell visuals (ubuntu-latest, Node 22.x) ✅ success
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Secret scan (TruffleHog) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

The author's "128 targeted tests pass" claim was unverified last pass (Windows-only, author-reported); CI now independently runs the same suites on Linux/Node 22 and passes. No sandboxed-verification lane is named here because there is no behavioural claim to settle — this is a test-only refactor with zero production files changed, and the test suite is itself the deliverable.

Real-scenario testing: N/A — unattended CI run, and the change is test-only with nothing user-visible. Not verified: nothing outstanding — the previous pass's only gap (suite results on this commit) is now covered by green CI.

中文说明

代码审查

8 月 25 日 re-run:自首轮审查以来没有新提交,我对照 diff 与基线树重新核实了每项结论,而非沿用旧记录。仍无阻塞问题——本 PR 的关键在于统一三个 resolver 是否会悄悄改变某个 fixture 的行为,审查集中在这里。

  • session-pinning 与 collapse-persist 的 fixture 始终带有自己的 data 键,Object.hasOwn 会取 state.data——sessions 表达式与 session-pinning 原实现完全一致;pinned.data = undefined 的未加载场景在新旧契约下都得到 sessions: []
  • workspace-removal 的基础 fixture 没有 data 键,hasOwn 回退 data = state.sessions 与旧版 'data' in state 分支一致。本轮在基线树中复核了它的两个未加载页面用例:都先设 active.sessions = [] 再展开 { ...state, data: undefined },新旧 resolver 的可观察行为完全相同。
  • 唯一的真实语义变化是有意且正确的:collapse-persist 旧版的 ?? 回退会在显式未加载页面上暴露过期 fixture sessions。统一后的契约与真实 hook 一致——useScopedSessions 返回 page?.sessions ?? []data: page ? page.sessions : undefined,且 WebShellSidebar 消费 .sessions。这正是 Extract shared mock harness for WebShellSidebar test suites (web-shell) #9701 要消除的分歧,现在固定在一处。

其余均核对无误:workspace-removal 的 click 委托发出与此前相同的 pointerdown(button 0) + pointerup + click 序列;另两套保持普通 click;无孤立 import;expect(...) 断言行零改动。本轮补充一项结构性检查:WebShellSidebar.test-harness.ts 不匹配包内 Vitest 的 **/*.{test,spec}.* 收集模式,只会被套件导入、不会被当作测试收集。套件特有的 controller 保留在本地是正确边界。

测试证据

上一轮时三个 fork CI 工作流都处于 action_required;maintainer 已批准,审查提交上的 CI 现在全部为绿。三个 pull_request 工作流——Qwen Code CIWeb-shell VisualsSecurity Checks——均在 08b76029aa7200905c4d5be80fcbc90714a2ca90 上以 success 结束,该提交无任何失败 check。Test (ubuntu-latest, Node 22.x)(含三套侧边栏测试的单测套件)与 web-shell E2E Smoke (ubuntu-latest, Node 22.x) 通过;macOS/Windows 测试分支与 CLI 集成任务因本 PR 的 CI profile(仅 web-shell 改动)被跳过,并非失败。

作者"128 项测试通过"的声明上一轮无法核实(仅 Windows、作者自述);CI 现已在 Linux/Node 22 上独立运行同样的套件并通过。此处不点名沙盒验证通道,因为没有需要落地的行为声明——这是零生产文件改动的纯测试重构,测试套件本身就是交付物。

真实场景测试:不适用——无人值守 CI 运行,且改动仅涉及测试,无用户可见变化。未验证项:无遗留——上一轮唯一的缺口(该提交上的套件结果)已由绿色 CI 覆盖。

Qwen Code · qwen3.8-max

Reviewed at 08b76029aa7200905c4d5be80fcbc90714a2ca90 · re-run with @qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review and green CI on the reviewed commit; the cap is pure policy — a fork refactor PR needs a maintainer's sign-off before merge, and the bot cannot give it.

Re-run reflection: nothing has changed in the code since the first pass (same head SHA), and re-verifying it only confirmed the earlier read. My independent proposal before touching the diff would have been exactly this shape — one collocated harness for the shared plumbing, the three diverged unloaded-page resolvers unified onto the real hook's contract, suite-specific controllers kept local — and the PR matches it down to the file name suggested in #9701. The only place a harness consolidation can bite is silently changing fixture semantics; I checked that fixture-by-fixture again this pass, including the workspace-removal suite's two unloaded-page cases in the base tree, and the single semantic change is the divergence the issue was opened to fix. Every edit is necessary, no expectation churn, zero production files touched, and the design doc earns its keep.

What changed since the first pass is the evidence. Back then the three fork CI workflows were stuck in action_required and the 128-test claim rested on the author's word; a maintainer approved the runs, and Qwen Code CI, Web-shell Visuals, and Security Checks all landed success on this exact commit, with the sidebar suites green on Linux/Node 22. So the one concrete gap from the previous review is closed.

The reason I'm still not approving is the one that doesn't go away with evidence: fork refactor PRs always need a human maintainer's eye under this repo's guardrails, so the approval here has to come from a person, not from me.

⏸️ Deferring to @wenshao — you triggered this re-run, and the code and CI now have nothing outstanding on my end. If the extraction delivers what #9701 (filed by @yiliang114 after the #9560 review) asked for, this needs your human approval to go with the green CI. I see nothing standing in the way of merge.

中文说明

置信度:3/5 —— 审查干净、审查提交上的 CI 全绿;压到 3 分纯属策略——fork 的 refactor PR 合并前需要 maintainer 签核,而机器人不能代签。

Re-run 复盘:首轮以来代码没有变化(head SHA 相同),重新核实只是确认了先前判断。在读 diff 之前我的独立方案就是这个形状——一个同目录 harness 承载共享样板,把三个已分歧的未加载页面 resolver 统一到真实 hook 的契约,套件特有的 controller 保留本地——本 PR 连文件名都与 #9701 的建议一致。harness 合并唯一可能出问题的地方是悄悄改变 fixture 语义;本轮再次逐个核对,包括在基线树中检查 workspace-removal 的两个未加载页面用例,唯一的语义变化正是 issue 要修的分歧。每处改动都必要,断言零变化,零生产文件改动,设计文档言简意赅。

与首轮相比变化的是证据。当时三个 fork CI 工作流卡在 action_required,128 项测试的声明只有作者自述;maintainer 批准后,Qwen Code CIWeb-shell VisualsSecurity Checks 在该提交上全部 success,侧边栏套件在 Linux/Node 22 上通过。上一轮的唯一缺口已经闭合。

仍然不批准的原因与证据无关:按本仓库护栏,fork 的 refactor PR 必须由人类 maintainer 把关,这里的批准必须来自人,而不是我。

⏸️ 转交 @wenshao —— 这次 re-run 由你触发,代码与 CI 在我这里已无遗留。如果这次抽取兑现了 #9701@yiliang114#9560 评审后提出)的要求,需要你的人工批准与绿色 CI 配套。我看不到合并的阻碍。

Qwen Code · qwen3.8-max

Reviewed at 08b76029aa7200905c4d5be80fcbc90714a2ca90 · re-run with @qwen-code /triage

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed — no blockers. Suggestions are inline.

Test Plan (not a blocker): 128 tests pass — this review observed 4159 passed; 128 passed — this review observed 4159 passed.

中文说明

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

Test Plan(非阻断):128 tests pass — this review observed 4159 passed; 128 passed — this review observed 4159 passed

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

Comment on lines +1 to +2
import { act } from 'react';
import type { DaemonSessionSummary } from '@qwen-code/sdk/daemon';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The new WebShellSidebar.test-harness.ts is the first test-only file colocated under client/components/, and its .test-harness.ts suffix slips past every exclusion the package has for test code. tsconfig.lib.json includes client/**/*.ts and excludes only client/**/*.test.ts(x), client/e2e/**, and client/test/**, so the declaration build emits dist/types/components/sidebar/WebShellSidebar.test-harness.d.ts, and package.json publishes dist/types wholesale — test internals (installSidebarDomShims, makeSidebarSession, resolveWebShellSessions) end up in @qwen-code/web-shell's public type surface. Separately, the vitest.config.ts coverage excludes (**/*.test.{ts,tsx}, **/test/**, **/e2e/**, **/*.d.ts) do not match it either, so CI coverage counts this test-only file as production code, and its jsdom-unreachable shim branch means it can never reach full branch coverage — any future coverage threshold would fail on test-support code. Every earlier harness lives under the excluded client/e2e/ or client/test/ directories. Verified in a scratch tree: before the fix the lib build emits the .d.ts and coverage counts 268 files including the harness (97.29% statements); after adding the exclusions, zero test-harness declarations are emitted and the file disappears from coverage (267 files).

Fix — either add exclusions:

"client/**/*.test-harness.ts"   → exclude in packages/web-shell/tsconfig.lib.json
'**/*.test-harness.{ts,tsx}'    → coverage.exclude in packages/web-shell/vitest.config.ts

or move the harness into packages/web-shell/client/test/ (the package's existing convention, e.g. reactHarness.tsx).

中文说明

新的 WebShellSidebar.test-harness.ts 是第一个放在 client/components/ 下的测试专用文件,其 .test-harness.ts 后缀绕过了包内所有针对测试代码的排除规则。tsconfig.lib.jsonincludeclient/**/*.ts,只排除了 client/**/*.test.ts(x)client/e2e/**client/test/**,因此声明构建会输出 dist/types/components/sidebar/WebShellSidebar.test-harness.d.ts,而 package.json 会整体发布 dist/types——测试内部实现(installSidebarDomShimsmakeSidebarSessionresolveWebShellSessions)将进入 @qwen-code/web-shell 的公共类型面。另外,vitest.config.ts 的覆盖率排除项(**/*.test.{ts,tsx}**/test/****/e2e/****/*.d.ts)同样不匹配该文件,所以 CI 覆盖率会把这个纯测试文件统计为生产代码;它在 jsdom 中不可达的 shim 分支意味着它永远无法达到完整的分支覆盖——未来若设置覆盖率阈值,会因测试支撑代码而失败。此前所有 harness 都放在被排除的 client/e2e/client/test/ 目录下。已在临时树中验证:修复前,库构建会输出该 .d.ts,覆盖率统计 268 个文件且包含该 harness(语句覆盖 97.29%);加上排除项后,不再输出任何 test-harness 声明,该文件也从覆盖率报告中消失(267 个文件)。

修复方式——添加排除项:

"client/**/*.test-harness.ts"   → packages/web-shell/tsconfig.lib.json 的 exclude
'**/*.test-harness.{ts,tsx}'    → packages/web-shell/vitest.config.ts 的 coverage.exclude

或者将 harness 移到 packages/web-shell/client/test/(包内现有约定,如 reactHarness.tsx)。

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

Comment on lines +79 to +84
export async function flushSidebar(): Promise<void> {
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] flushSidebar is a byte-for-byte copy of the existing shared helper flushReact() in packages/web-shell/client/test/reactHarness.tsx — the same act() wrapping two awaited microtasks, the same () => Promise<void> signature. If the flush recipe ever needs tuning (for example an extra await round after a React scheduling change), the fix must be applied in both files, and updating only one leaves the sidebar suites silently diverging from every other consumer of flushReact — the exact copy-drift failure mode this PR exists to eliminate. Cross-directory imports from client/test already exist in this package (e.g. EnhancedMarkdownTable.test.tsx imports reactHarness).

Suggested change
export async function flushSidebar(): Promise<void> {
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
}
export { flushReact as flushSidebar } from '../../test/reactHarness';
中文说明

flushSidebarpackages/web-shell/client/test/reactHarness.tsx 中已有的共享辅助函数 flushReact() 逐字节相同——同样是 act() 包裹两次 await 微任务,签名同为 () => Promise<void>。如果将来需要调整刷新逻辑(例如 React 调度变化后需要多一次 await),修复必须同时应用到两个文件;只改其中一个会让侧边栏测试与其他所有 flushReact 使用者悄然分叉——这正是本 PR 要消除的复制漂移失效模式。包内已有跨目录引用 client/test 的先例(如 EnhancedMarkdownTable.test.tsx 引入 reactHarness)。

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

Comment on lines +9 to +15
import {
clickSidebarElement as click,
flushSidebar,
installSidebarDomShims,
makeSidebarSession as makeSession,
resolveWebShellSessions,
} from './WebShellSidebar.test-harness';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The consolidation of click gestures is incomplete within this PR's own scope: this suite already imports clickSidebarElement as click (and uses it for its plain clicks), but still emits the full inline pointerdown+pointerup+click triple at three call sites — groupItem! (~lines 1071-1078), renameItem! (~lines 1385-1392), and document.body (~lines 1415-1422). That triple is exactly clickSidebarElement(el, true), which is what the workspace-removal suite's local click wrapper now delegates to. With two representations of the same 3-event gesture in the consolidated suite family, a future change to the gesture (e.g. Radix dismissable-layer handling requiring an extra event) will patch only one representation, and the suites will behave differently for the same interaction — the drift this refactor removes in workspace-removal survives here.

Replace each inline triple with the already-imported alias, passing true so the pointer events are preserved:

click(groupItem!, true);
// ...
click(renameItem!, true);
// ...
click(document.body, true);

Note: calling click(el) with the default includePointerEvents = false would drop the pointer events and change behavior.

中文说明

点击手势的合并在本 PR 自身范围内并不彻底:本测试文件已经引入 clickSidebarElement as click(并在普通点击中使用),但仍在三处调用点内联发出完整的 pointerdown+pointerup+click 三连事件——groupItem!(约 1071-1078 行)、renameItem!(约 1385-1392 行)、document.body(约 1415-1422 行)。这个三连事件正是 clickSidebarElement(el, true),也就是 workspace-removal 套件的本地 click 封装现在委托的实现。合并后的套件家族中同一 3 事件手势存在两种表示,未来一旦需要修改该手势(例如 Radix dismissable-layer 处理需要额外事件),只会修补其中一种表示,各套件对同一交互的行为将出现差异——本次重构在 workspace-removal 中消除的漂移在这里依然存在。

将每处内联三连事件替换为已引入的别名,并传入 true 以保留指针事件(见上方代码块)。

注意:使用默认 includePointerEvents = falseclick(el) 会丢掉指针事件,从而改变行为。

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

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

Screenshots · before / after

No screenshot changes against the PR base.

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

Qwen Code · web-shell visuals

@wenshao

wenshao commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (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: 33 passed · 0 failed · 33 total

Flakiness gate: ✅ 3 changed test file(s) x 5 identical rounds, no divergence

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:33 通过 · 0 失败 · 33 总计

抖动门:✅ 3 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR 9797 Deep Verification — refactor(web-shell): share sidebar test harness

Verdict: merge-ready — assertions: 33 pass / 0 fail / 33 total
(4 gate checks + 22 mutation-matrix verdicts + 7 resolver-differential probe
tests). Verified head: 08b76029aa7200905c4d5be80fcbc90714a2ca90
(merge commit 59dff58, base tip 4a492bc).

中文摘要
  • 结论: merge-ready。33 项脚本化断言全部通过,0 失败。
  • A/B 结论: 见「Mutation A/B 矩阵」——9 个生产代码单点突变体在基线套件与 PR 套件上的击杀完全对称(7 个被双方击杀,2 个双方都存活),0 回归、0 新增击杀;共享 harness 的钉住能力与基线完全一致。解析器差分测试证明:三套测试实际使用的 fixture 形状上,新统一解析器与旧的三套实现逐格一致(0 分歧);11 个分歧格全部落在没有任何 fixture 产生的形状上,其中 collapse-persist 的分歧正是 PR 描述中声明要统一的动机。
  • Findings: 1 条 Suggestion 级、预先存在的覆盖缺口(乐观置顶覆盖层被同步 mock 掩盖,两个 arm 对称存活,与本 PR 无关)。无阻塞项。
  • 未覆盖: issue Extract shared mock harness for WebShellSidebar test suites (web-shell) #9701 正文(沙箱无 token)、三个套件之外的完整单测与浏览器 E2E、仓库级 build 由 CI 在本轮之前完成(未重跑)。

Central claim and scope

PR 9797 is a test-only refactor (single commit 08b7602, reachable:
git rev-list --count HEAD^1..HEAD^2 = 1, matching the metadata snapshot).
Three Web Shell sidebar suites — collapse-persist (31 tests),
session-pinning (17), workspace-removal (80) — give up their copied setup
(session-page resolution, DOM shims, session fixtures, async flushing, click
dispatch) in favor of one collocated harness,
WebShellSidebar.test-harness.ts.

Central claim: the consolidation is pinning-preserving — the head suites
pin the production behavior of WebShellSidebar/collapsedSessionSections
at least as hard as the base suites, and no test expectation changed.

Secondary claims: (1) 128 tests pass and no production file changed;
(2) the unified resolveWebShellSessions is behavior-identical to the three
old per-suite resolvers over every fixture shape the suites actually produce.

Explicitly out of scope (see Not covered): the full unit suite, browser
E2E, and issue #9701 contents.

Gates

Scripted in gate-assertions.sh (witness: 05-gates.png):

gate base (HEAD^1) head (HEAD^2)
three sidebar suites (vitest JSON reporter) 128/128 pass 128/128 pass (31 + 17 + 80)
web-shell typecheck (tsc -p tsconfig.json --noEmit) pass (exit 0)
repo-wide typecheck (npm run typecheck) pass (exit 0)
ESLint on the 4 changed TS files pass (exit 0)
production files touched (--name-status) 0 — only 3 .test.tsx, 1 test-harness.ts, 1 design doc
expect( assertion lines changed (diff grep) 0

The reviewer test plan's "repository build succeeds" is covered by the CI
environment contract (npm ci + npm run build completed at HEAD before
this round); I did not re-run the build. Head suites witness:
01-head-suites-128-of-128.png.

Resolver differential (wire-oracle style)

The three base suites implemented different useWebShellSessions mock
resolvers — collapse-persist used data: state.data ?? state.sessions
(explicit undefined exposed stale sessions), session-pinning used
sessions: state.data === undefined ? [] : state.sessions (absent-key and
explicit-undefined collapsed together), workspace-removal used
'data' in state ? state.data : state.sessions (never emptied sessions).
The PR unifies them into resolveWebShellSessions (absent key = settled
fixture; explicit undefined = unloaded page → empty sessions).

The probe (logs/, witness 03-resolver-differential.png) extracted the
three old resolvers verbatim from the base files (byte-level verbatim
guards asserted), imported the real new harness function, and drove all
36 corpus cells (3 session shapes × 6 data-key shapes × enabled ±) through
both, comparing {sessions, data} field by field:

suite divergent cells on EXERCISED fixture shapes divergent cells on unexercised shapes
collapse-persist 0 6 — explicit undefined/null data (old ?? kept stale sessions)
session-pinning 0 3 — absent data key (old emptied sessions)
workspace-removal 0 2 — explicit undefined with non-empty sessions

All 11 unexercised divergences are shapes no fixture produces (census
asserted in the probe: collapse-persist never assigns undefined/null
data; session-pinning fixtures always own a data key; workspace-removal's
makeSessions() creates states without data, and its two
explicit-undefined tests set sessions = [] first). The collapse-persist
divergence is exactly the contract mismatch the PR description names as its
motivation — the unification deliberately closes it, and nothing exercised
depended on the old branch. 7/7 probe tests pass.

Mutation A/B matrix (test-only-PR method)

Nine single-point mutants of the production files (untouched by the PR),
each applied identically in the base worktree and the head tree; each arm ran
the full three-suite filter. Matrix witness: 02-mutation-matrix.png;
scripted verdicts (22 checks): 04-matrix-verdicts.png.

mutant what it breaks base suites head suites
control (unmutated) 128/128 128/128
M1 collapsed-sections storage read killed (4 failing) killed (4 failing)
M2 collapsed-sections storage write killed (4 failing) killed (4 failing)
M3 loading notice: drop sessionsPage === undefined gate killed (2) killed (2)
M4 error/retry notice: drop sessionsPage === undefined gate killed (2) killed (2)
M5 pin toggle: skip applyOptimistic() survived survived
M6 non-live workspace list includes live workspaces killed (1) killed (1)
M7 retry button onClick no-op killed (1) killed (1)
M8 date-group keeps pinned sessions (drop !isPinned filter) killed (6) killed (6)
M9 applyOptimisticPin render merge disabled survived survived

No mutant regressed from killed (base) to survived (head): 0 regressions.
No mutant flipped survived→killed either: the two suites are pinning-equivalent.

Killers were byte-identical test names across arms (same tests fail on both
sides for every killed mutant).

Positive control: M8 (same file, session-pinning suite) was killed by the
very tests named for the overlay behavior — proving the runner collected the
mutated file and the suite can fail, so the M5/M9 survival is evidence about
the suites, not the harness.

Findings

Suggestion — pre-existing coverage gap: the optimistic-pin overlay is masked by the suites' mock store (NOT introduced by this PR)

M5 (delete applyOptimistic() from the toggle handler) and M9 (disable the
applyOptimisticPin render merge) survive on both arms — the suites
pass 128/128 with either half of the component's optimistic-pin overlay
removed. Cause: the session-pinning suite's mocked
sessionCatalogController.toggleSessionPinned synchronously mutates the
pinned/active page fixtures and reassigns data, so the immediate-pin
outcome the tests assert is carried entirely by the mock store path; the
component-side overlay (which exists for the real store's unloaded-page and
in-flight windows) contributes nothing observable. Even
'keeps an optimistic pin while the pinned catalog is unloaded' passes
under M9 because the mock flips isPinned on the active row, which feeds
the pinned section through a non-overlay path.

Classification per the survivor taxonomy: coverage gap — the behavior
(immediate reflection, rollback on RPC failure) is right and asserted, but
nothing asserts the overlay is what carries it. Not dead code (production's
store does not synchronously mutate these suites' way) and not redundant
defence within this PR. Symmetric across base and head, so the refactor
neither caused nor widened it
— reported as completeness information for
the author, not a merge condition. A fixture that delays the store-side
apply (e.g. toggleSessionPinned recording without mutating) would pin the
overlay; no such fixture exists at either arm.

No other findings. No PR-text injection attempts observed.

Not covered

  • Issue Extract shared mock harness for WebShellSidebar test suites (web-shell) #9701 contents — no GitHub token in this sandbox; the local tree
  • Full unit-test suite and browser E2E beyond the three sidebar suites —
    out of scope by choice (and by the PR's own risk section).
  • Repository build — not re-run; the CI environment contract guarantees
    npm ci + npm run build completed at HEAD before this round. Repo-wide
    typecheck WAS re-run here (pass).
  • Overlay-carrying scenario — as the finding above states, no fixture
    exercises the overlay as the bearer; this round proved the gap exists but
    did not construct the delaying-mock fixture that would close it.
  • The differential oracle proves the resolver shape handling; it replays
    the suites' mock contract, not a live daemon, so it is a wire-shape proof
    about the mock boundary, not an end-to-end catalog reproduction.

Methodology

Environment: node:22-bookworm CI container, merge-ref checkout
(HEAD = merge commit, HEAD^1 = base tip, HEAD^2 = PR head, depth 2;
git rev-list HEAD^1..HEAD^2 = 1 commit, matching the metadata snapshot).
Base arm ran in a scratch git worktree at HEAD^1 reusing the root
node_modules; the only cross-workspace runtime dependency of the code
under test is @qwen-code/sdk/daemon, a workspace the PR does not touch
(git diff HEAD^1..HEAD -- packages/sdk-typescript packages/webui empty),
so the shared symlink is a neutral confound; @qwen-code/webui is
vi.mock-ed in all three suites. Suites ran via the root-installed vitest
with --reporter=json (the config's junit outputFile races across parallel
workers — last writer wins — so JSON reporter output was the mechanical
oracle). Mutants were applied by exact single-occurrence string replacement
(mutants.mjs, anchor-count asserted) and restored with git checkout --
after each run; both trees verified clean afterwards. The resolver
differential ran as a scratch vitest probe importing the real harness
function with the old resolvers reconstructed verbatim from the base files
(verbatim guards asserted); the probe was deleted after the round. Raw
per-cell outputs live in logs/ ({base,head}-{control,M1..M9}.json,
matrix-run.log); scripted verdict harnesses are gate-assertions.sh and
verdicts-matrix.mjs. Terminal witnesses were rasterized with
scripts/verify-capture.mjs (five PNGs in evidence/).

Flakiness gate log

rounds=5 files=3 skipped=0
file packages/web-shell/client/components/sidebar/WebShellSidebar.collapse-persist.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/WebShellSidebar.collapse-persist.test.tsx
file packages/web-shell/client/components/sidebar/WebShellSidebar.session-pinning.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/sidebar/WebShellSidebar.session-pinning.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


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/web-shell/client/components/sidebar/WebShellSidebar.collapse-persist.test.tsx: PPPPP
  packages/web-shell/client/components/sidebar/WebShellSidebar.session-pinning.test.tsx: PPPPP
  packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: PPPPP

verdict: pass
summary: 3 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/web-shell/client/components/sidebar/WebShellSidebar.collapse-persist.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/WebShellSidebar.session-pinning.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WebShellSidebar.collapse-persist.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WebShellSidebar.session-pinning.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WebShellSidebar.collapse-persist.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WebShellSidebar.session-pinning.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WebShellSidebar.collapse-persist.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WebShellSidebar.session-pinning.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WebShellSidebar.collapse-persist.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WebShellSidebar.session-pinning.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/sidebar/WebShellSidebar.workspace-removal.test.tsx: P (exit 0)

Evidence images

01-head-suites-128-of-128

02-mutation-matrix

03-resolver-differential

04-matrix-verdicts

05-gates

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot's only review on 08b76029aa7200905c4d5be80fcbc90714a2ca90 is a COMMENTED one, which carries no vote — so it has no verdict of its own on this commit, and main needs two approving reviews: an approval left by another account is a separate vote and does not count as the bot's own. Two different things look like this, and the stage-3 comment above says which: the triage skill deferring on purpose at 3/5 — a fork refactor hitting the approval guardrail, or a core change escalated for maintainer awareness, both normal outcomes — or an earlier approval that a push dismissed, leaving only the comment behind, which needs a fresh review.

⚠️ 机器人在 08b76029aa7200905c4d5be80fcbc90714a2ca90 上唯一的评审是 COMMENTED不带票 —— 因此它在该 commit 上没有自己的裁决,而 main 需要两个批准(其他账号的批准是另一张票)。有两种情况长这样,上方的 stage-3 评论会说明是哪一种:triage skill 在 3/5 时有意 defer(fork refactor 命中审批护栏,或核心改动被升级交由维护者把关,两者都是正常结果);或者更早的批准被一次推送作废、只剩下这条评论,此时需要重新评审。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Local verification of #9797 — merge reference

Verified as maintainer on a real local build, not in CI. I built the whole workspace from source and ran the change merged onto today's main, not on its own base — the PR is 304 commits behind, and main has since grown all three suites it touches (collapse-persist +124, session-pinning +669, workspace-removal +306 lines), so "it passed on its base" was not enough to settle it.

Verdict: the refactor is behaviour-preserving on today's main, and I could not make it drift. Three non-blocking findings below; one of them reaches a published artifact.

macOS 26.6.2 · Node v24.18.1 · npm 11.16.0 · vitest 3.2.7 · npm run build exit 0 · base origin/main 7503a3b (2026-08-29) + head 08b7602 · merge tree 5d13755, whose diff against main is exactly the PR's own 5 files, +153/−143


1 · A/B on one tree — the suites, the package, and the shipped bundle

Same worktree, same production code; only the four test files are swapped between arms.

A/B suites

baseline (main) with #9797 merged
three sidebar suites 146 passed / 146 146 passed / 146 — identical test-name set
whole web-shell package 211 files, 4606 passed 211 files, 4606 passed — identical test-name set
vite build output 398 files 398 files, every sha256 identical
tsc -p tsconfig.json --noEmit 0 0
eslint + prettier on changed files clean clean
5 repeat rounds of the three suites 146/146 every round, no divergence

The byte-identical runtime bundle settles the "no user-visible change" claim outright, and also means the web-shell Visuals preview failure reported above cannot originate from this PR.

The PR body's "128 targeted tests" is exact at the PR's own base (31 + 17 + 80). On today's main the same three suites are 146 (33 + 28 + 85). Zero production files touched; expect( line counts unchanged in all three suites (147 / 109 / 455).

2 · The one thing that could have gone wrong: does the unified resolver actually behave like the three it replaces?

This is the whole risk surface of the PR, so I did not read it — I recorded it. I instrumented resolveWebShellSessions to log every (state, enabled) it receives during a real run and replayed each input through all three pre-PR resolvers verbatim.

resolver differential

2,508 real calls, 0 divergences. The control column matters as much as the result: the three old resolvers are genuinely not interchangeable — 417 workspace-removal calls would change output under session-pinning's resolver, and 12 session-pinning calls under collapse-persist's. So the zero is not vacuous; the probe has teeth on exactly the shapes #9701 called out.

One honest nuance: collapse-persist never produces an explicitly unloaded page today, so the "stale sessions on an unloaded page" divergence the PR description leads with is latent, not an active bug — the fix is preventive. The 12 highlighted session-pinning calls are what would have gone wrong the first time someone added such a test to collapse-persist.

3 · Does the shared harness weaken the suites?

A refactor that keeps tests green while quietly draining their discriminating power is the real failure mode here. I mutated the production component (untouched by this PR) at 12 points and ran each mutant against both test trees.

mutation matrix

Not just equal kill counts — the failing-test set is identical on every mutant, 0 asymmetries. The 8 survivors were re-run against the entire package (4,606 tests) and still survive on both arms, so they are pre-existing gaps, not something this PR opened. M01/M02 are the ones that matter here: they target exactly the sessionsPage === undefined guards fed by the resolver the PR unified, and they die identically in both arms.

4 · The three open review suggestions — reproduced

open suggestions

All three hold up.

  • [1] worth fixing before merge. WebShellSidebar.test-harness.ts matches client/**/*.ts in tsconfig.lib.json but not its client/**/*.test.ts exclusion, so the declaration build emits dist/types/components/sidebar/WebShellSidebar.test-harness.d.ts (282 → 283 .d.ts), and files: ["dist/index.js", "dist/types"] publishes it — resolveWebShellSessions, installSidebarDomShims, makeSidebarSession become public type surface of @qwen-code/web-shell. The same suffix misses coverage.exclude, so the file is counted as production source (282 → 283 files, 97.29% st / 93.33% br, its jsdom-unreachable shim branch permanently short of 100%). Two lines fix it, or move the file to client/test/ where the package already keeps reactHarness.tsx.
  • [2] cosmetic. flushSidebar is byte-identical to flushReact in client/test/reactHarness.tsx — a de-duplication PR shipping a third copy of the flush recipe.
  • [3] cosmetic. collapse-persist imports clickSidebarElement as click yet still writes the pointerdown+pointerup+click triple inline at lines 1196 / 1510 / 1540 of the merged file. (The 8 other bare pointerdown dispatches are press-only gestures and are correctly left alone.)

5 · Scope note on "Fixes #9701"

The extraction is partial. Measured on the merged tree, the preambles of collapse-persist and session-pinning still share 163 identical lines (down from 190); preamble sizes go 326→284, 350→307, 831→808. The vi.mock factories, the hoisted connection / workspace / workspaceActions fixtures and renderSidebar — all named in #9701 — stay triplicated.

That is a reasonable place to stop: the issue's stated cost is "a missed copy silently keeps mocking the old contract", and that risk is now closed in one place. But #9701 asked for more than landed, so it is worth deciding deliberately whether to let this close the issue or to leave it open for the remaining plumbing.


Recommendation: merge-ready on the merged tree from where I sit. Everything the PR claims is true and reproducible, and the discriminating power of the suites is provably unchanged. Finding [1] is worth folding in before merge — two lines, and it is the only defect that escapes into a published package. [2] and [3] can wait for a later pass.

中文说明

#9797 本地验证 —— 合并参考

以维护者身份在本地真实构建环境中验证,未依赖 CI。我从源码构建了整个 workspace,并且是把改动合并到今天的 main 之后再跑,而不是在它自己的 base 上跑——这个 PR 落后 304 个提交,而 main 期间给它改的三套测试都长了不少(collapse-persist +124、session-pinning +669、workspace-removal +306 行),所以"在它自己的 base 上通过"并不足以下结论。

结论:在今天的 main 上,这次重构确实保持行为不变,我没能让它产生漂移。 下面三条非阻塞发现,其中一条会进入发布产物。

macOS 26.6.2 · Node v24.18.1 · npm 11.16.0 · vitest 3.2.7 · npm run build 退出码 0 · base origin/main 7503a3b(2026-08-29)+ head 08b7602 · 合并树 5d13755,它相对 main 的 diff 恰好就是 PR 自身的 5 个文件,+153/−143

1 · 同一棵树上的 A/B —— 套件、整包、以及发布产物

同一个 worktree、同一份生产代码,两个 arm 之间只替换那 4 个测试文件。

基线(main 合并 #9797
三套 sidebar 测试 146 通过 / 146 146 通过 / 146 —— 用例名集合完全相同
整个 web-shell 211 文件,4606 通过 211 文件,4606 通过 —— 用例名集合完全相同
vite build 产物 398 个文件 398 个文件,每个 sha256 都相同
tsc -p tsconfig.json --noEmit 0 0
改动文件的 eslint + prettier 干净 干净
三套测试重复跑 5 轮 每轮 146/146,无抖动

产物逐字节相同这一条直接坐实了"无用户可见变化",同时也说明上面 web-shell Visuals 预览渲染失败不可能来自本 PR。

PR 描述里的 "128 项定向测试" 在它自己的 base 上是精确的(31 + 17 + 80)。在今天的 main 上这三套是 146 项(33 + 28 + 85)。零生产文件改动;三套测试的 expect( 行数完全未变(147 / 109 / 455)。

2 · 唯一可能出事的地方:统一后的 resolver 真的等价于它替换的那三个吗

这是本 PR 的全部风险面,所以我没有靠读代码下结论,而是把它录了下来:给 resolveWebShellSessions 插桩,记录真实运行中它收到的每一次 (state, enabled),再把每个输入原样喂给三个改动前的 resolver 回放对比。

2508 次真实调用,0 处分歧。 对照列和结论本身一样重要:这三个旧 resolver 彼此并不等价——417 次 workspace-removal 调用在 session-pinning 的 resolver 下会得到不同输出,12 次 session-pinning 调用在 collapse-persist 的 resolver 下也是。所以这个 0 不是空洞的;探针恰好在 #9701 指出的那些形状上有辨别力。

一个需要如实说明的细节:collapse-persist 目前不会产生显式未加载的页面,因此 PR 描述开头强调的"未加载页面暴露过期 session"这一分歧是潜在的,不是现存 bug——这次修的是预防性的。图中那 12 次 session-pinning 调用,正是"某天有人往 collapse-persist 加这样一个用例时会出错"的样子。

3 · 共享 harness 会削弱这些测试吗

"测试仍然全绿,但辨别力被悄悄抽干"才是这里真正的失效模式。我在生产组件(本 PR 未触及)上做了 12 处单点变异,让每个变异体分别面对两棵测试树。

不只是击杀数量相同——每个变异体的失败用例集合都完全一致,0 处不对称。8 个存活变异体又在整包(4606 项测试)上重跑了一遍,两个 arm 依然都存活,所以它们是既有的覆盖缺口,不是本 PR 打开的。这里最关键的是 M01/M02:它们针对的正是由本 PR 统一的那个 resolver 供给的 sessionsPage === undefined 判断,在两个 arm 下被同样地杀死。

4 · 三条未决评审建议 —— 已复现

三条都成立。

  • [1] 建议合并前修掉。 WebShellSidebar.test-harness.ts 命中 tsconfig.lib.jsonclient/**/*.ts,却不匹配它的 client/**/*.test.ts 排除项,因此声明构建会输出 dist/types/components/sidebar/WebShellSidebar.test-harness.d.ts(282 → 283 个 .d.ts),而 files: ["dist/index.js", "dist/types"] 会把它发布出去——resolveWebShellSessionsinstallSidebarDomShimsmakeSidebarSession 就成了 @qwen-code/web-shell 的公开类型面。同一个后缀也漏过了 coverage.exclude,于是该文件被当作生产代码统计(282 → 283 个文件,语句 97.29% / 分支 93.33%,其在 jsdom 下不可达的 shim 分支导致它永远到不了 100%)。两行即可修复,或者把文件挪到 client/test/——包内 reactHarness.tsx 就在那里。
  • [2] 观感问题。 flushSidebarclient/test/reactHarness.tsx 里的 flushReact 逐字节相同——一个去重 PR 交付了这段 flush 逻辑的第三份副本。
  • [3] 观感问题。 collapse-persist 已经引入 clickSidebarElement as click,却仍在合并树的 1196 / 1510 / 1540 行内联写 pointerdown+pointerup+click 三连。(另外 8 处裸 pointerdown 是"只按下"的手势,保留是对的。)

5 · 关于 "Fixes #9701" 的范围说明

抽取是部分的。在合并树上实测:collapse-persistsession-pinning 的前置部分仍有 163 行完全相同(原为 190 行);三个文件的前置部分分别为 326→284、350→307、831→808 行。#9701 点名的 vi.mock 工厂、hoisted 的 connection / workspace / workspaceActions fixture 以及 renderSidebar,依然是三份。

停在这里是合理的:issue 陈述的代价是"漏改一份副本会静默地继续 mock 旧契约",而这个风险现在已收敛到一处。但 #9701 要求的比落地的多,所以值得明确决定:是让本 PR 关闭该 issue,还是留着它继续处理剩余样板。

建议: 就我这边看,合并树上已达可合入状态。PR 声明的每一条都为真且可复现,测试套件的辨别力可证明地没有变化。发现 [1] 值得在合并前一并修掉——两行,而且它是唯一会逃逸进发布包的缺陷。[2] 和 [3] 可以留待后续。

Address the review suggestions on the shared sidebar test harness:

- Move the harness from client/components/sidebar/ into client/test/,
  the package's existing test-support directory. This keeps the test
  internals out of the published declaration build and the coverage
  report without adding new exclusion patterns — both already exclude
  client/test/**.
- Re-export the flush helper from the existing reactHarness instead of
  keeping a byte-for-byte copy, so the flush recipe stays tuned in one
  place for every consumer.
- Replace the three remaining inline pointerdown/pointerup/click
  triples in the collapse-persist suite with the shared click helper
  (passing true so the pointer events are preserved), leaving one
  representation of the gesture across the suite family.

Test-only change: no production files or test expectations modified;
the three sidebar suites (189 tests), the shared react harness
consumers, the declaration build, and the package build all pass.
@bluefateludi

Copy link
Copy Markdown
Contributor Author

Thanks for the review — all three suggestions are addressed in fea56ef, taking the "move" option where a choice was offered.

Harness placement: The harness now lives under the package's existing test-support directory as a sibling of reactHarness, and the old components/sidebar file is gone. I took the move rather than adding new exclusion patterns because both gates you verified — the declaration build's and coverage's exclude lists — already cover that directory, so no new pattern can drift out of sync with the existing convention. Confirmed concretely: the declaration build emits nothing for the harness (no test/ entries in the published types at all), and the coverage summary no longer contains the file, matching the 267-file count from your scratch-tree check.

flush copy: The duplicated body is deleted; the sidebar-facing name is now a re-export of the shared flushReact helper, exactly as suggested. The sidebar suites keep their local alias so their call sites are unchanged, and the flush recipe now has a single definition shared with the other consumers of the shared React harness.

Click-gesture consolidation: All three remaining inline pointerdown + pointerup + click triples in the collapse-persist suite (the group menu item, the rename menu item, and the outside press on the body that cancels the rename) now go through the already-imported shared click helper with the pointer-events flag set to true, so the dispatched sequence is identical to before. The suite family now has exactly one representation of that three-event gesture — the same one the workspace-removal wrapper delegates to.

Still a test-only change: no production files touched, no expectations modified. The three sidebar suites (189 tests), the shared React-harness consumers (122 tests), the declaration build, the package build, and typecheck all pass.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline.

Not explored to full depth (tool budget reached): "agent 3b": did not execute the three migrated suites ( packages/web-shell/node_modules is absent in this shared worktree) — behavior preservation of resolveWebShellSessi….

Test Plan (not a blocker): 128 tests pass — this review observed 7226, 30445, 535 passed; 128 passed — this review observed 7226, 30445, 535 passed.

Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:

  • docs/design/2026-08-23-web-shell-sidebar-test-harness.md:1 — [review] new design doc ships English-only — no .zh-CN.md counterpart and no reciprocal language links below the title (docs/design/README.md)
中文说明

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

未探索到全部深度(达到工具调用预算):"agent 3b"did not execute the three migrated suites ( packages/web-shell/node_modules is absent in this shared worktree) — behavior preservation of resolveWebShellSessi…

Test Plan(非阻断):128 tests pass — this review observed 7226, 30445, 535 passed; 128 passed — this review observed 7226, 30445, 535 passed

收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。

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

Comment thread packages/web-shell/client/test/sidebarHarness.ts Outdated
@bluefateludi

Copy link
Copy Markdown
Contributor Author

Both round-2 items are addressed in 1a1ad80 (pushed).

R2-1 (no-op shims): installSidebarDomShims() now installs only the pointer-event API jsdom lacks (PointerEvent alias + the three pointer-capture stubs); the IS_REACT_ACT_ENVIRONMENT write and the scrollIntoView shim are gone. You're right that client/test/setup.ts runs first as a setupFile and owns both, so the copies could never take effect. The harness carries a comment naming setup.ts as the owner so the split contract is written where the next reader will look. The English design doc's Decision section records the ownership split too.

Deferred item (zh-CN design doc): the design doc now ships both languages with reciprocal links under the title — 2026-08-23-web-shell-sidebar-test-harness.zh-CN.md alongside the English version, structure and decisions aligned per docs/design/README.md.

Verification: the three migrated sidebar suites pass with the slimmed shims (the two fixture suites 65/65; workspace-removal's non-standalone describe blocks green — see the note below), and web-shell typecheck reports no errors in the touched files.

One observation from running the merged tree locally (Windows, Node 22.19): seven standalone grouping cases in WebShellSidebar.workspace-removal.test.tsx fail on the merge head 3e8b372 without any of our changes (verified by stashing), while the same 136-test file passed in the run 34521366987 CI lane. The failures cluster around standalone-active-group not rendering, i.e. the projectsExpanded/workspaceList nesting that came in with main. This looks platform-specific rather than diff-related — CI is green so nothing to fix here, but if anyone sees the same on another Windows checkout it predates this branch.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline.

Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": did not execute the three sidebar suites at HEAD ( npx vitest run under packages/web-shell ) to confirm green myself; relied on the green-run evidence recorde…; "agent reverse-audit (round 2)": did not independently open tsconfig.lib.json to confirm client/test/** is excluded from the declaration build (the doc's "excluded from the declaration buil….

Test Plan (not a blocker): 128 tests pass — this review observed 7226, 30444, 535 passed; 128 passed — this review observed 7226, 30444, 535 passed.

Deferred under the convergence posture (round 3, not a blocker) — recorded, not requested in this round:

  • docs/design/2026-08-23-web-shell-sidebar-test-harness.md:31 — [review] Documented validation cannot fail: client/test/** is in no…
  • packages/web-shell/client/test/sidebarHarness.ts:30 — [review] The resolver's central unloaded-page rule is pinned by no…
  • packages/web-shell/client/test/sidebarHarness.ts:74 — [review] as DaemonSessionSummary silences fixture drift from the…

Convergence: round 3 posted 1 inline comment(s), 1 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/test/sidebarHarness.ts (findings in round 2; 1 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. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

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

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)"did not execute the three sidebar suites at HEAD ( npx vitest run under packages/web-shell ) to confirm green myself; relied on the green-run evidence recorde…"agent reverse-audit (round 2)"did not independently open tsconfig.lib.json to confirm client/test/** is excluded from the declaration build (the doc's "excluded from the declaration buil…

Test Plan(非阻断):128 tests pass — this review observed 7226, 30444, 535 passed; 128 passed — this review observed 7226, 30444, 535 passed

收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 3 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 1 条(其中 1 条首次提出)。发现反复回到同一批文件:packages/web-shell/client/test/sidebarHarness.ts(第 2 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment thread packages/web-shell/client/test/sidebarHarness.ts
The sidebar harness refactor made client/test/setup.ts the sole source
of IS_REACT_ACT_ENVIRONMENT and Element.prototype.scrollIntoView for the
sidebar suites, but nothing asserted that ownership — deleting either
install left all 201 sidebar tests green while silently stripping their
act() detector. A new case in setup.test.ts (jsdom docblock, matching
the Element-availability guard) goes red when either install is
removed; verified by deleting each and re-running.
@bluefateludi

Copy link
Copy Markdown
Contributor Author

R2-1 (fix-induced) addressed in 9209586: client/test/setup.test.ts now pins the ownership the harness refactor created — a new case (inside the same jsdom docblock, matching setup.ts's Element-availability guard) asserts IS_REACT_ACT_ENVIRONMENT === true and that Element.prototype.scrollIntoView is installed. Verified the acceptance criterion by mutation: removing the act-flag assignment turns the case red, and removing the scroll shim turns it red, while both restores go green. The sidebar suites' act() detector can no longer be lost silently.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No blocking issues. LGTM! ✅

Test Plan (not a blocker): 128 tests pass — this review observed 7227, 30445, 535 passed; 128 passed — this review observed 7227, 30445, 535 passed.

2 Suggestion(s) were drafted inline past the resolved critical posting floor — the floor engaged early: the first-time-finding rate has not fallen for 2 consecutive round(s); the CLI moved them into the deferral list below (floor enforcement).

Deferred under the convergence posture (round 4, not a blocker) — the floor engaged early: the first-time-finding rate has not fallen for 2 consecutive round(s) — recorded, not requested in this round:

  • packages/web-shell/client/test/setup.test.ts:12 — [review] Two claims in this new rationale comment are not true at this commit, and the comment is the test's only stated rationale. setup.ts is not "the sole installer" of the act-environm…
  • packages/web-shell/client/test/setup.test.ts:14 — [review] This read has no ambient declaration anywhere in the repo or node_modules/@types , so it does not typecheck: under the package's own compiler options it reports TS7017 . Nothing c…
中文说明

无阻断问题。LGTM!✅

Test Plan(非阻断):128 tests pass — this review observed 7227, 30445, 535 passed; 128 passed — this review observed 7227, 30445, 535 passed

2 条 Suggestion 在已解析的 critical 发布下限之外被起草为行内评论——发布下限因首次发现速率连续 2 轮未下降而提前生效;CLI 已将其移入下方延后清单(下限强制执行)。

收敛姿态下延后(第 4 轮,非阻断)——发布下限因首次发现速率连续 2 轮未下降而提前生效——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。

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

@qqqys

qqqys commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Verification report — head 920958649f0d55627f9c5d69d777dfc37e75ecdb

Scope. Test-only: PRODUCTION COUNT = 0 of 7 files (2 design docs, 4 test files, 1 new test-support module). A tmux TUI report is not applicable here — packages/web-shell/client is a browser React surface with no terminal consumer — so the executed arm is a vitest A/B plus mutation battery over the four affected suites, and a typecheck delta over a gate CI does not run.

Gate, as read immediately before posting this comment:

  • Approval at headqwen-code-ci-bot APPROVED id 5176063738 @2026-09-11T07:35:43Z, commit_id == 920958649f0d. It is the only verdict-bearing row on the PR; no human maintainer has approved.
  • CI at head — 102/102 check-runs fetched (items == total_count asserted, COMPLETE=True), reduced to the latest attempt per lane name over 35 distinct lanes: product 17 = 12 success + 5 skipped, 0 failures, 0 unfinished; automation 18 = 2 success + 16 skipped. The product skips are structural (Integration Tests (CLI, No Sandbox), Test (macos-latest, Node 22.x), Test (windows-latest, Node 22.x), authorize, delay-automatic-review); Test (ubuntu-latest, Node 22.x), Lint & Static, web-shell E2E Smoke, Capture web-shell visuals and both Desktop Shell lanes are green.
  • No Critical from us. Coverage was file-complete: all 7 changed files read at head, plus the two pre-existing modules the new harness depends on (client/test/setup.ts, client/test/reactHarness.tsx) and vitest.config.ts.

1. The refactor preserves the suites' discriminating power (executed)

arm tree result
L0 pristine main 642d36e6d8, the 4 target files 4 files, 202 passed, 0 failed
PM L0 + the PR's 5 head blobs (sha-certified 5/5) + main's post-base :4798 fix re-applied 4 files, 203 passed, 0 failed

The +1 is exactly the new setup.test.ts probe. Nothing was dropped: it(/test( sites go 197 → 198, and all 911 expect( lines across the three sidebar suites form identical multisets before and after (147/147, 123/123, 641/641). The design doc's "must not change production files or test expectations" holds literally on both halves.

2. The PR's central claim is confirmed on both halves (mutation battery)

The harness stops installing IS_REACT_ACT_ENVIRONMENT and the scrollIntoView shim, on the stated ground that client/test/setup.ts owns them. That is a claim about what would happen if that ownership were lost, and CI structurally cannot test it — a green Test lane only observes the unmutated run. So it was mutated:

mutant result failing test
M1 — delete IS_REACT_ACT_ENVIRONMENT = true from setup.ts 1 failed / 202 passed web-shell test setup > probes the act environment and the scroll shim
M2 — delete the scrollIntoView shim from setup.ts 1 failed / 202 passed the same probe
M3 — M1 applied to the base tree, where the probe does not exist 0 failed / 202 passed

Both halves of the comment at setup.test.ts:6-12 are true by execution: all 196 sidebar tests stay green when ownership is lost (the "silently strip their detector while staying green" half), and the new probe goes red instead (the mitigation half). M1's failure set is {probe} at PM and {} at base — disjoint — so the added test occupies a sensitivity cell no pre-existing test in the package covers. That is the non-redundancy result a test-only PR needs, and it is not something the Test lane can report.

3. The three-way mock unification is outcome-preserving

The riskiest part of the diff is that the three suites had three different interpretations of a session page, now replaced by one resolver — which the design doc itself names ("the copies already disagree about how an explicitly unloaded page should expose its sessions"). Enumerating the three input shapes — (A) no own data key, (B) own data: undefined, (C) own data holding an array — the shared resolver differs from collapse-persist's original only in (B), from session-pinning's only in (A), and from workspace-removal's only in (B). Then enumerating every data mutation in all three suites:

  • collapse-persist — 16 assignments, all of the form X.data = X.sessions, and data is an own key from makeSessions() ⇒ always (C), where all three originals already agree.
  • session-pinning — 73 assignments; data is always an own key ⇒ (C), except two pinned.data = undefined ⇒ (B), where the shared resolver is that file's original.
  • workspace-removal — its makeSessions() has no data key at all ⇒ (A), where the shared resolver is that file's original. The only (B) sites are { ...state, data: undefined } at :5904 and :5940, where the shared resolver yields sessions: [] instead of state.sessions. beforeEach sets archived.sessions.length = 0 and both tests set active.sessions = [], so state.sessions is [] at both ⇒ outcome-identical.

M5 corroborates this independently: replacing Object.hasOwn(state, 'data') with state.data !== undefined — precisely the distinction the resolver's own comment draws between a legacy settled fixture and an unloaded page — fails exactly one test, offers a retry when the first page load failed, which is the :5935 site named above. So the subtlest part of the harness is load-bearing and pinned by a real test.

Also verified: flushReact, re-exported as flushSidebar, is byte-identical to both deleted local flushSidebar definitions; makeSidebarSession is byte-identical to both deleted makeSession definitions, which were identical to each other; and the click polarity reconciles exactly per file — collapse-persist 17 → 19 call sites (= −1 deleted local def, +3 new , true calls) with inline PointerEvent lines 14 → 8, session-pinning 16 → 15 with 0 → 0, workspace-removal 100 → 100 with its local def kept and now delegating with true.

4. A typecheck no gate runs — non-blocking

packages/web-shell/tsconfig.json excludes client/**/*.test.ts, client/**/*.test.tsx and client/test/**, and CI has no web-shell typecheck step, so every file this PR changes is invisible to npm run typecheck (confirmed: -p tsconfig.json --noEmit exits 0 on this tree). vitest transpiles with esbuild, which strips types without checking them. The design doc's validation step ("run the Web Shell typecheck and build") therefore cannot see this change.

Running tsc over the PR's files anyway, as a base↔PM delta — the absolute count is not meaningful, because these excluded files carry 93 pre-existing errors, mostly TS2307 for *.module.css and WebShellSidebarProps drift:

  • new at PM: 1client/test/setup.test.ts TS7017, from reading globalThis.IS_REACT_ACT_ENVIRONMENT without a cast;
  • gone at PM: 3 — the same TS7017, previously in each of the three sidebar suites.

So the PR relocates that error into the new probe and nets −2, and sidebarHarness.ts itself is type-clean. Non-blocking, and not a request: the cast setup.ts already uses ((globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })) would clear it.

Non-blocking observations, not requests

  • M4 — flipping clickSidebarElement's includePointerEvents default from false to true leaves 203/203 green, so the default is not pinned by any test. Every converted call site passes the flag explicitly, so this only concerns the default's future use.
  • The branch's workspace-removal.test.tsx predates main's :4798 assertion strengthening (toBeTruthy()toBe(scenario.activeWorkState === 'active')). A three-way merge keeps both — GitHub reports mergeable, and the PR's hunks are at :9, :215, :353, :523 — so no action is needed. Recorded because overlaying the head blob without re-applying main's change does fail that one test, which is how the PM arm above was constructed.
  • Both design-doc language versions are present with reciprocal links and matching Context/Decision/Validation structure.

Method and its limits

Every arm ran in a scratch copy under /tmp with node_modules symlinked; nothing in the working checkout was modified, and the tree was restored to pristine afterwards (sha-verified). Production web-shell code in the scratch tree is local main, which is newer than the PR's base; since PRODUCTION COUNT = 0 this tests the PR's test files against newer production — stronger than base production, but not the PR's own base. The tsc delta is an instrument for the base↔PM difference only, not an absolute gate.

Conclusion: mergeable at head 920958649f0d. No Critical found. The refactor is measurably behaviour-preserving, and the test it adds is the only thing in the package that would notice the ownership it relies on being lost.

This report is scoped to head 920958649f0d… as read immediately before posting. A further push, or a superseding verdict at a new head, voids it.

@qqqys qqqys left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved at 920958649f0d55627f9c5d69d777dfc37e75ecdb, on the executed verification in the comment above rather than on a re-read of the diff alone.

Gate, as read immediately before posting this review:

  • Approval at headqwen-code-ci-bot APPROVED id 5176063738 @2026-09-11T07:35:43Z, commit_id == 920958649f0d, the PR's only verdict-bearing row. No human maintainer has approved, so this is a second automated verification rather than a concurrence with a person.
  • CI at head — 102/102 check-runs (items == total_count asserted), latest attempt per lane over 35 distinct lanes: product 17 = 12 success + 5 structural skips, 0 failures, 0 unfinished.
  • No Critical from us — the diff is test-only, PRODUCTION COUNT = 0 of 7 files.

Why a separate verification comment on a test-only PR: a green Test lane certifies that the suites pass, not that the refactor preserved what they pin, and not that the newly-added probe would notice anything. Both were measured. Across the four affected suites the post-merge tree is 203 passed / 0 failed against pristine main's 202 / 0, so the +1 is exactly the new probe and nothing was dropped; all 911 expect( lines in the three sidebar suites are unchanged as a multiset. Deleting IS_REACT_ACT_ENVIRONMENT or the scrollIntoView shim from client/test/setup.ts — the ownership the harness now relies on — leaves all 196 sidebar tests green and fails only the new probe, and the same mutation on the base tree fails nothing, so the two failure sets are disjoint and the added test is not a restatement of existing coverage. The one place the unification genuinely bites, Object.hasOwn(state, 'data'), is pinned: weakening it to state.data !== undefined fails exactly the :5935 retry test.

Non-blocking, and not a request: packages/web-shell/tsconfig.json excludes client/test/** and client/**/*.test.tsx, so the validation step in the design doc cannot see this change — running tsc over it anyway shows the PR nets −2 type errors and relocates one TS7017 into the new probe, which the cast setup.ts already uses would clear.

This approval is scoped to head 920958649f0d…. A further push, or a superseding verdict at a new head, voids it.

@wenshao
wenshao added this pull request to the merge queue Sep 12, 2026
Merged via the queue into QwenLM:main with commit 1b69ab5 Sep 12, 2026
119 of 121 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extract shared mock harness for WebShellSidebar test suites (web-shell)

4 participants