返回 DeepSeek-Reasonix
READ_SNAPSHOT_PAGINATION.zh-CN.md
根目录 / docs / READ_SNAPSHOT_PAGINATION.zh-CN.md
1 # 列表与搜索不可变读取实施说明
2
3 [English](READ_SNAPSHOT_PAGINATION.md)
4
5 ## 问题与修复边界
6
7 本地会话持续写入标题、结果时,Desktop 可能提示 `session_operation:stale_cursor:The session list changed. Reload it.`。原先的元数据双重检查要求写入在整个读取期间保持静止,导致首屏也可能失败。只删除双重检查仍不够:续页重新读取、排序实时数据,会让旧游标失效;前端首屏超过 200 行时,还可能漏掉内部第二次 RPC 的过期错误。这条错误本身不能证明 SSH、Git、Docker 或远程 Reasonix 发生故障。
8
9 修复由 `desktop/read_snapshot_store.go` 统一持有读取结果。续页按固定序号读取冻结结果,不再重新排序,也不比较全局索引版本。快照只提供读视图;写命令继续校验当前真实身份、归属和生命周期。
10
11 ## 实施范围与代码落点
12
13 | 入口 | 改动 |
14 | --- | --- |
15 | 本地项目、全局、分组、筛选列表 | `session_workspace_sidebar.go` 生成冻结结果;移除 `session_topic_index.go` 的第二套排序缓存 |
16 | 置顶栏 | 使用相同快照分页,收集完成后释放;错误不提交部分置顶结果 |
17 | 旧版项目树入口 | `ListProjectTree` 返回标准 RPC 错误,不把部分子列表当成成功 |
18 | 历史会话元数据 | `history_read_snapshots.go` 配合一个 `sessioncatalog.WithReadView` |
19 | 历史全文搜索、单会话搜索 | 共用冻结候选与摘要构建器;`historycatalog.CaptureSearch` 流式捕获一次 SQL 排序结果 |
20 | 前端项目列表 | `projectTreeWindow.ts` 整窗暂存,`ProjectTree.tsx` 提交和调度 |
21 | 前端历史列表与搜索 | `useHistoryCatalog.ts` 统一请求代次、恢复和提交 |
22
23 不修改 canonical 正文 history-window、持久化会话格式或发送给 provider 的数据。
24
25 ## 读取协议和生命周期
26
27 1. 空游标创建或加入同查询的构建。查询身份包含入口类型、目标、筛选和排序,不包含页大小。
28 2. 成员、展示、分组和生命周期来自同一份 registry 投影;每个 canonical 会话只读取一次元数据。旧目录的内部多页查询使用一个 SQLite 读事务。
29 3. 固定结果集合和排序。全文搜索先捕获候选,再释放源库读事务、读取原文生成摘要;摘要与内容指纹来自同一次读取。指纹不匹配时请求重建索引,并标记覆盖不完整;真实 I/O 错误使构建失败。
30 4. 只有完整构建才能发布。同查询并发读可以共用构建数据,但每位调用者获得独立、可幂等释放的句柄。
31 5. v1 游标包含随机的进程内句柄、查询绑定和下一行序号。重复读取相同游标得到同一页。旧格式、重启、过期或回收后的游标明确失败,不尝试解释为实时偏移。
32 6. 消息、标题、结果和分组顺序变化不打断旧快照;下一次刷新读取新状态。相关归档、删除、移动、接管和源替换使依赖它的结果失效。
33 7. 响应增加可选 `snapshotId`、`snapshotExpiresAt`;历史入口保留 `staleCursor`,增加 `readError`。RPC 保留 `stale_cursor`,增加结构化 `readReason`。`ReleaseReadSnapshot` 可重复调用。
34
35 元数据列表通过 catalog/registry 校验生命周期,不为此打开正文。搜索另外验证源文件身份;携带 `contentDigest` 的上下文请求会在使用消息下标前拒绝变化后的内容。
36
37 ## 资源治理
38
39 | 项目 | 默认值 |
40 | --- | --- |
41 | App 内句柄总数,含共享构建缓存租约 | 64 |
42 | 已计量的内存行数据与保留的依赖元数据 | 32 MiB |
43 | 同时构建数 | 2 |
44 | 单结果转临时 SQLite 的阈值 | 512 KiB |
45 | 临时 SQLite 总预算 / 单文件预留 | 256 MiB / 64 MiB |
46 | 单份捕获结果 | 64 MiB |
47 | 空闲有效期 / 最长有效期 | 10 / 30 分钟 |
48 | 单次公开分页上限 | 200 行 |
49
50 未发布构建、搜索中间候选也计入存储预算。全局管理锁只处理账本,不持锁执行数据库、文件读取或生命周期回调。读取租约保护正在使用的结果;一个等待者取消不会取消其他等待者。关闭 App 时取消构建并清理临时文件,不扫描其他进程的目录。
51
52 访问时校验过期,后续构建或关闭时回收过期条目。这是快照存储预算,不是整个进程的 RSS 上限:SQLite 缓存、registry 投影和单份原文重放仍有临时工作内存。磁盘采用保守预留,因此四个已转磁盘的结果或候选文件就可能用完配额。预算不足必须明确报错,不能静默截断结果。
53
54 ## 前端恢复与自动刷新
55
56 - 一个逻辑窗口跨多次 RPC 时,所有分页先暂存,成功后一次提交。首屏内部第二页过期也能恢复。
57 - 每次逻辑操作最多自动重建一次。追加失败后重建整个已展开窗口,并整体替换;第二次失败保留当前内容并提供重试。
58 - 拒绝混用快照 ID、不前进的游标,以及空结果仍返回下一页的异常。
59 - 后台事件在固定 200 ms 窗口内合并;读取中只记录一次待刷新,不不断取消;同一列表后台刷新启动间隔至少 500 ms。显式改变查询或排序立即淘汰旧请求代次。
60 - 保留展开规模、稳定行身份和选中状态;项目列表恢复可见行位置,并避免覆盖用户在期间主动滚动的位置。结果替换、组件卸载和过时响应都会释放对应句柄。
61 - 历史元数据列表与正文命中共用一个 UI 代次,一起恢复和提交;命中身份包含 `partIndex`,避免同消息内多个工具参数相互覆盖。
62 - 项目、当前会话、查询或筛选切换时立即隔离旧结果和游标,旧按钮回调、预览和搜索上下文不能写入新身份。后台刷新失败仍保留同一身份的已有内容。
63 - 项目移除后重新加入不复用请求序号;旧请求完成时不能消费新代次的待刷新任务。列表失效、折叠窗口重置、项目移除及归档均释放退役快照。
64 - 历史列表与普通搜索的查询绑定包含捕获的当前会话身份,避免切换期间共用错误构建;明确指定目标会话的搜索仍以目标为准,不因前台焦点变化失效。
65
66 ## 验收与交付
67
68 确定性测试覆盖 205/405/1000 行、持续元数据写入、分组变化、归档边界、跨查询游标、无关索引写入期间排序稳定、源删除、磁盘转存、过期、预算失败、独立释放和关闭。前端测试覆盖两路历史结果恢复、保留错误前内容、待刷新合并、切换查询/排序和异常游标。Chromium `independent-sessions` 增加了三种大窗口规模。
69
70 验收分别执行根模块的 catalog/history 测试、Desktop 独立 Go 模块测试、定向 `-race`、生成的 host 契约检查、前端类型检查/lint 和浏览器场景。此前 `TestLargeTranscriptSwitchPhaseMeasurement` 在原始 HEAD 上也已复现卡住;排除它后的探索性广泛回归仍超过 10 分钟总时限,未取得完整测试集通过的证据。生成契约与检查必须顺序执行,避免测试期间修改生成产物造成无效的过期判断。
71
72 发布前还要用真正的 macOS/Windows 测试包验证:本地持续对话、SSH 已连接、展开超过 200 行、旧历史全文搜索、归档/恢复和关闭清理。本地 Go 与浏览器证据不能替代原生安装包验收。本次本地实施不包含 Git 推送、版本发布或生产部署。
73
73 lines MARKDOWN