MooTool

Handy tool set for developers

View on GitHub

Compose 技术架构与实现约束

1. 技术路线与版本冻结

采用 Kotlin Multiplatform + 一个 JVM desktop target。macOS、Windows、Linux 的 Compose Desktop 共用该 JVM 目标,再由各自平台能力适配;不要建立 macosArm64 Kotlin/Native 目标后假定可直接运行同一套 Desktop UI。

P0 优先验证 Compose Multiplatform 1.12.0 与官方支持的稳定 Kotlin/Gradle 组合,建议先用 JBR/JDK 21 工具链。org.jetbrains.kotlin.plugin.compose 与 Kotlin 插件版本必须相同;Gradle JVM、Kotlin jvmTarget、Java toolchain、jpackage JDK 明确对齐。这里是候选,尚未构建验证。官方版本规则

依赖全部写入 gradle/libs.versions.toml,提交 Wrapper 与校验值、必要锁文件/依赖验证元数据;不使用动态 +、未锁 SNAPSHOT 或机器私有绝对路径。开发机可设置自有 JDK 路径,但不得提交此机器路径。

Compose 1.12.0 的关联 Material3、Navigation 等并非全部稳定。默认 Foundation + 自有桌面组件,必要的稳定 Material 组件单独验证;不为了菜单/侧栏引入复杂 Android 导航框架。发布说明

正式支持矩阵取 Compose、Skiko、JDK、数据库/native helper 的交集;当前官方平台表未将 Intel Mac 列入 1.12.0 目标,不将其作为已支持平台。若计划兼容 Intel,P0 选择明确支持的版本组合并形成单独实验记录。

2. 建议工程结构

以下是待创建的结构;先建一个主应用模块,只有进程隔离或独立测试确有需要时再增加本产品内部模块。

next-compose/
  settings.gradle.kts
  build.gradle.kts
  gradle.properties                     # appVersion 唯一人工版本来源
  gradlew / gradlew.bat
  gradle/wrapper/
  gradle/libs.versions.toml
  composeApp/
    build.gradle.kts
    src/commonMain/kotlin/com/rememberber/mootool/next/compose/
      model/                            # ToolId、设置/会话 DTO、错误、任务状态
      domain/                           # 纯逻辑、格式语义、repository 接口
    src/desktopMain/kotlin/com/rememberber/mootool/next/compose/
      Main.kt
      app/                              # ProductIdentity、AppContainer、启动、注册表
      ui/theme/                         # MooTheme、Token、编辑器 theme adapter
      ui/components/                    # 分栏、工具栏、表格、弹层、树、状态栏
      ui/workbench/                     # 导航、搜索、设置宿主
      features/<feature>/               # Screen、Presenter、State、Actions
      editor/                           # EditorHost、Buffer、事务、Swing 适配
      sessions/                         # 工具会话、窗口 ownership、快照
      services/                         # 文件、HTTP、Git、进程、媒体、worker
      storage/                          # SQLite、设置、Vault、迁移、备份
      platform/                         # macOS / Windows / Linux adapters
    src/desktopMain/resources/          # 本产品资源、翻译、数据库 migration
    src/commonTest/kotlin/
    src/desktopTest/kotlin/
    src/desktopTest/resources/fixtures/
  resources/                            # 安装图标及按 OS/arch 分发的 helper
  scripts/                              # 独立构建、打包检查、证据收集
  docs/evidence/                         # 环境、截图、测试、平台结果
  docs/adr/
  release-notes/
  LICENSE.txt / THIRD_PARTY_NOTICES.md

commonMain 不导入 java.io、Swing、JDBC、JNA 或 Android SDK。桌面 UI 可以先在 desktopMain,不为未来移动端建立无实际用途的 expect/actual 壳。Mac/Windows/Linux 都在 JVM 的平台适配层,不伪造三个 KMP native UI 目标。

依赖方向:UI → Presenter/UseCase → Repository/Service 接口 → desktop 实现。禁止 @Composable 内直接操作数据库、Git 或文件;禁止数据层引用 UI。AppContainer 使用显式构造注入即可,暂不引入重型 DI 框架。

3. 状态与任务模型

工具注册包含:稳定 ID、分组、三语标题 key、关键词、图标、工厂、历史/收藏能力、开发状态。页面工厂延迟创建;启动时不构造所有编辑器、索引、Git 仓库或硬件扫描服务。

