触发与范围
桌面聊天本地存储同时存在非空 v1 草稿和 v2 时,v2 的最后一份草稿被清掉后,下一次读取会再次从 v1 迁移,恢复已经清掉的旧正文与选择。clearAllComposerDrafts 仅删 v2,也会留下同样的重新迁移来源。单视图即可触发,不依赖多窗口、失败写入或 Provider。
按 P2 跟踪迁移和清理语义。2026-09-30 的原模块 VM fixture 已确认合成旧稿复活,没有读取真实旧 profile、实际认证锁定、发送旧正文或产生用户事故。范围仅为 desktop-owned 草稿恢复与格式迁移;现有共享 Web 文件用于定位,不要求本轮修改 Web 或后端。
原存储证据与保护对照
原 composerDraftStore.ts TypeScript 转译后直接运行,以原测试 override 接入全成功的合成内存 StorageLike,定时器为内存记录;没有真实窗口、localStorage、HTTP、账号或工具。
- 非空 v1 第一次读取正常迁移到 v2;调用原
clearComposerDraft 清最后一项并 flush,v2 确为 {drafts:{}}。fresh VM 再读取时,原正文从 v1 回来,并重新写入 v2。
- 同样 v1 条件下调用原
clearAllComposerDrafts,v2 key 消失、v1 保留;fresh read 再恢复旧正文。
- 一开始已有合法空 v2 与非空 v1,也把旧稿迁移回来,说明判断的是“当前桶是否空”,没有区分“从未迁移”和“清理后的合法空状态”。
- 没有 v1 的对照:清最后一项并 flush 后,fresh read 正常返回 null,不复活。
完整旧/新格式输入、清理前后快照、存储操作与源码哈希仅存本地 ignored artifact。此证据限定为本存储中的合成内容恢复,不代表实际用户已经重发旧内容。
根因与影响边界
readBucketFromStorage 只要 parse 后无 key 就调用 migrateV1Drafts。后者会保留 v1 供旧 tab 使用,未记录一次性迁移完成;clearComposerDraft 只更新 v2,clearAllComposerDrafts 也仅删除 v2。保留兼容数据有用途,但当前读取路径把已清空误作需要迁移,旧格式成为可重复回退来源。
已有非空 v2 时不会走该分支;全程没有 v1 的正常清理也工作。风险只涉及升级后仍有旧格式内容的 profile。清理后恢复旧正文、模型或权限选择会让用户难以判断哪些本地状态已失效;没有测试后续提交或实际权限效果,不能报告越权、私有数据外发或 P0。
桌面优化与验收
- 区分尚未迁移、成功迁移和当前合法空状态;迁移完成后不能因空桶重新回退到旧稿。保留旧 tab 兼容时采用明确完成标记/删除语义,而非用内容是否为空判断版本。
- 对明确清理的范围覆盖旧格式,或保留可核验的清理标记,确保旧兼容副本不能覆盖用户决定。
- 重跑上述原模块 fixture:首次迁移完整保留正文、引用、附件元数据及选择;清最后一项、清所有和合法空 v2 后 fresh read 仍为空。无 v1 与非空 v2 的对照继续通过。
- 独立可弃置升级 profile 补实际桌面恢复验收。临时认证失效时应如何保留草稿仍按 #69 处理,本条不要求扩大 401 删除或把自动锁定当成用户清理授权。
去重与源码
跨 realm 整桶竞争是另一主因;本条只用单 realm/fresh read 和旧格式回迁。与 #58 的写盘失败、#61 的迟到发送清理、#69 的认证自动擦除分别去重,关联 Epic #31。
源码导航:composerDraftStore、ConversationShell 清理调用。公开固定提交仅供导航,隔离证据保存当前源码哈希,不宣称发行包或生产 profile 已复现。
触发与范围
桌面聊天本地存储同时存在非空 v1 草稿和 v2 时,v2 的最后一份草稿被清掉后,下一次读取会再次从 v1 迁移,恢复已经清掉的旧正文与选择。
clearAllComposerDrafts仅删 v2,也会留下同样的重新迁移来源。单视图即可触发,不依赖多窗口、失败写入或 Provider。按 P2 跟踪迁移和清理语义。2026-09-30 的原模块 VM fixture 已确认合成旧稿复活,没有读取真实旧 profile、实际认证锁定、发送旧正文或产生用户事故。范围仅为 desktop-owned 草稿恢复与格式迁移;现有共享 Web 文件用于定位,不要求本轮修改 Web 或后端。
原存储证据与保护对照
原
composerDraftStore.tsTypeScript 转译后直接运行,以原测试 override 接入全成功的合成内存 StorageLike,定时器为内存记录;没有真实窗口、localStorage、HTTP、账号或工具。clearComposerDraft清最后一项并 flush,v2 确为{drafts:{}}。fresh VM 再读取时,原正文从 v1 回来,并重新写入 v2。clearAllComposerDrafts,v2 key 消失、v1 保留;fresh read 再恢复旧正文。完整旧/新格式输入、清理前后快照、存储操作与源码哈希仅存本地 ignored artifact。此证据限定为本存储中的合成内容恢复,不代表实际用户已经重发旧内容。
根因与影响边界
readBucketFromStorage只要 parse 后无 key 就调用migrateV1Drafts。后者会保留 v1 供旧 tab 使用,未记录一次性迁移完成;clearComposerDraft只更新 v2,clearAllComposerDrafts也仅删除 v2。保留兼容数据有用途,但当前读取路径把已清空误作需要迁移,旧格式成为可重复回退来源。已有非空 v2 时不会走该分支;全程没有 v1 的正常清理也工作。风险只涉及升级后仍有旧格式内容的 profile。清理后恢复旧正文、模型或权限选择会让用户难以判断哪些本地状态已失效;没有测试后续提交或实际权限效果,不能报告越权、私有数据外发或 P0。
桌面优化与验收
去重与源码
跨 realm 整桶竞争是另一主因;本条只用单 realm/fresh read 和旧格式回迁。与 #58 的写盘失败、#61 的迟到发送清理、#69 的认证自动擦除分别去重,关联 Epic #31。
源码导航:composerDraftStore、ConversationShell 清理调用。公开固定提交仅供导航,隔离证据保存当前源码哈希,不宣称发行包或生产 profile 已复现。