MooTool Next Compose 的 Vault 外部冲突证据链实践:从 `prepare-vault-conflict-evidence.sh` 到产品主窗验收(DIFF-605)
MooTool Next Compose 的 Vault 外部冲突证据链实践:从 prepare-vault-conflict-evidence.sh 到产品主窗验收(DIFF-605)
MooTool Next Compose 是一套面向桌面端的多功能开发者工具箱(Compose Multiplatform 实现,next-compose 工程),其 Vault 功能为 JSON 数据与随手记提供本地文件库管理。当用户在编辑器内持有未保存的修改、而磁盘上的 Vault 文件被外部进程改写时,就会触发外部修改冲突——本文以 next-compose/docs/diff/605-vault-external-conflict-evidence-slice.md 为骨架,完整讲解该冲突场景的证据链构建:证据准备脚本如何生成隔离样本、呈现常量如何锁定路径与正文、自动化测试如何验证"监视器 → 冲突叠层"链路,以及产品主窗验收应当如何走查与登记。读完本文,你将掌握一套可复现的 Vault 外部冲突验收方法论:既能用脚本快速准备数据,也能用测试锁定行为,还能为最终产品窗截图保留清晰的手工走查步骤。
一、DIFF-605 定位:F04 §A 证据链的构成
DIFF-605 是 next-compose 工程内部一系列增量开发切片(DIFF)中的一环,其基线是 DIFF-604(Git merge §B / 帧 199),核心目标是为 F04 JSON Vault 外部冲突 补齐产品级证据链。整条链由三部分组成:
| 组成 | 角色 | 对应文件 |
|---|---|---|
VaultConflictProductEvidencePresentation |
证据样本的路径、正文与匹配常量的单一事实源,对齐 prepare-vault-conflict-evidence.sh 产物 |
VaultConflictProductEvidencePresentation.kt |
VaultConflictProductEvidenceFlowTest |
锁定"监视器轮询 → 冲突叠层 Presentation 就绪"的自动化链路 | VaultConflictProductEvidenceFlowTest.kt |
VaultConflictProductEvidenceCaptureTest |
输出证据帧 200-compose-vault-external-conflict-sample-reload-tab-focus.png(不重复 141 通用 reload 路径文案) |
VaultConflictProductEvidenceCaptureTest.kt |
DIFF-605 明确"不重复 604":Git merge §B 与帧 199 属于另一条证据链,本文只聚焦 §A 的外部磁盘冲突场景。
二、证据准备脚本:隔离目录下的样本生成
prepare-vault-conflict-evidence.sh 是整个 §A 证据链的起点。它通过环境变量 MOOTOOL_COMPOSE_DATA_DIR 指向的隔离数据目录(由 product-evidence-common.sh 的 mootool_evidence_resolve_data_dir 解析:已设置则复用,未设置则 mktemp -d /tmp/mootool-compose-evidence-XXXX 新建),绝不污染本机默认的 Application Support 目录。脚本生成两类样本:
- JSON 样本
data/vaults/json/sample.json,正文带fixture: vault-external-conflict标记; - 随手记样本
data/vaults/quick-note/sample-external.md(可选 §A 第二组 PNG),Markdown 前导元数据title: Vault external conflict sample、syntax: text/markdown,正文含"随手记外部冲突走查"标记。
脚本末尾通过 mootool_evidence_assert_file 断言两个样本文件均已落盘,并输出后续手动走查所需的提示(包括步骤 3 的外部改写、步骤 3b/3c 的外部删除命令),同时打印以 # 开头的注释性提示。注意:脚本输出均为注释行加单行 export,不要 eval 整段输出(见 README-product-evidence.md 的明确警告)。
2.1 脚本关键产物一览
| 产物 | 路径 | 用途 |
|---|---|---|
sample.json |
$MOOTOOL_COMPOSE_DATA_DIR/data/vaults/json/sample.json |
§A JSON 外部冲突走查样本 |
sample-external.md |
$MOOTOOL_COMPOSE_DATA_DIR/data/vaults/quick-note/sample-external.md |
F01 随手记外部冲突样本(可选) |
| 外部改写命令 | — | 步骤 3:printf '%s\n' '{"disk":true}' > …/sample.json |
| 外部删除命令 | — | 步骤 3b:rm …/sample.json;步骤 3c:rm …/sample-external.md(均须先打开文件并保持脏编辑) |
三、呈现常量:样本路径与正文的单一事实源
脚本写出的样本内容并非随意,而是与 VaultConflictProductEvidencePresentation.kt 中定义的常量一一对应,形成"脚本产物 = 测试断言"的等价链:
EVIDENCE_JSON_RELATIVE_PATH = "sample.json"、EVIDENCE_QUICKNOTE_RELATIVE_PATH = "sample-external.md";- JSON 初始正文含
"fixture": "vault-external-conflict"、"step": "open in app then edit without saving",匹配函数matchesEvidenceInitialJson以 marker 字符串命中判断; - 磁盘改写正文固定为
{"disk":true}(对应脚本注释中的printf示例); - 随手记初始正文含
随手记外部冲突走查标记,磁盘改写正文为disk rewrite from terminal; matchesEvidenceWalkthroughPath仅接受sample.json/sample-external.md两个相对路径。
这样设计的好处是:测试断言不再依赖脆弱的全文比较,而是以 fixture 标记做特征匹配;同时呈现常量本身就是"脚本应产出什么"的可执行文档,脚本若被改坏,测试立即失败。
四、自动化验证:监视器 → 冲突叠层的两条测试路径
4.1 Flow 测试:真实监视器链路
VaultConflictProductEvidenceFlowTest.kt 在临时目录中构建完整的应用容器(AppContainer + SettingsRepository + AppDatabase + HistoryRepository + SessionStore),随后:
- 用呈现常量创建
sample.json并写入初始正文; - 构造一个
JsonSession,把编辑器缓冲改成dirty editor buffer(对应"打开后编辑不保存"); - 启动
VaultRevisionMonitor(轮询间隔 60ms)并rebaseline()建立基线; - 通过
container.jsonVault.write写入EVIDENCE_JSON_DISK_REWRITE模拟外部改写; - 断言监视器在 3 秒内回调、
VaultConflictState.relativePath == "sample.json"、editorText保留脏缓冲、diskText等于磁盘改写正文。
这里涉及两个底层组件:VaultRevisionMonitor(VaultRevisionMonitor.kt)以固定间隔对 Vault 根目录做文件快照指纹比对(SHA-256,超过 8 MiB 的文件仅记 size),通过 expectedHashes 区分"应用自身写入"与"外部变更";VaultConflictEngine.decide(VaultConflictEngine.kt)则根据"编辑器文本 vs 已保存文本 vs 磁盘文本"三元组归类出 Reload / Conflict / Deleted / Ignored 等变更类型。从测试结构可以推断:jsonVaultApplyExternalChange 是监视器回调进入会话层、最终产出 VaultConflictState 的关键接线函数。
4.2 Capture 测试:焦点帧证据输出
VaultConflictProductEvidenceCaptureTest.kt 是 Compose UI 测试:在 680×460 的测试窗口中渲染 VaultConflictDialog,把"重新加载"按钮挂上 FocusRequester,请求焦点后用 captureToImage 截取整窗,写入 docs/evidence/2026-09-15-inspector-screencapture/windows/200-compose-vault-external-conflict-sample-reload-tab-focus.png,并断言焦点环颜色像素(0x316DC0)不低于 8 个——用像素级断言锁定"焦点确实落在 reload 按钮上"。
需要强调的是:该帧是 Compose 测试帧(非产品主窗),只能佐证焦点与 UI 结构,不能代替产品主窗的 runDistributable 手工 PNG(results.md 中对此有专门说明)。
4.3 脚本等价链测试
ProductEvidencePrepScriptTest.kt 用 JUnit 的 ProcessBuilder 直接执行 prepare-vault-conflict-evidence.sh(注入 MOOTOOL_COMPOSE_DATA_DIR 指向临时目录),随后断言:
data/vaults/json/sample.json存在且matchesEvidenceInitialJson命中;data/vaults/quick-note/sample-external.md存在且matchesEvidenceInitialQuickNote命中;- 脚本退出码为 0。
它把"路径与正文"的断言从 UI 层下沉到脚本层,与 Flow 测试共同构成 DIFF-605 的三段式验证。
五、冲突 UI 的展示规则(对齐 Electron 行为)
冲突叠层不是只弹一个对话框,其按钮与文案受 VaultConflictPresentation.kt 的统一规则驱动:
showReloadAction(deleted) = !deleted:外部删除时不显示"重新加载",主操作变为"另存副本"(saveCopyProminent(deleted));hintMessageKey(deleted):区分"修改 vs 外部删除"两条说明文案;buildUnifiedPreview:用DiffEngine.compare生成最多 4000 字符的统一 diff 预览;showDiffPreview(deleted) = !deleted,删除态只显示删除说明而不做 diff 预览;conflictCopyName(VaultConflictEngine.kt):保存副本命名规则为文件stem.local-<epochMs>.ext。
这些规则在 §A 走查的第 5 步(重新加载 / 保存副本 / 保留编辑三分支)中被逐一验证。
六、产品主窗手动验收走查(§A 步骤)
自动化链只能覆盖到叠层与焦点帧,最终产品窗验收仍需人工 GUI 走查(按 results.md §A 的步骤):
cd /path/to/next-compose
export MOOTOOL_COMPOSE_DATA_DIR="$(mktemp -d /tmp/mootool-compose-evidence-XXXX)"
./scripts/prepare-vault-conflict-evidence.sh # 末尾自检 sample.json;勿 eval 整段输出
./gradlew :composeApp:runDistributable --offline
- Vault 根为
$MOOTOOL_COMPOSE_DATA_DIR/data/vaults/json/,启动主窗后打开 JSON,在 Vault 中打开sample.json; - 在编辑器内修改内容不要保存(状态栏应出现未保存
•标记); - 用 Finder/编辑器直接改写同一
sample.json,或另开终端执行printf '%s\n' '{"disk":true}' > "$MOOTOOL_COMPOSE_DATA_DIR/data/vaults/json/sample.json"; - 等待监视器刷新(默认轮询间隔 400ms,测试态可缩短至 60ms,见
VaultRevisionMonitor的intervalMs),应弹出 外部修改冲突 对话框(重新加载 / 保存副本 / 保留编辑),状态栏可显示vault.conflict.banner; - 分别验证三条分支:重新加载恢复磁盘内容、保存副本生成
sample.local-<epoch>.json(不会覆盖原文件)、保留编辑关闭叠层且编辑器内容不变; - 截图登记到
docs/evidence/2026-09-15-inspector-screencapture/windows/NNN-json-vault-external-conflict-product.png。
无 GUI 环境下,可先跑非交互冒烟脚本 verify-product-evidence-prep.sh(会校验 §A 的两个样本文件、Git merge/rebase 冲突仓库、IME 样本与 tray 基线),或直接执行测试 ./gradlew :composeApp:desktopTest --offline。
七、验证命令与全链路上下文
DIFF-605 的验证命令为:
cd /path/to/next-compose
./gradlew :composeApp:desktopTest --offline
要求在 JDK 21 环境下执行。从 results.md 的记录可知:DIFF-513 已跑通 verify-product-evidence-prep.sh 与 ProductEvidencePrepScriptTest(隔离目录下三套 prepare 脚本退出码为 0,merge 仓库 conflict.json 处于 U 未合并状态),但 runDistributable 与产品窗 PNG 仍需人工 GUI 执行,因此 acceptance.md 中 F04/F01 冲突 UI 仍标记为待验收。
需要留意 DIFF-605 的后续延伸:同样的证据链方法论被复用到 sample-external.md(DIFF-607,帧 202)、外部删除冲突(DIFF-610,帧 205;DIFF-611,帧 206),它们共享 VaultConflictProductEvidencePresentation 常量与 VaultConflictPresentation 规则。整体验收清单见 results.md,脚本总览见 README-product-evidence.md。
八、小结
DIFF-605 展示了一条完整的"证据链工程"路径:脚本负责可复现地准备隔离数据,Presentation 常量负责统一路径与正文口径,Flow/Capture/PrepScript 三层测试分别锁定行为、焦点帧与脚本产物,最后以产品主窗手工走查补齐真实 GUI 证据。对于任何需要在桌面应用中处理"编辑器内脏数据 vs 外部磁盘改写"的开发团队,这套"脚本生成 + 常量对齐 + 分层测试 + 人工走查"的组合都是值得借鉴的验收范式。