状态层 内容 生命周期
AppState 设置、工具注册、当前工具、最近项 进程级,部分持久化
ToolSession 输入、选项、结果、当前 Tab、任务引用、文档 buffer 工具会话级,切页/转移窗口不销毁
EditorBuffer 文本、revision、选区、undo/redo、语法模式 文档级,独立于 Composable
WindowState 位置、大小、所承载 session、焦点归还 窗口级
ViewState hover、短暂弹层、展开动画 视图局部,不进数据库
PersistedSnapshot 草稿、文档路径、pane ratio、Tab、必要选区 按节流策略保存,不保存 Job/JComponent

建议用 StateFlow 向 UI 暴露不可变状态、SharedFlow 或通道表达一次性事件;执行由 Presenter 持有明确 CoroutineScope。remember 只用于视图细节,不能作为唯一业务存储。避免将 5 MiB String 在每帧复制到多个 UI state 和数据库字段。

任务状态统一为 Idle、Running(taskId, stage, progress?)、Succeeded、Failed(error)、Cancelled。输入每次编辑增 revision;结果只在 taskId 与输入 revision 符合当前会话时提交。自动分析用 debounce + latest-wins;已发 HTTP、进程或文件任务由显式句柄取消。

错误包含稳定 code、本地化 message key、可重试标记及必要细节,如 InvalidInput、ParseError(line,column)、PermissionDenied、Conflict、MissingRuntime、Unsupported、IO、Network、LimitExceeded。日志隐藏凭据,界面保留可修正输入。

4. 线程、生命周期与性能

5. 编辑器方案:先做验收实验

5.1 EditorHost 合约

EditorHost 必须支持文本/语言、只读、字体主题、软换行、行号、语法、选区、滚动、聚焦、查找/替换、事务编辑、undo/redo、状态快照、图片/文件拖放转交。JSON、随手记、HTTP 正文、代码运行和 Diff 共用适配规范,但各自有独立 Buffer。

文本以 revision 驱动增量 edit;格式化/替换用一次 compound transaction。自动保存只观察 dirty/revision,不调用 setText 刷新全文。历史恢复是一项显式编辑动作,不能暗中混入旧 undo 事务。

本项目默认不要求 undo 跨进程重启,但要求切页、开关预览、主题切换、分离/收回后仍然保留 undo。模型 offset 可以统一 UTF-16,鼠标可视列/字素与 Tab 展开需要明确转换,不能用字符数代替屏幕坐标。

5.2 首选实验:RSyntaxTextArea + SwingPanel

RSTA 作为第三方依赖由本产品自带,使用自有 theme、行号、滚动条和查找 UI,不加载 Java 版的 Form/全局管理器。语法高亮/折叠能力可直接评估,矩形选择、多光标和图文粘贴仍需本产品补足。RSTA 上游

factory 创建宿主组件,更新阶段只应用变化的属性;组件与 Document 的所有权明确,不因 Composable 重建丢掉文档状态。设置主题通过 adapter 映射 Compose Token,保留字体回退;现有 Java CJK painter 可作为一次性参考,不能直接依赖根源文件。

SwingPanel 默认处于 Compose 内容前方;菜单、命令搜索、对话框或面板覆盖可能被编辑器遮挡。应用内 MooOverlay 打开时通过 ModalOverlayState 暂时卸下 EditorHostSwingPanel,关闭后恢复同一 EditorBuffer,见 DIFF-058DIFF-059。experimental blending 仅做实验,不能未经验证当成完整解决方案。官方互操作约束

5.3 列编辑不能省略

矩形选择基于可视行列处理;明确软换行开启时使用逻辑行还是视觉行。本产品列编辑按逻辑行并在开启换行时提示;列单位按等宽网格,Tab 展开遵循 tabSize,宽字符按 Unicode 宽/窄启发式(汉字/emoji 为 2),不截断 surrogate/组合字符。实现见 DIFF-024

需要实现多行输入、删除、粘贴;每次动作统一 undo;短行补齐策略与换行分发策略固定测试。多行块与单行粘贴分别验证。普通选择和 IME composition 不受列模式破坏(IME 进行中不拦截列输入)。

实验若无法满足这些条件,替代方案为本产品自有 Compose 增量编辑器或其他经验证成熟组件;由 ADR 明确成本和覆盖度。WebView/CodeMirror 只能作为显式备选,需自带资源、加载/通信/安全边界和包体证据;不默认引入整个 Electron/JCEF,也不悄悄改成网页壳。

5.4 Markdown

