触发与范围
桌面聊天两个同服务视图在 debounce 期间分别编辑不同聊天,各自从存储读出整桶并保留自己的 dirty 快照;之后按顺序提交整桶,后写者会覆盖前一个视图的新内容。这个问题不要求 storage 写入失败,也不要求双方编辑同一个聊天。草稿、提示词历史和手动暂存存在同类整桶竞争,统一在本条处理。
按 P1 条件性本地数据丢失追踪。2026-09-30 仅使用原存储模块的独立 VM 与全成功内存存储复验;没有实际打开多个 Electron 窗口、读取真实 profile、删除用户草稿或触发认证/发送/工具。范围仅为 desktop-owned 聊天本地持久化协调,现有共享 Web 文件作证据定位,不要求本轮修改 Web 或后端。
原模块证据与对照
fixture 原样 TypeScript 转译并加载 composerDraftStore、composerRecallStore;两个 VM 拥有独立模块状态,共享合成 StorageLike。原测试 override 接入该存储,定时器仅记录在内存,使用原 flush 函数确定提交顺序。所有 get/set/remove 均成功,无 quota、HTTP、Provider 或真实 localStorage。
| 合成条件 |
原存储结果 |
| A、B 分别写不同 thread 草稿,先 A flush 后 B flush |
A 曾成功持久化,随后从持久桶和 fresh read 消失,B 保留 |
| A 清一个 key,B 持旧桶编辑另一个 key,A 后 B flush |
A 的已清 key 被 B 旧桶带回 |
| 不同 history session 交错 flush |
前写 session 消失 |
| 两个手动 stash 交错 flush |
前写 stash 消失 |
| 两 realm 顺序写入并各自 flush |
两份草稿均保留 |
| 同一 realm 在 debounce 期写两 thread |
dirty 桶合并正确,两份均保留 |
完整快照、成功存储轨迹、定时器记录和源文件哈希保存在本地 ignored artifact。这里证明指定存储的合成数据被覆盖;实际发生频率、UI 表现及外部备份未测,不能声称所有内容在任意环境永久不可恢复。
根因与桌面边界
draft 的 readBucketFromStorage 在 memoryDirty 时仅返回本 realm 快照,300ms 后 writeBucketToStorage 全量替换 drafts;recall 的 history/stash 同样在 dirty 时返回本地快照,200ms 后全量替换。提交时没有针对当前存储合并独立 key/entry 或校验版本。单 realm 的 dirty 保护正确,但不能协调多个 writer。
现有桌面同 origin 子窗使用相同 partitionFor(origin),因此共享偏好的多视图前提有源码依据;本轮未操作真实双窗口。不同 origin 的 partition 分离仍有效。逐个 setItem 的成功并不保证这些跨读取、延迟、写回操作构成同一个事务。
桌面优化与验收
- 为同 service/profile 的 draft/history/stash 使用统一 desktop persistence owner;按具体 key/entry 提交更新和删除,协调 pending 写入与 flush,而非用陈旧全桶替换。
- 保留独立聊天内容与删除语义;同一 key 的并发修改给出明确的版本/冲突合同,不能静默覆盖用户的更新。
- 重跑全部合成序列:不同 thread/session/stash 的交错保存均保留双方;删除不会被旧快照恢复。单 realm 与顺序对照继续通过,存储失败保持真实归因。
- 在可弃置桌面 profile 补双窗口、刷新、重连/关闭前 flush 验收,只使用合成内容;未经此测试不宣称真实桌面事故或全平台保证。
去重与源码
#58 是写盘失败后旧值覆盖内存;本条全部写入成功。#61 是迟到发送结果清理已变引用;本条不发送。#74 是通知设置旧字段覆盖;这里是 draft/recall 整桶并发导致不同聊天的用户数据丢失。#69 的认证主动清理策略另行处理。旧版 v1 的重新迁移也作为独立主因跟踪。本条不按各存储或场景拆计数,关联 Epic #31。
固定公开提交仅用于导航,当前源文件哈希为本次证据:composerDraftStore、composerRecallStore、ConversationShell、main.cjs。
触发与范围
桌面聊天两个同服务视图在 debounce 期间分别编辑不同聊天,各自从存储读出整桶并保留自己的 dirty 快照;之后按顺序提交整桶,后写者会覆盖前一个视图的新内容。这个问题不要求 storage 写入失败,也不要求双方编辑同一个聊天。草稿、提示词历史和手动暂存存在同类整桶竞争,统一在本条处理。
按 P1 条件性本地数据丢失追踪。2026-09-30 仅使用原存储模块的独立 VM 与全成功内存存储复验;没有实际打开多个 Electron 窗口、读取真实 profile、删除用户草稿或触发认证/发送/工具。范围仅为 desktop-owned 聊天本地持久化协调,现有共享 Web 文件作证据定位,不要求本轮修改 Web 或后端。
原模块证据与对照
fixture 原样 TypeScript 转译并加载
composerDraftStore、composerRecallStore;两个 VM 拥有独立模块状态,共享合成 StorageLike。原测试 override 接入该存储,定时器仅记录在内存,使用原 flush 函数确定提交顺序。所有 get/set/remove 均成功,无 quota、HTTP、Provider 或真实 localStorage。完整快照、成功存储轨迹、定时器记录和源文件哈希保存在本地 ignored artifact。这里证明指定存储的合成数据被覆盖;实际发生频率、UI 表现及外部备份未测,不能声称所有内容在任意环境永久不可恢复。
根因与桌面边界
draft 的
readBucketFromStorage在 memoryDirty 时仅返回本 realm 快照,300ms 后writeBucketToStorage全量替换 drafts;recall 的 history/stash 同样在 dirty 时返回本地快照,200ms 后全量替换。提交时没有针对当前存储合并独立 key/entry 或校验版本。单 realm 的 dirty 保护正确,但不能协调多个 writer。现有桌面同 origin 子窗使用相同
partitionFor(origin),因此共享偏好的多视图前提有源码依据;本轮未操作真实双窗口。不同 origin 的 partition 分离仍有效。逐个 setItem 的成功并不保证这些跨读取、延迟、写回操作构成同一个事务。桌面优化与验收
去重与源码
#58 是写盘失败后旧值覆盖内存;本条全部写入成功。#61 是迟到发送结果清理已变引用;本条不发送。#74 是通知设置旧字段覆盖;这里是 draft/recall 整桶并发导致不同聊天的用户数据丢失。#69 的认证主动清理策略另行处理。旧版 v1 的重新迁移也作为独立主因跟踪。本条不按各存储或场景拆计数,关联 Epic #31。
固定公开提交仅用于导航,当前源文件哈希为本次证据:composerDraftStore、composerRecallStore、ConversationShell、main.cjs。