فهرست منبع

Merge branch 'master' into fix/notices-serial-windows-timeout

Chinesezjc 2 هفته پیش
والد
کامیت
4f877cdab1
21فایلهای تغییر یافته به همراه103 افزوده شده و 53 حذف شده
  1. 2 2
      .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.i18n.yaml
  2. 1 1
      .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md
  3. 1 1
      .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md
  4. 2 2
      .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml
  5. 0 1
      .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md
  6. 3 3
      .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md
  7. 2 2
      .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml
  8. 3 3
      .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md
  9. 3 3
      .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md
  10. 5 3
      .github/workflows/ci.yml
  11. 7 3
      apps/web/tests/goal-command-presentation.e2e.ts
  12. 3 3
      packages/client/ui-conversation/src/client/input/editor/keymap.ts
  13. 3 1
      packages/client/ui-conversation/tests/keymap-routing.client.spec.tsx
  14. 2 2
      packages/client/ui-input-trigger/README.i18n.yaml
  15. 1 1
      packages/client/ui-input-trigger/README.md
  16. 1 1
      packages/client/ui-input-trigger/README.zh.md
  17. 16 9
      packages/client/ui-input-trigger/src/client/controller.ts
  18. 33 6
      packages/client/ui-input-trigger/tests/service.client.spec.ts
  19. 4 4
      packages/experimental/webworker-packer/tests/image-loadable.spec.ts
  20. 8 0
      scripts/ci-workflow.spec.ts
  21. 3 2
      scripts/coverage-exempt.ts

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md
-2026-07-25-web-input-machine-and-slash-pipeline.md: 3508de5e8a3980a87c344c5b76c060f6119ee686
-2026-07-25-web-input-machine-and-slash-pipeline.zh.md: eebfdae780157dfd0dace1386169c5fee8c1d564
+2026-07-25-web-input-machine-and-slash-pipeline.md: 69899efcda42eb1087aaa68d1eba8c08dd14f361
+2026-07-25-web-input-machine-and-slash-pipeline.zh.md: 7e37dd67a2d5a7943a8c601a890d2de7489b227d

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md

