docs(B04): record image lifecycle regression and pending native frame acceptance

This commit is contained in:
Codex committed 2026-10-21 15:17:15 +09:00
1 parent 342820690f
commit 1df1861a7a
3 files changed
+1027

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
# B04 交付 01:图片地址首帧闪现
日期:2026-10-21(Asia/Shanghai)。任务 YC-008。状态:**已实现、SSR/DOM 回归与构建通过;真实 Windows 连续帧待验**。
- 基线:`0b613e522d444cbd8cd44ca17e9b1be498517978`。
- **受测代码 SHA:`342820690f644fb1cb3b0a76b143dbcd41aac8c4`**。
- 需求:[B04-需求](B04-需求.md)。产品只改 `MarkdownImage.tsx`;未改 CompletedFold、动画、URL 安全 helper、预览/下载实现、历史数据或用户配置。
## 修复
1. 安全且不需要宿主解析的 src 首次渲染就进入 img,不再等 effect 后从文本切换。
2. 异步未完成显示 alt 或“图片加载中…”;失败显示 alt 或“图片加载失败”。任何 fallback 都不使用 src,不泄露整个 data/blob/文件地址。
3. src 作为内部组件生命周期 key,切换地址及 A→B→A 都有新的图片与失败状态。
4. 解析状态绑定 src/resolver/宿主偏好/回调的请求身份;在提交前清除不匹配状态,保留取消与过期结果隔离。旧解析、旧图片 error 不覆盖新图;即时 image error 也不会被被动 effect 的重置吞掉。
5. 原预览选图和点击回调保留;URL allowlist、宿主优先解析和下载失败处理仍走原路径。
## 实测
[DOM 报告](evidence/B04-delivery-01/dom-lifecycle.json)、[命令证据](evidence/B04-delivery-01/verification.txt)。Node 24.19.0/npm 11.9.0、Linux x64、jsdom 30.1.3,无新增依赖。
- `test:image-lifecycle`:**159 项断言 / 85 个 DOM 样本通过**。包括合成约 25KB Base64/空 alt,布局 effect 前与同步提交探针、错误/无 host/非法地址、同 src resolver 切换、host 偏好切换、立即 error、A→B→A、过期成功/失败、卸载取消、预览;真实 MessageBubble/CompletedFold 在多个合成会话里反复开关并确认图片重挂载。
- 基线负对照:同一新夹具只替换回基线 MarkdownImage,冷挂载失败,证明测试能捕获旧行为。负对照执行器退出 0 表示预期失败被确认,不代表旧产品通过。
- `npm run test:media`:Mermaid、图片 SSR/URL 安全/预览及下载错误处理、23 项路径回归通过。修正旧的“fallback 包含源路径”断言,新增直接安全图片 SSR 断言。
- `npm test`、指定 DOM 依赖后的 `test:alignment:dom`、`npm run build`:均退出 0。
- `test-image-lifecycle.mjs --build-only` 与原生 CJS 脚本语法检查:通过,仅证明夹具打包/语法,不是浏览器执行。
- 独立复审重跑 media 和 159/85 生命周期回归,无阻塞发现;`git diff --check` 通过。
- `check:tokens`:仍退出 1,同样六项既存失败及计数;本轮无 CSS 改动或 baseline 放宽。
**没有执行真实浏览器/Electron 连续帧**:当前图形环境此前已确认不可用,本轮没有重复绕过或禁用 sandbox。报告中的 jsdom height=0 不是正常布局高度,不能据此宣布需求 AC3 通过。真实图片解码、Windows 帧高度、预览窗口与实际下载仍需验收。
## 验收标准对照
- AC1:SSR、首次 DOM 提交到稳定态无 URI 文本,通过;真实首帧待验。
- AC2:实际 MessageBubble/CompletedFold 多次开关与跨合成会话重挂载无 URI 文本,通过 DOM;实际视觉闪现待验。
- AC3:提供 MutationObserver + 连续 rAF 夹具,**未执行原生高度验收**,不能只用稳定截图关闭。
- AC4/5:失败短占位与快速切换/取消/过期结果覆盖通过离线回归;浏览器实际加载事件矩阵待验。
- AC6:既有 URL/预览/下载错误回归通过,产品相关代码未改;真正下载与系统预览窗口未在本环境执行。
## Windows 连续帧复跑
固定代码 SHA 的独立检出安装构建,不使用用户会话或图片。以下只生成/运行受控组件夹具:
```powershell
npm ci --legacy-peer-deps
npm run build
npm install --prefix .tmp/b04-dom --ignore-scripts --no-audit --no-fund jsdom@30.1.3
$env:YCODE_TEST_DOM_MODULE = Join-Path $PWD '.tmp/b04-dom/node_modules/jsdom'
npm run test:image-lifecycle
npm run test:media
node scripts/test-image-lifecycle.mjs --build-only
# 用上一条输出的 output 绝对目录替换 <fixture目录>
node node_modules/electron/cli.js scripts/verify-b04-image-native.cjs <fixture目录>
```
原生 wrapper 使用 CJS、临时 userData、sandbox:true,阻止网络请求;只加载合成图片和本地夹具,20 秒超时。输出 `native-report.json`,连续帧记录在 report.samples 中。夹具验证图片 complete/naturalWidth=320、出现非零图片尺寸,以及无 URI 文本/异常数千像素增高;探针结果写在采样根之外,避免采到报告自身。
可以在获准浏览器打开 build-only 目录的 index.html 观察相同夹具;这仍不是完整安装应用或同版本 WorkBuddy 像素对照。验收端另用隔离历史夹具复核真正对话详情,不修改正式历史数据。
远端推送并核对 SHA 后才完成 Git 交接;本轮不改变 B01/B02/B03 的验收结论。
@@ -0,0 +1,763 @@
{
"checks": [
"cold: layout-before-passive-effects: no URI text",
"cold: sync-commit: no URI text",
"safe first commit image",
"layout probe sees image before passive effects",
"stable: no URI text",
"native image error: no image",
"native image error: concise",
"native image error: no URI text",
"error recovery new src: layout-before-passive-effects: no URI text",
"error recovery new src: sync-commit: no URI text",
"new safe src resets prior error synchronously",
"invalid/no-host: layout-before-passive-effects: no URI text",
"invalid/no-host: sync-commit: no URI text",
"invalid/no-host initial: no image",
"invalid/no-host initial: concise",
"invalid/no-host initial: no URI text",
"invalid/no-host settled: no image",
"invalid/no-host settled: concise",
"invalid/no-host settled: no URI text",
"invalid/no-host: layout-before-passive-effects: no URI text",
"invalid/no-host: sync-commit: no URI text",
"invalid/no-host initial: no image",
"invalid/no-host initial: concise",
"invalid/no-host initial: no URI text",
"invalid/no-host settled: no image",
"invalid/no-host settled: concise",
"invalid/no-host settled: no URI text",
"invalid/no-host: layout-before-passive-effects: no URI text",
"invalid/no-host: sync-commit: no URI text",
"invalid/no-host initial: no image",
"invalid/no-host initial: concise",
"invalid/no-host initial: no URI text",
"invalid/no-host settled: no image",
"invalid/no-host settled: concise",
"invalid/no-host settled: no URI text",
"same source first resolver: layout-before-passive-effects: no URI text",
"same source first resolver: sync-commit: no URI text",
"same source second resolver: layout-before-passive-effects: no URI text",
"same source second resolver: sync-commit: no URI text",
"old resolver identity ignored: no image",
"old resolver identity ignored: concise",
"old resolver identity ignored: no URI text",
"new resolver identity wins",
"local direct: layout-before-passive-effects: no URI text",
"local direct: sync-commit: no URI text",
"existing unpreferred local semantics",
"prefer host enabled: layout-before-passive-effects: no URI text",
"prefer host enabled: sync-commit: no URI text",
"host preference removes direct file before effects: no image",
"host preference removes direct file before effects: concise",
"host preference removes direct file before effects: no URI text",
"preferred host result",
"same direct first adapter: layout-before-passive-effects: no URI text",
"same direct first adapter: sync-commit: no URI text",
"direct failed before new adapter: no image",
"direct failed before new adapter: concise",
"direct failed before new adapter: no URI text",
"same direct new adapter: layout-before-passive-effects: no URI text",
"same direct new adapter: sync-commit: no URI text",
"immediate error after adapter reset retained: no image",
"immediate error after adapter reset retained: concise",
"immediate error after adapter reset retained: no URI text",
"A to B: layout-before-passive-effects: no URI text",
"A to B: sync-commit: no URI text",
"B to A retry: layout-before-passive-effects: no URI text",
"B to A retry: sync-commit: no URI text",
"A to B to A resets old failure",
"pending old: layout-before-passive-effects: no URI text",
"pending old: sync-commit: no URI text",
"pending new: layout-before-passive-effects: no URI text",
"pending new: sync-commit: no URI text",
"no old during new: no image",
"no old during new: concise",
"no old during new: no URI text",
"stale success ignored: no image",
"stale success ignored: concise",
"stale success ignored: no URI text",
"stale success has no onResolve",
"latest result wins",
"latest onResolve only",
"pending failure clears previous: layout-before-passive-effects: no URI text",
"pending failure clears previous: sync-commit: no URI text",
"old resolved image removed synchronously: no image",
"old resolved image removed synchronously: concise",
"old resolved image removed synchronously: no URI text",
"safe switch while rejection pending: layout-before-passive-effects: no URI text",
"safe switch while rejection pending: sync-commit: no URI text",
"stale rejection cannot fail new safe image",
"active rejection: layout-before-passive-effects: no URI text",
"active rejection: sync-commit: no URI text",
"active rejection: no image",
"active rejection: concise",
"active rejection: no URI text",
"failure not busy",
"unsafe resolver result: layout-before-passive-effects: no URI text",
"unsafe resolver result: sync-commit: no URI text",
"unsafe resolver rejected: no image",
"unsafe resolver rejected: concise",
"unsafe resolver rejected: no URI text",
"unmount pending: layout-before-passive-effects: no URI text",
"unmount pending: sync-commit: no URI text",
"unmounted: layout-before-passive-effects: no URI text",
"unmounted: sync-commit: no URI text",
"unmounted completion ignored",
"preview: layout-before-passive-effects: no URI text",
"preview: sync-commit: no URI text",
"safe image preview click retained",
"synthetic-A: layout-before-passive-effects: no URI text",
"synthetic-A: sync-commit: no URI text",
"actual CompletedFold header exists",
"synthetic-A: before-toggle-0: no URI text",
"synthetic-A: toggle-0: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"actual CompletedFold header exists",
"synthetic-A: before-toggle-1: no URI text",
"synthetic-A: toggle-1: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"actual CompletedFold header exists",
"synthetic-A: before-toggle-2: no URI text",
"synthetic-A: toggle-2: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"synthetic-B: layout-before-passive-effects: no URI text",
"synthetic-B: sync-commit: no URI text",
"actual CompletedFold header exists",
"synthetic-B: before-toggle-0: no URI text",
"synthetic-B: toggle-0: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"actual CompletedFold header exists",
"synthetic-B: before-toggle-1: no URI text",
"synthetic-B: toggle-1: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"actual CompletedFold header exists",
"synthetic-B: before-toggle-2: no URI text",
"synthetic-B: toggle-2: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"synthetic-A: layout-before-passive-effects: no URI text",
"synthetic-A: sync-commit: no URI text",
"actual CompletedFold header exists",
"synthetic-A: before-toggle-0: no URI text",
"synthetic-A: toggle-0: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"actual CompletedFold header exists",
"synthetic-A: before-toggle-1: no URI text",
"synthetic-A: toggle-1: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"actual CompletedFold header exists",
"synthetic-A: before-toggle-2: no URI text",
"synthetic-A: toggle-2: no URI text",
"actual MessageBubble preserves image",
"actual CompletedFold remounts image across toggle",
"no renderer errors"
],
"samples": [
{
"label": "cold: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "cold: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "stable",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "native image error",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "error recovery new src: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "error recovery new src: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host initial",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host settled",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host initial",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host settled",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host initial",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "invalid/no-host settled",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "same source first resolver: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "same source first resolver: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "same source second resolver: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "same source second resolver: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "old resolver identity ignored",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "local direct: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "local direct: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "prefer host enabled: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "prefer host enabled: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "host preference removes direct file before effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "same direct first adapter: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "same direct first adapter: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "direct failed before new adapter",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "same direct new adapter: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "same direct new adapter: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "immediate error after adapter reset retained",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "A to B: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "A to B: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "B to A retry: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "B to A retry: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "pending old: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "pending old: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "pending new: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "pending new: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "no old during new",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "stale success ignored",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "pending failure clears previous: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "pending failure clears previous: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "old resolved image removed synchronously",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "safe switch while rejection pending: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "safe switch while rejection pending: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "active rejection: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "active rejection: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "active rejection",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "unsafe resolver result: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "unsafe resolver result: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "unsafe resolver rejected",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "unmount pending: layout-before-passive-effects",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "unmount pending: sync-commit",
"textLength": 6,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "unmounted: layout-before-passive-effects",
"textLength": 5,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "unmounted: sync-commit",
"textLength": 5,
"imageCount": 0,
"height": 0,
"imageHeight": 0
},
{
"label": "preview: layout-before-passive-effects",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "preview: sync-commit",
"textLength": 0,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: layout-before-passive-effects",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: sync-commit",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: before-toggle-0",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: toggle-0",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: before-toggle-1",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: toggle-1",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: before-toggle-2",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: toggle-2",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-B: layout-before-passive-effects",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-B: sync-commit",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-B: before-toggle-0",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-B: toggle-0",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-B: before-toggle-1",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-B: toggle-1",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-B: before-toggle-2",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-B: toggle-2",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: layout-before-passive-effects",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: sync-commit",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: before-toggle-0",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: toggle-0",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: before-toggle-1",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: toggle-1",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: before-toggle-2",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
},
{
"label": "synthetic-A: toggle-2",
"textLength": 35,
"imageCount": 1,
"height": 0,
"imageHeight": 0
}
],
"errors": [],
"limits": "DOM lifecycle only; jsdom heights are not layout acceptance. Synthetic data only.",
"codeSha": "342820690f644fb1cb3b0a76b143dbcd41aac8c4"
}
@@ -0,0 +1,205 @@
B04 verification
Code SHA: 342820690f644fb1cb3b0a76b143dbcd41aac8c4
Base: 0b613e522d444cbd8cd44ca17e9b1be498517978
Linux x64 / Node24.19.0 / npm11.9.0 / jsdom30.1.3
All images synthetic. No original app, user history, real provider or native browser execution.
159 DOM assertions /85 DOM samples passed; heights are NOT native layout evidence.
Independent review repeated media and final lifecycle checks; no blocking finding.
===== image-lifecycle.txt: exit 0 =====
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test:image-lifecycle
> node scripts/test-image-lifecycle.mjs
{"result":"PASS","checks":159,"samples":85,"output":"/workspace/scratch/0aaa9774aecc/single-package-staging/ycode/.tmp/b04-image-uu7ShO","limits":"DOM lifecycle only; jsdom heights are not layout acceptance. Synthetic data only."}
===== media.txt: exit 0 =====
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test:media
> esbuild scripts/mermaid-regression.ts --bundle --platform=node --format=esm --packages=external --jsx=automatic --loader:.css=empty --outfile=.tmp/media-tests/mermaid-regression.mjs --log-level=warning && node .tmp/media-tests/mermaid-regression.mjs && esbuild scripts/image-renderer-regression.tsx scripts/path-renderer-regression.tsx --bundle --platform=node --format=cjs --jsx=automatic --loader:.css=empty --out-extension:.js=.cjs --outdir=.tmp/media-tests --log-level=warning && node .tmp/media-tests/image-renderer-regression.cjs && node .tmp/media-tests/path-renderer-regression.cjs
Mermaid cache, sanitizer, queue, and abort regression checks passed
image-renderer-regression: all checks passed
Path renderer regression checks passed (23).
===== core.txt: exit 0 =====
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test
> npm run test:core
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test:core
> npm run typecheck && npm run test:chat && npm run test:sessions && npm run test:persistence && npm run test:conversation
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 typecheck
> tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test:chat
> esbuild scripts/message-order-regression.ts scripts/chat-store-regression.ts scripts/chat-stream-committer-regression.ts --bundle --platform=node --format=esm --out-extension:.js=.mjs --outdir=.tmp/chat-tests --log-level=warning && node .tmp/chat-tests/message-order-regression.mjs && node .tmp/chat-tests/chat-store-regression.mjs && node .tmp/chat-tests/chat-stream-committer-regression.mjs
message-order-regression: all checks passed
chat-store-regression: all checks passed
chat-stream-committer-regression: all checks passed
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test:sessions
> esbuild scripts/session-store-regression.ts --bundle --platform=node --format=esm --external:esbuild --outfile=.tmp/session-store-regression.mjs --log-level=warning && node .tmp/session-store-regression.mjs
session-store-regression: all checks passed
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test:persistence
> esbuild scripts/chat-persistence-regression.ts --bundle --platform=node --format=esm --external:esbuild --outfile=.tmp/chat-persistence-regression.mjs --log-level=warning && node .tmp/chat-persistence-regression.mjs
chat-persistence-regression: all checks passed
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test:conversation
> esbuild scripts/conversation-renderer-regression.tsx scripts/composer-regression.tsx scripts/completed-fold-regression.tsx scripts/streaming-footer-regression.tsx --bundle --platform=node --format=cjs --jsx=automatic --loader:.css=text --out-extension:.js=.cjs --outdir=.tmp/conversation-tests --log-level=error && node .tmp/conversation-tests/conversation-renderer-regression.cjs && node .tmp/conversation-tests/composer-regression.cjs && node .tmp/conversation-tests/streaming-footer-regression.cjs && node .tmp/conversation-tests/completed-fold-regression.cjs
Conversation specialized renderer SSR regression checks passed
Composer/message action regression cases passed
streaming-footer-regression: persistent stream status, delayed/paused rotation, brand/locale mapping, source CSS, and completed model footer passed
Completed-fold model and SSR regression checks passed
npm notice
npm notice New major version of npm available! 11.9.0 -> 12.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0
npm notice To update run: npm install -g npm@12.2.0
npm notice
npm notice
npm notice New major version of npm available! 11.9.0 -> 12.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0
npm notice To update run: npm install -g npm@12.2.0
npm notice
===== alignment-dom.txt: exit 0 =====
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 test:alignment:dom
> node scripts/run-alignment-regression.mjs --require-dom
PASS model alignment: fresh/legacy migration, new/edit/default/delete/reload, valid selection (offline host fixtures)
PASS model settings: deferred owner switch, rename mapping, addition selection, empty deletion, immutable history
PASS session factory: first-send preference, mode preservation, invalid permission rejection (isolated ports)
{"historyMessages":10000,"bytes":747781,"parseMs":2.913390000000007,"storeHydrationMs":0.17233299999999474,"note":"Node parsing/store benchmark only, not browser rendering or Windows IPC performance"}
session-composer-alignment: loading/cache/retry/races/model/permissions/modes/SSR passed
alignment-conversation: local groups, original expanded order, question projection and truthful status passed
A01 native MessageBubble identity aliases, immutable inputs and ordinary raw output passed
A01 imported native card, linked dedup and unknown-position compatibility passed
question-answer-shell: structural and recovered CSS contracts passed
question-answer-shell: DOM hierarchy, controls linkage, click, Enter, Space and inert contracts passed
Runtime progress, session-grant scope and approval lifecycle regressions passed
Permission intercept static rendering passed; interactive keyboard/focus remains UI acceptance
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"full-access","agentMode":"ask","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"mode-or-inactive-deny"}
[claude-permission] {"permissionMode":"full-access","agentMode":"plan","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"mode-or-inactive-deny"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":true,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"subcommandResults","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"safetyCheck","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":true,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"full-access","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"full-access-preauthorized"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":true,"branch":"rule-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"error-deny"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"e3b98a4da31a127d","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"permission-approval"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"85aafbf52a5144b5","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"business-question"}
[claude-permission] {"permissionMode":"default-sandbox","agentMode":"craft","sdkMode":"unknown","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"callback","toolCall":"85aafbf52a5144b5","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":false,"branch":"business-question"}
Claude host approval policy unit checks passed (no SDK process/application/matching verification)
[claude-permission] {"permissionMode":"full-access","agentMode":"plan","sdkMode":"plan","sdkModeSource":"host-query-or-acknowledged-control","policyTransition":false,"source":"hook","toolCall":"unknown","decisionCategory":"unknown","decisionCode":"unknown","decisionCodePresent":false,"matchedAskRule":"unknown","branch":"mode-or-inactive-deny"}
Mock SDK replay lifecycle passed: fail closed, retry, session isolation, independent mode guard
B03 offline host policy: 276 checks passed; no real SDK/CLI/model execution.
task popconfirm DOM regression: 34 checks passed (jsdom, not pixel validation)
question-interaction: 24 DOM behavioral checks passed
question-identity: 30 A01 DOM checks passed (not Electron/pixel/SDK acceptance)
completed-fold-nested: real question/tool/thinking/local-summary remount, event, keyboard and identity contracts passed
completed-fold-native-identity: real reducer done correction, native IDs, insertion/reorder, repeated outer toggles, message isolation and legacy fallback passed
Permission intercept DOM: 43 checks passed (not pixel/SDK verification)
{"checks":37,"passed":true,"scope":"Isolated React DOM and CSS contracts; no native keyboard/layout/pixel acceptance"}
{"result":"PASS","phase":"alignment-offline","suites":11}
{"result":"PASS","phase":"alignment-dom","suites":7}
npm notice
npm notice New major version of npm available! 11.9.0 -> 12.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0
npm notice To update run: npm install -g npm@12.2.0
npm notice
===== frame-fixture-build.txt: exit 0 =====
{"result":"BUILD_ONLY","output":"/workspace/scratch/0aaa9774aecc/single-package-staging/ycode/.tmp/b04-image-ksu9Gc","limits":"Not executed in a native browser; open index.html in a permitted browser for synthetic consecutive-frame probe."}
===== baseline-negative.txt: exit 0 =====
Error: cold: layout-before-passive-effects: no URI text
at check (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:270772:24)
at sample (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:270784:5)
at eval (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:270791:7)
at Object.react_stack_bottom_frame (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:21620:23)
at runWithFiberInDEV (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:2438:17)
at commitHookEffectListMount (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:11076:165)
at commitHookLayoutEffects (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:11056:62)
at commitLayoutEffectOnFiber (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:11982:28)
at recursivelyTraverseLayoutEffects (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:13021:15)
at commitLayoutEffectOnFiber (eval at <anonymous> (file:///workspace/scratch/0aaa9774aecc/single-package-staging/ycode/[eval1]:5:7), <anonymous>:12041:15)
An error occurred in the <CommitProbe> component.
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://react.dev/link/error-boundaries to learn more about error boundaries.
{"baseline":"0b613e522d444cbd8cd44ca17e9b1be498517978","expectedFailure":true,"error":"Error: safe first commit image","scope":"Same lifecycle fixture with baseline MarkdownImage only"}
===== build.txt: exit 0 =====
Successful full build including typecheck, tail:
out/renderer/assets/cytoscape.esm-bVGYt-iW.js 869.69 kB
out/renderer/assets/ArtifactPierreDiff-D5xhRc5y.js 1,119.58 kB
out/renderer/assets/chunk-FOHPRMQF-BCllzKJD.js 1,154.18 kB
out/renderer/assets/index-BYPIoTxk.js 3,766.11 kB
out/renderer/assets/editor.api2-dPwET-7w-BYIQ9bT3.js 4,889.23 kB
✓ built in 2.38s
npm notice
npm notice New major version of npm available! 11.9.0 -> 12.2.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.0
npm notice To update run: npm install -g npm@12.2.0
npm notice
===== tokens.txt: exit 1 =====
npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm.
> ycode@0.0.7 check:tokens
> node scripts/check-design-tokens.mjs
设计 token 漂移检查(静态)
扫描:src/renderer/src 共 367 个文件(用时 46 ms)
baseline:scripts/design-token-baseline.json(334 个文件)
门禁分类:adapter 8 | core 6 | local 279(合计 293 个文件)
合计:裸 hex=611 裸 px=2680 !important=6 --wb-* 定义=525 var(--yc-*) 使用=1651 var(--wb-*) 使用=1209
非门禁分类:vendor 2 | recovered 22 | source 47 | tokenSource 3(合计 74 个文件,仅记录不阻塞)
合计:裸 hex=10642 裸 px=13642 !important=920 --wb-* 定义=17966 var(--yc-*) 使用=77 var(--wb-*) 使用=19638
非门禁相对 baseline:文件 +0;裸 hex +78 裸 px +64 !important +0 --wb-* 定义 +0 var(--yc-*) 使用 +22 var(--wb-*) 使用 -120
结果:失败,共 6 项:
[新增门禁文件] src/renderer/src/capabilities/recommended-flow.css(local):裸 px=21
[新增门禁文件] src/renderer/src/capabilities/skill-detail-shell.css(local):裸 px=21
[回归] src/renderer/src/capabilities/skills-host.css(local):裸 px 25 → 45(+20)
[新增门禁文件] src/renderer/src/workbuddy/composer/capability-picker.css(local):裸 hex=26,裸 px=48
[回归] src/renderer/src/workbuddy/composer/composer.css(local):裸 hex 23 → 38(+15);裸 px 83 → 139(+56)
[新增门禁文件] src/renderer/src/workbuddy/composer/prompt-queue.css(local):裸 hex=8,裸 px=35
提示:确认变更符合预期后运行 `node scripts/check-design-tokens.mjs --update-baseline` 刷新 baseline;
否则请改用 --yc-* token、复用现有值,或去掉重复的 --wb-* 定义。
提示(不失败,仅记录):3 个门禁文件 token 使用数上升:
src/renderer/src/capabilities/skills-host.css(local):var(--wb-*) 使用 12 → 19(+7)
src/renderer/src/workbuddy/composer/composer.css(local):var(--yc-*) 使用 35 → 48(+13);var(--wb-*) 使用 0 → 1(+1)
src/renderer/src/workbuddy/conversation/conversation.css(local):var(--yc-*) 使用 0 → 4(+4)
提示(不失败):baseline 中 8 个文件已不存在:src/renderer/src/components/ReportedRunUsage.tsx;src/renderer/src/workbuddy/subagents/SourceMemberIcon.tsx;src/renderer/src/workbuddy/subagents/SubagentCollaboration.tsx;src/renderer/src/workbuddy/subagents/SubagentDetailPanel.tsx;src/renderer/src/workbuddy/subagents/adapters.css;src/renderer/src/workbuddy/subagents/member-view.ts;src/renderer/src/workbuddy/subagents/source.css;src/renderer/src/workbuddy/subagents/theme.css
提示:新增门禁文件 36 个且无受控计数(允许):src/renderer/src/capabilities/CapabilityMaintenance.tsx(local);src/renderer/src/capabilities/RecommendedSkills.tsx(local);src/renderer/src/capabilities/SkillAvatar.tsx(local);src/renderer/src/capabilities/SkillDetailShell.tsx(local);src/renderer/src/capabilities/SkillModalPortal.tsx(local);src/renderer/src/capabilities/skill-modal-focus.ts(local);src/renderer/src/components/SessionLoading.tsx(local);src/renderer/src/components/TaskPopconfirm.tsx(local);src/renderer/src/components/session-loading.css(local);src/renderer/src/components/task-popconfirm.css(local);src/renderer/src/components/updates/UpdateSidebarCard.tsx(local);src/renderer/src/components/updates/update-ui-model.ts(local);src/renderer/src/components/updates/update-ui-store.ts(local);src/renderer/src/components/updates/updates-ui.css(local);src/renderer/src/ui/Button.tsx(local);src/renderer/src/ui/IconButton.tsx(local);src/renderer/src/ui/Input.tsx(local);src/renderer/src/ui/Popover.tsx(local);src/renderer/src/ui/Select.tsx(local);src/renderer/src/ui/Switch.tsx(local);…(还有 16 项,详见 --json)