Uninote
Uninote
用户根目录
brdr
common
programming
docs
tests
后端试题
问题讨论

Markdown Editor 技术架构与实施计划

返回文档导航

TypeScript 编译边界

  • 客户端、同步服务、脚本和测试共用 strict 基线,分开检查 Node/Electron、浏览器 ESM、浏览器冒烟夹具和 Windows Script Host 脚本。
  • CommonJS 源码使用 .ts,浏览器模块使用 .mts;运行时分别加载 .js 和 .mjs。
  • 渲染模块通过 ESM 命名导入连接,窗口状态集中在 src/renderer/runtime/state.mts,共享类型在 src/renderer/runtime/types.mts;preload API 保留类型声明、IPC 通道、序列化格式和隔离设置。
  • 动态原型组合保留原实现,宿主接口与显式 this 类型表达组合后的方法。
  • Windows 回收站脚本单独面向 Windows Script Host 编译为 ES5 普通脚本;它不进入 Node 模块配置,避免 CommonJS 标记破坏系统回收站枚举和恢复。
  • 构建输出统一到忽略的 .build/;Rollup 只构建现有编辑器依赖,安装包从编译目录取文件。

1. 规划结论

  • 本项目已经基于 Electron、原生 HTML/CSS/TypeScript 实现了可运行的桌面端 Markdown 编辑器。

  • 后续应以现有代码为基础渐进演进,不再将 Flutter 重写作为默认路线。

  • Windows/macOS 本地笔记库、可靠性基线、同步协议 v1 和托管同步 Beta 已经落地。

  • 近期工作转向编辑与导出增强、同步 Beta 生产化、正式发布链路和真实负载验证;移动端与团队协作仍根据用户需求和运行数据单独决策。

整个产品遵循以下原则:

  • 本地优先:无账号、无网络时仍可完整编辑和管理普通 .md 文件。
  • 开放格式:正文、附件和目录结构可被其他工具直接访问,不把用户数据锁进私有数据库。
  • 可靠性优先:保存、恢复、外部文件变更和冲突处理先于高级语法与云功能。
  • 渐进式改造:保留已实现行为,按模块提取现有代码,不进行无收益的全量重写。
  • 协议先行:桌面端和未来移动端共享文件约定、同步协议和测试用例,而不是强求共享 UI 技术栈。

2. 当前架构基线

2.1 已有技术栈

  • Electron 43.4.0,使用 electron-builder 26.15.3 打包。
  • 主进程:Node.js CommonJS,负责窗口、文件读写、图片落盘、Markdown 渲染、工作区和同步调度;托管同步请求通过 Electron net.fetch 发出。
  • 预加载脚本:通过 contextBridge 暴露受限 IPC 接口。
  • 渲染进程:原生 HTML、CSS 和 TypeScript(编译为 JavaScript),无前端框架。
  • Markdown:markdown-it 15.0.1,开启原始 HTML 解析和自动链接,普通源码换行遵循 CommonMark 的软换行行为;mermaid 11.17.2 负责受控图表预览。
  • HTML 安全:sanitize-html 2.17.7,配合页面 CSP。

2.2 已有产品能力

  • 普通文件夹笔记库、目录树、搜索、批量失效链接检查、快速打开、最近文件、收藏和可恢复的文件管理操作。
  • 多标签编辑、原子保存、按笔记库恢复未保存内容、外部文件监听和双版本冲突处理。
    • 渲染端通过 renderer/editor/pages.mts 按文档 ID 保留独立的 CodeMirror 视图、历史和装饰状态。
    • 标签切换只切换叠放页面的可见性,不重写编辑器值,从而保留每个打开标签的滚动和选区。
    • 每个 CodeMirror 页面持有独立的编辑状态与撤销/重做历史,连续输入与输入法组合文本由编辑器历史机制分组;旧 textarea 的自建补丁历史仅保留在 renderer/editor/legacy/ 审计材料中。
    • 文档模型独立保留最近一次成功保存的正文基线;输入、撤销或重做后的正文与该基线一致时清除未保存标记,越过保存点后再次出现差异时恢复标记,不以撤销栈是否为空作为判断依据。
    • 可编辑正文中按 Tab 插入两个空格;有选区时对选中的完整逻辑行统一缩进两个空格,按 Shift+Tab 则从每行移除最多两个前导空格。转换后继续选中处理过的行,支持一次撤销和重做,并同步更新文档和预览。
    • 命令 editor.selectionToList 有选区时切换选中的完整逻辑行,无选区时只切换光标当前行并保持逻辑光标位置;
      • 目标行全部带有 - 时统一移除,否则为未带标记的行补上 - ,包括选区中的空行。
      • 该命令在可编辑 Markdown 文档的命令面板中不依赖选区始终展示,默认快捷键为 Alt+Shift+L,原生 Edit 菜单同步显示该 accelerator 但不重复注册按键,也可在快捷键设置中修改;
      • 菜单入口通过只读 IPC 通知复用同一渲染端命令。
    • 可编辑正文中按 Ctrl+X(macOS 为 Command+X),有选区时剪切选区,无选区时剪切光标所在的完整逻辑行(含换行符);末行没有后续换行符时删除前一个换行符,剪贴板保留末行正文,末尾空行剪切为换行符。
    • 整行剪切支持未保存文档、空行以及撤销和重做;空文档不修改剪贴板,已保存文档继续保留跨文档粘贴时的相对图片路径调整。
    • Ctrl+C(macOS 为 Command+C)无选区时按相同的逻辑行规则复制当前行,有选区时仅复制选区;复制不修改正文、光标位置或选区,支持未保存文档,空文档保持剪贴板不变。
    • Ctrl+Tab 等组合键保留原有快捷键行为;只读编辑器和输入法组合输入期间不处理缩进。
  • GFM 与 Mermaid 实时预览、标题大纲、双向滚动同步、编辑/预览页内查找(全部结果与当前结果分层高亮)、源码位置高亮和完整键盘导航。
    • 页面内查找的状态、事件与定位集中在 renderer/find/controller.mts,样式集中在 renderer/find/styles.css。
    • 编辑器查找使用 CodeMirror decorations,在可见源码范围生成命中标记;焦点返回正文后仍保留高亮,编辑器负责选区、输入和滚动。
    • 结果导航及页面缩放重排后,编辑器查找直接根据命中标记的实际矩形补偿横纵视口,避免中英文混排或无换行长行因估算坐标产生偏移、结果落在视口之外。
    • 搜索框保持打开但焦点返回编辑器时,Escape 仍会关闭搜索框。
    • 编辑区查找框每次重新聚焦或文本变化时,按当前 caret 同步匹配序号和高亮;仅聚焦查找框不会移动 caret 或视口。
    • 查找栏关闭时按 F3 或 Shift+F3 会在当前编辑区或预览区重新打开查找栏并保留查询。
    • 编辑器中的 F3 / Shift+F3 每次从最新 caret 或选区边界重新计算目标匹配,之后继续循环查找。
    • 预览搜索框保持打开时,焦点位于预览正文、正文链接或搜索栏按钮上,Escape 也会关闭搜索框并清除搜索高亮,焦点返回预览正文。
    • 预览内容因编辑而重渲染时,仅针对新 DOM 重建查找范围,不把普通内容刷新解释为结果导航,从而避免第一个结果的定位滚动反向重置编辑区。
    • 应用快捷键通过 renderer/keyboard/shortcuts.mts 精确匹配声明的修饰键,未声明的 Shift、Alt 或同时按下 Command 与 Control 都不会触发命令。
    • src/renderer/keyboard/ 集中维护键位转换、When 条件、配置校验、命令分派与设置界面;文件头说明模块职责,函数定义头部说明作用,定义之间保留空行;调用处仅保留解释复杂逻辑或特殊行为的必要注释。
  • 本地图片与剪贴板图片落盘、assets/ 相对路径管理,以及移动/复制/跨文档粘贴时的图片路径调整。
  • 托管同步 Beta:账号、自动同步、仓库发现与克隆、revision/cursor、Markdown 保守三方合并与冲突版本、设备、配额、版本历史和原始库导出。
  • ESLint、Node 单元测试、Electron 冒烟测试、Windows/macOS CI 和未签名目录打包。

