MooTool Next Compose:Vault Git merge 冲突「产品主窗走查(§B)」的证据链设计与帧 199 验收

原创2026-10-15 13:53:511,346 阅读
文章标签:开发工具桌面应用MCP 服务

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 冲突。

脚本核心逻辑分四步:

  1. 隔离与净化环境:通过 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 全局配置。
  2. 建立 bare 远端:git init --bare "${BARE}",把本地 JSON Vault 初始化为以 main 为默认分支的仓库,提交 {"side":"base"} 作为 conflict.json 的基线(Base),推送到 bare 远端。
  3. 制造历史分叉:克隆一份上游工作区(upstream clone)把 conflict.json 改为 {"side":"remote"} 并提交、推送(Remote);随后本地把同一文件改为 {"side":"local"} 提交(Local)。
  4. 触发冲突: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 临时目录完整复刻证据脚本的仓库拓扑:

  1. 以 GitIdentity("MooTool Evidence", "mootool-evidence@local") 初始化上游仓库,写 {"side":"base"} 提交 Base 并推送到 bare 远端;git clone 得到本地;
  2. 上游改写为 {"side":"remote"} 提交推送,本地改写为 {"side":"local"} 提交,GitEngine.pull 返回失败(进入 merge 冲突);
  3. 断言 GitEngine.status 的 operation == "merge"、merging == true、conflicts >= 1,未合并路径包含 conflict.json;
  4. 断言 evidenceReady 为真、autoSelectConflictPath 返回 conflict.json、productFlowHintKey 为 git.mergeProductFlowResolve;
  5. 断言 冲突期禁用:mergeContinueActionEnabled 为 false、pushActionEnabled 与 pullActionEnabled 为 false、commitActionEnabled 为 false(即使提供了提交信息),而 fetchActionEnabled 保持 true;
  6. GitEngine.fetch 成功后,用 GitEngine.resolveConflict(root, "conflict.json", "theirs") 解决冲突,断言 conflicts == 0、mergeContinueActionEnabled 变为 true;
  7. 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)步骤如下:

  1. 准备隔离目录并运行证据脚本(见第二节命令;§B 与 §A 外部冲突必须使用不同隔离目录,勿先后混用);
  2. 主窗打开 JSON 功能,打开 Vault 中的 Git 面板,变更列表应自动选中 conflict.json;
  3. 状态栏应显示「合并进行中」(git.operationMerge)与 §B 走查提示;选中 conflict.json 后提示切换为 resolve 分支(git.mergeProductFlowResolve);
  4. 对 conflict.json 点击「使用本地版本」或「使用远程版本」,左侧冲突计数应归零;
  5. 冲突期 push/pull 应禁用(GitVaultRemotePresentation,另有 204-compose-git-merge-push-disabled-tab-focus.png 帧佐证)、冲突未清时「提交」应禁用(GitOperationPresentation.commitEnabled);fetch 保持可用;
  6. 冲突归零后「继续合并 / Rebase」按钮启用,点击完成 merge,工作区内容应与 Electron 版行为一致;
  7. 产品窗截图登记为 NNN-json-vault-git-merge-conflict-product.png,并在 results.md「执行记录」登记。

七、与相邻切片的边界与演进

DIFF-604 明确不重复 603(F04 检查器 JSONPath toast / 帧 198)。在其后的切片中,该产品流被持续扩展:

这种「主切片 + 证据帧 + 后续边界切片」的组合,正是 MooTool Next Compose 产品化验收节奏的缩影:自动化测试锁定状态机,Compose 焦点帧锁定可聚焦性,证据脚本锁定可复现的产品现场,最终以产品主窗人工走查收尾。

八、复现与运行前提

  • 运行/验证前提:JDK 21(产品线锁定的工具链详见 README,含 Gradle Wrapper 8.14.3、Kotlin 2.2.21、Compose Multiplatform 1.12.0);本机需安装 git CLI(无 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 交互做成"可回归、可验收"的桌面产品中。

登录后查看全文
MooTool