# 录音说话人统一目录对接

基线：2026-09-30 刷新的 `origin/dev`，提交 `bc33e583`（0.1.86）。
后端合同：用户提供的 2026-09-29 `frontend-integration.md`，jotmo-audio 提交 `45478168880082f4cc8243d2086e0fccbdaff080`。

## 当前行为

- 录音入口只读统一 summary，人数与新增数完全由后端提供。首次 building/null 显示整理中；有完整空快照才显示零。
- 目录以 `all/marked/unmarked + frequent/recent + query` 查询全局列表，每页 50 人，保持服务端顺序和本人置顶。查询按 300ms 防抖，不再扫描所有旧名单，不再用前端 2,000 人上限限制目录。
- 改变查询重新取首屏；翻页固定原 snapshot_version 和查询。重复人物去重，版本变化、循环游标拒绝追加，snapshot_expired 重取摘要及首屏。缓存按账号、查询和快照隔离，最多缓存 12 个查询。
- 仅“全部、无搜索”目录首屏实际渲染且页面可见时提交 list.through_cursor。完整空目录也可确认。预取、入口展示、搜索、筛选、未知快照、隐藏页面不确认。
- seen 成功后重新读取 summary，不自行清零新增数。离线待确认凭据仅保留于当前运行的原账号；失效凭据停止重试，重新展示首屏后才确认新凭据。跨端以 summary 同步。
- 可见期间约 30 秒检查摘要，building/stale/failed 遵从 retry_after_ms。后台停止；在线、重新可见和窗口激活时检查。并发请求合并；HTTP 429 同时尊重 Retry-After 和 retry_after_ms 的较长值，手动刷新不绕过等待。
- failed/stale 保留可用内容，disabled 保留入口、停止目录轮询和确认。未知状态不伪装为空；待处理诊断字段不加入人数。
- 列表 detail_ref 由 Host 封装为账号及环境绑定的引用。选中人物时再接入现有详情；未标记先通过既有 detail 获取候选信息，再复用 mark-options、候选版本、标记和签名音频链路。已标记保留 presence expected_version，不使用目录版本替代。失效候选和 stale presence 刷新目录引用后恢复。
- 已标记人物按当前页的稳定 speaker ID，通过既有说话人绑定及公开资料补齐头像；头像异步加载，不阻塞目录、排序或分页。无图片时使用名称首字头像及按 personKey 固定的浅色背景，空名称才使用默认人形。首字按完整字素取值，忽略前后空格，不截断 emoji。未标记人物保留编号头像，编号为零时不制造“说话人 0”。后端当前空 avatar_ref 不拼接为 URL。
- 本人引导只依据无搜索主目录的可用首屏；搜索页缺少本人不触发引导。声纹状态读取失败显示不可用。

新增 Host 操作：`speaker-directory.summary/list/seen/open/avatars`。沿用 ServiceRuntime 的当前环境地址、登录恢复和业务成功码 200 校验，不硬编码测试服务地址。seen 需要同源页面 Origin。

## 初次验证与边界（后续进展见下文）

- 15 个相关测试文件、209 项测试通过，覆盖目录状态机、页面、Host 分发、真实 HTTP 包装、候选详情及原有录音说话人流程；包括 2,050 人分页、跨账号迟到响应、查询竞争、查看期间新增保留、限流退避、首屏呈现前不确认。
- 类型检查和生产构建链（凭据助手校验、声明生成、tsdown 打包、入口校验）通过。使用既有依赖执行等价脚本，未改动主工作区依赖。
- 独立 3102 预览使用本分支构建的 tgz、独立 DSH_HOME/Arkme 状态目录及已有账号会话；未替换 3100 预览或已安装桌面客户端。
- 生产目录首次返回 building/unknown、人数 null、retry_after_ms=3000，之后成功发布完整快照：9,712 人（已标记 68、未标记 9,644），新增数 0。两种排序各读取前两页（每页 50），版本一致、跨页无重复，本人首屏置顶；名称/编号查询与 marked/unmarked 筛选、已标记 presence 详情成功。没有遍历全部 9,712 人来声称全量验收。
- 两个未标记候选的既有 `/unmarked-speakers/detail` 读取均触发 Host 30 秒请求超时；前端有错误及重试入口，但这条真实详情链路尚未验收。尚无服务端日志，不能断定超时根因。真实非零新增、跨端确认与播放未验证；未直接调用生产 seen，因为本轮没有实际展示目录页面。
- 浏览器工具打开本地地址失败，本机 UI 控制连续超时。页面交互已通过组件测试，但浏览器视觉、真实音频播放和真实标记写入未验收；没有为测试修改真实说话人或声纹。

