# 消息显示与缓存恢复

本次修复沿用 Host 的统一时间线和账号正文同步 owner。浏览器只维护当前阅读窗口、发送回执与有界实时尾部，不产生新的网络轮询或同步任务。

## 消费面

| 消费面 | 入口 | 本次变化及验证 |
| --- | --- | --- |
| UI | `source.timeline`、实时消息事件 | 保留发送中消息，立即应用删除，读取已提交的本地正文；真实 React 会话测试覆盖慢响应、失败与旧快照 |
| Tools | 现有统一时间线和消息同步工具 | 继续调用同一 Host owner；本地读取和 Worker 恢复修复自动生效，注册与会话调用测试保持覆盖 |
| SDK | 现有公开 timeline 与 chat.sync 入口 | 不新增业务方法；已有仓外 consumer 验证公开导出和读取合同 |
| 桌面客户端 | 承载插件页面与窗口 | 无协议或业务实现改动；常驻正式客户端未替换 |

`message-sync-progress` 增加可选的 `sourceKeys`，仅在正文批次提交成功且账号代际仍有效时发送。每批最多 50 个会话；旧消费者可以忽略此字段。浏览器只对当前账号、当前打开的会话读取 SQLite，不将同步进度解释为已读或完整历史。

## 状态约束

1. 统一窗口刷新不能结算发送任务。保留当前发送中/失败回执，由真实发送结果更新或移除；成功回执继续使用已有的有界保留 owner。远端回执晚到时可以从原始发送项恢复展示，避免刷新已移除临时项时丢失结果。
   发送回执的最大 sequence 不代表窗口已展示到该位置。未读补齐、刷新判定和已读提交使用实际可见的消息窗口，避免自己的较新回执掩盖中间尚未显示的未读正文。
2. 请求开始时间不能证明响应包含实时消息。只有事件身份确认、完整最新窗口达到对应 sequence，或明确删除，才能移出实时尾部。已确认窗口领先于后续响应时，也复用连续尾部投影；历史窗口不拼接另一个最新尾部。
3. 删除标记同时作用于 canonical rows、实时尾部和发送保留项。网络失败与晚到响应都不能重新显示已收到删除标记的正文。
4. 最新窗口与账号正文缓存分别提交。最新本地读取检查正文 frontier；正文领先时提供明确标记为不完整的本地页，不伪造窗口 token 或完整覆盖。保存的历史锚点、远端历史游标仍沿用原来的定位规则。连续的本地正文追加到已有窗口；不连续的正文页只在用户跟随最新消息时替换窗口。
5. Worker 死亡后由原 Host owner 按 1–30 秒有上限的退避重建。健康 Worker 的容量拒绝和冷启动单次超时不会触发重建。恢复使用原 SQLite 事务状态，先补交失效标记再允许读取；旧实例失败不能关闭替代实例。

## 容量与失败恢复

- 实时尾部和活动窗口沿用 2,000 条上限，发送回执沿用 64 条上限。
- 本地正文每次读取 40 条，既有 owner 最大允许 100 条；浏览器不按全账号会话数量发起读取。
- 删除/失效队列最多 512 个去重条目。溢出时设置全局恢复屏障，丢弃不再能证明安全的缓存投影，再从网络恢复；同步身份保留，旧 ticket 的窗口与正文写入被持久化屏障拒绝。
- Worker 重建按后续读取或既有同步重试触发，无新增定时器。dispose 停止恢复并关闭 Worker。
- 此处验证的是受控时序和错误恢复；真实账号的长期稳定性、正式制品发布与 Windows/Linux 运行验收需单独记录。

## 回归入口

- `conversation-send-directory.test.tsx`：发送与刷新交错、失败恢复、旧快照、删除、正文提交早于结构响应。
- `unified-timeline-live-tail.test.ts`：sequence 覆盖、部分/历史窗口、显式删除与容量。
- `unified-timeline-sync.test.ts`：旧窗口与正文读取、账号隔离、重建退避、删除补交、溢出后的旧写入屏障。
- `chat-message-sync-service.test.ts`、`realtime-client-events.test.ts`：提交后通知、账号和批次边界、不修改未读事实。
- `timeline-cache-worker.test.ts`：真实 Worker 崩溃、容量拒绝、冷启动超时。
