# ADR-0002: 时间线编辑采用可序列化命令 + 有界快照撤销 状态:已接受。日期:2026-09-01。来源:借鉴 Concat 编辑循环与撤销/自动保存设计。 > 更新记录(2026-09-01):采纳 ADR-0005 后,命令 handler 可经插件注册(时间线工具/效果插件贡献自定义 op),但命令队列、echo、撤销与保存仍是宿主核心;插件不得绕过命令协议改模型。 ## 背景 Concat 的编辑循环:UI 把命令推入单队列 → 引擎 `editor.apply(command)` 返回全量视图 → UI 重渲染。拖拽用本地 echo 预演位置,手势结束才提交**一个**命令,撤销粒度是手势而非像素;撤销栈是有界完整克隆(200 层);自动保存 1.5 秒防抖 + 临时文件原子改名。Concat 自述的教训:撤销/重做失败被吞错(§6.4)、重做栈无上限(§6.3)。 影策现有画布数据走 localforage(user-scope)+ 后端项目接口;时间线一期已落地字幕数据,二期按 `TimelineProject` v2 持久化轨道/片段。 ## 决策 - 时间线唯一修改入口是**可序列化编辑命令** `{ op, payload }`,经单队列顺序应用到 TS 状态机,每次 apply 返回新的 `TimelineProject` 作为唯一真相;任何 UI 代码不得直接改写模型。 - **命令 handler 可插件化注册**:预设插件(时间线工具、效果、字幕工具)通过宿主 API 贡献自定义 op 及其 reducer/handler;宿主负责校验权限(ADR-0005)后入队执行,插件不持有队列与撤销栈。 - 拖拽/裁剪/变速等手势采用 **echo**:本地预演位置与效果,`release`/手势结束提交一个命令。 - 撤销栈为**有界完整快照**(上限 200 层,与 Concat 一致),重做栈同样限界。 - 保存沿用项目保存链路:localforage user-scope + 后端项目接口,防抖 1.5 秒,写失败必须上报,不吞错、不静默降级。 - 编辑命令对象序列化形式即对外协议:画布 Agent、未来多端协作通过命令日志同步,不经由此协议不得改动时间线。 ## 后果 - 快照撤销简单可靠,但内存随项目规模线性增长:快照只存结构化模型,媒体大对象一律以 `directMedia` 引用/存储 key 入快照,不复制二进制。 - 命令队列天然支持幂等重放,为服务端协同与测试(命令黄金文件)留出路径。 - 撤销粒度是手势:移动/裁剪一整段是一次撤销,不提供逐帧回退。 - 与 Concat 不同,命令执行、撤销、自动保存都发生在浏览器进程,无跨进程边界;同步失败的兜底是本地缓存 + 明确的状态标识,不能冒充服务端已保存(遵循 AGENTS.md 写路径约定)。 - 插件贡献的命令 handler 必须保持纯函数、可序列化、可重放:撤销快照与命令黄金文件对插件命令同样生效,插件不能依赖组件实例状态。