# 新发送消息的未读状态连续性

## 行为与边界

- 私聊、群聊的本地发送消息从占位行出现起显示未读；没有正式消息序号时不查询已读接口。
- 查询中、实时通知失效、缺项、零人数结果和失败重试均保留默认未读。查询失败沿用现有原位重试交互。
- 有效统计优先于本地提示；迟到发送回执不覆盖已读统计，也不使正在进行的查询失效。后续有效统计仍可正常更新群成员人数。
- 发送失败取消本地默认未读。历史消息没有有效统计时仍为未知，不猜测群人数。
- 本地标记使用 Symbol，仅存在于 Browser 消息投影，随原有账号/消息生命周期清理，不序列化到 Host 或持久缓存。

## 原因与 owner

之前 `provisional` 同时承担网络状态和未读展示状态，刷新改为 `loading/stale/error` 后蓝点会退回占位；`provision()` 又会替换已有查询 entry、丢弃真实结果。

现在消息合并 owner 保留本地发送提示，已读 store 独立保存 `provisionalUnread`；有效统计到达后清除提示。刷新失败从当前 loading entry 继承提示，避免丢失查询过程中到达的发送回执。

| 能力面 | 本次影响 |
| --- | --- |
| UI | 消息原位合并和已读状态展示；保留现有图标、按钮、详情面板 |
| Host owner | 不新增路由、查询或持久化；既有已读统计仍是事实来源 |
| Tools / SDK | N/A：纯 Browser 展示状态，不增加业务能力 |
| Client / Harness | 不修改桌面或 DSH；通过既有插件包与隔离测试 Profile 装配 |

## 资源约束与验证

沿用现有可见性注册、账号/环境隔离、180ms 合批、15–60s 轮询、2000 条统计缓存和64条本地发送记录上限；不增加请求、定时器或订阅 owner。

回归覆盖私聊/群聊从无序号占位到实时推送、迟到 ACK、零人数、查询失败、重试、有效未读/已读统计的完整状态链，同时验证 DOM 槽位稳定和查询次数；store 单独覆盖请求进行中补入提示后的缺项、零人数、异常及成功返回。消息合并测试覆盖 ACK、实时推送、后台读取的六种顺序，以及失败/重试与 JSON 序列化隔离。

## 提交前性能验证

2026-10-15 在 macOS / Node 25.5.0 同一进程内对比 `2234ed0b` 与 `694e4a64` 的运行代码；预热5组、交替执行30组样本。后续提交只补测试和本文档，不改变运行代码。

| 工作负载 / 指标 | 修改前 | 修改后 |
| --- | ---: | ---: |
| 2000条可见消息，首次查询和100次突发失效通知后的请求总数 | 80 | 80 |
| 最大单批 / 最大并发 | 50 / 1 | 50 / 1 |
| 已有统计后20000次重复发送回执产生的 store 订阅通知 | 20000 | 0 |
| 隐藏页面60秒的额外请求 / 释放后的定时器 | 0 / 0 | 0 / 0 |
| 2000条消息、64个订阅者、20000次 provision，P50 / P95 耗时 | 5.17 / 5.67 ms | 1.94 / 2.35 ms |
| 2000条消息、64条本地发送记录，连续20轮合并，P50 / P95 耗时 | 26.10 / 30.19 ms | 27.83 / 30.70 ms |

持久回归新增三项：6000条未挂载提示仍只缓存2000条，重复回执不通知订阅者；2000条可见消息的批次、并发和后台暂停边界；20轮会话挂载/卸载后均无遗留轮询。

本次没有增加网络调用或长期资源 owner；重复回执减少了无效通知。20轮合并的中位耗时增加1.73ms，约每轮0.09ms。上述为受控函数与资源计数验证，不代表真实浏览器帧率、长期 heap 曲线或其他操作系统的性能保证。
