diff --git a/packages/ui/src/lib/gitApiHttp.ts b/packages/ui/src/lib/gitApiHttp.ts index b771feb0..d9908dfc 100644 --- a/packages/ui/src/lib/gitApiHttp.ts +++ b/packages/ui/src/lib/gitApiHttp.ts @@ -35,6 +35,7 @@ import type { RevertCommitResponse, ResetToCommitResponse, } from './api/types'; +import { normalizePath } from './pathNormalization'; import { runtimeFetch } from './runtime-fetch'; import { getRuntimeUrlResolver } from './runtime-url'; import { getRuntimeKey } from './runtime-switch'; @@ -146,16 +147,18 @@ export async function listGitDirectories(root: string): Promise { if (!response.ok) { throw new Error(`Failed to list git directories: ${response.statusText}`); } - const data = await response.json(); - if (!data || !Array.isArray(data.repositories)) { + // SAFETY: the route is ours (`GET /api/fs/git-dirs`) and answers this exact + // shape on every 2xx; a malformed body fails the array check below. + const data = await response.json() as { repositories?: Array<{ path?: string | null }> }; + if (!Array.isArray(data?.repositories)) { throw new Error('Unexpected git directories response'); } + // The server joins paths with the platform separator; every other git + // directory key in the UI is normalized, so match that here or a Windows + // repository never equals its own selection or root prefix. return data.repositories - .map((entry: unknown) => { - const path = entry && typeof entry === 'object' && 'path' in entry ? (entry as { path?: unknown }).path : undefined; - return typeof path === 'string' && path.trim() ? path.trim() : null; - }) - .filter((path: string | null): path is string => path !== null); + .map((entry) => normalizePath(entry?.path ?? null)) + .filter((path): path is string => path !== null); } export async function getGitStatus(directory: string, options?: { mode?: 'light' }): Promise { diff --git a/packages/ui/src/stores/DOCUMENTATION.md b/packages/ui/src/stores/DOCUMENTATION.md index fdc00ab4..2fbce401 100644 --- a/packages/ui/src/stores/DOCUMENTATION.md +++ b/packages/ui/src/stores/DOCUMENTATION.md @@ -149,6 +149,7 @@ Important properties: - `ensureStatus()` and `ensureAll()` are the preferred entry points for consumers - in-flight dedupe exists for status and `ensureAll()`; status dedupe is scoped to the per-directory status mutation revision, so a refresh requested after a mutation never joins a pre-mutation in-flight request - nested repository discovery (`nestedReposByRoot`, `nestedRepoSelection`, `ensureNestedRepos`) is per-root state for roots that are not themselves git repositories; discovery failure is a `null` marker (never a valid empty result), a runtime without the discovery route (VS Code) commits an `'unsupported'` marker, and an in-flight discovery whose runtime switched is discarded at commit time instead of repopulating the cleared map. Selections are persisted per runtime + root, and `useEffectiveGitDirectory(root)` resolves the directory git surfaces operate on (`root` when the root is a repository, the selected nested repository otherwise). A selection whose repository fails its probe is dropped and remembered session-only (`staleClearedSelections`) so auto-select does not re-pick it and loop walk+probe; manual picker picks bypass the memory. `hooks/useNestedGitDirectory.ts` owns the resolution flow (root probe, discovery, auto-select, stale-selection recovery) for every consuming surface (Git tab, diff view, pull-request view, walkthrough view, mobile changes), and `git/NestedRepoResolutionStates.tsx` renders the shared pending/failed/unsupported/empty states +- worktree bootstrap polling and session/worktree machinery stay keyed on the project root even while a nested repository is selected; only git data and actions follow the selection - runtime reset replaces all live entries with that runtime's persisted branch seeds and invalidates old completions - status, branches, log, identity, repository probes, and prefetch diffs commit through runtime and per-channel generations - status mutations advance a revision so older refreshes cannot undo optimistic or confirmed index changes