4.8 KiB
Search index + prompt display fixes
Working plan. Findings come from an audit of a real cache.db
(9,252 Drive nodes, 110,187 local files) on 2026-08-12.
Evidence
nodes 9252
nodes_fts 6547 ← 2705 rows never indexed
nodes in orphan subtrees 2705 ← exactly the missing set
local_files 110187
/home/narl/go/pkg/mod 63399 (58% of the local index)
query "test" 46405 local matches, candidate pool capped at 1000
top 500 by fts rank 367 (73%) from go/pkg/mod
pdfs search tickets returns only Documents/Tickets (My Files); the
tickets.pdf sitting in the device folder Downloads is invisible.
P1 — 29% of Drive is unsearchable
node_is_indexable_tx (crates/pdfs-core/src/db/nodes.rs:606) indexes a node
only when the ancestor walk terminates at a row with parent_uid IS NULL. The
DB holds exactly one such row (the My Files root). Device roots live in
device/sync_folder, never in nodes, so every device-folder subtree
(Downloads, Pictures, Documents, Music, Videos under device
nthinkpad) walks up to a parent_uid with no row and is judged unindexable.
Stale pdfs-acceptance-* roots hit the same path.
- Relax the rule to "the walk terminates and no ancestor is trashed", keeping the existing cycle guard. A subtree whose top row's parent is simply not cached is still real, reachable data.
MIGRATION_V21: rebuildnodes_ftsunder the new rule, so existing installs recover the missing rows instead of waiting for each node to be upserted again.- Tests: a node under an uncached parent is indexed; a trashed ancestor still excludes; the migration backfills.
Found while testing: a parent cycle hangs the daemon
Writing the cycle test surfaced a live defect, not a test artefact. A query
below the trigram minimum takes the LIKE lane, which reads nodes directly
and never consults the index, and every row it returns goes through path_of —
whose parent walk is an unguarded UNION ALL. On a parent_uid cycle it never
terminates, while holding the daemon's only SQLite connection.
- Depth-cap
path_of(db/utils.rs), matching the 1024 cappath_relative_toalready carried. - Depth-cap the
descendantsCTE inupsert_node_tx, which has the same exposure on the write side. - Keep cycles out of the index (part of the P1 rule above).
Still unguarded, not touched here: pin_is_pinned's ancestor walk.
P2 — local index noise crowds real files out of the candidate pool
SKIP_DIRS (crates/pdfs-core/src/localindex.rs:23) misses the Go module
cache and build output dirs. search_v2 clamps candidates to ≤1000 and scores
after SQLite, so for any common term the pool is exhausted by vendored files
before a real one is considered.
- Skip
go/pkg/mod(path-relative, not a bare name),dist,build,.cache-style artefact dirs. - Give
search_local_candidatesa name-prefix lane, mirroring whatsearch_candidatesalready does for Drive, so an exact-prefix match cannot be starved by trigram rank. - Tests for both.
P3 — display lies about where a hit lives
Hit::location()(crates/pdfs-gui/src/prompt.rs:150) hardcodesMy files / …for every Drive hit. Derive the label frommounted_pathwhen the daemon resolved one (device folder, sync folder) and fall back toMy filesonly for the primary mount.- Pin rows set
is_dir: pin.recursive(dmenu.rs:133,prompt.rs:872) — a non-recursively pinned folder renders and activates as a file. pdfs locationsprintsDevice folder 1..5rather than the device and folder name. (Cosmetic; deferred.)
Verification
Done, 2026-08-12:
cargo fmt --all -- --check clean
cargo clippy --workspace --all-targets -- -D warnings clean
cargo test --workspace --locked 439 passed, 0 failed
The V21 backfill was also replayed against a read-only .backup of the live
cache.db:
live_nodes 9276
indexed 9276 (was 6547)
downloads_subtree 300
tickets.pdf -> Downloads/tickets.pdf
That run is what caught the last defect: the backfill originally seeded an
uncached-parent root with an empty path, so a node indexed by the migration
(tickets.pdf) and the same node after a later upsert (Downloads/tickets.pdf)
disagreed. The seed now keeps the name for any row that has a parent, matching
path_of.
Still to do, on the live system: rebuild, restart proton-drive.service, and
confirm pdfs search tickets returns the device folder's tickets.pdf. The
schema move to 21 is one-way — an older pdfs refuses a v21 database. The
local_files shrink (~110k → ~47k) lands with the next background scan.