@@ -48,7 +48,7 @@ Calls that stay un-evented (registry registration → explicit call → await):
 A trigger/menu/pick pipeline with zero knowledge of "commands":
 
 - The service holds only the source registry (`InputTriggerSource{trigger: '/'|'@', name, order?, candidates, onPick, matchSpace?, matchEnter?}`; (trigger,name) unique; the optional `order` sorts the roster — lower first, default 0, ties keep registration order — and that sorted roster is both group order and polling order) and `sessionOf(sctx)`. Implementing a match hook IS the declaration of participation in space/enter adjudication; the pipeline polls in roster order, the first non-undefined answer wins, and no claimant means the default sink. matchSpace is synchronous (space fires mid-keystroke; hot cache only); matchEnter is asynchronous (it may await the source's own warmup, and a warmup failure rejects).
-- The controller holds the single authoritative hit (span included; retained for Space after the menu closes), the per-session menu store, the candidate-fetch generation, keyboard arbitration (combobox mode: focus stays in the composer surface, ↑↓/Enter/Escape are intercepted and all pass the IME composition guard, with the single exception Shift+Enter unconditionally going first), and pick orchestration (outcome → self-dispatched bail events). `toggleSource(name, syntheticHit)` is the chrome-launch path: it seeds only that registered source over the caller's composer selection and publishes `launcher = name` until close; ordinary typed tracking clears the launcher and restores the full trigger roster. Both paths render the same MenuView and execute the same `onPick` chain. A `dismiss()` verb backs MenuView's injected `onDismiss` (a pointer down outside both the menu and the surrounding composer card closes the menu; MenuView also localizes group titles through the `slash.menu` locale namespace and clamps its height to the viewport space above the composer via ui-primitives' `useAnchoredMaxHeight`); at each session scope's birth it runs `warm(projection)` once over the source roster — within that scope the projection holds only the stable sessionId, with no published/capability transitions; the scope disposer tears down the controller.
+- The controller holds the single authoritative hit (span included; retained for Space after the menu closes), the per-session menu store, the candidate-fetch generation, keyboard arbitration (combobox mode: focus stays in the composer surface; ↑↓/Enter/Escape are intercepted; Tab settles a highlighted completion, using the candidate's drill action when available and its ordinary pick otherwise, while no highlight preserves native focus traversal; all arbitration passes the IME composition guard, with the single exception Shift+Enter unconditionally going first), and pick orchestration (outcome → self-dispatched bail events). `toggleSource(name, syntheticHit)` is the chrome-launch path: it seeds only that registered source over the caller's composer selection and publishes `launcher = name` until close; ordinary typed tracking clears the launcher and restores the full trigger roster. Both paths render the same MenuView and execute the same `onPick` chain. A `dismiss()` verb backs MenuView's injected `onDismiss` (a pointer down outside both the menu and the surrounding composer card closes the menu; MenuView also localizes group titles through the `slash.menu` locale namespace and clamps its height to the viewport space above the composer via ui-primitives' `useAnchoredMaxHeight`); at each session scope's birth it runs `warm(projection)` once over the source roster — within that scope the projection holds only the stable sessionId, with no published/capability transitions; the scope disposer tears down the controller.
 - Trigger-detection word boundaries (`user@host` and URL `/` never trigger) and the guard tiers (plain: `/` everywhere + `@` inline / claimed: `/` suppressed, `@` live / frozen: none) are the frozen pure core.
 
 ### hub / facade: the resident shell and the strict-session input body

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.zh.md

@@ -48,7 +48,7 @@ Status: implemented
 对「命令」零知识的触发/菜单/pick 流水线:
 
 - 服务只有 source 注册表(`InputTriggerSource{trigger: '/'|'@', name, order?, candidates, onPick, matchSpace?, matchEnter?}`;(trigger,name) 唯一;可选 `order` 对 roster 排序——越小越靠前、默认 0、同值保持注册序——排序后的 roster 同时是组序与轮询序)与 `sessionOf(sctx)`。实现 match 钩子即参与空格/回车裁决的声明;流水线按 roster 序轮询,首个非 undefined 应答胜出,无人认领落 default sink。matchSpace 同步(空格在击键中触发,只许热缓存);matchEnter 异步(可 await 源自身预热,预热失败即 reject)。
-- controller 持有唯一权威 hit(含 span;菜单关闭后为 Space 保留)、每会话 menu store、候选 fetch generation、键盘仲裁(combobox 模式:焦点始终在编辑器表面,↑↓/Enter/Escape 拦截且全程过 IME composition 守卫,唯一例外 Shift+Enter 无条件先行),以及 pick 编排(outcome → 自派 bail 事件)。`toggleSource(name, syntheticHit)` 是 chrome launcher 路径:它基于调用方的编辑器 selection,只 seed 对应的已注册 source,并发布 `launcher = name` 直至关闭;普通的键入式 tracking 会清除 launcher 并恢复完整的 trigger roster。两条路径渲染同一个 MenuView,并执行同一条 `onPick` 链。`dismiss()` 动词支撑 MenuView 注入的 `onDismiss`(指针落在菜单与所在 composer 卡片之外即关闭菜单;MenuView 还经 `slash.menu` locale 命名空间本地化组标题,并经 ui-primitives 的 `useAnchoredMaxHeight` 把高度收敛到 composer 上方的视口空间);每个会话作用域出生时对 source roster 做一次 `warm(projection)`,projection 在该 scope 内只有稳定的 sessionId,无 published/能力跃迁;scope disposer 拆除 controller。
+- controller 持有唯一权威 hit(含 span;菜单关闭后为 Space 保留)、每会话 menu store、候选 fetch generation、键盘仲裁(combobox 模式:焦点始终在编辑器表面;↑↓/Enter/Escape 会被拦截;Tab 会选定高亮补全项,候选项可下钻时走 drill 动作,否则走普通 pick,无高亮时保留原生焦点遍历;所有仲裁都经过 IME composition 守卫,唯一例外是 Shift+Enter 无条件先行),以及 pick 编排(outcome → 自派 bail 事件)。`toggleSource(name, syntheticHit)` 是 chrome launcher 路径:它基于调用方的编辑器 selection,只 seed 对应的已注册 source,并发布 `launcher = name` 直至关闭;普通的键入式 tracking 会清除 launcher 并恢复完整的 trigger roster。两条路径渲染同一个 MenuView,并执行同一条 `onPick` 链。`dismiss()` 动词支撑 MenuView 注入的 `onDismiss`(指针落在菜单与所在 composer 卡片之外即关闭菜单;MenuView 还经 `slash.menu` locale 命名空间本地化组标题,并经 ui-primitives 的 `useAnchoredMaxHeight` 把高度收敛到 composer 上方的视口空间);每个会话作用域出生时对 source roster 做一次 `warm(projection)`,projection 在该 scope 内只有稳定的 sessionId,无 published/能力跃迁;scope disposer 拆除 controller。
 - 触发检测词边界(`user@host`、URL `/` 永不触发)、守卫分档(plain:`/` 到处 + `@` 行内 / claimed:`/` 抑制、`@` 活 / frozen:全无)为冻结纯核。
 
 ### hub / facade:常驻外壳与严格会话输入体

+ 2 - 2
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md
-2026-08-08-native-windows-pull-request-ci.md: ff01add055b6daa3cd2ea1dd774c4c8973388113
-2026-08-08-native-windows-pull-request-ci.zh.md: 5378607687d03f1c98b4f6abc0b65b5d8e9ed2bb
+2026-08-08-native-windows-pull-request-ci.md: ade3b19bc1adbcd75ec7d3908670b9664186cba8
+2026-08-08-native-windows-pull-request-ci.zh.md: 10db2657a7f02813152d2905693627e44ce6caf7

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 1
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md


+ 3 - 3
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md

@@ -18,9 +18,9 @@ Status: implemented
 
 `windows-build` 与 `windows-native-tests` 是 `all checks passed` 的依赖项;其工作区构建和定向原生进程结果具有阻断性。`windows-coverage` 仍是常规作业,但不在聚合流程的 `needs` 中,因此逐文件 100% 覆盖率结果会保持红灯并可见,却不会延迟必需判定。`windows-observational` 同样不在聚合流程的 `needs` 中,并使用 `continue-on-error`,因为静态检查、文档、包与构建产物的阻断性判定由 Linux 负责。
 
-`windows-coverage` 会先完成一次工作区构建,再由[job 内分区覆盖率](2026-08-18-in-job-partitioned-coverage.zh.md)启动 4 个单 worker 插桩分片,并与一个双 worker 的豁免重型门禁并行运行。两项覆盖率门禁都将 Vitest 默认的单测试和轮询时间预算设为 30 秒。`windows-observational` 拥有自己的工作区构建和生产网站验证,会一起启动相互独立的静态门禁,并将 `publint` 限制为最多 8 个 worker。其 built-bin 冒烟测试只在其他所有观测性门禁结算后启动;冒烟测试的 `needs` 边仍要求构建成功,而 `after` 边会在其他门禁失败后保留这项诊断。这可避免有界的真实应用启动测量与 tool-catalog、NodeNext、包及文档进程争抢资源。translation-pairing 合并套件只导入 `scripts/` 源码和子进程,因此放入豁免重型套件门禁;V8 插桩不会为它贡献任何阈值覆盖率,却会放大 Git 进程延迟。Lefthook 并发 fixture 保留原有结果,采用 30 秒单用例预算与 10 秒进程就绪探测;安装器则允许被抢占的 lock 持有者在独占创建后用 5 秒发布记录。directory-picker 组合为防抖配置写入提供显式的 15 秒轮询预算;workspace-context 组合 fixture 使用测试自有、没有无关 1 秒截止时间的信号。LSP 源码与 ACL 沙箱源码仍计入 Windows 分母:基于 stub 的失败路径套件把每个进程内 ACL 沙箱文件都带到 100%,只有 runner 入口保持排除——它只作为 spawn 出的子进程在插桩运行之外执行,其行为由 runner 套件端到端钉住。窄范围且带注释的 V8 ignore 只覆盖不可达分支(另一平台专属分支、生命周期内不可达的防御守卫),其行为测试仍保留在所属平台。
+`windows-coverage` 与 Linux 覆盖率通道一致,不先构建工作区就运行[job 内分区覆盖率](2026-08-18-in-job-partitioned-coverage.zh.md):4 个单 worker 插桩分片与一个双 worker 的豁免重型门禁并行运行;工作区导入通过 tsconfig paths 映射解析到 `src`,消费构建产物的套件在未构建的检出上会自跳。两项覆盖率门禁都通过 `DSH_COVERAGE_TEST_TIMEOUT_MS=90000` 提高 Vitest 的单测试、expect.poll 与 hook 预算。`windows-observational` 拥有自己的工作区构建和生产网站验证,会一起启动相互独立的静态门禁,并将 `publint` 限制为最多 8 个 worker。其 built-bin 冒烟测试只在其他所有观测性门禁结算后启动;冒烟测试的 `needs` 边仍要求构建成功,而 `after` 边会在其他门禁失败后保留这项诊断。这可避免有界的真实应用启动测量与 tool-catalog、NodeNext、包及文档进程争抢资源。translation-pairing 合并套件只导入 `scripts/` 源码和子进程,因此放入豁免重型套件门禁;V8 插桩不会为它贡献任何阈值覆盖率,却会放大 Git 进程延迟。Lefthook 并发 fixture 保留原有结果,采用 30 秒单用例预算与 10 秒进程就绪探测;安装器则允许被抢占的 lock 持有者在独占创建后用 5 秒发布记录。directory-picker 组合为防抖配置写入提供显式的 15 秒轮询预算;workspace-context 组合 fixture 使用测试自有、没有无关 1 秒截止时间的信号。LSP 源码与 ACL 沙箱源码仍计入 Windows 分母:基于 stub 的失败路径套件把每个进程内 ACL 沙箱文件都带到 100%,只有 runner 入口保持排除——它只作为 spawn 出的子进程在插桩运行之外执行,其行为由 runner 套件端到端钉住。窄范围且带注释的 V8 ignore 只覆盖不可达分支(另一平台专属分支、生命周期内不可达的防御守卫),其行为测试仍保留在所属平台。
 
-16 核配置是这项清单经实测选定的容量规格。使用 6 个 coverage worker 的试验分别以 6 分 27 秒和 7 分 50 秒跑出完整通过结果,而在单个插桩 Vitest 进程内使用 4 个、3 个和 2 个并发 worker 的分支头精确试验暴露出不稳定的 fixture 与 worker 退出。相互独立的单 worker 子进程保留进程隔离。历史上的 16 分片样本把插桩覆盖率缩短到 112.66–122.01 秒。拉取请求覆盖率作业会在构建后调度 4 个插桩子进程和 2 个豁免 worker,而自托管完整参考流程会用 1 个 worker 串行运行未分片的覆盖率门禁。拉取请求若采用 6 分片配置,就会产生足以违反有界测试截止时间的进程与类型感知 lint 争用。16 个插桩分片加 2 个豁免 worker 会在计入系统开销前就超过 16 核分配。32 核对比仅将聚合门禁时间缩短 1.47 秒,且仍在 fork worker 内触发 CJS lexer 致命故障,因此增加核心数没有带来可靠的墙钟时间改善。
+16 核配置是这项清单经实测选定的容量规格。使用 6 个 coverage worker 的试验分别以 6 分 27 秒和 7 分 50 秒跑出完整通过结果,而在单个插桩 Vitest 进程内使用 4 个、3 个和 2 个并发 worker 的分支头精确试验暴露出不稳定的 fixture 与 worker 退出。相互独立的单 worker 子进程保留进程隔离。历史上的 16 分片样本把插桩覆盖率缩短到 112.66–122.01 秒。拉取请求覆盖率作业会在没有前置构建的情况下调度 4 个插桩子进程和 2 个豁免 worker,而自托管完整参考流程会用 1 个 worker 串行运行未分片的覆盖率门禁。拉取请求若采用 6 分片配置,就会产生足以违反有界测试截止时间的进程与类型感知 lint 争用。16 个插桩分片加 2 个豁免 worker 会在计入系统开销前就超过 16 核分配。32 核对比仅将聚合门禁时间缩短 1.47 秒,且仍在 fork worker 内触发 CJS lexer 致命故障,因此增加核心数没有带来可靠的墙钟时间改善。
 
 首次原生运行暴露出两项被兼容性通道掩盖的故障。文档投影测试此前只按 `/` 拆分来派生图片 basename;现在改为使用 Node 根据平台计算的 basename。Chokidar 消费方收到的 `%TEMP%` 以 `C:\\Users\\RUNNER~1` 这个 8.3 别名表示,而 libuv 返回的是长目录名,导致其 Windows 事件路径断言失败。共享的设置 watcher 与凭据 watcher,以及 Cordis 的模块 HMR(热模块替换)与精确配置 HMR,现在都会在打开 watcher 前规范化现有的原生监听基准路径或层级最深的现有祖先路径,并保留尚不存在的后缀;文件访问和诊断仍使用配置路径。模块 HMR 会挂接监听器并等待主 watcher 的 ready 事件,之后插件启动才会完成,因此启动后立即发生的编辑无法与初始扫描形成竞态。HMR 验收通过相同的异步原生 realpath 操作派生预期身份,避免同步 Windows 路径写法仍保留 8.3 别名。
 
@@ -52,6 +52,6 @@ Shiki 会禁用 TextMate 正则的延迟编译,并在用户内容进入保持
 
 Wine 保留必需聚合流程现有的关键路径和作业身份。`all checks passed` 变绿时,原生覆盖率与观测性结果仍可能处于待处理或红灯状态,因此分支保护采用 Wine 加定向原生构建和进程检查,而评审者和后续自动化采用其余原生结果。
 
-尽管如此,每个拉取请求都会获得真实 NT 内核、NTFS、PowerShell、Windows 进程、原生插件和受支持源码覆盖率信号。原生作业会重复设置流程,并在构建、覆盖率与观测性工作区中重复构建,但它们会降低每个作业的进程数,并暴露兼容性通道掩盖的路径、watcher、生命周期与 fixture 缺陷。
+尽管如此,每个拉取请求都会获得真实 NT 内核、NTFS、PowerShell、Windows 进程、原生插件和受支持源码覆盖率信号。原生作业会在构建、覆盖率与观测性工作区中重复设置流程,并在构建与观测性工作区中重复构建,但它们会降低每个作业的进程数,并暴露兼容性通道掩盖的路径、watcher、生命周期与 fixture 缺陷。
 
 维护者必须保留两种有意设计的执行拓扑:Wine 快照使用 Linux 安装加 hoisted 布局来触达 win32 二进制文件,而原生作业在组织自有的 16 核 Windows 运行器上使用相互独立的不可变工作区。任一拓扑独有的失败都必须依据该边界分类,不得削弱或静默跳过。

+ 2 - 2
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md
-2026-08-18-in-job-partitioned-coverage.md: 33824f0aa6f3541df8ca2e0cc8c417b40b5d7933
-2026-08-18-in-job-partitioned-coverage.zh.md: c32738462f7e33d9377d314a9ded5f82cebe9db3
+2026-08-18-in-job-partitioned-coverage.md: ded8d4a84d24fa42f6b48ac56472c9e54925772b
+2026-08-18-in-job-partitioned-coverage.zh.md: cf37d61744ab6482d67423275fa8ffb52cb851e0

+ 3 - 3
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md

@@ -18,7 +18,7 @@ When partitioning is enabled, `scripts/run-gates.ts` selects `pnpm run test:cove
 
 The coordinator waits for every child, validates that the blob directory contains exactly the expected files, and then runs one `vitest --merge-reports ... --coverage` command. Only that merged command applies the repository's per-file statement, branch, function, and line thresholds, so a partition is never judged against an intentionally partial inventory.
 
-`DSH_COVERAGE_MAX_WORKERS` continues to size the uninstrumented exempt gate and the ordinary non-partitioned path; it does not resize partition children. Native Windows gives the exempt gate two workers and admits four concurrent outer gates. The workspace build and production-site validation start immediately; both coverage gates wait for the complete build. The instrumented suite contains packer assertions over built `lib/` output, so this dependency prevents it from reading a partially emitted package closure, while also preventing the exempt gate's temporary Oxlint probes from racing source compilation. The observational inventory waits only for both coverage gates to settle, so it still runs after a coverage failure; each gate's `needs` dependencies remain pass-required. Linux overlaps four instrumented partition processes with two exempt workers, restoring the ordinary path's former four-way instrumented concurrency while keeping every instrumented process single-worker.
+`DSH_COVERAGE_MAX_WORKERS` continues to size the uninstrumented exempt gate and the ordinary non-partitioned path; it does not resize partition children. Native Windows gives the exempt gate two workers and admits four concurrent outer gates. In the complete reference, the workspace build and production-site validation start immediately and both coverage gates wait for the complete build; the wait also keeps the exempt gate's temporary Oxlint probes from racing source compilation. The pull-request coverage job runs the same zero-build coverage as Linux: workspace imports resolve to `src` through the tsconfig paths map, and the lib-consuming suites — the exempt gate's packer image assertions and full-corpus import sweep, and the instrumented corpus's client-bundle artifact check — self-skip on unbuilt checkouts. The observational inventory waits only for both coverage gates to settle, so it still runs after a coverage failure; each gate's `needs` dependencies remain pass-required. Linux overlaps four instrumented partition processes with two exempt workers, restoring the ordinary path's former four-way instrumented concurrency while keeping every instrumented process single-worker.
 
 ## Failure and output semantics
 
@@ -30,7 +30,7 @@ A normal failed test still emits a blob through `--coverage.reportOnFailure`, al
 
 `scripts/coverage-partitions.spec.ts` pins argument construction, package-script separator removal, one-worker partitions, weighted longest-processing-time assignment (including a case that fails when assignment ignores recorded weights), the single merged threshold command, failed-test merging, failure diagnostics before complete-blob validation, waiting for sibling partitions after a spawn failure, and link-safe cleanup. `scripts/run-gates.spec.ts` pins opt-in selection, invalid-count rejection, both native Windows coverage gates' complete-build dependency, the complete Windows inventory with its blocking split, and unbuffered streamed output. React fake-timer cases that can move between partitions advance timers inside `act()`; geometry-dependent portal tests stub their element rectangles so a different shard schedule cannot turn deferred updates or jsdom coordinates into coverage-only failures.
 
-Completed native Windows comparisons measured two partitions near 405 seconds and sixteen partitions at 112.66–122.01 seconds under the earlier gate ordering; those values compare partition latency, not the current peak. The current post-build phase runs four instrumented partition processes beside two exempt workers, for six coverage execution units. Sixteen partitions would raise that phase to eighteen before any still-running production-site work or system overhead. Four partitions keep separate-process isolation and match Linux, at the cost of a longer single-job coverage wall time; the trade-off is accepted to reduce vitest worker startup failures under high self-hosted concurrency. Two Linux samples measured the conservative two-partition configuration at 276.68 and 282.27 seconds; that configuration was stable but halved the ordinary path's four instrumented workers. Four partitions restore that fan-out, for six total coverage execution units on the 16-core hosted runner and at most 36 across the failover VM's six runner instances. These values come from completed runs or fixed capacity bounds; an unfinished run crossing an arbitrary elapsed-time mark is not evidence for increasing concurrency.
+Completed native Windows comparisons measured two partitions near 405 seconds and sixteen partitions at 112.66–122.01 seconds under the earlier gate ordering; those values compare partition latency, not the current peak. The current coverage phase runs four instrumented partition processes beside two exempt workers, for six coverage execution units. Sixteen partitions would raise that phase to eighteen before any still-running production-site work or system overhead. Four partitions keep separate-process isolation and match Linux, at the cost of a longer single-job coverage wall time; the trade-off is accepted to reduce vitest worker startup failures under high self-hosted concurrency. Two Linux samples measured the conservative two-partition configuration at 276.68 and 282.27 seconds; that configuration was stable but halved the ordinary path's four instrumented workers. Four partitions restore that fan-out, for six total coverage execution units on the 16-core hosted runner and at most 36 across the failover VM's six runner instances. These values come from completed runs or fixed capacity bounds; an unfinished run crossing an arbitrary elapsed-time mark is not evidence for increasing concurrency.
 
 ## Alternatives considered
 
@@ -46,6 +46,6 @@ Completed native Windows comparisons measured two partitions near 405 seconds an
 
 Coverage pays one Vitest startup/configuration cost per partition and one report-merge cost, but it avoids another workflow topology and keeps one final threshold verdict. Partition output may interleave, while the partition start labels and Vitest file identities retain attribution.
 
-Linux and Windows use the same coordinator with platform-specific partition counts and surrounding worker budgets. Native Windows starts both coverage gates after the complete build because its instrumented corpus can consume built artifacts; Linux's dedicated coverage job does not share a workspace with a concurrent build. Local coverage stays simple unless a caller explicitly chooses the partitioned package script and supplies a valid count greater than one.
+Linux and Windows use the same coordinator with platform-specific partition counts and surrounding worker budgets. Both pull-request coverage lanes run without a preceding build; the complete reference (serial-windows standby) still starts its coverage gates after the build gate so the lib-consuming suites execute against real artifacts. Local coverage stays simple unless a caller explicitly chooses the partitioned package script and supplies a valid count greater than one.
 
 Future tuning starts from completed runs at one fixed configuration. Slow progress alone never raises partition count or outer concurrency, because repeated restarts would erase the only evidence needed to choose a stable setting.

+ 3 - 3
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md

@@ -18,7 +18,7 @@ Status: implemented
 
 协调器等待全部子进程结束,验证 blob 目录只包含预期文件,然后执行一次 `vitest --merge-reports ... --coverage`。只有这条合并命令应用仓库的逐文件语句、分支、函数与行阈值,因此系统不会拿有意不完整的测试清单单独判定任一分区。
 
-`DSH_COVERAGE_MAX_WORKERS` 继续控制无插桩豁免门禁和普通非分区路径的规模,不会调整分区子进程。原生 Windows 为豁免门禁分配 2 个 worker,并允许 4 道外层门禁并发。工作区构建与生产网站验证会立即启动;两道覆盖率门禁都等待完整构建。插桩套件包含针对已构建 `lib/` 输出的打包器断言,因此这项依赖可避免它读取只完成部分产出的包闭包,也可避免豁免门禁的临时 Oxlint 探针与源码编译竞态。观测性清单只等待两道覆盖率门禁结算,因此在覆盖率失败后仍会运行;各门禁自身的 `needs` 依赖仍要求前置门禁通过。Linux 让 4 个插桩分区进程与 2 个豁免 worker 重叠运行,在保持每个插桩进程只有 1 个 worker 的同时,恢复普通路径原有的 4 路插桩并发。
+`DSH_COVERAGE_MAX_WORKERS` 继续控制无插桩豁免门禁和普通非分区路径的规模,不会调整分区子进程。原生 Windows 为豁免门禁分配 2 个 worker,并允许 4 道外层门禁并发。在完整参考流程中,工作区构建与生产网站验证会立即启动,两道覆盖率门禁都等待完整构建;这次等待也能避免豁免门禁的临时 Oxlint 探针与源码编译竞态。拉取请求覆盖率 job 与 Linux 一样以零构建方式运行:工作区导入通过 tsconfig paths 映射解析到 `src`,而消费构建产物的套件——豁免门禁的打包器镜像断言与全语料导入 sweep,以及插桩语料的 client-bundle 产物校验——在未构建的检出上会自跳。观测性清单只等待两道覆盖率门禁结算,因此在覆盖率失败后仍会运行;各门禁自身的 `needs` 依赖仍要求前置门禁通过。Linux 让 4 个插桩分区进程与 2 个豁免 worker 重叠运行,在保持每个插桩进程只有 1 个 worker 的同时,恢复普通路径原有的 4 路插桩并发。
 
 ## 失败与输出语义
 
@@ -30,7 +30,7 @@ Status: implemented
 
 `scripts/coverage-partitions.spec.ts` 固定了参数构造、包脚本分隔符移除、单 worker 分区、加权最长处理时间分配(含一个在分配忽略记录权重时必然失败的用例)、唯一一次合并阈值命令、失败测试合并、完整 blob 校验前的失败诊断、spawn 失败后等待兄弟分区,以及链接安全清理。`scripts/run-gates.spec.ts` 固定了显式启用、非法数量拒绝、两道原生 Windows 覆盖率门禁对完整构建的依赖、完整 Windows 清单及其阻断性划分,以及不缓冲的流式输出。可能在分区间移动的 React fake-timer 用例会在 `act()` 内推进计时器;依赖几何位置的 portal 测试会固定元素矩形,使不同分片调度不会把延迟更新或 jsdom 坐标变成只在覆盖率运行中出现的失败。
 
-已完成的原生 Windows 对比中,双分区耗时约 405 秒,16 分区耗时 112.66–122.01 秒;这些数据来自先前的门禁顺序,只用于比较分区延迟,不代表当前峰值。当前的构建后阶段会让 4 个插桩分区进程与 2 个豁免 worker 并行,共形成 6 个覆盖率执行单元。若改为 16 个分区,则在尚未结束的生产网站工作或系统开销计入之前,该阶段就会达到 18 个执行单元。4 个分区保留独立进程隔离并与 Linux 对齐,代价是单 job 覆盖率墙钟更长;这是为了降低自托管高并发下 vitest worker 启动失败而接受的取舍。两个 Linux 样本中,保守的双分区配置耗时 276.68 秒和 282.27 秒;该配置运行稳定,却把普通路径原有的 4 个插桩 worker 减半。4 个分区恢复这份并发,使 16 核托管 runner 上的覆盖率执行单元总数为 6,故障切换虚拟机的 6 个 runner 实例最多合计 36 个执行单元。这些数值来自完整运行或固定容量上限;运行尚未结束时跨过任意耗时刻度,不构成增加并发的证据。
+已完成的原生 Windows 对比中,双分区耗时约 405 秒,16 分区耗时 112.66–122.01 秒;这些数据来自先前的门禁顺序,只用于比较分区延迟,不代表当前峰值。当前的覆盖率阶段会让 4 个插桩分区进程与 2 个豁免 worker 并行,共形成 6 个覆盖率执行单元。若改为 16 个分区,则在尚未结束的生产网站工作或系统开销计入之前,该阶段就会达到 18 个执行单元。4 个分区保留独立进程隔离并与 Linux 对齐,代价是单 job 覆盖率墙钟更长;这是为了降低自托管高并发下 vitest worker 启动失败而接受的取舍。两个 Linux 样本中,保守的双分区配置耗时 276.68 秒和 282.27 秒;该配置运行稳定,却把普通路径原有的 4 个插桩 worker 减半。4 个分区恢复这份并发,使 16 核托管 runner 上的覆盖率执行单元总数为 6,故障切换虚拟机的 6 个 runner 实例最多合计 36 个执行单元。这些数值来自完整运行或固定容量上限;运行尚未结束时跨过任意耗时刻度,不构成增加并发的证据。
 
 ## 曾考虑的替代方案
 
@@ -46,6 +46,6 @@ Status: implemented
 
 每个分区都要支付 1 次 Vitest 启动与配置开销,最后还要执行 1 次报告合并,但它不引入另一套工作流拓扑,并保留唯一的最终阈值判定。分区输出可能交错,但分区启动标签和 Vitest 文件标识仍可用于归因。
 
-Linux 与 Windows 使用相同的协调器,并各自设置分区数量与外围 worker 预算。原生 Windows 会在完整构建之后启动两道覆盖率门禁,因为其插桩语料可能消费构建产物;Linux 的专用覆盖率 job 不会与同一工作区中的并发构建共享目录。本地覆盖率默认保持简单;只有调用方显式选择分区包脚本并提供大于 1 的合法数量时,才启用分区。
+Linux 与 Windows 使用相同的协调器,并各自设置分区数量与外围 worker 预算。两条拉取请求覆盖率通道都以零构建方式运行;完整参考流程(serial-windows standby)仍在构建门禁之后启动其覆盖率门禁,使消费构建产物的套件对真实产物执行。本地覆盖率默认保持简单;只有调用方显式选择分区包脚本并提供大于 1 的合法数量时,才启用分区。
 
 未来调优从一个固定配置的完整运行开始。进度缓慢本身绝不会提高分区数量或外层并发,因为反复重启会抹掉选择稳定设置所需的唯一证据。

+ 5 - 3
.github/workflows/ci.yml

@@ -531,9 +531,11 @@ jobs:
           } else {
             pnpm install --frozen-lockfile
           }
-      - name: Build before coverage
-        shell: pwsh
-        run: pnpm run build
+      # No build before coverage, matching the Linux lane: workspace imports
+      # resolve to src through the tsconfig paths map, and the lib-consuming
+      # suites (webworker-packer image-loadable, webworker-runtime
+      # transform-corpus, client ui-trajectory client-bundle) self-skip on
+      # unbuilt checkouts.
       - name: Run Windows coverage
         shell: pwsh
         run: pnpm run check:ci:coverage

+ 7 - 3
apps/web/tests/goal-command-presentation.e2e.ts

@@ -43,15 +43,19 @@ describe('web e2e: /goal human transcript presentation', () => {
     await scaffold?.close()
   })
 
-  it('shows the bare input and result from a fresh session without a model turn', async () => {
+  it('completes with Tab and shows the bare input and result without a model turn', async () => {
     onTestFailed(() => saveFailureShot(page, 'web-e2e-goal-command-presentation'))
     await expect.poll(() => page.getByText('Into the Unknown', { exact: false }).count(), {
       timeout: 15_000,
     }).toBe(1)
     const input = page.locator('[data-composer-input]').first()
-    await input.fill('/goal')
-    await input.press('Enter')
+    await input.fill('/go')
+    const menu = page.getByRole('listbox', { name: 'Trigger suggestions' })
+    await menu.getByRole('option', { name: 'goal set or view the goal for a long-running task' })
+      .waitFor({ timeout: 10_000 })
+    await input.press('Tab')
     await expect.poll(() => input.textContent()).toBe('/goal ')
+    await expect.poll(() => menu.count()).toBe(0)
     await input.press('Enter')
 
     const commandInput = page.locator('[data-command-input]')

+ 3 - 3
packages/client/ui-conversation/src/client/input/editor/keymap.ts

@@ -68,7 +68,7 @@ export function registerComposerKeymap(editor: LexicalEditor, handlers: Composer
 
   const arrow = (key: ArbitrateKey) => (event: KeyboardEvent | null): boolean => {
     const inComposition = event !== null && isComposingEvent(event, recentlyComposing)
-    if (handlers.arbitrate(key, inComposition) === 'consumed') {
+    if (handlers.arbitrate(key, inComposition) !== 'pass') {
       event?.preventDefault()
       return true
     }
@@ -84,8 +84,8 @@ export function registerComposerKeymap(editor: LexicalEditor, handlers: Composer
     }),
     editor.registerCommand(KEY_ARROW_UP_COMMAND, arrow('up'), COMMAND_PRIORITY_CRITICAL),
     editor.registerCommand(KEY_ARROW_DOWN_COMMAND, arrow('down'), COMMAND_PRIORITY_CRITICAL),
-    // Tab drills into a drillable highlighted row; otherwise it passes so the
-    // browser keeps its native focus traversal.
+    // Tab acts only when the trigger menu has a highlighted completion;
+    // otherwise it passes so the browser keeps its native focus traversal.
     editor.registerCommand(KEY_TAB_COMMAND, arrow('tab'), COMMAND_PRIORITY_CRITICAL),
     editor.registerCommand(KEY_ESCAPE_COMMAND, (event) => {
       // Escape layering: an open overlay closes; claimed without an overlay

+ 3 - 1
packages/client/ui-conversation/tests/keymap-routing.client.spec.tsx

@@ -43,6 +43,7 @@ describe('keymap keydown routing', () => {
     registerPlainText(editor)
     const arbitrate = vi.fn<(key: string, composing: boolean) => 'consumed' | 'pick-highlighted' | 'pass'>()
       .mockReturnValueOnce('consumed')
+      .mockReturnValueOnce('pick-highlighted')
       .mockReturnValue('pass')
     registerComposerKeymap(editor, {
       arbitrate,
@@ -56,8 +57,9 @@ describe('keymap keydown routing', () => {
     const consumed = fireEvent.keyDown(root, { key: 'Tab', keyCode: 9 })
     expect(arbitrate).toHaveBeenCalledWith('tab', false)
     expect(consumed).toBe(false) // consumed: preventDefault fired
+    const picked = fireEvent.keyDown(root, { key: 'Tab', keyCode: 9 })
+    expect(picked).toBe(false) // picked: the completion replaces native traversal
     const passed = fireEvent.keyDown(root, { key: 'Tab', keyCode: 9 })
     expect(passed).toBe(true) // pass: the browser keeps native focus traversal
   })
-
 })

+ 2 - 2
packages/client/ui-input-trigger/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/client/ui-input-trigger/README.md
-README.md: 1f316229327d33f48e0950a2c27e143c6c991f99
-README.zh.md: 324541120406eac8677afcfda0967540dd74848e
+README.md: 83fcffbd1dbf20ad070ef9edccb74f2e5beed9e6
+README.zh.md: a7c2bbad1a1a982a99327d21d7afd6466cbd201e

+ 1 - 1
packages/client/ui-input-trigger/README.md

@@ -29,7 +29,7 @@ Mount this plugin alongside `ui-conversation`; the menu then appears in the inpu
 
 ### Keyboard and mouse
 
-The composer surface keeps focus while the menu is open: rows pick on mousedown, the highlight rides `aria-activedescendant`, and a pointer press outside both the menu and the composer card dismisses it. Space and Enter adjudication polls the optional `matchSpace`/`matchEnter` hooks in registration order; the first non-undefined answer wins, and a source can refuse a submission it cannot consume whole. A candidate declaring `drill: true` carries a second verb beside the settling pick: its trailing chevron and the Tab key route the same row through `onPick` with `action: 'drill'` (every other path reports `'pick'`), and Tab passes untouched on rows without the flag so native focus traversal survives. A source implementing the optional `header` hook additionally publishes crumbs above its group: the pipeline re-polls it on every hit with the live query and whether a drill, rather than typing, produced it, and a crumb pick routes back through `onPick` with `action: 'drill'`.
+The composer surface keeps focus while the menu is open: rows pick on mousedown, the highlight rides `aria-activedescendant`, and a pointer press outside both the menu and the composer card dismisses it. Space and Enter adjudication polls the optional `matchSpace`/`matchEnter` hooks in registration order; the first non-undefined answer wins, and a source can refuse a submission it cannot consume whole. Tab acts on the highlighted completion: a candidate declaring `drill: true` routes through `onPick` with `action: 'drill'`, while an ordinary candidate settles through `action: 'pick'`; without a highlight, Tab passes untouched so native focus traversal survives. A drillable row's trailing chevron exposes the same second verb to pointer users. A source implementing the optional `header` hook additionally publishes crumbs above its group: the pipeline re-polls it on every hit with the live query and whether a drill, rather than typing, produced it, and a crumb pick routes back through `onPick` with `action: 'drill'`.
 
 -----
 

+ 1 - 1
packages/client/ui-input-trigger/README.zh.md

@@ -29,7 +29,7 @@ kind: "package-reference"
 
 ### 键盘与鼠标
 
-菜单打开期间 composer 表面保持焦点:行在 mousedown 时完成 pick,高亮由 `aria-activedescendant` 承载,指针落在菜单与所在 composer 卡片之外即关闭菜单。空格与回车裁决按注册序轮询可选的 `matchSpace`/`matchEnter` 钩子;第一个非 undefined 的应答胜出,source 也可以拒绝它无法整体消费的提交。声明 `drill: true` 的候选行在选定 pick 之外携带第二个动词:行尾的 chevron 与 Tab 键把同一行以 `action: 'drill'` 送入 `onPick`(其余路径一律报告 `'pick'`);未声明该标记的行上 Tab 原样放行,原生焦点遍历不受影响。实现可选 `header` 钩子的 source 还会在其分组上方发布面包屑:管线在每次命中时用实时查询、以及该查询由下钻还是由键入产生这一事实重新询问它,点击面包屑经 `onPick` 以 `action: 'drill'` 回到该 source。
+菜单打开期间 composer 表面保持焦点:行在 mousedown 时完成 pick,高亮由 `aria-activedescendant` 承载,指针落在菜单与所在 composer 卡片之外即关闭菜单。空格与回车裁决按注册序轮询可选的 `matchSpace`/`matchEnter` 钩子;第一个非 undefined 的应答胜出,source 也可以拒绝它无法整体消费的提交。Tab 会作用于高亮补全项:声明 `drill: true` 的候选项以 `action: 'drill'` 进入 `onPick`,普通候选项则以 `action: 'pick'` 完成选定;没有高亮项时 Tab 原样放行,原生焦点遍历不受影响。可下钻行尾的 chevron 向指针用户提供同一个动词。实现可选 `header` 钩子的 source 还会在其分组上方发布面包屑:管线在每次命中时用实时查询、以及该查询由下钻还是由键入产生这一事实重新询问它,点击面包屑经 `onPick` 以 `action: 'drill'` 回到该 source。
 
 -----
 

+ 16 - 9
packages/client/ui-input-trigger/src/client/controller.ts

@@ -214,7 +214,11 @@ export class InputTriggerController {
    * Keyboard arbitration while the menu is open.
    * @param key - intercepted key.
    * @param composing - inside IME composition: everything passes.
-   * @returns consumed / pick-highlighted / pass.
+   * @returns `pass` when the browser keeps the key (closed menu, no
+   * highlight, or a vanished candidate), `consumed` when the menu handled
+   * the key without a settling pick (move, close, drill descent, or a
+   * pending-refinement no-op), or `pick-highlighted` when the highlighted
+   * candidate settled and the menu closed.
    */
   arbitrate(key: ArbitrateKey, composing: boolean): ArbitrateOutcome {
     if (composing || this.disposed) return 'pass'
@@ -245,16 +249,19 @@ export class InputTriggerController {
         return 'pick-highlighted'
       }
       case 'tab': {
-        // Tab drills into the highlighted candidate when it offers descent;
-        // otherwise the key passes so native focus behavior is untouched.
         if (state.highlight === null) return 'pass'
         const group = state.groups.find(g => g.source === state.highlight?.source)
-        const item = group !== undefined && group.status === 'ready'
-          ? group.items[state.highlight.index]
-          : undefined
-        if (item?.drill !== true) return 'pass'
-        this.pick(state.highlight.source, state.highlight.index, 'drill')
-        return 'consumed'
+        // Pending refinement keeps the stale highlight visible: consume the
+        // gesture rather than pick a stale row or let Tab move focus away.
+        if (group === undefined || group.status !== 'ready') return 'consumed'
+        const item = group.items[state.highlight.index]
+        if (item === undefined) return 'pass'
+        if (item.drill === true) {
+          this.pick(state.highlight.source, state.highlight.index, 'drill')
+          return 'consumed'
+        }
+        this.pick(state.highlight.source, state.highlight.index)
+        return 'pick-highlighted'
       }
     }
   }

+ 33 - 6
packages/client/ui-input-trigger/tests/service.client.spec.ts

@@ -889,7 +889,7 @@ describe('arbitrate', () => {
     expect(controller.menu.getSnapshot().open).toBe(false)
   })
 
-  it('tab drills into a drillable highlight and passes on plain rows', async () => {
+  it('tab drills into a drillable highlight and picks a plain completion', async () => {
     const drillable = readySource('/', 'command', [{ name: 'src', drill: true }, { name: 'plan' }], () => undefined)
     const { controller } = controllerBench([drillable.source])
     controller.track('/s', 2, { tier: 'plain' }, 1)
@@ -897,12 +897,37 @@ describe('arbitrate', () => {
     expect(controller.arbitrate('tab', false)).toBe('consumed')
     expect(drillable.picks[0]!.action).toBe('drill')
     expect(drillable.picks[0]!.candidate.name).toBe('src')
-    // Plain row (no drill flag): the key passes so native focus stays intact.
+    // Plain row (no drill flag): Tab settles the highlighted completion.
     controller.track('/s', 2, { tier: 'plain' }, 2)
     await tick()
     controller.arbitrate('down', false)
-    expect(controller.arbitrate('tab', false)).toBe('pass')
-    expect(drillable.picks).toHaveLength(1)
+    expect(controller.arbitrate('tab', false)).toBe('pick-highlighted')
+    expect(drillable.picks[1]!.action).toBe('pick')
+    expect(drillable.picks[1]!.candidate.name).toBe('plan')
+    expect(controller.menu.getSnapshot().open).toBe(false)
+  })
+
+  it('tab during a pending refinement is consumed: no pick, no focus traversal', async () => {
+    const picks: string[] = []
+    const cmd = deferredSource('/', 'command', {
+      onPick: (pick) => { picks.push(pick.candidate.name); return undefined },
+    })
+    const { controller } = controllerBench([cmd.source])
+    controller.track('/g', 2, { tier: 'plain' }, 1)
+    cmd.pending[0]!.resolve([{ name: 'goal' }, { name: 'plan' }])
+    await tick()
+    expect(controller.menu.getSnapshot().highlight).toEqual({ source: 'command', index: 0 })
+    // Refinement: previous rows and highlight stay visible while the fetch pends.
+    controller.track('/go', 3, { tier: 'plain' }, 2)
+    expect(controller.menu.getSnapshot().highlight).toEqual({ source: 'command', index: 0 })
+    expect(controller.arbitrate('tab', false)).toBe('consumed')
+    expect(picks).toHaveLength(0)
+    expect(controller.menu.getSnapshot().open).toBe(true)
+    // Settled: the same gesture settles the highlighted completion.
+    cmd.pending[1]!.resolve([{ name: 'goal' }])
+    await tick()
+    expect(controller.arbitrate('tab', false)).toBe('pick-highlighted')
+    expect(picks).toEqual(['goal'])
   })
 
   it('a settling pick reports the pick action', async () => {
@@ -913,19 +938,21 @@ describe('arbitrate', () => {
 
   it('IME composition passes every key untouched', async () => {
     const { controller } = await menuBench()
-    for (const key of ['up', 'down', 'enter', 'escape'] as const) {
+    for (const key of ['up', 'down', 'enter', 'escape', 'tab'] as const) {
       expect(controller.arbitrate(key, true)).toBe('pass')
     }
     expect(controller.menu.getSnapshot().open).toBe(true)
   })
 
-  it('closed menu passes; an open menu without a highlight passes enter', () => {
+  it('closed menu passes; an open menu without a highlight passes picking keys', () => {
     const cmd = deferredSource('/', 'command')
     const { controller } = controllerBench([cmd.source])
     expect(controller.arbitrate('enter', false)).toBe('pass')
+    expect(controller.arbitrate('tab', false)).toBe('pass')
     // Open with the only group still pending: nothing to pick yet.
     controller.track('/g', 2, { tier: 'plain' }, 1)
     expect(controller.arbitrate('enter', false)).toBe('pass')
+    expect(controller.arbitrate('tab', false)).toBe('pass')
   })
 
   it('enter during a pending refinement is consumed: no pick, no submit fallthrough', async () => {

+ 4 - 4
packages/experimental/webworker-packer/tests/image-loadable.spec.ts

@@ -68,10 +68,10 @@ describe('preview example overlays', () => {
 })
 
 /**
- * The pack consumes built `lib/` output. An unbuilt checkout (the unit
- * coverage lane runs before any build) self-skips. Native Windows routes this
- * suite through its post-build uninstrumented gate, and preview builds exercise
- * the same path against complete real artifacts.
+ * The pack consumes built `lib/` output. An unbuilt checkout (both coverage
+ * lanes run before any build) self-skips; the serial-windows complete
+ * reference routes this suite through its post-build uninstrumented gate, and
+ * preview builds exercise the same path against complete real artifacts.
  */
 const subjectBuilt = existsSync(join(repoRoot, 'packages/util/timeout/lib/index.js'))
 

+ 8 - 0
scripts/ci-workflow.spec.ts

@@ -156,6 +156,14 @@ describe('CI workflow', () => {
       isRecord(step) && typeof step.run === 'string'
     ))
     expect(coverageCommands.map(step => step.run)).toContain('pnpm run check:ci:coverage')
+    // Windows coverage runs zero-build like the Linux lane: workspace imports
+    // resolve to src through the tsconfig paths map, and the lib-consuming
+    // suites (webworker-packer image-loadable, webworker-runtime
+    // transform-corpus, client ui-trajectory client-bundle) self-skip on
+    // unbuilt checkouts. The regex catches a regression spelled as
+    // 'corepack pnpm run build' or folded into a multi-line run block, which
+    // an exact string match would miss.
+    expect(coverageCommands.every(step => !/\bpnpm\s+run\s+build(?:\s|$)/.test(step.run))).toBe(true)
 
     // windows-native-tests runs the Windows-specific specs.
     expect(windowsNativeTests.name).toBe('windows node 24 / native tests')

+ 3 - 2
scripts/coverage-exempt.ts

@@ -50,8 +50,9 @@ export const coverageExemptHeavySuites: readonly CoverageExemptSuite[] = [
   { filter: 'scripts/change-scope.spec.ts', exclude: 'scripts/change-scope.spec.ts' },
   { filter: 'scripts/translation-pairing-merge.spec.ts', exclude: 'scripts/translation-pairing-merge.spec.ts' },
   // Built-artifact proof. Packer/runtime src is threshold-excluded, and the
-  // native Windows aggregate makes this uninstrumented gate wait for build so
-  // the suite never observes a partially emitted workspace closure.
+  // suite self-skips on unbuilt checkouts; the serial-windows complete
+  // reference still starts this uninstrumented gate after its build gate, so
+  // the assertions execute against complete real artifacts there.
   {
     filter: 'packages/experimental/webworker-packer/tests/image-loadable.spec.ts',
     exclude: 'packages/experimental/webworker-packer/tests/image-loadable.spec.ts',

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است