* perf(git): cache project-root resolution to stop N² polling cascade
Opening a workspace with many projects/worktrees fired hundreds of
`POST /api/fs/exec` requests (e.g. ~700 for 19 projects) within seconds,
dominated by repeated `git rev-parse --absolute-git-dir` /
`--git-common-dir` for the same directories.
Root cause: in `useProjectRepoStatus`, each project's `ensureStatus`
settles independently and mutates the git store, which re-derives
`projectGitBranchesKey` and re-runs `getRootBranch` for *all* projects on
every change. `getRootBranch` had no caching, so this produced an N×N
burst of uncached git plumbing calls.
Changes:
- worktreeStatus: extract `resolveProjectRoot` to module scope with a
60s TTL cache + in-flight dedupe (root resolution is static within a
session). Combine the two `rev-parse` queries into one subprocess.
Add `getRootBranch(dir, { knownBranch })` fast-path that skips a
redundant git status when the directory is its own root, while still
resolving the primary-root branch correctly for linked worktrees.
Export `invalidateResolvedProjectRootCache`.
- useProjectRepoStatus: replace the cascade effect with a debounced,
diff-based pass that only resolves projects that are new or whose
branch actually changed, passing the known branch through.
- worktreeManager: invalidate the root cache on worktree create/remove.
- Add unit tests for caching, dedupe, invalidation, rev-parse
precedence, non-git fallback, linked-worktree resolution and the
knownBranch fast-path.
Reduces startup from hundreds of requests to roughly one root
resolution per project.
* fix(git): clear in-flight resolves and guard write-back on cache invalidation
`invalidateResolvedProjectRootCache` cleared `resolvedRootCache` but left
`inFlightRootResolves` intact, so during a worktree topology change a
resolution already in flight could (1) be handed to callers arriving after
invalidation and (2) re-seed the cache with the pre-invalidation root when it
settled, defeating invalidation for up to the full TTL.
Drop the in-flight entry on invalidation and add an epoch guard so a resolve
that was invalidated mid-flight does not write its now-stale result back.
Add a regression test for the concurrent-invalidation scenario.
* fix(git): bound root cache and avoid early sidebar resolves