Implement durable picker cache semantics #722

Closed
opened 2026-05-12 15:09:54 +00:00 by barrettruth · 0 comments
Owner

Problem

Picker list caching is not useful enough for large repositories. Opening a picker after restarting Neovim refetches even when forge.nvim has recently fetched the same list.

Current implementation facts:

  • lua/forge/state/init.lua uses a process-local list cache with a short TTL.
  • lua/forge/state/cache.lua stores values in an in-memory Lua table only.
  • Restarting Neovim loses the cache entirely.

Final design decisions

Decision Chosen behavior
Durable surfaces PR lists, issue lists, and release lists only for the first implementation.
CI/checks Do not disk-cache yet. CI/checks are highly volatile; durable rows can make cancel/rerun labels and check status wrong quickly. Checks also need stronger commit-SHA-aware keys before persistence is safe.
Read policy Show last-known-good disk rows immediately, then revalidate in the background.
Freshness window 15 minutes for PRs, issues, and releases.
Stale label No visible cached/stale marker during normal operation.
Revalidation failure Leave stale rows visible and warn that refresh failed.
Manual refresh Bypass and clear durable cache for that family/scope, cancel in-flight work, and force network.
Mutation behavior Picker-local optimistic patches are fine for immediate UX, but disk should be updated only from authoritative successful fetch/revalidation or cleared when authoritative state is uncertain.
Pagination/load-more Persist expanded fetched pages too. They are real fetched forge data and should not be thrown away solely because the user requested more than the base limit.
Keying Preserve fork-aware scope identity. Durable keys must include schema version, git root/repo identity, forge/backend identity, scope, picker kind, state/filter input, and fetched limit/exhaustion metadata.
Storage shape Store raw or normalized list rows, not rendered picker entries.
Storage location Use Neovim cache storage, e.g. stdpath("cache")/forge.nvim. Storing private repo titles/branches there is acceptable for this plugin.
Config surface No public config for the initial behavior unless implementation exposes an unavoidable need.

Rationale for excluding CI/checks initially

CI and checks are different from PRs/issues/releases. They change quickly, and the row contents directly drive actions like cancel, rerun, open log, and status interpretation. A durable CI row can become misleading in seconds. Checks are also not safely keyed by PR number alone because the PR head commit can move; persistence should wait until checks can be keyed by commit SHA across backends.

In-memory or active-picker reuse remains fine. The first durable cache pass should improve picker startup without making volatile action state look authoritative.

Acceptance criteria

  • Picker list cache survives Neovim restart for PRs, issues, and releases.
  • A cached picker displays rows before the network/CLI fetch completes.
  • Cached rows revalidate in the background.
  • Revalidation failure keeps stale rows visible and logs a warning.
  • Manual refresh clears/bypasses durable cache and forces network.
  • Expanded load-more results are persisted and reused when the requested limit fits.
  • Cache files are versioned and safe to invalidate across schema changes.
  • Cache keys preserve fork-aware scope separation.
  • Tests cover disk hit/miss/expiry, restart behavior, scope separation, refresh invalidation, warning-on-refresh-failure, and expanded-limit reuse.

Non-goals

  • Do not disk-cache CI runs or PR checks in this issue.
  • Do not cache rendered fzf lines.
  • Do not require fzf-lua for Ex-mode commands.
  • Do not add public cache configuration unless a hard implementation constraint appears.
## Problem Picker list caching is not useful enough for large repositories. Opening a picker after restarting Neovim refetches even when forge.nvim has recently fetched the same list. Current implementation facts: - `lua/forge/state/init.lua` uses a process-local list cache with a short TTL. - `lua/forge/state/cache.lua` stores values in an in-memory Lua table only. - Restarting Neovim loses the cache entirely. ## Final design decisions | Decision | Chosen behavior | |---|---| | Durable surfaces | PR lists, issue lists, and release lists only for the first implementation. | | CI/checks | Do not disk-cache yet. CI/checks are highly volatile; durable rows can make cancel/rerun labels and check status wrong quickly. Checks also need stronger commit-SHA-aware keys before persistence is safe. | | Read policy | Show last-known-good disk rows immediately, then revalidate in the background. | | Freshness window | 15 minutes for PRs, issues, and releases. | | Stale label | No visible cached/stale marker during normal operation. | | Revalidation failure | Leave stale rows visible and warn that refresh failed. | | Manual refresh | Bypass and clear durable cache for that family/scope, cancel in-flight work, and force network. | | Mutation behavior | Picker-local optimistic patches are fine for immediate UX, but disk should be updated only from authoritative successful fetch/revalidation or cleared when authoritative state is uncertain. | | Pagination/load-more | Persist expanded fetched pages too. They are real fetched forge data and should not be thrown away solely because the user requested more than the base limit. | | Keying | Preserve fork-aware scope identity. Durable keys must include schema version, git root/repo identity, forge/backend identity, scope, picker kind, state/filter input, and fetched limit/exhaustion metadata. | | Storage shape | Store raw or normalized list rows, not rendered picker entries. | | Storage location | Use Neovim cache storage, e.g. `stdpath("cache")/forge.nvim`. Storing private repo titles/branches there is acceptable for this plugin. | | Config surface | No public config for the initial behavior unless implementation exposes an unavoidable need. | ## Rationale for excluding CI/checks initially CI and checks are different from PRs/issues/releases. They change quickly, and the row contents directly drive actions like cancel, rerun, open log, and status interpretation. A durable CI row can become misleading in seconds. Checks are also not safely keyed by PR number alone because the PR head commit can move; persistence should wait until checks can be keyed by commit SHA across backends. In-memory or active-picker reuse remains fine. The first durable cache pass should improve picker startup without making volatile action state look authoritative. ## Acceptance criteria - Picker list cache survives Neovim restart for PRs, issues, and releases. - A cached picker displays rows before the network/CLI fetch completes. - Cached rows revalidate in the background. - Revalidation failure keeps stale rows visible and logs a warning. - Manual refresh clears/bypasses durable cache and forces network. - Expanded load-more results are persisted and reused when the requested limit fits. - Cache files are versioned and safe to invalidate across schema changes. - Cache keys preserve fork-aware scope separation. - Tests cover disk hit/miss/expiry, restart behavior, scope separation, refresh invalidation, warning-on-refresh-failure, and expanded-limit reuse. ## Non-goals - Do not disk-cache CI runs or PR checks in this issue. - Do not cache rendered fzf lines. - Do not require fzf-lua for Ex-mode commands. - Do not add public cache configuration unless a hard implementation constraint appears.
barrettruth changed title from WIP: design durable picker cache semantics to Implement durable picker cache semantics 2026-05-12 20:27:49 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
barrettruth/forge.nvim#722
No description provided.