# local search query 每次重建索引导致大工作区搜索卡顿

> 状态：done
> Owner：04-tree-domain / 03-rust-web
> 发现日期：2026-05-27
> 来源：Sidex 全项目 review / 本地搜索性能审查

## 现象

在 local-first 工作区中，侧边栏搜索输入虽然有前端 debounce，但后端每次 query 都会重建并写入整个 local search index。大工作区下，连续输入会触发多次递归扫描、读取 Markdown、写 `.mnote/index/search-index.json`，导致搜索和主界面卡顿。

## 证据

- `rust/crates/mnote-web/src/routes/local_search_index.rs`
  - `query_local_search_index()` 第一步调用 `rebuild_local_search_index(...)`。
  - `rebuild_local_search_index()` 递归 `collect_markdown_documents(...)` 后写入 index。
- 前端 `sidebar-tree-runtime.js` 的搜索渲染 debounce 不能避免后端每次 query 全盘 rebuild。
- 同文件已经存在 `refresh_local_search_index_for_path(...)` 增量更新入口，说明 watcher/单文件刷新方向已有基础，但 query path 没有复用它。

## Sidex / VSCode 对照

Sidex / VSCode 搜索路径不会把每次 query 等价为全盘同步 rebuild：

- quick access/search 使用 throttle、cache state 和 cancellation token。
- Rust search crate 有 max_results、ignore-aware、binary skip 和并行扫描。
- watcher 变化应驱动增量缓存更新，query 只消费当前 cache 或启动后台 refresh。

MNote 不需要照搬 VSCode 搜索 UI，但应采用同一原则：query path 不做强制全量重建。

## 期望行为

- query 优先读取已有 index。
- index 缺失、版本不匹配、rootUri/workspaceId 不匹配时，才同步 rebuild。
- index 过期但仍可读时，query 返回旧 index 结果，并后台触发 refresh。
- watcher/path event 继续调用 `refresh_local_search_index_for_path(...)` 做增量更新。
- 连续输入时，旧 query 可以被取消或自然过期，不能排队多次全盘扫描。

## 建议修复切片

1. 新增 `load_or_rebuild_local_search_index(root_path, root_uri, workspace_id)`：
   - 先 `read_local_search_index(root_path)`。
   - 若 version/rootUri/workspaceId 匹配，直接返回 index。
   - 不匹配或缺失时才调用 `rebuild_local_search_index(...)`。
2. 将 `query_local_search_index()` 改为调用 `load_or_rebuild_local_search_index(...)`。
3. 保留显式 refresh API 调用 `refresh_local_search_index(...)`。
4. 加单测：
   - 首次 query 会创建 index。
   - 第二次 query 不改写 index `built_at`。
   - refresh 后 query 能读到新内容。
   - 单文件 `refresh_local_search_index_for_path(...)` 后 query 命中更新内容。
5. 后续中期再做后台 refresh、取消令牌和搜索 worker。

## 验收

- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_index -- --nocapture` 通过。
- 连续两次相同 query 时，第二次不会调用 full rebuild，不更新 `built_at`。
- 侧边栏连续输入 5 个字符时，不出现 5 次全盘 rebuild。
- 大目录 smoke 中搜索响应不阻塞 FileTree 展开。

## 修复记录

- 2026-05-27：新增 `load_or_rebuild_local_search_index(...)`，`query_local_search_index(...)` 优先读取已有匹配 version/rootUri/workspaceId 的索引；只有索引缺失、无效或 root/workspace 不匹配时才 full rebuild。
- 2026-05-27：新增回归测试 `local_search_query_reads_existing_index_without_rebuilding`，验证 query 不会看到未 refresh 的磁盘新内容，增量 refresh 后才命中新内容。
- 验证：`cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_index -- --nocapture`，4 passed。

## 非目标

- 不在本缺陷单实现全文搜索排序重构。
- 不引入新的搜索 UI。
- 不实现全局后台 index worker；这是后续性能设计项。
