文档索引

ReactiveMD 功能与架构简化方案

状态:用户后续确认的收缩范围已完成本地实现与验收(2026-11-02);未推送、未部署。 本站核对基线:9a9b99b7;当时使用 reactive-md@0.7.0。 上游核对提交:dc276866cc53f33fab099a1f1eb439debe24fa62。 本方案保留统一 Markdown / ReactiveMD 的产品目标,调整快照、发布、源码升级和部署 的职责。此前实施记录保留在 docs/plan/reactive-md-unification-plan.md,其中持久快照、 原文与快照同事务、全站编译通过后才完成迁移的要求,在本方案实施后退出。 本文不是上线记录;没有修改生产数据库、部署脚本或当前正文。

2026-11 校订(R3-703):§5(保存、发布和错误处理)与 §11(验收矩阵)里的两项 未实现 —— ①「允许语法中间态」:剪贴板保存仍整篇编译、失败返回 reactive_invalid (clipboard-service.ts);②「worker 可终止」:全仓 worker_threads 零命中。 文首「收缩范围已完成」只指其余项。

本次执行范围(用户后续确认)

本次按“替换正文解释器”的边界做减法:阅读直接使用现有编译与内存缓存;保存只写 源码与必要元数据;SQL up 仅做结构;移除持久快照和回填,保留旧源码必要兼容与独立 升级命令。旧表暂留,不改已应用 SQL。

现有编辑器、Markdown 兼容、引用权限、首屏、交互和导出保留;保存 / 发布的现有语法 与格式校验保留为临时检查,不再持久化结果。本次不新增服务器 worker、不重做保存 语义、不重新开发编辑器。下文是完整调研提案,其更大范围的候选不等于本轮承诺。

1. 结论与产品定位

ReactiveMD 是 Markdown 的交互增强:普通作者正常写 Markdown,需要时直接加入控件、 表达式、图表和条件内容。博客与云剪贴板共用编辑器与渲染规则,不增加模式切换步骤。

推荐采用:持久保存源码与必要元数据,按需编译,浏览器运行交互,编译结果只做可丢弃缓存。

服务端仍承担正文授权、站内引用取数、静态首屏和搜索正文提取。保留这些能力不需要在 数据库里长期保存编译树,也不需要把所有文章重新编译作为部署成功的条件。

2. 上游调研与本站差异

核对上游文档和本地已安装包的声明文件,以下能力均有实际公开接口依据:

上游设计 对本站的含义
compileLocal 生成 LocalDocument,React 视图消费该结果 编译与显示可分离;同一次编译结果可直接经 RSC 传给客户端
文档明确说明 LocalDocument 不应作为持久化格式 源码是长期数据;编译树应随包版本和缓存生命周期失效
LocalDocumentView 与 createLocalRuntime 可保留现有静态首屏与客户端交互,无须重建一套渲染器
LocalCompileScheduler、worker 和增量编译会话 编辑预览可复用现有实现,不必每次按键请求服务器
源码修订与 migrateLocalSource 是显式协议 删除数据库快照不能顺便忽略旧语法的真实语义变化
运行状态默认属于当前文档实例 滑块、输入值与计时器不需要数据库表或自动同步接口
宿主 schema、hostData、hostRenderer 是公共扩展点 站内引用沿用宿主组件,不另造一个 RMD 方言
编辑器与作者工具已有 CM6、目录、补全和模板能力 复用其语法事实与功能,避免维护第二份组件功能清单

上游还明确限定:表达式不执行任意 JavaScript,权限和业务数据由宿主负责;开启作者 CSS / JavaScript / 扩展 HTML 会改变整篇文档的解释路径,不能拿这些开关修补 Markdown 兼容。本站继续关闭这些能力,不增加面向作者的脚本执行入口。

现有实现的问题不是缺少缓存:compile.ts 已有有界内存 LRU,阅读页已有现编译路径。 冗余主要来自额外的持久快照契约、摘要与签名复核、引用再验证、快照回填、写入事务绑定, 以及又把这些工作接入 SQL 迁移。另有 snapshot.ts 的旧 HTML 快照契约,并不参与生产路径。