本地接口验证记录：`/Users/tison/arkme/.codex-artifacts/recording-directory-0930/live-verification.json`。不含账号凭据、人物姓名或音频正文。

## 继续验收（2026-09-30 21:43）

- 直接对生产接口做同候选串行对照：mark-options 6,581 ms、segments(limit=1) 6,330 ms 成功；detail 使用 90 s 诊断上限，60,073 ms 收到上游 HTTP 504。插件超时配置未改动，延长它不能解决此样本的服务端失败。
- 合同后端提交的 getSpeakerDirectoryCandidateDetail 会先 buildSpeakerDirectoryCandidateCatalog；后者对账号 Source.Walk / ReadChildren 并聚合、聚类，然后才定位选中候选。此全账号重建路径是最可能的主要耗时原因，尚缺服务端 trace，不能确认为唯一瓶颈。相关源文件与本轮刷新后的 origin/master 无差异。
- 现有 Host 试听链路的一个旧目录样本成功取得音频 HTTP 206（FLAC，Range 0–63，共 64 字节）。这是媒体授权/读取证据，未覆盖新目录入口、解码或真实播放。
- CUA 能发现 Edge，打开页面和读取 UI 状态仍各 30 s 超时；未完成视觉验收。未调用生产 seen，未修改真实人物/声纹/录音。
- 完整验收仍未通过。本轮不修改问题代码，待用户确认根因与修复方向。详细对照、影响范围及后端源文件快照见 `/Users/tison/arkme/.codex-artifacts/recording-directory-0930/acceptance-2026-09-30.md`。

## 头像回归修复与浏览器验收（2026-09-30 22:39）

- 根因：后端列表的 avatar_ref 当前为空，新目录组件又遗漏了旧流程的 avatarRef，导致有真实头像的已标记人物统一显示默认图。实测旧说话人选项中 70 人有 30 人携带头像引用，本人也有；新目录已标记首屏 50 人的 avatar_ref 全为空。
- 头像补齐仅接受 Host 封装、账号及环境绑定的已标记人物 detailRef，每批至多 50 个。按 speaker ID 匹配绑定用户，不按同名匹配，不扫描联系人、转写或出现统计。沿用公开资料与图片缓存；失败重试不重载目录，账号切换取消并丢弃旧响应。
- 修复相关 9 个测试文件、175 项测试通过，覆盖同名人物、翻页只补新人物、延迟加载、失败重试、跨账号迟到响应、密封引用校验及已有头像缓存。类型检查、生产构建链和 git diff --check 通过。
- 独立 3102 预览已安装修复构建，沿用原有生产账号登录。浏览器工具恢复后实际进入录音及说话人目录；主目录正常展示 9,712 人、已标记 68 人、未标记 9,644 人。真实首屏共 50 行，其中 5 张人物头像全部解码成功（naturalWidth > 0），包括本人；未标记编号头像正常。截图：`/Users/tison/arkme/.codex-artifacts/recording-directory-0930/avatar-fix/avatars-restored.png`。
- 主目录在可见浏览器中实际呈现，允许正常的首屏 seen 流程；此前“未展示页面/未触发 seen”仅描述早期诊断阶段。未为测试修改人物、声纹或录音。
- 本次完成头像显示验收。未标记详情的服务端超时、真实音频播放、真实非零新增和跨端确认仍未完成验收；本次未修改后端。

## 名称首字头像（2026-09-30 22:43）

- 按确认的样式，将无图片的已命名人物改为 38px 圆形首字头像，复用录音人物色板，与主题底色混合后显示深浅适配的文字。颜色绑定稳定 personKey，真实头像加载后优先显示图片；仅说话人目录启用这一可选占位内容。
- 4 个现有头像及目录相关测试文件、34 项测试通过，类型检查、生产构建和 git diff --check 通过。
- 3102 预览已更新并在浏览器确认：首屏 6 位已标记人物中 5 位正常显示真实头像，1 位显示名称首字“安”，未标记编号显示正常。截图及 DOM 验证记录位于 `/Users/tison/arkme/.codex-artifacts/recording-directory-0930/initial-avatars/`。
