Skip to content

fix(sessions): harden content-search lifecycle and Unicode matching - #8150

Merged
Owlbay merged 6 commits into
farion1231:feat/session-content-searchfrom
xue-ding-e:fix/session-search-five-bugs
Oct 14, 2026
Merged

Owlbay merged 6 commits into
farion1231:feat/session-content-searchfrom
xue-ding-e:fix/session-search-five-bugs

Conversation

@xue-ding-e

@xue-ding-e xue-ding-e commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Scope

This is a follow-up to #7896, targeting its feat/session-content-search branch at 078031d37d16320961230e66d0b366470fa1782c. It contains five focused fix commits, not another implementation of the whole feature. Related investigation: #7896 (comment).

Fixes

  1. Retry indexing after transient message-load failures: do not commit a failed read's fingerprint or discard previously indexed text. Successful empty reads still replace the old content.
  2. Synchronize the index after a successful empty session scan, including removal of the final session; loading and failed scans do not masquerade as successful empty lists.
  3. Attempt cleanup of all DB/WAL/SHM files and report non-NotFound failures, including failed recovery cleanup, instead of silently reporting success.
  4. Serialize the settings-save and index-clear operation through a QueryClient-scoped mutation. The guard survives page remounts and prevents an older disable operation from deleting an index built by a newer enable. A false save result never triggers cleanup.
  5. Unify Unicode substring matching and highlighting around whole-string lowercase: index an explicit normalized shadow with case-sensitive trigram search, use exact normalized matching for short queries, and preserve original-text UTF-16 highlight spans through snippet truncation and whitespace compression. Regression coverage includes expansion, final sigma, supplementary characters, short queries and reader snippets.

Index migration and storage

The disposable local search index schema moves from version 1 to 2. Existing v1 indexes are rebuilt through the existing cache-rebuild mechanism; original session files are unchanged. Version 2 retains original text and adds a lowercase shadow, so local index storage grows. Search remains within the Text-only scope of #7896; this does not add tool-output indexing or reader projection changes.

Validation

Verified in an isolated Linux worktree against this exact five-commit branch:

  • Full Rust library tests: 3392 passed, 0 failed, 9 ignored.
  • Strict Clippy: passed.
  • Frontend tests: 196 files, 2302 tests passed.
  • TypeScript typecheck, renderer build, formatting and git diff --check: passed.
  • Real Tauri crate with Linux GTK/WebKit/libsoup/OpenSSL dependencies, not stubbed backend modules.

Not run in the original Linux validation: the 9 ignored Rust tests, native desktop/UI E2E, or Windows/macOS builds. Windows/macOS checks were subsequently completed below. The renderer build has the existing large-chunk warning. GitHub CI status should be checked separately on the PR head.

Checklist

  • TypeScript typecheck
  • Frontend formatting
  • Rust formatting and strict Clippy
  • Frontend unit tests and full Rust library tests
  • No new user-facing text requiring translations
  • Windows/macOS Rust formatting, strict Clippy and nextest (see isolated QA below)
  • Native desktop/UI E2E

AI-assisted contribution, with locally executed regression tests and explicit validation limits above. Kept as a draft for review of the dependent changes.

Isolated fork QA: passed

Run: https://github.com/xue-ding-e/cc-switch/actions/runs/38150771788

The PR targets a feature branch, while the existing upstream CI only triggers for main. An isolated QA branch therefore ran frontend checks and the existing Linux/Windows/macOS backend checks without modifying this PR or main. QA commit 1200ed6db03f6462236f0e0095cd9ed17619e7a6 adds only .github/workflows/qa7896.yml; every product/source file is identical to PR head 4d3c86890feb7bf1e443066166429438387c472b.

  • Frontend: typecheck, formatting, 196 files / 2302 tests, and renderer build passed.
  • Linux: Rust formatting, strict Clippy and nextest passed; 3576 passed, 9 skipped.
  • Windows: Rust formatting, strict Clippy and nextest passed; 3556 passed, 10 skipped.
  • macOS: Rust formatting, strict Clippy and nextest passed; 3596 passed, 9 skipped.

All four jobs and the workflow completed successfully. These are fork QA results for the identical code snapshot, not upstream required checks on the PR SHA. Native desktop/UI E2E and ignored tests remain unverified. The QA workflow uses read-only permissions, no secrets or release steps, and does not persist checkout credentials.

使用整串小写 shadow 索引和区分大小写的 trigram,短词在规范化正文上精确匹配。

摘录携带完整正文产生的 UTF-16 高亮范围,保留大小写上下文、空白压缩与多字节位置;补充检索、点击定位和安全渲染回归。
@github-actions github-actions Bot added frontend Frontend (React/TypeScript) backend Backend (Rust/Tauri) labels Oct 11, 2026
@farion1231

Copy link
Copy Markdown
Owner

GPT-6 Astra 自动代码审查

审查提交:4d3c86890feb7bf1e443066166429438387c472b
Base:078031d37d16320961230e66d0b366470fa1782c

由 Codex 定时任务使用 GPT-6 Astra 子 agent 审查,并经主 agent 复核。

[P2] 空列表清理前需区分扫描失败与真实无会话

位置:src/hooks/useSessionContentSearch.ts:107

