You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Search: span every indexed volume, not just the root drive
Filename search was root-only by construction: it loaded the root drive's index and pinned `SEARCH_VOLUME_ID = "root"`, so searching a scope on a NAS/SMB share or external drive silently returned "No files found" even when that volume's `index-<id>.db` held millions of entries. Search now routes a scope to its owning volume(s) and, unscoped, fans out across every volume with a persisted index, merging the ranked results.
- **Per-volume arenas (`search/volumes.rs`).** Generalized the global `SEARCH_INDEX` singleton into a per-volume registry. `ensure_volume(id)` lazily loads and caches each volume's arena: root from the live `get_read_pool()`, a non-root volume read-only straight from `index-<id>.db` on disk — deliberately NOT via `INDEX_REGISTRY`, so an ejected/unmounted drive's index is still searchable. The mount root comes from the DB's `volume_path` meta (known offline). Dialog-scoped idle/backstop timers drop every arena at once to reclaim RAM; a long root pre-load stays cancelable.
- **Honest coverage signal.** A scope pointing at a volume with no persisted index no longer reads as empty success: `run_blocking` collects those paths into the new typed `SearchResult.uncovered_scopes` (callers branch on emptiness, never string-match), and the UI/MCP render "Cmdr hasn't indexed X yet" instead of "no files found". Partial coverage still returns the covered volumes' hits alongside the note.
- **Mount-relative path spaces.** A non-root index is rooted at its mount, so it stores mount-relative paths. Two mirror transforms bridge the gap: `engine::search_ranked` PREFIXES the mount root onto reconstructed paths (a NAS result reports `/Volumes/naspi/...` and opens in a pane), and `query::resolve_include_path_ids` STRIPS the mount root from scope include paths before `resolve_path`.
- **Cross-volume merge.** The engine returns ranked entries carrying their `RankKey` (match-quality band + importance-boosted recency + id — all volume-independent), so `execute.rs` k-way-merges each volume's already-ranked slice with one global sort, sums per-volume totals, and fills directory sizes per volume from the owning pool.
- **Per-volume importance weights.** Weights load per volume into a `WEIGHTS` map: root stays live via the recompute subscriber, a non-root volume takes a load-time snapshot. A volume with no `importance-<id>.db` degrades to match-quality + recency (the existing contract).
- **Shared runner for dialog + MCP.** `commands/search.rs` and `mcp/executor/search.rs` both route through `execute::run_blocking`; the MCP `search`/`ai_search` handlers surface the uncovered-scope note. Removed the now-redundant per-caller load/postprocess paths.
- **Supporting exposure.** Made `volume_id_for_local_path` `pub(crate)` (re-exported from `indexing`); un-gated `IndexStore::get_meta` (was test-only) so the loader can read `volume_path`.
Tests: cross-volume merge ordering (exact match on one volume beats a prefix match on another), mount-path prefixing, the directory-size post-filter, scope→volume grouping, per-volume load from a persisted DB with mount root from meta, the missing-DB honesty signal, per-volume and missing importance weights, and recompute-notify weight refresh.
`file-length-allowlist.json` is a check-run shrink-wrap: `query.rs` dropped below 800 lines after its dir-size helpers moved to `execute.rs`, so its entry was auto-removed.
0 commit comments