| 1 | # 轮次大纲与跨分页跳转 |
| 2 | |
| 3 | [English](TRANSCRIPT_OUTLINE_NAVIGATION.md) |
| 4 | |
| 5 | > **历史验收记录——已被 PR #10385(Follow v2)替代。** |
| 6 | > 本文记录 PR #10276 的完整大纲实现及当时执行的验证。文中的前后对比与测试结果 |
| 7 | > 不构成当前生产链路的验收证据。 |
| 8 | |
| 9 | 当前行为采用双向有界历史窗口,导航条仅展示已加载轮次;完整持久化历史仍可通过 |
| 10 | 权威历史搜索与定位访问。现行约定见[会话同步 v2](TRANSCRIPT_V2.md)、 |
| 11 | [滚动与历史契约](TRANSCRIPT_SCROLL_CONTRACT.zh-CN.md)及 |
| 12 | [自然流正文](TRANSCRIPT_ARCHITECTURE.zh-CN.md)。以下历史设计与验证结果保留用于追溯。 |
| 13 | |
| 14 | ## 原始问题 |
| 15 | |
| 16 | 在工具密集的长对话中,首个正文分页只包含最新的 120 条记录。当这一页里少于两次用户提问时,导航条会整体隐藏:用户必须反复点击"加载更早消息"才会出现导航。切换到其他对话再切回,正文分页被重建,导航可能再次消失;标记还会从 1 重新编号,因为轮次序号取自已加载窗口。窗口之外的轮次完全无法到达。 |
| 17 | |
| 18 | | | 修复前 | 修复后 | |
| 19 | | --- | --- | --- | |
| 20 | | 导航内容 | 仅已加载的用户提问 | 完整会话的全部轮次 | |
| 21 | | 长会话首次进入 | 需手动加载历史后才出现 | 不加载正文即可完整显示 | |
| 22 | | A → B → A | 随正文窗口重建而重置 | 由快照索引恢复 | |
| 23 | | 轮次编号 | 按已加载窗口从 1 重排 | 会话内的绝对轮次号 | |
| 24 | | 未加载轮次 | 不列出 | 列出,可预览、可点击 | |
| 25 | | 点击结果 | 无法点击 | 连续加载历史后准确定位 | |
| 26 | |
| 27 | ## 设计 |
| 28 | |
| 29 | - `internal/transcript` 在正文分页旁提供有界轮次大纲,与正文绑定同一不可变快照、投影修订和事件覆盖边界。每条携带用户记录的稳定 `RecordID`、可选 `MessageID`、绝对轮次号、该记录在快照中的 `order`,以及两段仅取展示正文的预览:提问 50 个字形簇,回答取该轮分组内最后一条非空助手正文的 120 个字形簇。推理、工具输出、提交文本和注入上下文不会进入大纲。 |
| 30 | - 索引每个冻结快照只构建一次,重复读取复用同一遍扫描;正文分页不会缩减索引;预览内存计入现有快照缓存预算与生命周期。 |
| 31 | - `GET /transcript/outline` 提供该数据,远程握手公布 `transcript-outline-v1`。`TranscriptOutlineAPI` 是 `TranscriptProjectionAPI` 之外的可选能力,旧控制器实现无需改动即可编译,不支持的路由返回 404/405/501 而不是空页。 |
| 32 | - 前端为本地与远程会话共用一份大纲存储,按 tab generation 与快照身份设围栏。快照被回收时如实上报过期;只有用户明确重试才会替换正文。 |
| 33 | - 点击未加载轮次会启动跳转事务:串行复用普通历史分页,每页之间等待逐批挂载推进,仅在目标节点真正挂载后才写入视口。阅读意图、显式取消、更新的目标或会话/快照替换都会结束待执行事务,且不会重新取得滚动控制权。 |
| 34 | |
| 35 | ## 验证 |
| 36 | |
| 37 | 本地与远程路径的代码及自动化检查已完成。平台验证按下述分层如实记录,未运行的部分不予声明。 |
| 38 | |
| 39 | ### Go(已运行) |
| 40 | |
| 41 | ```sh |
| 42 | go test ./internal/transcript/ ./internal/control/ ./internal/serve/ |
| 43 | go test -race ./internal/transcript/ |
| 44 | cd desktop && go test ./... |
| 45 | ``` |
| 46 | |
| 47 | 覆盖:工具密集尾部导致正文分页不含用户提问时大纲仍完整;与正文记录一致的身份;绝对编号及更早分页后的保持;被回收快照的过期上报;预览长度、空白折叠、Unicode 安全及排除推理/工具输出;空提问保留轮次身份;字节预算与游标前进;响应上限;越界 offset;空会话编码为 `[]`;HTTP 端点的分页、会话绑定、能力公布,以及无该能力控制器返回 501;以及大纲读取与流式提交的并发竞争。 |
| 48 | |
| 49 | ### 前端(已运行) |
| 50 | |
| 51 | ```sh |
| 52 | cd desktop/frontend |
| 53 | pnpm test:transcript # 含 transcript-outline-store、chat-turn-jump、 |
| 54 | # chat-turn-outline-jump |
| 55 | pnpm test:remote |
| 56 | pnpm build # 类型检查、scroll-writer 门禁、CSS/主题、bundle 预算 |
| 57 | ``` |
| 58 | |
| 59 | `transcript-outline-store` 覆盖多页组装、重复身份、过期快照、游标停滞、能力缺失与真实失败的区分,以及释放对在途读取的围栏。`chat-turn-jump` 覆盖挂载确认后的分页、无产出分页、历史耗尽、用户抢占、新目标取代、会话替换、显式取消,以及有界的挂载等待。`chat-turn-outline-jump` 在 jsdom 中驱动真实 `Transcript`:只加载最新几轮时导航条仍完整、较早轮次标记为未加载、点击后连续加载直到节点挂载、该过程不改变编号、busy 状态正确结束。 |
| 60 | |
| 61 | ### 浏览器(已运行) |
| 62 | |
| 63 | ```sh |
| 64 | cd desktop/frontend |
| 65 | CHAT_BROWSER=chromium node bench/chat-transcript.mjs |
| 66 | ``` |
| 67 | |
| 68 | 确认真实页面的渲染、流式与滚动在本次改动后保持正常。该次运行两个场景均报告零错误。记录时间 2026-09-14,arm64 darwin,Chromium 153.0.8010.12: |
| 69 | |
| 70 | | 指标 | 240 轮 | 1000 轮 | 门槛 | |
| 71 | | --- | --- | --- | --- | |
| 72 | | 输入 P95 | 46.5 ms | 137.6 ms | ≤ 200 ms | |
| 73 | | 切换 P95 | — | 38.2 ms | ≤ 300 ms | |
| 74 | | 最长任务 | 53 ms | 218 ms | ≤ 500 ms | |
| 75 | | 堆增长 | — | 0.33 MiB | ≤ 20 MiB | |
| 76 | | 锚点 / 前置漂移 | 0 / 0.09 px | — | 无漂移 | |
| 77 | | 已挂载 DOM 节点 | 12089 | 47833 | 导航有界 | |
| 78 | |
| 79 | 这是对真实页面的回归检查,不替代下述原生平台验证。1000 轮数据同时说明导航不会按轮次创建等量 DOM:只渲染可视范围内的标记。 |
| 80 | |
| 81 | ### 窗口应用(未运行) |
| 82 | |
| 83 | Electron 基准(`node bench/transcript-layout.mjs --electron`)及打包后的桌面构建需要已签名应用包,本环境未执行。 |
| 84 | |
| 85 | ### Windows(本环境未验证) |
| 86 | |
| 87 | 本环境未在 Windows 上重放原始路径,因此无法记录构建 SHA。在真实 Windows 桌面构建上重放原始场景仍属待验证的外部事项,本记录不作声明。 |
| 88 | |
| 89 | ## 兼容性 |
| 90 | |
| 91 | 本次为增量扩展。不具备该能力的客户端继续使用已加载轮次导航,也不宣称拥有完整导航。不修改持久化会话格式、provider 消息、工具 schema、权限或提示缓存字节,因此不影响提示缓存。回退到上一版前端并忽略新增只读端点即可恢复。 |
| 92 |