2.1 调研来源

以下链接固定在本次核对的上游提交,避免 main 之后变化影响判断:

上游 main 的描述不自动代表已发布包具备全部相同细节。实施按锁文件里的包验证,使用 现有 compileLocal + LocalDocumentView 路径,不依赖未确认的新属性。上游通用 SSR 水合保证有限,本站已有包装组件的 SSR / 无 JS / 实际交互仍须独立验收。

3. 必须保留的完整能力

范围 保留的用户体验
普通 Markdown 标题、列表、任务列表、引用、代码、GFM 表格、脚注、公式、链接、图片、既有软换行及历史 HTML 的可读结果
RMD 当前版本支持的输入控件、派生计算、校验、条件、集合、图表、绘图、图示、布局、主题、导航及其合法别名
编辑 CM6、撤销重做、选区、快捷键、IME、补全、源码诊断、编辑 / 并排 / 预览、草稿恢复
工具栏 Insert、Components、上传图片、插入引用四个主入口,手机上可用;低频功能留在菜单或快捷键
阅读 博客和云剪贴板统一渲染,首屏有正文,无 JS 有初始值,有 JS 可交互;实例之间状态隔离
站内引用 图床、音频、剪贴板、投票、收藏夹;保留当前内联 / 卡片语义与权限边界
输出 原文复制 / 下载、静态与交互 HTML 导出、打印,保持授权范围与实际资源可用性
消费者 API、草稿、摘要、公开搜索、旧引用消费者能准确辨别增强内容

不把评论、讨论、故事和站内文档一并重构。不重写已验收的工具栏,不用“简化”为由删去 云剪贴板、SSR、引用权限或导出。上游运行状态导入 / 导出只复用其现有能力,不新增云同步、 协作编辑、文档脚本平台、组件市场或多套编译器常驻系统。

4. 数据与渲染职责

4.1 长期数据

正文表保留 content、现有 format、必要的 syntax_version 和业务元数据。format 服务于现有 API 与消费者兼容,不再决定是否要保存快照,也不呈现为作者必须选择的模式。 源码版本与编译器包版本分别管理;不得把升级包版本等同于改写所有正文。

文章复制、编辑、下载原文始终使用作者源码;兼容改写只发生在解释用的临时副本上。 不持久保存 LocalDocument、hostData、HTML 首屏或读者运行时状态。

公开搜索索引仍可留在数据库:它是独立的检索派生数据,缺失时应降级检索,而不影响 正文是否存在、能否保存或能否部署。删除 AST 快照不等于删除搜索索引。

4.2 唯一内容管线

数据库源码 → 当前请求授权 → RMD 编译(可命中内存缓存)
           → 在编译后的文档节点中按权限展开引用
           → SSR 初始正文 → 浏览器实例接管交互

编辑中的源码 → 浏览器 worker 编译 → 展开引用 → 相同文档视图

复用 compile.ts、cache.ts 和现有文档视图。服务器和浏览器共用编译配置与引用展开逻辑。

RSC 传输本次编译的普通数据是正常请求处理,不是数据库快照。首屏沿用已验收的 staticFirst 路径;客户端不为接管交互再把同一份源码完整编译一次。

4.3 缓存与资源成本

先复用现有有界 LRU,不新增 Redis、磁盘缓存、常驻队列服务或必须运行的预热任务。 缓存键覆盖实际编译输入、精确编译版本、语法版本、兼容规则和选项;同输入并发编译合并。 缓存清空、进程重启、首次访问均应正确,只影响速度。

权限判定和引用准备发生在命中缓存之前;hostData 不进入跨请求共享的编译缓存。带成员 数据的页面不进入公共整页缓存,登出、换账号、撤权后必须重新授权。

删除数据库快照不消除 CPU 成本。原有测量显示结构复杂的长文可能达到秒级,不能直接 把所有冷编译同步放回 Next 主事件循环。推荐一个惰性启动的进程内 Node worker 执行器: 只接收纯编译输入、有界并发与排队、真实超时终止 worker,重建后可继续服务;不是一个 新后端服务,也不持久保存任务。先完成 Next 生产构建及部署打包可行性验证。

沿用当前源码、节点与引用展开预算,依据冷编译、事件循环延迟、内存和排队数据标定 执行超时,不凭“20 万字几毫秒”放开。Promise.race 无法中断同步解析,不能冒充超时 隔离。浏览器沿用已有 worker / 调度器,Worker 出错时有界回退并保留编辑内容。

5. 保存、发布和错误处理

核心改变是取消“正文必须与可信快照同事务写入”。校验、保存源码、生成阅读结果三者 分别有明确结果;编译结果被丢弃不影响已保存的正文。

动作 推荐行为
编辑与本地草稿 允许标签、表达式暂未完成;立即显示诊断,不阻止记录源码
云剪贴板手动 / 自动保存 保存合法权限与大小范围内的当前源码,允许语法中间态;显示已保存与渲染诊断,不伪报保存失败
博客显式发布 保留发布前增强语法检查;有阻断诊断时定位问题并保留草稿,检查结果不落快照表
普通 Markdown 保存 历史 HTML / 兼容告警不应被升级成 RMD 发布错误
历史正文阅读 不重新追究“当年是否通过当前发布规则”;尽量显示可恢复内容,失败只影响该篇
数据库迁移 不读取全站正文做编译验收,不因某篇文章错误判数据库迁移失败

剪贴板现在的保存即更新共享源码,本方案不额外引入“已发布副本 + 云端草稿副本”系统。 允许语法中间态意味着读者暂时可能看到诊断,这是源码保存产品的明确取舍;本地草稿仍 保留为断网 / 冲突恢复。实现必须同时调整服务写入闸门与编辑器提交前阻断,不能只改后端。

前端检查不替代服务器权限、资源大小和协议校验。博客发布的诊断检查也可在服务器 复用同一个编译器执行,完成后丢弃结果;无需保存编译树来证明检查发生过。

区分恢复能力:上游提供局部运行错误时只影响该组件;整篇语法编译失败没有可靠 AST 时,不能承诺自动救出所有普通段落。显示明确的文档不可用提示,并在正文权限允许时 提供转义原文。保留页面其它部分,不把失败源码送进旧 Markdown 解析器,也不额外开发 一套“抢救半篇 AST”的解析器。

6. Markdown 兼容与组件保护

这次 radio-group 事故说明:一个 HTML 标签清洗名单不能同时充当 RMD 语法定义。

  1. 先用上游语法分析事实 / 组件 schema 保护增强组件范围及合法结构子项,再处理历史 HTML;名字同为 option 不意味着可以剥除。别名也要保护,目录模板覆盖不了全部别名。
  2. 当前阶段保留已验证的兼容层修复和长度守恒,逐条收敛适配,不一次性重写全部 HTML。 优先使用上游已支持的行为;确实需要站点补齐的规则集中存放。
  3. 仅对本站历史净化行为需要的内容做适配,代码、数学、属性、转义范围保持正确。 所有清洗都只处理临时编译副本,源码和编辑器选区不被永久改变。
  4. 上游分析在歧义输入上无法提供可靠范围时,保留原内容交给诊断;不能猜测剥掉组件 来制造“编译成功”。普通 Markdown 的字面量容错仍按已识别格式处理。
  5. 完整覆盖原生目录、合法别名、混合文档和历史同名 HTML。以 AST / 实际 DOM 和 最终行为判断,通过诊断数量或仅看 ok 不能代替功能验收。

永久保留本次最小复现:相同的 radio-group 示例用于博客、云剪贴板、预览、阅读、 发布检查、旧版本适配、静态和交互导出;同时保留旧 HTML select/option 不误激活的反例。

7. 源码版本升级

上游 0.7.0 仅接受当前 local-draft-3;显式 draft-2 不能原样交给它。这是必须处理的 真实协议约束,不能通过删掉版本字段、直接替换注释或取消所有检查来隐藏。

推荐分离为两层:

  • **阅读兼容:**对已知旧修订调用现有安全升级适配(复用 source-migration.ts 的上游 检查、站点预算和动态约束保护),只在内存中生成当前编译输入。确认可安全升级的 文档正常阅读,数据库和下载原文保持不变。所有入口,包括预览、搜索和导出,复用它。
  • **持久升级:**保留独立运维命令。默认只读报告;显式应用时逐篇事务、核对原文未变化, 只保存确认安全的变更,失败文章保留原文和位置报告。完成标记不能替代每篇源版本判断, 不因一篇失败回滚无关文章,也不再绑定快照或公开索引的成功。

存在新增标签激活、动态约束变化、声明与字段冲突或未知修订时,只阻止该文档的解释 / 升级;不自动同意语义变化。作者修复并显式保存后才更新持久版本。升级依赖前先报告 受影响文章;确有不兼容时先修正文或暂缓换包,不常驻一整套旧编译器。

内存兼容成功不代表源码已持久升级,状态与日志必须区分;它也不应自动更新文章时间、 触发通知、改变内容摘要或写入数据库。

8. 引用、状态、导出和检索

8.1 站内引用

继续用已有 site-embed 公共宿主扩展,引用选择器插入 [@...],不要求作者手写内部标签。 成员视图按当前查看者权限取数;访客视图保持既有外部规则,对剪贴板、投票、收藏夹 保留字面量,连正文和标题也不读取。图片、音频继续由各自资源接口授权。

保持 Markdown 剪贴板内联一层、Reactive 剪贴板卡片及现有数量 / 深度预算;引用无法 访问时局部占位,不让父文档失败。不要把引用内容或会话数据持久写进父文章编译结果。 预览中的投票等业务操作继续只读,阅读中的业务动作仍由原 API 授权。

8.2 浏览器状态与输出

每份文档独立 runtime,销毁时释放计时器和订阅;默认刷新回到作者初值。状态保留 / 导入导出只采用上游显式机制,不自动上传读者填写值。条件隐藏只是显示行为,不能保护 秘密数据;权限不足的数据从一开始就不能下发。

源码导出使用原文;静态 / 交互 HTML 导出按当前授权准备内容后临时编译。默认使用作者 初值,需要分享当前参数时由用户显式选择。无法访问的引用保留引用标记 / 占位并说明; 离线资源与允许内联的图片使用既有处理,不以“导出成功”掩盖资源缺失或权限越界。

8.3 摘要与公开搜索

提取公开可见的静态文字,不索引声明值、条件隐藏数据、读者状态或引用资源正文。 索引版本从独立编译配置 / 提取规则派生,去掉对 snapshot-store.ts 的依赖。

正文写入先完成,索引随后尽力更新;失败不回滚正文。保留既有可恢复状态和 search:rebuild 独立重建。版本升级后的索引不完整时明确降级为元数据查询,不用原始 正文 LIKE 兜底;可见性过滤、软删除撤索引及计数 / 分页一致性保持不变。

9. 部署与具体删改范围

npm run migrate -- up 恢复为手写 SQL 结构迁移,不再自动进入旧源码全量检查、快照 回填或搜索重建。status / verify 保持只读。保留已经改进的数据库路径解析、参数数组 子进程调用和明确失败提示,不为撤销数据编排退回有跨库风险的旧实现。

现有部分 处理
snapshot-store.ts、snapshot-revalidation.ts 从生产读写路径移除,职责不搬到新文件继续维持
snapshot.ts 的 HTML 快照骨架 核对零业务调用后删除接口和仅守着它的测试
publish.ts 拆出可复用的发布诊断,删除生成 / 写入持久快照的要求
read-render.ts 收敛成授权后的按需编译与明确错误处理,删除快照状态分支
backfill.ts / reactive:backfill 退出常规流程,先给废弃说明,再随快照代码退出;不再是发布前置
source-migration.ts / 旧语法命令 保留真实语义检查,改成独立兼容与逐篇显式升级
compile.ts / cache.ts / worker / 预览 复用有用部分,补有界服务器执行,不新增持久编译层
flags.ts / 渲染选择器 去掉已不用的 percent、shadow 和逐篇 override 分支;只保留必要事故开关
博客 / 剪贴板 service 删除源码与快照同事务写入;按各自保存 / 发布语义处理诊断
搜索 保留独立索引及修复命令,与快照和 SQL up 解耦