当用户仅有一个提供商的会话,且其会话目录临时无法读取(例如权限暂时丢失),并且故障持续到后台二次扫描时,本次取消 hasSessions 检查会把原本保留的正文索引全部清空。后端扫描器将 read_dir 等错误吞掉并返回空列表,list_sessions 仍返回 Ok([]),所以这里的 listIsSuccess 为 true、listUpdatedAt 非零,不能阻止同步。run_sync 随后以空 seen 调用 retain_sources,删除所有缓存正文和指纹;目录恢复后必须全量重建,期间正文结果缺失并产生不必要的解析和写盘开销。原始会话文件不会被删除。base 在该全空场景跳过同步,因此这是本次扩展空列表同步引入的回归,而非仅报告既有的部分扫描失败问题。

证据:

  • src-tauri/src/session_manager/providers/claude.rs:38-43,234-241:collect_jsonl_files 遇到 read_dir 错误直接返回;scan_sessions 对空文件列表返回空会话列表。
  • src-tauri/src/session_manager/mod.rs:57-96:聚合各提供商 Vec,没有扫描完整性或错误状态;src-tauri/src/commands/session_manager.rs:13-17:只传播任务 join 错误,普通扫描失败最终仍为 Ok(sessions)。
  • src/hooks/useSessionContentSearch.ts:107-111:只检查 enabled、listIsSuccess 和时间戳,成功解析的空数组会触发 syncContentIndex。base 的对应 effect 以 !hasSessions 返回。
  • src-tauri/src/session_manager/search.rs:699-715,399-412:后台再次扫描,直接将 seen 交给 retain_sources;空 seen 会移除每个源的消息行和指纹。
  • 隔离执行原样提取的 collect_jsonl_files:对真实存在但由 sandbox 拒绝读取的临时目录,read_dir 返回 PermissionDenied,而收集结果为空。原样提取的 head hook effect 对 listIsSuccess=true/listUpdatedAt=1 调用同步一次。内存 SQLite 执行同样删除范围后,合成缓存行由 4 变为 0。
  • tests/hooks/useSessionContentSearch.test.tsx 的失败用例将 sessionsApi.list mock 为 rejected Promise,未覆盖真实提供商吞掉 IO 错误后 resolve([]) 的路径。

建议:在扫描结果中传递错误或完整性状态,并让后台仅对确认扫描成功的提供商执行破坏性的 retain_sources 清理;真实成功的空扫描仍应清理。增加“已有索引 + 唯一会话目录暂时不可读 + 返回空列表 + 恢复读取”的回归测试,断言失败期间保留旧索引和指纹。

验证与范围

完整覆盖 13 文件差异及搜索、索引、设置、阅读器和扫描上下文,确认一项 P2。主 agent 复核了取消 hasSessions 防护、list_sessions 返回 Ok([])、扫描器吞掉目录读取错误,以及 retain_sources 空集合删除路径;影响为缓存索引误清,不是原始会话文件丢失。

  • 子 agent 原样提取纯函数及同步 effect,15 组匹配、3 组摘录和 10000 次 Unicode 边界检查通过。
  • 5 项无依赖 Rust 隔离测试通过,包含真实存在但不可读目录返回空集合;内存 SQLite 最小验证中 4 条合成缓存行被空集合清理。
  • 主 agent 核对新旧 effect 与后端 IPC/扫描/清理调用链;git diff --check 通过。

未验证或受限范围:

  • 未运行项目 Vitest、Cargo、TypeScript 检查或完整构建。
  • 隔离前端桩不是 React/TanStack 集成测试;SQLite 使用系统版本,而非项目 bundled SQLite。
  • 未实测完整桌面 IPC 故障链、真实会话或跨平台端到端场景。

本评论仅针对以上提交的代码审查结果,不构成合并批准。

- 按提供商记录扫描完整性,仅清理确认扫描成功的源
- 保留读取失败时的正文和指纹,恢复后正常增量同步
- 覆盖目录故障、独立清理、数据库恢复和 UTF-8 尾部读取

Copy link
Copy Markdown
Contributor Author

已在 8546386 修复这条审查指出的回归。

  • 后端按提供商记录本轮扫描完整性,目录/条目读取、会话读取或数据库扫描失败时保留该提供商已有的正文和指纹;其他确认扫描成功的提供商仍可正常清理。
  • 成功的空扫描仍会清理,包括最后一个会话被外部删除的情况。读取恢复后恢复增量同步,未变化的指纹不会触发重新解析。
  • 保持 list_sessions 的 IPC 返回结构和前端 API 不变;不删除原始会话文件。读取尾部窗口时先按字节跳过不完整行,再解码 UTF-8,避免窗口落在多字节字符中间时误报读取失败。

验证:

  • 在原 head 4d3c868 加入回归测试,用真实不可读的 Claude 目录复现:列表和后台扫描都返回空,已有正文命中从 1 变成 0,测试失败。
  • 修复后覆盖不可读目录、恢复读取/保留指纹、最后会话删除、无效目录、独立提供商清理、SQLite 故障恢复,以及 UTF-8 尾部窗口和完整行读取错误。
  • Linux、Rust 1.95、真实 Tauri crate 与 bundled SQLite:完整 cargo test 3582 passed / 0 failed / 9 ignored;严格 cargo clippy -- -D warnings、cargo fmt --check、git diff --check 均通过。推送后核对远端 tree 与本地测试 tree 完全一致。

本次修订由 AI 辅助完成。前端未改动,本次未重跑前端检查;Windows/macOS、原有 9 项忽略测试和原生桌面 E2E 未在新提交上验证。原先的跨平台 QA 只覆盖 4d3c868,不作为新提交已通过 CI 的依据。

@xue-ding-e
xue-ding-e marked this pull request as ready for review October 13, 2026 11:36
@Owlbay
Owlbay merged commit 11f39a5 into farion1231:feat/session-content-search Oct 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Backend (Rust/Tauri) frontend Frontend (React/TypeScript)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants