refactor: centralize git and pr store refresh
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
# UI Stores
|
||||
|
||||
## Purpose
|
||||
|
||||
`packages/ui/src/stores` contains app-level Zustand stores for persistent UI state, runtime state, and feature caches.
|
||||
|
||||
Not all state in the UI belongs here.
|
||||
|
||||
Use a store when state is:
|
||||
|
||||
- shared across distant parts of the app
|
||||
- needed outside a single component subtree
|
||||
- cache-like and keyed by runtime identity (for example directory, branch, session id)
|
||||
- updated imperatively from multiple surfaces
|
||||
|
||||
Do not put high-frequency local component state here just because it is convenient.
|
||||
|
||||
## Architecture
|
||||
|
||||
There are multiple store categories in this directory.
|
||||
|
||||
### Feature cache / query stores
|
||||
|
||||
These are the most performance-sensitive.
|
||||
|
||||
- `useGitStore.ts`
|
||||
- `useGitHubPrStatusStore.ts`
|
||||
- `useFilesViewTabsStore.ts`
|
||||
|
||||
These stores act like centralized keyed caches. UI should consume narrow slices from them instead of re-fetching the same data in multiple places.
|
||||
|
||||
### UI state stores
|
||||
|
||||
Examples:
|
||||
|
||||
- `useUIStore.ts`
|
||||
- `useDirectoryStore.ts`
|
||||
- `useFeatureFlagsStore.ts`
|
||||
- `useUpdateStore.ts`
|
||||
|
||||
These stores coordinate visible app state, navigation, selected tabs, dialogs, and lightweight feature flags.
|
||||
|
||||
### Session / project coordination stores
|
||||
|
||||
Examples:
|
||||
|
||||
- `useProjectsStore.ts`
|
||||
- `useGlobalSessionsStore.ts`
|
||||
- `useSessionFoldersStore.ts`
|
||||
|
||||
These stores coordinate persistent project/session metadata across multiple views.
|
||||
|
||||
## Git / PR Stores
|
||||
|
||||
The Git and PR stores are the most important stores to understand before editing this directory.
|
||||
|
||||
### `useGitStore.ts`
|
||||
|
||||
`useGitStore` is a centralized per-directory Git cache.
|
||||
|
||||
Core model:
|
||||
|
||||
- top-level keyed by `directory`
|
||||
- each directory entry contains:
|
||||
- repo detection
|
||||
- status
|
||||
- branches
|
||||
- log
|
||||
- identity
|
||||
- diff cache
|
||||
- per-directory loading flags
|
||||
- freshness timestamps
|
||||
|
||||
Important properties:
|
||||
|
||||
- `directories: Map<string, DirectoryGitState>` is the source of truth
|
||||
- loading state is per-directory, not global
|
||||
- `ensureStatus()` and `ensureAll()` are the preferred entry points for consumers
|
||||
- in-flight dedupe exists for status and `ensureAll()`
|
||||
- diff data is separately cached and capped with size + count limits
|
||||
|
||||
### `useGitHubPrStatusStore.ts`
|
||||
|
||||
`useGitHubPrStatusStore` is a centralized PR cache keyed by `directory::branch`.
|
||||
|
||||
Core model:
|
||||
|
||||
- each entry stores:
|
||||
- current PR status payload
|
||||
- loading / error state
|
||||
- whether initial status was resolved
|
||||
- refresh timestamps
|
||||
- watch count
|
||||
- runtime params
|
||||
- resolved identity
|
||||
|
||||
Important properties:
|
||||
|
||||
- `ensureEntry()` initializes a key lazily
|
||||
- `setParams()` attaches runtime context
|
||||
- `startWatching()` / `stopWatching()` are for true live PR consumers only
|
||||
- `refreshTargets()` supports one-shot multi-target bootstrap without turning on live watching
|
||||
- persisted cache is for page refresh continuity, not for broad background syncing
|
||||
|
||||
## Ownership Rules
|
||||
|
||||
These rules are important. Breaking them tends to reintroduce idle CPU churn, stale UI, or rerender fanout.
|
||||
|
||||
1. No broad `directories` or `entries` subscriptions in normal UI components.
|
||||
2. No root pollers for Git or PR.
|
||||
3. No broad idle sweeps across many directories.
|
||||
4. Prefer store `ensure*` methods over direct runtime API calls from views.
|
||||
5. Visible consumers should drive refresh. Hidden consumers should not.
|
||||
6. Header should not depend on PR store.
|
||||
7. Closed sidebar should not create live PR work.
|
||||
8. File tree Git status should update only when the file tree is visible.
|
||||
|
||||
## Selector Rules
|
||||
|
||||
Use leaf selectors.
|
||||
|
||||
Good:
|
||||
|
||||
- `useGitStatus(directory)`
|
||||
- `useGitBranches(directory)`
|
||||
- `useGitBranchLabel(directory)`
|
||||
- `useGitRepoStatusMap(directories)`
|
||||
- `usePrVisualSummaryByKeys(keys)`
|
||||
|
||||
Bad:
|
||||
|
||||
- `useGitStore((state) => state.directories)` in feature components
|
||||
- `useGitHubPrStatusStore((state) => state.entries)` in feature components
|
||||
- render-time scans over every PR entry for a single project/group badge
|
||||
|
||||
Why this matters:
|
||||
|
||||
- Zustand reruns selectors on every `set`
|
||||
- rerenders are avoided only if the selected result stays referentially stable
|
||||
- broad subscriptions magnify fanout even when only one directory changed
|
||||
|
||||
## Performance Rules
|
||||
|
||||
### 1. Preserve references for unaffected entities
|
||||
|
||||
If directory `A` changes, directory `B` should keep the same derived reference where possible.
|
||||
|
||||
### 2. Keep loading state per entity
|
||||
|
||||
Do not add new global `isLoadingWhatever` flags for keyed cache work.
|
||||
|
||||
### 3. Avoid hidden work
|
||||
|
||||
If a surface is not visible, it should not keep refreshing Git/PR state.
|
||||
|
||||
Examples:
|
||||
|
||||
- `PullRequestSection` may watch a PR while visible
|
||||
- `SessionSidebar` may bootstrap missing PR data for expanded visible groups
|
||||
- hidden sidebar should not watch PRs
|
||||
|
||||
### 4. Prefer one-shot event hints over polling
|
||||
|
||||
Example already in use:
|
||||
|
||||
- successful mutating tools emit a centralized Git refresh hint through `sessionEvents`
|
||||
- visible `GitView` / `DiffView` consume the hint and refresh current-directory status
|
||||
|
||||
This is preferred over background polling.
|
||||
|
||||
### 5. Treat `diffStats` carefully
|
||||
|
||||
`GitStatus.diffStats` may be omitted by light status fetches.
|
||||
|
||||
Rules:
|
||||
|
||||
- do not erase richer existing `diffStats` with a lighter payload
|
||||
- if a UI surface requires per-file `+/-` stats, it must ensure a full enough status payload exists
|
||||
|
||||
### 6. Keep diff cache bounded
|
||||
|
||||
Diff cache has explicit limits because large repos can otherwise blow up memory.
|
||||
|
||||
Do not raise limits casually.
|
||||
|
||||
## Refresh Model
|
||||
|
||||
### Git
|
||||
|
||||
Expected model:
|
||||
|
||||
- `GitView` / `DiffView` ensure current-directory Git state when visible
|
||||
- explicit Git actions refresh status/branches/log as needed
|
||||
- successful file-mutating tools can issue a one-shot Git refresh hint
|
||||
- no root-level background Git polling
|
||||
|
||||
### PR
|
||||
|
||||
Expected model:
|
||||
|
||||
- `PullRequestSection` is the only true live PR watcher
|
||||
- `SessionSidebar` may do one-shot bootstrap for expanded visible project/worktree groups if PR info is missing
|
||||
- no live PR work for header
|
||||
- no background PR sweeps outside visible demand
|
||||
|
||||
## Known Intentional Fallbacks
|
||||
|
||||
There is still one explicit fallback path worth knowing about:
|
||||
|
||||
- `SessionSidebar` may call `checkIsGitRepository(...)` during initial worktree/project discovery when store state is not populated yet
|
||||
|
||||
This is currently acceptable as a narrow bootstrap fallback.
|
||||
|
||||
Do not widen it into a polling or broad refresh system.
|
||||
|
||||
## When Editing These Stores
|
||||
|
||||
Before changing store shape or selectors, ask:
|
||||
|
||||
1. Is this keyed by the right identity (directory, branch, session, root)?
|
||||
2. Will this force unrelated consumers to rerender?
|
||||
3. Should this be visible-demand-driven instead of background-driven?
|
||||
4. Is there already a store cache for this data?
|
||||
5. Am I duplicating fetch ownership in a component when it should live in a store action?
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
After meaningful Git/PR store changes, verify manually:
|
||||
|
||||
1. Idle desktop app stays quiet on draft/chat screen.
|
||||
2. Git view still loads status, branches, log, identity.
|
||||
3. Diff view still opens the correct file and stays in sync.
|
||||
4. Worktree sessions still show branch labels in header.
|
||||
5. Expanded sidebar projects/worktrees can show PR state without requiring prior selection.
|
||||
6. Hidden surfaces do not reintroduce live background work.
|
||||
Reference in New Issue
Block a user