MooTool Next Compose:Vault Git merge 冲突「产品主窗走查(§B)」的证据链设计与帧 199 验收
MooTool Next Compose:Vault Git merge 冲突「产品主窗走查(§B)」的证据链设计与帧 199 验收
这是一篇面向 MooTool Next Compose 产品线的技术指南。它以增量切片 DIFF-604 为核心,讲解该产品如何在 Git merge 冲突场景下,用 conflict.json 证据脚本与 GitMergeProductFlowPresentation 状态机,构建并锁定「pull → 自动选中冲突 → ours/theirs 解析 → continue 继续合并」的完整产品走查链路,并给出桌面端 Compose Tab 焦点帧 199-compose-git-merge-continue-tab-focus.png 的生成与断言方法。读完本文,你将掌握该产品证据化验收的完整流程:如何用隔离目录脚本制造真实 merge 冲突、如何阅读 VaultGitDialog 对自动选中/禁用态/继续合并的接线,以及如何用自动化测试与像素级断言固化这一 UI 行为。
一、切片背景:DIFF-604 在 A03 证据链中的位置
MooTool Next Compose 是仓库 next-compose 下基于 Compose Multiplatform Desktop 的独立产品线,它的开发方式是按「DIFF 切片」推进:每个切片锁定一个明确范围,并配套自动化测试与证据帧。DIFF-604 是其中关于 A03 Vault Git merge §B 产品走查 的切片,其基线与范围定义在 604-git-merge-product-evidence-flow-slice.md:
- A03:新增
GitMergeProductFlowPresentation.preferredConflictSelectionPath(多冲突时优先选中conflict.json,与 prepare-git-merge-conflict-evidence.sh 对齐);GitMergeProductEvidenceFlowTest锁定 pull→autoSelect→resolve→continue 整条链路与mergeContinueActionEnabled。 - 证据:
GitMergeContinueCaptureTest生成199-compose-git-merge-continue-tab-focus.png(不重复 158 ours / 153 hint 帧)。 - 不重复 603:F04 检查器 JSONPath toast / 帧 198。
切片的验证命令为 JDK 21 下的桌面测试:
./gradlew :composeApp:desktopTest --offline
这一「范围-证据-验证」三段式结构,是该项目对产品主窗功能进行"可回归、可引用、可追溯"验收的标准手法:凡是能在自动化层证明的,先用代码与测试锁定;凡是必须依赖真实 GUI 的(产品主窗截图),则通过证据脚本准备隔离现场,留待人工 runDistributable 走查。
二、冲突现场怎么来:prepare-git-merge-conflict-evidence.sh 的隔离仓库构造
产品主窗验收不允许污染本机默认数据目录,因此 DIFF-604 对应的现场构造脚本 prepare-git-merge-conflict-evidence.sh 做了两件事:隔离数据目录 与 在隔离的 JSON Vault 内制造真实 merge 冲突。
脚本核心逻辑分四步:
- 隔离与净化环境:通过
mootool_evidence_resolve_data_dir解析数据目录(优先MOOTOOL_COMPOSE_DATA_DIR环境变量,否则mktemp -d /tmp/mootool-compose-evidence-XXXX);导出MOOTOOL_COMPOSE_DATA_DIR,并用GIT_CONFIG_GLOBAL=/dev/null、GIT_CONFIG_SYSTEM=/dev/null、GIT_CONFIG_COUNT=1+init.defaultBranch=main隔离 Git 全局配置。 - 建立 bare 远端:
git init --bare "${BARE}",把本地 JSON Vault 初始化为以main为默认分支的仓库,提交{"side":"base"}作为conflict.json的基线(Base),推送到 bare 远端。 - 制造历史分叉:克隆一份上游工作区(upstream clone)把
conflict.json改为{"side":"remote"}并提交、推送(Remote);随后本地把同一文件改为{"side":"local"}提交(Local)。 - 触发冲突:
git pull origin main --no-rebase,本地与远端对同一文件各有一份提交,Git 自然进入 merge 冲突状态。
脚本末尾通过 mootool_evidence_assert_file 与 mootool_evidence_assert_git_merge_conflict 自检(见 product-evidence-common.sh):要求 conflict.json 存在、.git/MERGE_HEAD 存在、且 git diff --name-only --diff-filter=U 输出非空(即有未合并路径)。
执行方式(勿 eval 整段输出,脚本输出的 # 注释行会导致 shell 报错):
cd next-compose
export MOOTOOL_COMPOSE_DATA_DIR="$(mktemp -d /tmp/mootool-compose-git-evidence-XXXX)"
./scripts/prepare-git-merge-conflict-evidence.sh
./gradlew :composeApp:runDistributable --offline
脚本会输出用于人工走查的提示:打开 F04 JSON Vault 的 Git 面板后,变更列表应自动选中 conflict.json;对 conflict.json 使用 ours/theirs 后冲突计数归零;merge 进行中 push/pull 应禁用、冲突未清时「提交」应禁用;最后点「继续合并」完成 merge。这条产品走查路径,就是 DIFF-604 要锁定的 §B。
三、面板状态机:GitMergeProductFlowPresentation 源码级解析
DIFF-604 的核心代码是 GitMergeProductFlowPresentation.kt,它以纯函数形式定义 merge 冲突面板的「表现层状态机」,与 Electron 版 VaultGitDialog 行为对齐。各方法与语义如下:
| 方法 | 输入 | 行为 / 返回 |
|---|---|---|
preferredConflictSelectionPath(conflictPaths) |
全部冲突路径列表 | 为空返回 null;含 conflict.json 时优先返回它(证据脚本产物),否则返回列表首个冲突路径 |
autoSelectConflictPath(merging, conflicts, changes, currentSelected) |
是否合并中、冲突数、变更对 (path, isConflict)、当前选中项 |
非合并或冲突数为 0 返回 null;当前选中已在冲突列表则跳过;否则交给 preferredConflictSelectionPath |
showResolveActions(merging, selectedConflict) |
合并中 + 当前项是否冲突文件 | 两者同时成立才显示 ours/theirs 解析按钮 |
productFlowHintKey(merging, conflicts, selectedConflict, operation) |
同上 + 操作类型 | 输出 git.mergeProductFlowSelect / git.mergeProductFlowResolve;operation="rebase" 时输出 git.rebaseProductFlow*(DIFF-612 扩展) |
evidenceReady(merging, conflicts, unmergedPaths) |
合并中、冲突数、未合并路径 | 合并中且冲突数 > 0 且未合并路径非空 |
showProductFlowPanel(merging, conflicts) |
合并中 + 冲突数 | 合并中且有冲突才显示走查面板 |
resolveActionsEnabled(merging, selectedConflict, busy) |
合并中、是否选中冲突、busy | 依赖 GitOperationPresentation.resolveConflictActionEnabled(busy) 且 showResolveActions 成立 |
mergeContinueActionEnabled(merging, conflicts, busy) |
合并中、冲突数、busy | 委托 GitOperationPresentation.continueActionEnabled(busy, merging, conflicts) |
其中 EVIDENCE_CONFLICT_FILE = "conflict.json" 是证据常量,将产品 UI 与证据脚本绑定。mergeContinueActionEnabled 的底层规则在 GitOperationPresentation.kt:
fun continueOperationEnabled(merging: Boolean, conflicts: Int): Boolean =
merging && conflicts == 0
fun continueActionEnabled(busy: Boolean, merging: Boolean, conflicts: Int): Boolean =
!busy && continueOperationEnabled(merging, conflicts)
也就是说,「继续合并」按钮只在 merge 进行中、冲突全部清零、且面板空闲 时可点。同文件还定义了 commitEnabled(!merging && conflicts == 0 && hasChanges && message 非空)等,共同构成 merge 冲突期的按钮语义。
在 UI 层,VaultGitDialog.kt 通过 LaunchedEffect(status.merging, status.conflicts, status.changes, busy) 监听状态变化并调用 autoSelectConflictPath:一旦计算出应选路径且与当前选中不同,就更新 selected 并 loadFileDiffs(path) 加载差异(第 210–224 行)。状态栏中 productFlowHintKey 输出走查提示,顶栏按钮(fetch/pull/push/abort/continue)的 enabled 分别委托 GitVaultRemotePresentation 与 GitMergeProductFlowPresentation(第 390–448 行);变更列表中选中冲突文件后,showResolveActions 为真时渲染「使用本地版本 / 使用远程版本」两个解析按钮(第 606–644 行)。这些提示文案的中文/英文/日文资源位于 Translator.kt,例如 git.mergeProductFlowSelect 为「产品走查 §B:在变更列表选中带「冲突」标记的文件(如 conflict.json)。」,git.mergeProductFlowResolve 为「产品走查 §B:对该文件使用「保留本地/远端」,冲突清零后点「继续合并」。」。
四、双测试锁定:GitMergeProductEvidenceFlowTest 与 GitMergeProductFlowPresentationTest
DIFF-604 的自动化验证由两个测试共同承担。
4.1 全链路等价测试
GitMergeProductEvidenceFlowTest.kt 的方法 pullConflictMatchesEvidenceScriptAndProductFlowPresentation 用 JDK 临时目录完整复刻证据脚本的仓库拓扑:
- 以
GitIdentity("MooTool Evidence", "mootool-evidence@local")初始化上游仓库,写{"side":"base"}提交Base并推送到 bare 远端;git clone得到本地; - 上游改写为
{"side":"remote"}提交推送,本地改写为{"side":"local"}提交,GitEngine.pull返回失败(进入 merge 冲突); - 断言
GitEngine.status的operation == "merge"、merging == true、conflicts >= 1,未合并路径包含conflict.json; - 断言
evidenceReady为真、autoSelectConflictPath返回conflict.json、productFlowHintKey为git.mergeProductFlowResolve; - 断言 冲突期禁用:
mergeContinueActionEnabled为false、pushActionEnabled与pullActionEnabled为false、commitActionEnabled为false(即使提供了提交信息),而fetchActionEnabled保持true; GitEngine.fetch成功后,用GitEngine.resolveConflict(root, "conflict.json", "theirs")解决冲突,断言conflicts == 0、mergeContinueActionEnabled变为true;GitEngine.continueOperation成功后,断言operation == "none"、merging == false,push/pull 恢复可用,且conflict.json内容包含"remote"。
该测试与 GitEngineTest.pullLeavesMergeConflictWhenHistoriesDiverge(见 GitEngineTest.kt 第 202–242 行)保持同一套冲突构造逻辑,验证了「证据脚本现场 == 引擎能力 == 产品走查期望」三方一致。
4.2 表现层纯函数测试
GitMergeProductFlowPresentationTest.kt 直接覆盖 DIFF-604 引入的核心函数:
autoSelectPicksFirstConflictWhenNothingSelected:空选中时自动选中首个冲突conflict.json;autoSelectPrefersEvidenceConflictFileAmongMany:在aaa.json、conflict.json、zzz.json三个冲突中优先选conflict.json;autoSelectSkipsWhenConflictAlreadySelected:当前已选中冲突文件时返回null,不打断用户选择;productFlowHintDependsOnSelection:未选中输出git.mergeProductFlowSelect、选中输出git.mergeProductFlowResolve,并验证 rebase 变体git.rebaseProductFlowSelect/Resolve;evidenceReadyMatchesScriptExpectations、resolveActionsRequireSelectedConflictAndIdle、mergeContinueRequiresResolvedConflictsAndIdle:分别锁定 evidenceReady 判定、解析按钮需「合并中 + 选中冲突 + 空闲」、continue 需「合并中 + 冲突归零 + 空闲」。
五、证据帧 199:GitMergeContinueCaptureTest 的生成与像素断言
DIFF-604 的证据交付物是 Compose Tab 焦点帧 199-compose-git-merge-continue-tab-focus.png,由 GitMergeContinueCaptureTest.kt 生成,用于验证「冲突清零后继续合并按钮可聚焦」这一 §B 收尾状态。
测试使用 runDesktopComposeUiTest(width = 480, height = 120) 渲染一个轻量面板:浅色 MooTheme 下,渲染一个 MooButton,文案取 zh.t("git.continue")(即「继续合并 / Rebase」),以 mergeContinueActionEnabled(merging = true, conflicts = 0, busy = false) 传入 enabled,并用 FocusRequester 显式申请焦点。随后:
- 截图写入
docs/evidence/2026-09-15-inspector-screencapture/windows/199-compose-git-merge-continue-tab-focus.png(目录不存在时mkdirs()创建); - 以
countRingPixels统计图中颜色值0x316DC0的像素数并断言>= 8,即按钮获得焦点后必须有足够的焦点环像素,否则视为焦点未生效。
这条断言链的价值在于:它把「continue 按钮可用 + 可键盘聚焦」这一产品主窗 §B 的关键收尾体验,固化成了可离线重复运行的像素级证据。该帧与既有帧序列互补:153 是 merge 走查 hint 帧、158 是 ours/theirs 解析帧,199 专门覆盖 continue,三者共同构成 §B 三个关键节点。产品验收登记见 results.md 的「已自动化」表格。
六、手动产品主窗走查:§B 完整步骤
按 results.md 的 §B 章节,产品主窗走查(runDistributable + 人工 GUI)步骤如下:
- 准备隔离目录并运行证据脚本(见第二节命令;§B 与 §A 外部冲突必须使用不同隔离目录,勿先后混用);
- 主窗打开 JSON 功能,打开 Vault 中的 Git 面板,变更列表应自动选中
conflict.json; - 状态栏应显示「合并进行中」(
git.operationMerge)与 §B 走查提示;选中conflict.json后提示切换为 resolve 分支(git.mergeProductFlowResolve); - 对
conflict.json点击「使用本地版本」或「使用远程版本」,左侧冲突计数应归零; - 冲突期 push/pull 应禁用(
GitVaultRemotePresentation,另有204-compose-git-merge-push-disabled-tab-focus.png帧佐证)、冲突未清时「提交」应禁用(GitOperationPresentation.commitEnabled);fetch 保持可用; - 冲突归零后「继续合并 / Rebase」按钮启用,点击完成 merge,工作区内容应与 Electron 版行为一致;
- 产品窗截图登记为
NNN-json-vault-git-merge-conflict-product.png,并在 results.md「执行记录」登记。
七、与相邻切片的边界与演进
DIFF-604 明确不重复 603(F04 检查器 JSONPath toast / 帧 198)。在其后的切片中,该产品流被持续扩展:
- 612-git-rebase-product-evidence-slice.md:
productFlowHintKey增加operation参数,merge/rebase 分流到git.mergeProductFlow*/git.rebaseProductFlow*,VaultGitDialog传入status.operation;配套GitRebaseProductEvidenceFlowTest与207-compose-git-rebase-continue-tab-focus.png; - 609-git-merge-push-disabled-evidence-slice.md:锁定 merge 冲突期 push 禁用、完成后恢复,配帧 204;
- 618-git-vault-commit-disabled-slice.md:merge/rebase 冲突期提交禁用,配帧 212/213;
- 619-git-fetch-during-conflict-slice.md:冲突期 fetch 可用、pull 禁用,配帧 214。
这种「主切片 + 证据帧 + 后续边界切片」的组合,正是 MooTool Next Compose 产品化验收节奏的缩影:自动化测试锁定状态机,Compose 焦点帧锁定可聚焦性,证据脚本锁定可复现的产品现场,最终以产品主窗人工走查收尾。
八、复现与运行前提
- 运行/验证前提:JDK 21(产品线锁定的工具链详见 README,含 Gradle Wrapper 8.14.3、Kotlin 2.2.21、Compose Multiplatform 1.12.0);本机需安装
gitCLI(无 git 时GitMergeProductEvidenceFlowTest通过Assume.assumeTrue跳过)。 - 自动化验证:
cd next-compose
./gradlew :composeApp:desktopTest --offline
- 证据脚本自检(无 GUI 的 CI/本机):
./scripts/verify-product-evidence-prep.sh或ProductEvidencePrepScriptTest,详见 README-product-evidence.md。 - 注意:证据脚本只准备隔离目录与 Git 状态,不能替代
docs/evidence/中的产品窗截图;产品主窗 PNG 仍需runDistributable后人工 GUI 走查登记。
九、小结
DIFF-604 展示了 MooTool Next Compose 对「Git merge 冲突」这一高风险场景的可信验收方法:用 prepare-git-merge-conflict-evidence.sh 构造隔离且真实的冲突现场,用 GitMergeProductFlowPresentation.kt 将「自动选中、resolve 显隐、continue 门禁」抽象成可测试的纯函数,用 GitMergeProductEvidenceFlowTest 与 GitMergeProductFlowPresentationTest 双向锁定引擎链路与表现层规则,再用 GitMergeContinueCaptureTest 输出带像素断言的焦点证据帧 199。对读者而言,这套「脚本造现场 + 状态机分层 + 双测试 + 焦点帧」的方法论,可以直接借鉴到任何需要把复杂 Git 交互做成"可回归、可验收"的桌面产品中。