2.3 当前限制

  • 正文编辑器已接入 CodeMirror 6;预览已有去抖和过期结果丢弃,但超大文档编辑体验仍需真实设备数据验证。
  • 编辑器自动换行通过 CodeMirror 动态配置应用于所有已打开及后续创建的编辑页,并作为共享设置持久化;可从 View 菜单或命令 editor.toggleWordWrap 切换,默认快捷键与 VS Code 一致为 Alt+Z。
  • Mermaid 与代码高亮已实现;当前笔记和仓库批量 PDF/Word 导出、保存时 HTML/TXT 导出均已交付。脚注、数学公式及其他扩展语法仍待评估。
  • Windows/macOS 可以生成测试制品,但正式签名、公证、自动更新和升级回滚链路尚未交付。
  • 托管同步仍为单节点 Beta,不包含非图片通用附件、密码重置、团队共享、端到端加密、对象垃圾回收或产品化的版本保留策略。
  • iOS、Android 和 Web 客户端尚未立项实现。

2.4 CodeMirror 实施结构

  • renderer/editor/pages.mts 为每个文档持有一个 CodeMirrorAdapter,关闭页面调用 destroy()。
  • renderer/editor/codemirror-adapter.mts 提供正文、anchor/head 选区、视图恢复、历史、坐标和装饰接口。
  • renderer/editor/codemirror/ 拆分入口、主题、键位及可见区域装饰;不启用自动续列表或自动缩进。
  • runtime/editor/selection-toolbar.mts 统一承载选区工具栏与编辑器右键菜单;右键菜单复用现有格式转换,支持撤销、重做、剪贴板、全选及八项常用 Markdown 操作。
  • 鼠标右键、ContextMenu 和 Shift+F10 均可打开菜单;只读页保留复制与全选,粘贴通过 Electron 原生编辑命令继续进入现有文本、图片及内部 Markdown 粘贴流程。
  • Rollup 输出 .build/src/renderer/editor/generated/editor.mjs;其余 renderer 源码编译为同目录结构的 ESM,开发模式直接加载维护中的编辑器源码。
  • 正文变化生成缓存字符串并按文档 ID 更新 DocumentStore;选区和滚动更新不重新序列化全文。
  • 保存回执按文档 ID 记录实际写盘基线,等待保存期间的新输入仍保持 Dirty。
  • 预览滚动锚点使用编辑器行块几何坐标;源码链接用 posAtCoords 命中现有链接解析结果。
  • 主菜单撤销、重做、复制和剪切通过应用命令调用编辑器;普通输入控件保留浏览器命令路径。
  • 旧镜像及历史代码保存在 renderer/editor/legacy/ 供审计,不进入正文运行路径。
  • 验证数据和未完成的平台验收见 迁移记录。

3. 原规划需要调整的内容

原规划 调整 原因
Flutter 四端统一为推荐方案 Electron 继续承担 Windows/macOS;移动端单独立项 当前已有 Electron 产品,重写会丢失已有投入;Electron 不能直接覆盖 iOS/Android
一开始覆盖四端 先桌面双端,再决定移动端 文件系统、后台同步和编辑交互的平台差异很大,应先验证核心体验
同时建设自研云和四家云盘接入 第一阶段支持普通本地目录;系统云盘目录天然可用,自研同步后置 多套同步引擎会成倍扩大冲突、权限和测试矩阵
Markdown 文件采用二进制差分上传 首版按完整文件上传,附件按内容哈希去重 Markdown 通常较小,差分算法的复杂度和故障面收益有限
以时间戳决定冲突胜者 使用服务端版本号/基线版本检测;冲突时两份都保留 设备时钟不可靠,自动覆盖可能造成不可恢复的数据丢失
对所有冲突进行“智能合并” 仅对存在共同祖先且修改区间不重叠的 Markdown 执行保守三方合并;其余情况保留 Base/Local/Remote Markdown 结构合并容易破坏列表、代码块和引用,无法证明安全时必须交给用户处理
.lock 防止多设备同时打开 本机进程锁只处理本机并发;跨设备依赖版本协议 云盘无法可靠同步锁的生命周期,陈旧锁会阻塞正常使用
SQLite 文件放在笔记库内 不在仓库内放 SQLite;同步基线、队列和 cursor 使用 .mynote 下的轻量 JSON 保持仓库状态可定位、可随仓库移动,同时避免数据库锁定与恢复复杂度
客户端库级 AES 加密与纯 .md 同时成立 先使用操作系统磁盘保护、HTTPS 和服务端存储加密;端到端加密单独设计 加密后的文件不再是可被其他工具直接读取的纯 Markdown,两者需作为不同模式明确取舍
Go + MySQL + Redis + 对象存储一次到位 同步服务从单体 API、关系数据库和对象存储起步,达到规模指标后再引入 Redis 早期没有需要独立缓存和消息基础设施的负载证据
2 核 4G 支撑万级用户 通过压测和同步负载模型确定容量 用户数不能代表在线连接数、附件流量和版本存储量
4 个月完成完整清单 以里程碑和验收门槛推进,不承诺未经估算验证的总工期 原清单同时包含编辑器、四端、云服务、加密和企业能力,范围远超单一版本

