Dogfooding bare path resume (#154) surfaced a startup-latency chain: the pre-picker gather originally took 80+ seconds. Two PRs got it to ~2.5 s cold in a release build — #156 (stream codex rollout metadata instead of full-parsing every file) and #157 (gather the seven providers in parallel) — but every invocation still re-scans every session file end to end just to rebuild the same ArtifactRow metadata (title, cwd, last activity, count).
Proposal
Cache listing metadata keyed by the sync manifest's existing stat stamps. p cache sync already proves "nothing changed" in milliseconds without reading bodies — each ArtifactRef carries an mtime+size fingerprint (claude: whole-chain stamp; codex: rollout stat; opencode/cursor: row updated-at). Extend that machinery so gather_artifacts:
- enumerates
ArtifactRefs (stat-level, milliseconds),
- reuses cached row metadata for every artifact whose stamp matches,
- scans only new/changed artifacts (typically zero or one),
- writes refreshed metadata back through the same locked read-merge-save the manifest already uses.
Warm picker opens (share, bare resume) become effectively instant; cold first runs stay at the current ~2.5 s. Benefits every provider — claude's 1.7 s metadata pass and opencode's ~1 s SQLite read are the next-largest costs after codex.
Design questions
- Where the metadata lives: extra optional fields on manifest records vs a sibling
listing-cache.json. Manifest records are per-artifact already and share the advisory-lock discipline; a sibling file keeps the manifest schema stable.
- Claude chain stamps cover the whole chain — cached metadata must be keyed to the chain head id, matching
claude_chain_stamp.
- Eviction: follow the manifest's archive-not-mirror semantics (a source file deleted upstream keeps its record; gather should drop rows whose files no longer exist, same as sync's self-heal check).
Alternative considered
Progressive hydration (stream rows into the picker as providers finish — fzf and skim both accept streamed input, and the ratatui backend (#155) could use a nucleo Injector). Rejected as the primary fix: the ranking contract (cwd-matches first, newest first) needs a global sort, so rows would visibly reshuffle while filling, and it masks the cost instead of removing it. Worth revisiting as cold-start polish only if the cache misses badly somewhere.
Refs: #154, #156, #157.
Dogfooding bare
path resume(#154) surfaced a startup-latency chain: the pre-picker gather originally took 80+ seconds. Two PRs got it to ~2.5 s cold in a release build — #156 (stream codex rollout metadata instead of full-parsing every file) and #157 (gather the seven providers in parallel) — but every invocation still re-scans every session file end to end just to rebuild the sameArtifactRowmetadata (title, cwd, last activity, count).Proposal
Cache listing metadata keyed by the sync manifest's existing stat stamps.
p cache syncalready proves "nothing changed" in milliseconds without reading bodies — eachArtifactRefcarries an mtime+size fingerprint (claude: whole-chain stamp; codex: rollout stat; opencode/cursor: row updated-at). Extend that machinery sogather_artifacts:ArtifactRefs (stat-level, milliseconds),Warm picker opens (
share, bareresume) become effectively instant; cold first runs stay at the current ~2.5 s. Benefits every provider — claude's 1.7 s metadata pass and opencode's ~1 s SQLite read are the next-largest costs after codex.Design questions
listing-cache.json. Manifest records are per-artifact already and share the advisory-lock discipline; a sibling file keeps the manifest schema stable.claude_chain_stamp.Alternative considered
Progressive hydration (stream rows into the picker as providers finish — fzf and skim both accept streamed input, and the ratatui backend (#155) could use a nucleo
Injector). Rejected as the primary fix: the ranking contract (cwd-matches first, newest first) needs a global sort, so rows would visibly reshuffle while filling, and it masks the cost instead of removing it. Worth revisiting as cold-start polish only if the cache misses badly somewhere.Refs: #154, #156, #157.