解析为结构化 AST,Compose 预览渲染标题、列表、表格、任务、代码、链接、相对附件;大文档在后台线程解析。禁止执行正文脚本/任意 HTML;外部图片默认不请求。实现锁定 commonmark-java 0.24.0 + GFM 表格/删除线/任务列表,见 DIFF-023。不使用 JEditorPane HTML 冒充完整预览。附件解析统一经过 Vault 路径边界。

6. 多窗口和会话转移

使用单一应用进程的 Window 集合。SessionManager 持有会话,WindowManager 只管理宿主。默认每个 Tool ID 一份活动会话,笔记/JSON 会话内部可有多个 Buffer;不把“分离”做成新建一个空工具。

转移状态:Docked → Transferring → Detached → Transferring → Docked。转移过程中冻结重复命令,旧宿主停止接收事件后才挂新宿主;失败回滚到旧宿主。捕获未提交 IME 状态并在安全时点转移,不能丢输入。

同一个 JComponent 不可同时加入两个父容器。可以转移同一 host,或在新窗口创建 host 并绑定持久 Document/UndoManager;无论方案,生命周期和旧监听释放要通过测试。不要序列化 JComponent,也不要为了序列化文本重置 undo。

7. JVM 能力与算法选型

下表是首选实现方向,精确 artifact/version 在对应阶段核验上游后锁定。不整体照搬 pom.xml,尤其不为一个算法拉入整个 Java UI、旧数据库层或不适合本产品分发的许可依赖。

能力 首选方向 关键验收/边界
设置/DTO kotlinx.serialization schema v1、默认值、未知/损坏数据、原子写
DB SQLite JDBC + 显式 SQL repository 三平台 native 解包、WAL、事务、迁移与备份
备份/恢复 自有 BackupEngine zip + SHA-256 清单 不含缓存/日志/凭据;恢复前 pre-restore 快照;拒绝路径穿越。见 DIFF-022
HTTP OkHttp 4.12.0 + 自有 HttpEngine 冻结 GET/表单语义;重复 Header 按多值发送;10 MiB 解压上限;可取消。见 DIFF-018
翻译 OkHttp 4.12.0 + 自有 TranslationEngine Google 1800/并发 3 保序;Bing 全文 POST + 会话缓存;15s 总超时;冷却 fallback。单词本/历史 JSON。见 DIFF-019
JSON Jackson 流式/token/树 + 自有适配 重复 Key、数字字面量、深度上限;普通 Map 不保留重复 Key
JSONPath Jayway 候选 + Electron 样本适配 filter/union/slice/escape;禁止执行任意 JS
XML / YAML JAXP + SnakeYAML 2.3 SafeConstructor 禁用外部实体、限制深度/别名膨胀;不声明注释无损;配置转换见 DIFF-006
Diff java-diff-utils 或同类成熟实现 行与字符差异、统一模式、重复行、偏移
Java 格式化 JavaParser 3.26.4 PrettyPrinter 语法处理、错误不改原文;无外部 Node 依赖;见 DIFF-005
HTML/XML/Nginx Jsoup + JAXP 自写缩进 + Electron Nginx tokenizer 保留字符串、注释、文本节点语义,幂等
Cron cron-utils / Quartz 语义适配器候选 6/7 字段、周编号、?、L/#、year、IANA zone/DST
正则 Java Pattern 为首选引擎 显示 Java 语义差异;独立 worker 超时/终止;不能宣称完全 JS 兼容
UA 维护中的 UA 规则库 浏览器/OS/设备/bot 样本与版本记录
加密 JCA/JCE + Bouncy Castle 1.80 精确 key/模式/padding/字节/DER/SM2 参数,跨实现验签;对称路径见 DIFF-008
QR ZXing 3.5.4 PNG/剪贴板真回读、Logo/纠错、坏图;历史见 DIFF-009
调色板 自有 ColorEngine + AWT Robot 冻结截图 主题/标准色 SHA-256、五运算、Shift 选对比色;权限/全黑拒绝见 DIFF-010
留言板 自有 MessageBoardEngine + OS 唤醒进程 80 字 UTF-16、8 预设/6 主题、自动适配字号;演示唤醒见 DIFF-011
图像 ImageIO/Java2D + 自有 data/images 图片库 EXIF 方向、alpha 棋盘格、1600 万像素上限、压缩/水印、区域截图。见 DIFF-013
SVG 本产品内嵌 ImageTracer.java 1.1.2(Unlicense) poster/photo/bw 参数映射到 ltres/pathomit 等;输出含 path,禁止嵌入 bitmap。见 DIFF-013
PDF PDFBox 3.0.4 真页面 importPage;页码顺序/去重对齐 Electron;加密 PDF 拒绝;取消删除本批半成品。见 DIFF-012
系统信息 OSHI 6.8.2 真机采集;序列号默认遮蔽;OS 与本产品 JVM 信息分区;切走工具取消采集。见 DIFF-014
网络/IP 自有 NetEngine + 平台进程 argv IPv4↔Long fixture;InetAddress DNS;ping/ifconfig/netstat 可取消;WHOIS 端口 43。见 DIFF-015
环境变量 自有 EnvEngine + 本产品 data/environment 用户/系统文件备份后写入;进程/JVM 只读;Unix 钩子使用 Compose 标记。见 DIFF-016
Host 自有 HostEngine + data/hosts/profiles.json 保存方案不改系统文件;应用前 diff/备份/指纹冲突;提权失败保持原 hosts。见 DIFF-017
代码运行 自有 CodeRunEngine + ProcessBuilder argv Java 源文件模式;白名单环境;1 MiB/2 MiB 上限;ProcessHandle 杀树。见 DIFF-020
随手记 自有 NoteVault + NoteFrontmatter + QuickReplaceEngine + MarkdownPreviewEngine + ColumnEditEngine 默认 data/vaults/quick-note;frontmatter 与检索见 DIFF-029;24 项替换对齐 Electron 样本。预览见 DIFF-023;替换见 DIFF-021;列编辑见 DIFF-024;Git 见 DIFF-025/028/029;外部冲突见 DIFF-026
Git 自有 GitEngine + 外部 Git CLI argv + 空闲/失焦调度器 仓库根串行锁;-c 身份不改 global config;ASKPASS token 不写入 URL。见 DIFF-025、DIFF-028、DIFF-029
更新 自有 UpdateEngine + UpdateDownloader + OkHttp HTTPS 只读 products.next-compose;字节数与 SHA-512 校验后打开安装包,不自动安装。见 DIFF-027
时间/计算 java.time、BigInteger/BigDecimal、自有表达式 AST 时区、DST、精度、算符、溢出;不 eval 用户文本