4. 当前客户端架构

4.1 进程职责

┌──────────────── Renderer ────────────────┐
│ UI / 编辑器 / 标签 / 目录树 / 预览 / 搜索 │
└───────────────────┬──────────────────────┘
                    │ 类型化、白名单 IPC
┌──────────────── Preload ─────────────────┐
│ 参数收敛 / 通道封装 / 不暴露 Node 能力     │
└───────────────────┬──────────────────────┘
                    │
┌────────────── Electron Main ─────────────┐
│ 窗口生命周期 / 文件服务 / 监听 / 系统集成   │
│ 工作区服务 / 恢复服务 / 同步调度 / 安全边界  │
└───────────────┬───────────────┬──────────┘
                │               │
       普通文件、assets、.mynote   应用数据目录
          同步状态 JSON          偏好 / 恢复稿 / 凭据

4.2 模块边界

当前代码已经形成以下边界:

  • window / menu:窗口创建、安全配置、菜单、退出和外链处理;原生窗口菜单使用所有平台一致的 Ctrl+Shift+Alt+S 在已打开窗口间循环聚焦,并可固定系统级切换命令的主窗口目标。

    • window-hotkey:注册 Alt+Shift+S 系统级热键;主窗口已显示且激活时在 macOS 隐藏整个应用、在 Windows/Linux 最小化主窗口,其他情况下恢复、显示并激活。
  • workspace-manager / workspace-state-store / mynote-file-times:笔记库选择、目录扫描、搜索、最近记录、带时间戳的文件/大纲收藏和路径约束。

    • 文件树扫描统一取 mynote 最近历史时间与磁盘 mtime 中较早的一个;打包筛选提交也重新获取工作区快照;
    • 首次加入的新增历史保存文件当时的磁盘 mtime,只有新增历史时使用该值,旧历史缺失该值时回退到新增版本时间,尚无历史时使用当前磁盘 mtime,全程不调用 Git。
    • 该选择算法集中在 effectiveFileModifiedAt,便于统一调整;每次应用文件树筛选或比较时都会重新扫描候选文件。
    • 主进程为每个 BrowserWindow 创建独立 WorkspaceManager,IPC 按请求所属窗口解析当前库;
    • WorkspaceStateStore 仍共享最近列表、普通偏好和后台同步配置。
      • WorkspaceStateStore.initialize() 创建并维护进程单例;使用方默认调用 getInstance(),主进程不再逐层传递实例。
      • 菜单、工作区、同步及工具窗口的参数类型与调用链均不接受 stateStore;内部直接获取单例。
      • 测试通过模拟 getInstance() 返回隔离实例,避免临时目录或多个模拟客户端互相影响。
      • 初始化时等待一次 loadFromDisk(),完成磁盘读取、校验与旧设置迁移后再启动依赖服务。
      • load() 同步返回已初始化的内存状态,不触发磁盘读取;未初始化时抛出错误。
      • 鼠标微调与光标锁定内部直接读取单例设置,主进程不传入 getSettings。
        • 每次微调或新一轮按键保持开始时读取最新设置,同一轮操作沿用设置快照。
        • 鼠标设置的默认值和规范化逻辑由 workspace-state-store 定义,mouse-adjust 直接导入,依赖方向保持单向。
    • 文件树关联、空文件夹显示及收藏排序属于仓库 UI 状态,不进入普通设置;
    • 普通设置保存成功后由主进程广播到所有窗口,使各渲染进程立即应用同一份已提交配置。
  • outline-index-store / quick-open-search / shared/markdown-outline:原子持久化大纲缓存、后台增量校验、拼音检索,以及大纲面板和索引共用的标题解析规则。

  • snippet-window / snippets / snippet-encoding:独立受限窗口通过 IPC 管理 .cfg 集合与片段操作;主进程读取来源文件、自动判定编码,并复用 quick-open-search 的匹配评分。集合状态保存在应用数据目录,不属于笔记库。

  • broken-links / renderer/search/editor:解析仓库内 Markdown 与 HTML 链接,校验本地路径和标题锚点,并生成可跳回源码位置的只读诊断报告;协议链接不发起网络请求。

  • file-service / file-access / document-watcher:授权文件读取、原子写入和外部变化监听。

  • recovery-store:按笔记库隔离恢复会话并隔离损坏状态。

  • markdown-service / markdown-images:Markdown 解析清洗、源码范围和图片路径处理;

    • 普通 fenced code block 在主进程按需加载 Highlight.js 完整语言目录,有标识时按语言或别名高亮、无标识时自动识别,未知语言和超过 102,400 个 UTF-16 单元的代码块回退为安全纯文本,Mermaid 继续由渲染端专用模块处理。
  • publish-export / publish-renderer:读取仓库级 [export] 配置,在共享原子 Markdown 写入后生成 HTML/TXT/共享 CSS、复制库内图片,并通过按需隐藏渲染页复用 Mermaid 与浏览器 innerText;

    • document-export / document-export-handlers 提供当前笔记与仓库批量 PDF/Word 导出,复用同一串行渲染队列和图片嵌入流程。
  • 渲染端 document/store / preview/outline / preview/mermaid / preview/cache / sidebar/favorites:标签状态、标题大纲、收藏分组与三态时间排序,以及受控 Mermaid 渲染;

    • Mermaid 的 Dagre 连线定位若因 display:none 路由节点坍缩而失败,会使用保留布局盒的不可见节点进行一次定向重试。
    • 隐藏的预览面板只准备普通 Markdown,显示后再执行依赖真实容器尺寸的 Mermaid 布局;
    • 已完成的未修改标签预览通过 DocumentFragment 保留 DOM、SVG 几何和事件监听,切回时不重新解析与绘图。
  • 大纲收藏入口(星标、右键菜单、命令面板及 Alt+F)统一调用 outline.toggleFavorite 对应的渲染端切换流程;命令上下文仅在仓库内 Markdown 文件中启用,执行时从最新正文解析光标或大纲焦点所属标题。

  • 文件树的“文件筛选与差异”由 createFileFilterDiff(document) 一次性组装,内部直接调用 createFileFilterControls() 创建预设编辑器及时间控件。

    • 优化后的文件职责、主要依赖图、初始化顺序及保留的双向依赖见文件树模块职责与依赖。
    • createFileFilterControls() 是筛选主组件,在空容器内统一创建预设编辑器、修改时间子组件和预设操作区。
    • 预设操作按钮直接声明在主组件模板底部的操作区中,初始化时从该区域收集按钮并绑定事件。
    • 主组件模板通过 ID 预留修改时间插槽,再从当前容器按插槽 ID 获取元素,作为 host 传给 createFileFilterTimeControls();未挂载到文档的打包弹窗也使用同一流程。
    • 修改时间子组件 createFileFilterTimeControls() 在插槽内自行创建时间字段,并管理时间选择、校验和日历行为;调用方只传入容器、控件 ID 和业务回调。
    • renderer/dependencies.mts 初始化并导出 treeFileFilters;tree-controls.mts 通过命名导入访问它,调用 connect() 接入仓库状态、预设保存、应用筛选和通知回调,不再补建 UI。
    • 回调在用户操作时才读取运行时状态,避免启动阶段提前访问尚未初始化的仓库数据;打包界面继续独立复用同一个过滤器创建函数。
    • runtime/tree-view.mts 集中管理树可见性缓存、节点渲染、焦点与键盘导航、目录展开折叠、路径定位和关联当前文档。
      • context-menus.mts 保留标签与文件树菜单行为;dialogs.mts 保留通用对话框行为。
    • runtime/tree-difference-form.mts 集中管理差异表单的字段、草稿、校验、选项存取、状态文案和控件事件。
      • editor/differences.mts 保留编辑器差异展示;editor/difference-baseline.mts 保留基线异步读取、差异高亮刷新与 Git HEAD 轮询。
    • runtime/tree-controls.mts 管理标题控件、筛选应用与持久化、设置菜单和筛选弹层的开关及外部点击关闭。
      • 文件打包按钮也在此绑定,按需加载 sidebar/file-pack.mts 并注入仓库操作回调;runtime/publish-export.mts 保留文档与站点导出。
      • 树展开折叠按钮由 tree-view.mts 绑定,差异表单事件由 tree-difference-form.mts 绑定,不再分散到全局事件或编辑器基线模块。
      • 原模块保留迁移函数的命名再导出,内部调用者直接引用职责所属模块。
    • 新模块继续同步初始化,先准备编辑器差异状态,再连接差异表单和树筛选回调,最后恢复工作区。
      • 命令 ID、快捷键、仓库 UI 状态格式及文件打包按需加载流程保持不变。
  • 渲染端启动入口 renderer.mts:先初始化 renderer/dependencies.mts 中的对话框与共享 UI,再按依赖顺序调用 renderer/runtime 中各业务模块的初始化函数。

    • 业务模块通过命名导入共享函数,并读写 runtime/state.mts 中的窗口状态;ESLint 与 TypeScript 逐模块检查,不再拼接脚本。
    • 主窗口、片段窗口、Quick Cmd Form 和发布渲染页均使用 ESM 模块脚本;开发模式的加载和热更新边界见 Vite renderer 开发。
  • 渲染端 DOM 构建:

    • renderer/dom.mts 提供返回真实节点的 createElement(type, props, ...children);runtime 通过 renderer/dependencies.mts 的命名导出共用该函数。
    • 最近笔记库、编辑器差异行和文件树差异列表用嵌套调用表达 DOM 层级。
    • 按需加载的 commands/palette.mts 复用该函数构建命令面板及结果行;dialogs/application.mts 在初始化时挂载应用对话框。
    • editor/pages.mts 的引用刷新按钮和 editor/input-method-indicator-ui.mts 的设置项、徽标也使用该函数;旧版 editor/legacy/ 仍保存原样供审计。
    • keyboard/page.mts 的动态快捷键表格、preview 的代码块工具栏与 Mermaid 错误提示,以及 runtime 的列表、目录树、标签和同步详情也用嵌套调用创建节点。
    • 其余运行时代码中的布局拖拽条、提示浮层、文件打包树、筛选预设和同步版本历史使用同一函数,通过 ESM 导入访问。
    • 独立运行的编辑器基准脚本也导入该函数创建测试用节点。
    • 原有 innerHTML 构建的快捷键对话框、选择工具栏、Markdown 预览和 Mermaid SVG 保持原有生成方式。
    • 属性对象支持 className、样式对象和事件函数;文本子节点用 createTextNode 创建。
  • main/input-method-status.ts 在 Windows 通过 User32/Imm32 读取前台 Chromium 编辑控件的真实 IME 开启状态和 IME_CMODE_NATIVE 转换模式;

    • 渲染端启用输入法徽标且编辑器聚焦时按 120 ms 间隔读取,并用 CodeMirror 的 coordsAtPos 将“中/英”徽标定位在活动 caret 下方。
    • 原生接口不可用时回退到组合输入与直接字符事件;
    • 设置默认关闭,失焦、隐藏编辑区、只读或差异模式会隐藏徽标。
  • 版本历史视图 renderer/sync/version-history-view.mts:统一生成同步中心“最近版本”和指定文件历史对话框的条目、元数据、差异与加载状态;

    • 具体恢复策略由各入口注入,单文件入口不暴露仓库整体恢复。
    • 共享 buildLineDiff 在每个连续变化段内通过有界动态规划对内容相似行做保序匹配,再输出“旧行、新行”,从而避免中间插入造成后续修改错配;
    • 无法配对的纯删除或纯新增保持单行,超大变化段不建立额外无界矩阵。
    • 每次手动同步或安全自动同步完成前由 main/sync/history-store.ts 以本地连续 revision 为起点、以本轮文件 cursor 为固定上限分页接收新增版本,只写入新的不可变版本详情并逐页原子推进清单;
    • 首次同步或缓存损坏修复才下载完整历史。
    • 同步中心、文件历史和编辑器时间差异只按本地文件身份读取这份账号级缓存,因此未同步内容不会触发历史查询网络请求,也不依赖 Git。
  • 同步中心打开或点击“刷新状态”时,会重新读取账号概览、本地同步状态、配额、远端仓库、设备、版本历史和待推送预览;

    • “本地/远端游标”分别显示本机已应用 revision 与远端仓库最新 revision,“待拉取”按两者的 revision 差值计算,刷新本身不会触发文件同步。
    • 本地版本历史使用独立 Promise 立即读取并渲染;
    • 配额、远端仓库、设备和待推送预览继续并行加载,网络响应不会阻塞“最近版本”的首屏内容。
    • 账号概览因暂时断网返回远端错误时,渲染状态会保留本地历史区而仅隐藏远端专属模块,并直接触发本地版本读取。
  • 同步辅助函数通过 ESM 命名导入供 runtime 使用;共享 UI 在 initializeDependencies() 中创建,业务状态按启动顺序初始化,模块求值期间不读取尚未初始化的状态。

    • test/module-extraction.test.ts 检查初始化覆盖与顺序,test/renderer-access.test.ts 检查测试访问真实 ESM 状态,不向生产页面恢复旧全局绑定。
  • 当前文件差异模式由 renderer/runtime/editor/differences.mts 管理,模式标记保存在该文档的 viewState 中并随恢复会话校验和持久化。

    • 模式启用时,长期存活的 CodeMirror 页面保持只读并隐藏,renderer/editor/difference.css 对同一共享逐行 diff 输出进行全编辑区渲染;
    • 差异导航直接定位并标记可见变化块,同时同步隐藏编辑器的光标位置;
    • 临时最大化不写入仓库级面板偏好,通用面板恢复入口会在异步设置或工作区刷新后重新断言差异模式布局。
  • 渲染端样式:

    • 基础工作区、文档面板、预览代码高亮、对话框与响应式样式分别位于 renderer/styles/base.css、renderer/styles/document.css、renderer/styles/preview-code.css、renderer/styles/dialogs.css 和 renderer/styles/responsive.css。
    • document.css 紧随 base.css 加载,保留提取前的样式级联顺序。
    • 页面查找与编辑器差异模式样式分别位于 renderer/find/styles.css 和 renderer/editor/difference.css。
  • sync-handlers / sync/*:按需加载的同步入口、账号编排、仓库状态机、协议校验、持久化队列、自动调度和远端适配器。

  • 同步客户端:

    • sync/client/local 负责初始化、扫描和队列协调,sync/client/pending-preview 负责待推送内容预览。
    • sync/client/files 负责文件写入、远端应用和冲突副本,sync/client/merge 负责合并基线与冲突三份文件。
    • sync/client/transfer 负责上传、拉取及运行状态,sync/client/restore 负责整体恢复。
    • sync/client/helpers 保存共享安全辅助逻辑,目录入口 sync/client/index 组合公开类。
  • server/hosted-sync:独立托管 HTTP 服务、SQLite 元数据、加密对象存储、备份和运维脚本。

  • 托管存储:

    • store/index 保留初始化、数据库事务和文件操作,store/accounts 负责账号、设备和会话,store/libraries 负责仓库管理。
    • store/versions 负责历史、快照、恢复、导出与指标,store/validation 负责账号和设备输入约束。
  • 集中模块提取的职责表、加载约束与验证入口见 模块边界与文件长度维护。

  • 暂不为了“架构统一”引入大型 UI 框架。

  • 只有当目录树、设置、版本历史和冲突界面的状态复杂度成为明确瓶颈时,才通过小型原型决定是否迁移到 React/Vue 等方案。

正文搜索目前保留在工作区服务的内存缓存中;大纲索引使用仓库 .mynote 下的版本化 JSON。达到性能阈值后再评估 SQLite FTS。

4.2.1 文件筛选、差异与打包模块

以下文件位于 src/renderer/sidebar/,按界面组装、共享控件、筛选规则和时间计算划分职责。

file-filter-diff.mts:文件树筛选与差异界面
  • 入口是 createFileFilterDiff(document),由 src/renderer/dependencies.mts 的初始化函数在启动时调用。
  • 创建“文件筛选与差异”浮层,包括文件筛选区域、差异模式、独立差异时间、匹配数量和底部操作按钮。
  • 调用 createFileFilterControls() 创建共享筛选主组件,复用 fileTimeRangeFields() 生成独立差异时间字段。
  • 返回共享筛选组件的接口,并增加 connect(callbacks),供 runtime 接入仓库状态、预设保存、应用筛选和通知。
  • 本文件负责界面组装;实际文件树筛选、差异模式应用和整个文件树比较由 runtime 及对应差异模块处理。
file-filter-ui.mts:共享筛选主组件及时间子组件
  • createFileFilterControls() 同时供文件树浮层和打包弹窗使用。
  • 主组件模板声明预设选择、名称、包含规则、排除规则、修改时间插槽和底部预设操作按钮。
  • 按插槽 ID 从当前容器获取元素,作为 host 传入内部的 createFileFilterTimeControls()。
  • 时间子组件在插槽内创建时间控件,管理字段启用状态、有效性、焦点和日历按钮。
  • 主组件管理筛选草稿、预设选择及另存、更新、重命名和删除交互,通过回调保存预设、应用筛选和通知结果。
  • 返回 load、read、clear、refresh 和 panel,供调用方恢复、读取、清除草稿、刷新预设列表和管理忙碌状态。
  • 导出的 fileTimeRangeFields() 只生成时间字段模板,还供 sync/file-tree-diff-ui.mts 的比较基线界面复用。
file-filter.mts:文件筛选与预设规则
  • 定义空筛选条件、内置预设和预设操作文案。
  • fileFilterSelection() 判断草稿是否仍与所选预设一致;修改条件后将选择标记为自定义。
  • validateFilePresetTarget() 校验预设操作目标,changeFilePreset() 返回新的预设列表及选中项;ID 创建和持久化由调用方负责。
  • createFileMatcher() 使用传入的 ignore 匹配器编译包含与排除规则。
  • matchingTreePaths() 合并路径与修改时间条件,返回可见路径集合、匹配文件数和文件总数,并保留匹配文件的祖先目录。
  • resolveFilterTime() 将保存的时间选择分派给 file-time-filter.mts,转换为实际截止时间。
  • 该模块提供不依赖 DOM 的规则函数,文件树与打包筛选共用同一套计算逻辑。
file-pack.mts:文件打包与 DeepSeek 问答
  • showFilePackDialog() 创建或复用当前笔记库的打包弹窗,复用共享筛选主组件,并维护独立的筛选与勾选状态。
  • createPackTree() 创建打包用的树副本;setPackNodeChecked() 和 packFiles() 管理勾选与已选文件,避免修改工作区原树。
  • 应用筛选时刷新文件树,再调用共享规则计算打包范围;重新筛选后默认勾选所有匹配文件,并清除旧输出。
  • formatPackSize() 统一大小显示,readPackedFiles() 按树顺序读取已保存内容、添加行号,并按选项添加相对路径头部。
  • 通过传入的回调完成读取、复制、另存、筛选记忆、API Key 保存和 DeepSeek 请求。
  • queryPackedFiles() 校验并执行受限的 search-files 查询;弹窗支持查询后提问、仅查询打包和直接对已选文件提问。
  • 管理异步忙碌状态、关闭后的过期结果,以及按笔记库保存的最近一次完整问答。
file-time-filter.mts:时间表达式解析与修改时间判断
  • 定义最近一天、七天、一个月等固定时长预设,并按当前时间计算滚动截止时间。
  • customFileTimeFilter() 将指定日期转换为本地当天零点。
  • durationFileTimeFilter() 解析数字天数、单位时长、ISO 8601 固定时长和本地日期时间,返回截止时间与显示文案;非法输入返回 null。
  • fileNodeMatchesTimeFilter() 按文件的 modifiedAt 判断是否满足截止时间,包含恰好等于截止时间的文件。
  • 该模块不创建界面;createFileFilterTimeControls() 位于 file-filter-ui.mts。
调用关系
  • 文件树入口:renderer.mts → renderer/dependencies.mts → sidebar/file-filter-diff.mts → sidebar/file-filter-ui.mts。
  • 打包入口:runtime/tree-controls.mts 按需加载 sidebar/file-pack.mts,再复用 sidebar/file-filter-ui.mts。
  • 共享规则:file-filter-ui.mts、file-pack.mts 和文件树 runtime 使用 file-filter.mts。
  • 时间计算:file-filter.mts → file-time-filter.mts;renderer 也直接复用时间计算函数处理差异时间。

4.3 数据约定

笔记库保持用户可读:

笔记库/
├── 任意目录/
│   └── note.md
├── assets/
│   └── image.png
└── .mynote/
    ├── library.json     # 库 ID 和格式版本,不存账号密钥
    ├── outline-index-v1.json # 可重建的大纲、文件修改时间和源码位置缓存
    └── sync-hosted-v1/
        └── <账号命名空间>/
            └── state.json # 基线、队列、cursor 和诊断日志

仓库级同步元数据和可重建的大纲索引放在 .mynote,路径不使用仓库绝对路径哈希;大纲索引不参与应用同步。以下设备或应用级数据仍放在操作系统应用数据目录,不进入笔记库:

  • 最近打开的库、窗口状态和用户偏好。
  • 正文搜索内存缓存、标签和星标。
  • 自动恢复稿。
  • 登录令牌;令牌应使用系统安全凭据存储。

标签和星标若需要跨工具可移植,后续可选择 YAML front matter;若只服务本应用,则保存在元数据中。该选择应先通过产品需求确认,避免悄悄修改用户正文。

5. 当前同步架构与演进边界

5.1 首版协议

每个库、文件和设备使用稳定 ID。路径是可变属性,不能兼任文件身份。每次变更记录:

  • libraryId、fileId、规范化相对路径和内容哈希。
  • baseRevision、操作类型、设备 ID 和客户端操作 ID。
  • 服务端生成的单调递增 revision 和时间;客户端时间只用于展示。

同步过程:

  1. 本地操作先原子写盘,再写入持久化变更队列。
  2. 客户端按操作 ID 幂等上传完整 Markdown 文件或附件对象。
  3. 服务端仅在 baseRevision 等于当前 revision 时接受覆盖。
  4. revision 不一致时下载双方版本,保留原文件并创建冲突副本。
  5. 客户端拉取游标后的远端变更,应用成功后再推进本地游标。
  6. WebSocket 只用于“有新版本”的提示,正确性仍依赖可重试的拉取接口。

删除采用墓碑记录,不能立即物理删除;当前不预设固定保留期。附件按 SHA-256 内容寻址和账号内去重,v1 不实现块级差分。当前字段、压缩快照、崩溃恢复和冲突矩阵见阶段 5 账号与托管同步 Beta,端到端源码位置见同步流程与代码位置。

5.2 当前服务端组成

  • 单体 HTTP API:账号鉴权、仓库、操作提交、变更拉取、设备、配额、版本、导出、指标和客户端报告。
  • SQLite 元数据:账号、设备、会话哈希、仓库、文件、版本、幂等结果、对象计量和审计事件。
  • AES-256-GCM 加密对象目录:保存 Markdown 与图片历史正文;对象键不直接使用用户输入路径或明文内容哈希。
  • 运维工具:在线 SQLite 备份、加密对象复制、校验清单、恢复演练和负载基准。

当前没有产品化的历史清理、孤立对象回收或主密钥轮换任务。只有目标环境压测和运行指标证明单节点 SQLite 架构达到瓶颈时,才迁移受支持的数据库驱动、拆分服务或引入缓存/消息基础设施。

5.3 云盘兼容边界

OneDrive、iCloud Drive 等在桌面端首先按“用户选择的普通文件夹”处理,不承诺应用理解其云端同步状态。应用负责检测文件变化并避免静默覆盖,但不再额外实现每家云盘 API。

6. 安全与可靠性基线

  • 保持 contextIsolation: true、nodeIntegration: false 和严格 CSP。
  • IPC 通道使用白名单,并在主进程校验参数、文件扩展名和规范化后的路径边界。
  • 链接导航先按外部 URL、当前文档标题、库内 Markdown 文档和独立文档旁的相对 Markdown 文件分流;库内路径继续经过工作区边界校验,独立文档链接由主进程基于已授权源文件解析和读取。
  • 外部链接只允许 HTTP、HTTPS 和邮件协议,并由主进程调用系统应用打开;其他协议不会执行。
  • 库内 Markdown 链接读取失败时使用非模态应用内提示,避免原生错误对话框导致编辑器失焦;保留来源文档的光标位置,允许直接继续输入。
  • 原有原生 alert 提示统一使用 renderer/dialogs/alert.mts 提供的自定义模态框,沿用新建文件对话框样式,仅显示一个“确定”按钮;Enter 确认、Escape 关闭。
  • 提示内容按纯文本展示并保留换行,长消息可以滚动;连续消息按队列逐条确认,不覆盖尚未读完的消息。
  • 关闭提示后恢复原控件焦点、文字选区方向及滚动位置;也支持从设置等已有模态框中弹出错误提示后返回原输入框。
  • 异步操作等待确认后继续后续流程;同步未保存检查仍立即阻止操作,文件删除事件保留编辑内容并用标签中划线提示,保存时在原路径重建文件;正文未偏离加载基线时关闭不确认。初始化错误使用应用提示组件。
  • 关闭未保存标签、移入回收站、删除远端仓库、设备退出、版本恢复及退出时恢复稿保存失败的确认均使用应用内“确定 / 取消”模态框;Escape 等同取消,取消后恢复原输入焦点及选区。
  • 文件树内部移入回收站成功后关闭对应的已打开标签,未保存保护仍会阻止删除。
  • 未保存标签在确认前保持打开;重复确认请求按取消处理,避免将另一个操作的确认结果用于当前操作。源码不再调用原生 alert、confirm 或 prompt。
  • Markdown HTML 必须经过清洗;Mermaid、数学公式等扩展逐项评估 CSP 和脚本执行风险。
  • 保存采用“同目录临时文件 → 刷盘 → 原子替换”,并处理 Windows 文件占用失败。
  • 文件监听事件需要去抖,并区分应用自身保存与外部修改。
  • 自动恢复稿不能替代用户文件;正常保存后清理,异常退出后明确提示恢复。
  • 自动更新必须校验签名;Windows 和 macOS 发布包分别执行签名、公证和升级回滚验证。
  • 端到端加密作为独立设计,不在未定义密钥恢复、设备加入和搜索能力前进入实现阶段。

7. 分阶段实施计划

每个阶段必须通过验收门槛后再扩大范围。优先级按 P0、P1、P2 表示。

阶段 0:工程与安全基线(P0,已完成)

目标

让现有功能可持续修改和发布。

  • 升级到受支持的 Electron、Markdown 和清洗依赖版本,逐项回归现有功能。
  • 增加 lint、格式检查、单元测试和最小端到端测试。
  • 为打开、保存、图片插入、Markdown 清洗、未保存关闭建立回归用例。
  • 收紧 IPC 参数校验、外链处理和错误返回;渲染层不直接获取系统能力。
  • 建立 Windows/macOS CI 构建;先生成测试制品,再配置正式签名。
  • 按模块提取现有代码,保持界面和功能不变。

验收

自动化检查在两端通过;核心操作有回归覆盖;安全检查无已知高危问题;现有功能无回退。

完成状态

  • 2026-08-14 已升级运行时依赖,建立 lint、单元测试、Electron 冒烟测试、性能基准、Windows/macOS CI 和未签名测试制品构建。
  • 主进程已拆分文件、Markdown、IPC、校验和窗口模块,并增加文件授权与外链安全边界。
  • 验证范围见阶段 0 工程基线。

阶段 1:可靠的本地编辑(P0,已完成)

目标

即使崩溃、断电或文件被其他程序修改,也不静默丢失内容。

  • 原子保存、另存为、编码/换行策略和明确错误提示。
  • 定时恢复稿、异常退出恢复和工作区/标签会话恢复。
  • 外部文件变更监听;无本地修改时重载,有本地修改时进入冲突对比。
  • 大文档渲染去抖、取消过期任务,并建立性能基准。
  • 完整键盘操作、焦点管理和基础无障碍检查。

验收

通过崩溃恢复、磁盘写入失败、外部并发修改和大文档测试;任何失败都不覆盖唯一副本。

完成状态

  • 2026-08-14 已实现原子保存、另存为、严格 UTF-8 与换行保留、会话和未保存内容恢复、关闭前刷新、外部文件监听、双版本冲突处理、文件删除保护、预览去抖与过期结果丢弃,并补齐标签键盘语义和焦点样式。
  • 设计与验证范围见阶段 1 可靠性设计。

阶段 2:桌面笔记库 MVP(P0,已完成)

目标

从“文件编辑器”升级为可日常使用的本地笔记应用。

  • 打开/切换笔记库、最近笔记库和目录树。
  • 新建文件夹、重命名、移动、复制、移入系统回收站。
  • 文件名和正文搜索;先采用简单索引,达到性能阈值后再引入 SQLite FTS。
  • 最近文件、星标和基础设置。
  • 统一附件策略,并处理文件移动后的相对图片引用。

验收

在约定规模的测试库中,扫描、搜索和文件操作达到性能目标;所有破坏性操作可恢复或二次确认。

完成状态

  • 2026-08-14 已实现笔记库恢复与切换、目录树、新建/重命名、移动、复制、系统回收站、文件名与正文搜索、最近文件、收藏和基础设置。
  • 工作区真实路径边界与符号链接逃逸会被拒绝,移动文档时同步迁移打开状态并调整常见 Markdown 图片相对路径。
  • 工作区设计与限制见笔记库与文件管理;1000 文件基准见工作区性能与验证。

阶段 3:编辑与导出增强(P1)

目标

补齐高频 Markdown 工作流,不扩大同步复杂度。

  • 根据用户反馈选择任务列表、脚注、目录和数学公式等剩余扩展语法。
  • Mermaid 采用隔离渲染或严格安全模式,不能放宽整个页面 CSP。
  • 维护已交付的 PDF/Word 和 HTML/TXT 导出;长图及其他格式在需求验证后再做。
  • CodeMirror 6 正文迁移已接入,继续按性能与跨平台回归结果评估后续优化。

验收

扩展语法有快照测试和恶意输入测试;导出结果在 Windows/macOS 进行视觉回归。

当前状态

  • Mermaid 围栏预览、错误降级和回归测试已经交付。
  • 代码高亮、当前笔记及仓库批量 PDF/Word 导出、保存时 HTML/TXT 导出已交付,导出行为见功能说明。
  • 脚注、数学公式及剩余扩展语法仍需按用户反馈确定优先级后实施。

阶段 4:同步协议原型(P1,已完成)

目标

先证明不丢数据的同步模型,再建设正式云产品。

  • 在独立测试环境实现版本号、游标、幂等操作、墓碑和冲突副本。
  • 构建设备离线、乱序、重复请求、重命名/删除冲突和网络中断测试矩阵。
  • 客户端同步模块按需加载,不阻塞编辑和启动。
  • 提供可理解的同步状态、失败重试和诊断日志。

验收

双设备随机操作和故障注入测试无数据丢失;协议文档和回归测试固定当前 v1 契约。

完成状态

  • 2026-08-23 通过独立文件式模拟服务端验证了 fileId、revision、cursor、幂等操作、墓碑及冲突副本协议 v1。
  • 2026-08-25 产品运行时已移除本地模拟模式,仅保留托管同步;协议测试继续使用测试目录内的内存远端替身。
  • 2026-08-27 按产品决定在不升版本号、也不兼容旧客户端的前提下,把 v1 拉取改为每个 fileId 只返回最终状态的单次压缩快照,并补充重复修改一次合并、批次中断恢复和重命名路径循环测试。
  • 当前契约与恢复规则统一记录在阶段 5 账号与托管同步 Beta。

阶段 5:账号与托管同步 Beta(P1,已完成)

目标

把已验证协议产品化。

  • 账号、设备、配额、对象存储、版本历史和远程登出。
  • 默认强制 HTTPS、系统凭据存储、服务端静态加密、审计和备份恢复演练;受控开发网络可通过 MYNOTE_SYNC_FORCE_HTTPS=false 显式关闭双端 HTTPS 限制。
  • 分批发布、指标监控、崩溃报告和协议成套升级策略。
  • 版本历史保留时间与配额由产品和成本模型决定,不预设固定 30 天。

验收

完成安全评审、容量压测、备份恢复和灰度升级;Beta 用户可以导出完整原始库。

完成状态

  • 2026-08-23 已交付可独立部署的单体托管服务、SQLite 关系元数据、AES-256-GCM 加密对象存储、账号、设备、配额、版本历史、远程登出,以及使用系统安全凭据的桌面 Beta 客户端。
  • 已加入审计、Prometheus 指标、客户端错误报告、在线备份验证和空目录恢复演练。
  • 2026-08-27 的 v1 压缩快照需要客户端与服务端同步升级,不兼容旧分页实现;客户端仍可导出服务端持有的完整原始 Markdown 库。
  • 部署、安全评审、容量基线和剩余 Beta 风险见阶段 5 账号与托管同步 Beta。

阶段 6:移动端可行性与产品决策(P2)

目标

根据桌面端和同步 Beta 的数据决定是否投入 iOS/Android。

  • 明确移动端是轻量阅读/快速记录,还是完整编辑器。
  • 对 Flutter、React Native 和原生方案做文件访问、后台同步、编辑器性能原型。
  • 复用同步协议、Markdown 兼容测试和设计规范,不默认复用桌面 UI 代码。
  • iOS/Android 分别验证 Files/SAF、离线队列和系统后台任务限制。

验收

原型达到启动、编辑、文件访问和同步可靠性目标后再进入正式开发。

阶段 7:高级能力(P2)

以下能力分别立项,不纳入基础版本承诺:

  • 端到端加密及密钥恢复。
  • 团队共享、权限、评论和实时协作。
  • Web 编辑器。
  • 插件系统、AI 辅助和思维导图。
  • 企业 SSO、私有化部署和管理审计。

8. 测试与质量策略

  • 单元测试:路径处理、文档状态、Markdown 清洗、同步状态机和冲突规则。
  • 集成测试:IPC 契约、原子保存、文件监听、附件移动和本地元数据恢复。
  • 端到端测试:新建到保存、异常退出恢复、目录操作、搜索和冲突界面。
  • 兼容测试:Windows/macOS 的路径、权限、中文文件名、长路径和换行符。
  • 安全测试:恶意 Markdown、危险链接、目录穿越、超大文件和畸形图片。
  • 同步测试:模型测试与故障注入,覆盖重复、丢包、乱序、离线和版本升级。
  • 性能指标:启动时间、输入延迟、预览耗时、目录扫描、搜索和托管同步延迟;已有基准用于比较相对回退,发布前仍需在目标设备、磁盘和网络环境复测。
  • 文件规模:产品源码中的 TypeScript、JavaScript、CSS、HTML、JSON、MTS 和 MJS 文件通过 npm run check:file-length 检查,单文件上限为 800 行;Markdown 文档、test/ 和自动生成的 package-lock.json 不参与该检查,test/ 也不参与 ESLint。
  • 源码说明:维护中的客户端、服务端、脚本和测试文件在头部说明用途;函数与方法定义前说明作用,定义之间保留空行。
    • 注释覆盖独立函数、类方法和对象中的具名函数;普通调用及匿名回调注册不重复添加动作说明。
    • 复杂逻辑、安全边界和错误恢复的必要注释保留;补充说明时不改变既有行为、日志或断言。
    • CSS、HTML 和工作流使用对应格式的文件头注释;生成物、第三方依赖、二进制资源及不支持注释的 JSON 保持原格式。

9. 近期执行清单

阶段 0、1、2、4、5 已完成。后续工作按风险和用户价值排序:

  1. 根据用户反馈确定阶段 3 剩余编辑语法及导出增强的最小范围,不扩大同步协议复杂度。
  2. 完善托管同步 Beta 的生产化边界:固定受支持运行时、评估数据库驱动迁移、主密钥轮换、版本保留和对象垃圾回收。
  3. 在目标磁盘、正文尺寸和并发度下复测同步容量,并补齐 HTTP 延迟、状态码、进程资源和备份时长监控。
  4. 建立 Windows/macOS 正式签名、公证、自动更新和升级回滚验证链路。
  5. 根据桌面端与同步 Beta 的使用数据决定是否启动移动端文件访问、后台同步和编辑性能原型。

每项继续拆分为独立、可回归的小提交;同步协议破坏性变化必须明确选择版本升级/双栈迁移,或像当前 v1 压缩快照一样安排停止流量后的成套升级。

10. 已确定与待决事项

已确定的架构选择包括:

  • Electron 继续承担桌面端。
  • 客户端正文保持普通文件。
  • 应用级状态使用 JSON;托管服务元数据使用 SQLite,正文使用加密对象目录。
  • 产品同步入口只连接托管服务。
  • 同步协议继续使用 v1,拉取采用不兼容旧实现的单次压缩快照,上传身份、幂等、revision 和墓碑字段保持现状。

以下问题仍需在对应阶段用原型、目标环境数据或用户反馈决策:

  • 是否引入前端框架,以及选择哪一个框架。
  • CodeMirror 6 已实施正文迁移;跨平台人工验收和大文档业务链路性能仍需完成,见 迁移记录。
  • 阶段 3 各项扩展语法和导出格式的优先级。
  • 托管服务何时从单节点 node:sqlite 迁移到受支持的数据库驱动或多节点架构。
  • 版本保留期、对象垃圾回收和配额成本模型。
  • 移动端的产品范围和技术栈。
  • 是否提供端到端加密,以及它与全文搜索、版本历史和开放文件格式的取舍。

sync-flow

test-results

点赞(0) 阅读(33) 举报
目录
标题