首次上线先停止快照表的读写,保留表与数据作为兼容期历史,不在这次切换中 DROP 表。 已有 SQL 迁移文件和 checksum 不改写。物理清理另列维护事项,在备份与回滚窗口安排后 单独执行;不把“少几张表”作为作者能正常阅读的前置条件。

部署继续安装锁定依赖、生成 Prisma Client、执行 SQL up、构建并重启。与当前包兼容 性有关的源码预检和搜索索引修复是独立、可观察的操作,发布脚本不得暗中把它们重新 拼成“任一历史文档失败则部署失败”的强依赖。

10. 实施顺序与退出条件

P0:固定用户行为与技术可行性

  • 固定普通 Markdown、完整 RMD、radio-group 别名、混合引用、复杂长文和旧修订夹具。
  • 核对已安装包与上游主分支的 API 差异;验证 Node worker 在 Next 生产产物中的路径、 ESM 加载与终止重建,并测首次编译和事件循环延迟。
  • 证明清空持久快照后,现有按需编译 + staticFirst 可完成真实 SSR / 水合 / 无 JS 阅读。
  • 明确剪贴板中间态保存和博客发布校验的 UI / API 行为,先锁定契约再改 service。

P1:解除部署、发布与快照绑定

  • 让 SQL up 仅做结构;删除两个 service 的持久快照写入。
  • 阅读页使用按需编译和现有 LRU;完成服务器有界执行及故障恢复。
  • 旧修订临时适配覆盖阅读 / 预览 / 导出 / 检索,保留原始源码不变。
  • 出口:新旧文章均无需快照即可保存 / 阅读;单篇错误不阻塞 SQL up;真实首屏可读。

P2:收敛兼容、编辑和消费者

  • 保护所有合法组件与别名,删除无业务作用的快照骨架和重复选择分支。
  • 保留四入口工具栏,统一诊断和保存状态;处理剪贴板自动保存中间态。
  • 摘要、索引、导出使用相同版本与兼容准备,不依赖持久快照。
  • 出口:功能矩阵逐项通过,不为了减少文件数丢弃正确边界。

P3:文档与运维收口

  • 更新 docs/architecture.md、docs/deploy.md、../../CLAUDE.md、../../README.md 和作者指南。
  • 废弃快照命令有明确退出说明;旧表保留兼容期,不篡改历史迁移。
  • 做一次停服务 / 冷启动 / 无快照 / 独立索引失败的部署演练,不操作生产正文验证方案。
  • 出口:正常部署没有 RMD 全量回填步骤;空缓存能运行,失败能定位到单篇。

各阶段独立提交并运行相关测试和类型检查;实现完成后一次全量回归、生产构建,再按 实际页面做桌面 / 手机验收。该方案不要求再次重写已完成的编辑器或重跑无关功能开发。

11. 验收矩阵

场景 必须满足
快照表为空或无当前行 博客 / 剪贴板可正常阅读;保存不写 AST;部署无回填要求
首次访问、服务重启、LRU 淘汰 结果正确,不要求手动 warm-up
普通 Markdown 加一个滑块 作者无需切模式,预览与保存后正文软换行、排版一致
完整组件示例与 radio-group 别名 合法结构不被清洗,所有入口可编译且真实交互有效
旧 HTML select/option 与代码里的 RMD 示例 不误激活、不损坏子文本或源码范围
draft-2 安全 / 不安全升级 安全者临时可读且数据库源码不变;不安全者仅该篇报告,不强改声明
一篇故意错误的历史 RMD SQL up 成功,其它文章可读,错误文章仍可编辑恢复
剪贴板未闭合标签时自动保存 源码已保存、诊断可见;恢复编辑不丢内容,恢复合法后正常渲染
无 JavaScript与水合 初始 HTTP 响应含真实正文;启用 JS 后控件计算正确,无重复正文
换账号、撤权、私密 / 外部引用 不串缓存;访客不请求成员资源;占位不带私密标题与正文
导出与打印 静态件可读,交互件可运行;离线资源与权限限制说明准确
搜索失败、旧索引或索引缺失 正文写入成功;搜索按规则降级、过滤与分页一致、可独立修复
复杂长文、病态输入、并发冷编译 保留现有预算;worker 可终止恢复;同输入合并;其它请求不被秒级同步解析拖住