8. 动态 Protobuf 的具体路线

首选随本产品分发对应 OS/arch 的 protoc 4.29.3(构建时从 Maven 分类器复制到 classpath helpers/),启动受控子进程输出 descriptor set,再由 JVM DynamicMessage 编解码。这样用户临时粘贴 .proto 不需要重新编译应用,也不要求用户自己安装 protoc。JSON 适配见 DIFF-007

仅允许临时工作目录及用户明确选择的 import roots;禁止通过 import 跳出授权根、自动联网下载未知 schema。超时、大小、递归深度、编译 stderr 和临时文件清理统一处理。well-known types 需要随包带所需定义或明确支持策略。

DynamicMessage 依赖 Descriptor,不能用它冒充 proto 源码解析器。官方 API Java 现有 ProtoBufUtil 的轻量解析可参考,但必须通过 nested/map/oneof/enum/import 等样本后才能成为替代。

JSON 适配固定字段原名、默认字段、64 位字符串、enum 名和 bytes Base64;不要直接套 JsonFormat 默认行为就声称与 protobufjs 相同。部分输入协议允许差异时在工具内说明,并留可重复 fixtures。

9. 进程与不可信计算

ProcessService 输入为可执行文件路径、argv 列表、工作目录、限定环境、时间/输出限制,输出为流与退出状态;不能拼用户字符串执行 shell。cURL 导入是解析文本,不是运行该命令。

本产品自己的 regex/格式化 worker 可以使用随包 JVM 的 java 启动独立 Main;只提供受限 IPC/stdio 协议,指定 -Xmx、超时和最大输入输出。需要结束整个进程树:Unix process group/后代追踪、Windows Job Object 或经验证等效实现,不能只取消 UI Job。

用户代码运行属于本机代码执行,界面显示运行环境与目录;不称为安全沙箱。Java/Groovy/Python/Node 工具由显式外部 runtime 启动,JRE 精简包不保证有 javac。应用本身的 JVM 和用户选择的 Java 执行环境分别管理。

10. API 示例边界

本文未提供可直接声称构建通过的 Gradle 模板。P0 从当前官方兼容配置建立工程,再将真实命令写入 README。若使用 jvm("desktop"),约定任务入口为 :composeApp:run:composeApp:desktopTest:composeApp:createDistributable,需先以 :composeApp:tasks --all 核实。

安装包必须执行 runDistributable 或直接启动镜像验证,因为完整 JDK 下 run 通过不能证明 jlink 后无缺少模块。不要只运行 JVM 单测而跳过 Swing/Compose/安装包行为。