- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
DIFF-572 是 MooTool next-compose 版本在 Electron 功能对等(parity)推进过程中的一个增量切片,它把 F03 格式化工具的「文件 Tab」导入/另存逻辑收敛进ReformatWiringPresentation,补齐了mooFileDropRow样式组件,并新增了 Vault MCP stdio 双库 notes search→read 的 body offset 端到端测试。本文结合该文档与仓库源码,讲解每一条改动背后的实现与验证依据,读完可以掌握 Compose 版 F03 文件 Tab 的完整接线链路、Vault MCP 只读工具的调用约定,以及本项目惯用的「切片文档 + 证据脚本 + Compose 帧」验证模式。
背景:DIFF-572 在整个 parity 链中的位置
根据 next-compose/docs/diff/572-reformat-mcp-css-slice.md 与 next-compose/docs/evidence/2026-09-14-parity-gap/results.md,MooTool 的 Compose 桌面版通过一串连续的 DIFF 切片逐步向 Electron 版对齐。DIFF-571 已完成 F04JsonWiringPresentation.run*、JSON 检查器样式与 Vault MCP 双库json search→readbody offset、Compose 帧 166;DIFF-572 在其后继续推进:
- F03 格式化工具的
ReformatWiringPresentation.runReadSourceFile/runWriteResult/inferTypeFromFileName; - Electron
.file-drop-row对应的 Compose 样式mooFileDropRow; - Vault MCP 双库notes search→readbody offset(与 571 的 json search→read、567 的 notes search only、570 的 notes read only 链条相互区分)。
parity-gap 结果文档中明确记载,截至该切片,六套 CSS 逐选择器皮肤、产品主窗 Tab/IME/TCC PNG、P7 三平台安装仍未完成,「目标未达成」。
F03 格式化 run*:文件读取、类型推断与结果写入
读取源文件并推断格式类型
ReformatWiringPresentation.runReadSourceFile是文件 Tab「导入」的核心入口,实现位于 ReformatWiringPresentation.kt:
fun runReadSourceFile(file: File): ReadSourceOutcome = runCatching { ReadSourceOutcome.Success( fileName = file.name, content = file.readText(StandardCharsets.UTF_8), inferredType = inferTypeFromFileName(file.name), ) }.getOrElse { ReadSourceOutcome.Failure(it) }它一次性完成三件事:以 UTF-8 读取文件内容、记录文件名、通过扩展名推断格式化类型。类型推断规则同样在 Presentation 层集中定义:
fun inferTypeFromFileName(fileName: String): ReformatType? { val ext = fileName.substringAfterLast('.', "").lowercase() return when (ext) { "conf", "nginx" -> ReformatType.Nginx "java" -> ReformatType.Java "xml", "xsd", "svg" -> ReformatType.Xml "html", "htm" -> ReformatType.Html else -> null } }注意两点:扩展名被统一lowercase(),且svg/xsd会归入 XML 类型,conf/nginx归入 Nginx 类型。支持的类型集合在 ReformatEngine.kt 中定义为enum class ReformatType { Nginx, Java, Xml, Html },并配有四种语言的内置示例文本(samples)。
inferTypeFromFileName的映射行为在 ReformatWiringPresentationTest.kt 中被逐一验证:App.java→Java、site.conf→Nginx、index.htm→Html、readme.txt→null。
写入结果与默认另存文件名
runWriteResult用 UTF-8 写回格式化结果:
fun runWriteResult(file: File, content: String): WriteResultOutcome = runCatching { file.writeText(content, StandardCharsets.UTF_8) }.fold( onSuccess = { WriteResultOutcome.Success }, onFailure = { WriteResultOutcome.Failure(it) }, )而「另存」时默认文件名的生成逻辑defaultSaveFileName依据当前ReformatType决定扩展名(Nginx→.conf、Java→.java、Xml→.xml、Html→.html),并以去掉原扩展名后的文件名为基础;若源文件名为空则回退为formatted:
val base = sourceFileName.replace(Regex("\\.[^.]+$"), "").ifEmpty { "formatted" } return "$base.$extension"该行为在测试中有对应断言:"App.java"+ Java →App.java;空文件名 + Nginx →formatted.conf。由于该函数在 DIFF-551 就已被引入,DIFF-572 并未重做,只在其后的切片中持续被复用。
与 ReformatScreen 的 UI 接线
文件 Tab 的 UI 侧由 ReformatScreen.kt 驱动:loadSourceFile调用runReadSourceFile,成功时把fileName/fileSource填入会话、清空fileResult,并通过outcome.inferredType?.let { session.type = it }自动切换格式化类型;失败时走reformat.error.read文案与 error toast。saveResult调用defaultSaveFileName生成默认名、runWriteResult写盘,成功后持久化导出目录(persistToolsExportDirectory)。因此导入/另存全部经过 Presentation,runCatching+ 直接读写不再散落在 Screen 各处——这正是 DIFF-572 所强调的「ReformatScreen 文件 Tab 导入/另存经 Presentation」接线目标。
样式组件:mooFileDropRow
Electron 端.file-drop-row选择行在 Compose 端的对应物是mooFileDropRow修饰符,定义于 Controls.kt:
/** Electron `.file-drop-row` 选择行(10dp 间距 + 最小高度对齐 control)。 */ @Composable fun Modifier.mooFileDropRow(): Modifier = heightIn(min = 34.dp)它设定 34dp 的最小行高。而 10dp 间距来自FileDropRow组件内部(同一文件的 Controls.kt):
Row( modifier = modifier.fillMaxWidth().mooFileDropRow().then(dropModifier) .padding(if (onDropFiles != null) 2.dp else 0.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp) ) { MooButton(chooseLabel, onClick = onChoose, enabled = enabled, p5Toolbar = p5Toolbar) Text(fileName.ifEmpty { emptyLabel }, ... maxLines = 1, overflow = TextOverflow.Ellipsis) }FileDropRow是通用组件:左侧「选择文件」按钮 + 右侧 11sp muted 省略文件名,可选挂接desktopFileDropTarget支持系统文件拖放(acceptMultiple控制多选),并支持p5Toolbar密度与enabled禁用态。DIFF-572 引入该组件后,F03 文件 Tab 直接复用它,随后在 DIFF-573 等后续切片中被其他工具继续引用。
Vault MCP stdio:双库 notes search→read 的 body offset 分页
调用约定
Vault MCP 提供四个只读工具,由 VaultMcpTools.kt 登记:
mootool_notes_search、mootool_notes_read(随手记/Notes Vault)mootool_json_documents_search、mootool_json_documents_read(JSON Vault)
工具的调用参数经过严格校验,searchArgs与readArgs定义了明确的边界:
| 工具 | 参数 | 默认值 | 范围 |
|---|---|---|---|
*_search | query | "" | 最长 200 |
*_search | limit | 20 | 1–50 |
*_search | offset | 0 | 0–2000 |
*_read | path | 必填 | 非空、最长 1000 |
*_read | offset | 0 | 0–2,000,000 |
*_read | length | 20000 | 1–50000 |
边界越界会直接抛错(Invalid ...),与 Electron 端vaultTools.ts的 Zod strict 语义对齐,「不静默钳制」。search→read 的标准工作流在 MCP SKILL 中给出:SKILL.md 明确要求「先 search,把返回的相对路径交给对应_read工具,按nextOffset翻页;search 报告truncated时收窄查询而非声称穷尽;返回的 note 内容不含 YAML metadata 头」。服务经McpLaunchResolver.clientLaunch以--mcp --access-file <path>启动子进程(McpLaunchResolver.kt),access 文件 JSON 形如{"version":1,"notes":"...","json":"..."},可同时授权双库。
双库场景下的端到端验证
DIFF-572 对应的测试subprocessDualVaultNotesSearchThenReadHonorsBodyOffset位于 AiIntegrationVaultMcpConnectionTest.kt。它构造一个 notes 根(含alpha.md/beta.md)与一个 json 根,两者同时写入同一 access 文件,模拟「双库同会话授权」。随后:
mootool_notes_search以{query:"572", limit:1, offset:1}翻页,断言命中beta.md而非alpha.md——验证search 的 offset在双库授权下依然生效;mootool_notes_read以{path:"beta.md", offset:7, length:7}精确取 body 片段,断言命中572tail且不出现skip572前缀——验证read 的 body offset/length。
这条链路与 571(json search→read)、567(notes search only)、570(notes read only)刻意区分:每个切片只补一类组合,测试名以subprocessDualVault...前缀统一标识双库场景。同文件中 562–577 附近还有DualVaultNotesSearchHonorsOffsetWhenJsonGranted、DualVaultJsonReadThenNotesSearchHonorsBodyOffset等组合测试,共同覆盖 search/read × notes/json 的排列,可据此推断该测试类就是 MCP 双库矩阵的主阵地。
证据脚本与 Compose 帧 167
每个切片都会配套「证据」产出,便于人工验收与产物归档:
mootool_evidence_print_reformat_file_drop_tab_focus_hint在 product-evidence-common.sh 中打印提示,指出对应截图路径为docs/evidence/2026-09-15-inspector-screencapture/windows/167-compose-reformat-file-drop-tab-focus.png;- verify-product-evidence-prep.sh 在全部证据准备脚本的冒烟校验通过后调用该 hint 函数,用来核对脚本语法并登记产物位置。
帧 167 由 ReformatFileDropCaptureTest.kt 生成:它在 420×120 的 Compose 测试窗口内渲染FileDropRow(选择按钮 + 无文件提示文本),对按钮requestFocus()后截图到167-compose-reformat-file-drop-tab-focus.png,并断言截图中出现不少于 8 个0x316DC0焦点环像素(countRingPixels)。因此该帧是非产品主窗的组件级焦点证据,用于验证.file-drop-row在 Compose 端的最小行高、间距与焦点环呈现。
验证方式
DIFF-572 文档声明的验证链路与仓库中的实际测试一致,均可离线复现:
ReformatWiringPresentationTest:覆盖canRunFormat/formatActionEnabled守卫、复制/另存可用性、defaultSaveFileName、runFormat引擎调用、formatErrorMessage定位文案(reformat.error.located携带 line/column)、inferTypeFromFileName与runReadSourceFile临时文件读取(见 ReformatWiringPresentationTest.kt);AiIntegrationVaultMcpConnectionTest:经真实StdioClientTransport启动 MCP 子进程,执行 search→read 双库分页断言;ReformatFileDropCaptureTest:生成帧 167 并做像素级焦点环断言。
命令行侧,仓库提供两层校验:
./scripts/verify-product-evidence-prep.sh # 证据准备脚本语法 + hint 输出 ./gradlew :composeApp:desktopTest --offline # JDK 21 下离线跑全部桌面测试未做与后续切片的衔接
按文档声明,DIFF-572 未做的内容包括:Electron 六套 CSS 逐选择器的全量移植、产品主窗 F03/IME/TCC 的手工 PNG、P7 三平台安装/公证。后续 DIFF-573(TextDiffPresentation.runReadImportFile、mooDiffToolbarOptions等 + json search→notes read + 帧 168)开始承接下一批切片;mooFileDropRow与 FileDropRow 也被后续 F02 等工具的导入行复用。这说明 DIFF-572 的价值不仅在于当次增量,也在于为后续文件的 IO 接线提供了统一的 Presentation 模式与证据流模板。
- 桌面应用
- 开发工具
【免费下载链接】MooTool
Handy tool set for developers
相关推荐
MooTool Next(Compose)DIFF-570 解读:F06 配置转换 run* 接线、validate CSS 与 Vault MCP 双库 notes read 分页
MooTool Next(Compose)DIFF 570 解读:F06 配置转换 run 接线、validate CSS 与 Vault MCP 双库 not
桌面应用开发工具MooTool Next Compose 的 F06 配置工具接线与 Vault MCP 双库读取偏移:DIFF-570 技术解析
MooTool Next Compose 的 F06 配置工具接线与 Vault MCP 双库读取偏移:DIFF 570 技术解析 导读 DIFF 570 是
桌面应用开发工具MooTool next-compose 开发切片解读:F 工具 run* 引擎接线、Vault 冲突/MCP 分页与 CSS 组件批次
MooTool next compose 开发切片解读:F 工具 run 引擎接线、Vault 冲突/MCP 分页与 CSS 组件批次 导读 DIFF 563
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考