性能记录应区分冷 / 热编译、引用读取、RSC 传输、DOM 渲染和事件循环延迟。没有实际 测量前,不宣称任意 20 万字正文只需几毫秒,也不以新增持久快照来回避冷编译验证。

本次实现与验证记录(2026-11-02)

代码提交:9013ea7e。本轮按上文“本次执行范围”完成,未实施更大范围的候选设计。

  • 阅读由当前源码按需编译,复用现有内存 LRU 与静态首屏 / 浏览器交互。
  • 博客和云剪贴板保存仅写源码与必要元数据;发布语法 / 格式检查保留,结果不落库。
  • 删除持久快照读写、HTML 快照骨架、回填脚本和自动数据阶段;旧表与历史 SQL 不变。
  • SQL up 只处理结构;真实测试确认错误正文不阻塞,重复 up 不写正文、快照或索引。
  • 已知旧修订可安全临时解释,数据库原文和版本不变;独立源码升级和检索修复可运行。
  • 公开索引使用独立编译身份;运维脚本统一用已有 ESM loader,兼容 ESM-only RMD 包。

验证结果:

检查 结果
类型检查 通过
相关编译 / 引用 / 版本 / 搜索 / 文档回归 12 文件、123 项通过;收尾另有 6 文件、86 项通过
无快照访问、冷读、原文保持与旧版安全拒绝 4 项通过
真实 SQL 入口及 URL / 只读检查 8 项通过,1 项非本平台路径用例跳过
全量回归(一次) 288 文件通过、1 文件失败;5,702 项通过、1 项失败、1 项跳过,240.75 秒
生产构建 隔离工作树通过,159 个静态页面生成成功
真实浏览器 16 个不同场景通过:冷启动无 JS 首屏 1,既有渲染 / 引用 / 预览 / 媒体 / 导出 13,旧 RMD 剪贴板桌面与窄屏 2
独立运维 CLI 匿名 QA 库重建 2 篇公开索引成功;旧源码预检返回已完成、0 阻塞

全量唯一失败是未修改的 tests/unit/wand2.test.ts 断输入停步位置断言(预期 91、 得到 99.11)。未修改 HEAD 的独立树连续三次单文件均 7 项通过;当前项目单文件复跑 也 7 项通过。本轮未修改游戏实现,不把这次全量描述为全绿。

本机 Prisma SQL 引擎对系统 TEMP 数据库报 readonly,独立实验确认标准测试目录可写; 入口测试改用项目既有的独立测试库目录,并保留每轮唯一文件和路径 / 诱饵库检查。 浏览器测试用全新合成库,未读取生产数据;没有执行 SSH、推送或部署。

完整验证日志保存在本地 TEMP:raricy-rmd-simple-full-20261102.log、 raricy-rmd-simple-build-20261102.log、raricy-rmd-simple-e2e-cold3.log、 raricy-rmd-simple-e2e-main.log、raricy-rmd-simple-e2e-clipboard.log。 首轮新增浏览器用例因 API 包装字段断言写错失败,按实际 clip 对象校正后桌面与窄屏均 通过;既有 13 个场景不重复计数。所有失败及完整输出均保留,未截断测试日志。

本页内容来自仓库 docs/plan/reactive-md-simplification-plan.md·在 GitHub 上查看

ReactiveMD 功能与架构简化方案 · 聪明山