# 通话页保留与窄屏联系人回归（2026-09-20）

## 已确认的需求

- 当前登录期间，通话列表、已加载历史分页、选中详情、搜索条件与阅读位置在板块切换后保留。返回时后台检查更新，不清空重载；退出登录或切换账号清空。
- 联系人窄屏不再缩成不可见目录：先显示列表，选中后显示详情与返回入口；左侧功能导航始终保留。放宽恢复目录与详情双栏。
- 本轮仅调整插件与独立 3098 Web 预览，不更新已安装的 Arkme 客户端，不发布、不合并代码。

## 根因

1. `ArkmeSidebar` 的通话模式分支切换会卸载 `ArkmeCallSurface`；工作区进入非活动状态时另有提前返回分支，也会卸载整棵通话界面。
2. 通话页重新挂载后从空状态请求首屏；原刷新直接替换整页，丢失此前加载的历史分页。
3. 后端每次封装通话会重新生成 `callRef`，原选中状态及详情节点依赖此临时标识，刷新后可能失选、重挂载。
4. 联系人旧窄屏样式只覆盖 820px 以下，而宿主在更宽时已折叠侧栏。实测 900px、821px 下目录仅约 1px，剩下空白详情占位。

## 实现

- 通话页首次访问才挂载，离开时隐藏并传入活动状态。活动/挂起分支的外层容器和通话节点使用稳定键值；账号、环境或登录状态改变会销毁保留内容。
- 返回后通过现有 `calls.history.list` 拉取最新页，并以 `stableId` 合并。新数据超过一页时继续补齐到已缓存记录，保留旧分页游标与去重逻辑；完整响应按服务器结果收敛。
- 选中与行节点、详情容器使用稳定记录标识，接口调用仍使用最新 `callRef`。只在刷新后检查当前选中详情，不因加载旧分页重复请求详情。
- 列表刷新前后按可见行锚点保持阅读位置；后台失败保留旧内容，后续访问重试。
- 隐藏时不自动填满分页、停止已加载页的后台补页，关闭临时选择弹窗并停止详情音视频播放；不会停止全局正在进行的真实通话。
- 联系人跟随宿主实际折叠状态，而非另设不一致断点；窄屏仅隐藏目录或详情之一，不卸载目录，不隐藏第一栏导航。

## 验证

- 15 个相关测试文件、482 项测试通过，包含真实 `ArkmeSurface` 工作区挂起/恢复、账号切换/退出、旧分页保留、访问标识轮换、刷新失败、多页补齐、无效游标、迟到响应、音视频暂停及原有发送交互。
- 类型检查与完整构建通过。
- 3098 生产组件浏览器验证：1440、1100、1099、1024、1000、900、821、820、720、420px 下联系人目录均可见；窄屏选择/返回、搜索保留、放宽恢复双栏通过。
- 通话浏览器受控响应验证：已加载 60 条，离开/返回后列表、详情仍是同一 DOM 节点，列表滚动 2600px、详情 480px 保留；新增一条后共 61 条，列表补偿至 2695px 保持原阅读锚点；模拟后台失败不清空。
- 真实账号只读验证：30 条通话记录，离开/返回后同一列表及详情节点、240px 列表位置保留，不出现“正在读取通话记录”。没有发起通话或修改数据。
- 原“发给自己”输入框回归：1440/720px、底部/历史位置各三轮聚焦与失焦，消息位移为 0，未出现页面异常。

浏览器脚本保存在独立预览目录：`check-contacts-calls-retained.mjs`、`check-calls-live-retained.mjs`、`check-composer-focus.mjs`。

## 边界

- 保留范围是当前页面运行期间；刷新整个浏览器或重启后仍需要首次加载，不把通话详情长期写入磁盘。
- 使用现有分页接口实现后台检查，没有新增后端增量接口。未返回范围内的历史记录全量变更/删除不等于已完整同步；当前选中详情会再次读取。
- 多页新增、网络错误和取消场景使用受控测试响应验证，不是通过真实制造通话验证。
