Skip to content

Instant picker startup: stat-stamp metadata cache for session listing #158

Description

@od0

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:

  1. enumerates ArtifactRefs (stat-level, milliseconds),
  2. reuses cached row metadata for every artifact whose stamp matches,
  3. scans only new/changed artifacts (typically zero or one),
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions