Fix WSL preflight context snapshot stability (#2749)

This commit is contained in:
Neil
2026-05-24 13:03:41 -07:00
committed by GitHub
parent 3d85148e95
commit 05af0e92ef
2 changed files with 105 additions and 1 deletions
@@ -0,0 +1,89 @@
import { describe, expect, it } from 'vitest'
import type { AppState } from '@/store/types'
import {
getLocalPreflightContext,
getWslDistroFromPath,
localPreflightContextKey
} from './local-preflight-context'
function makeState(args: { repoPath?: string | null; worktreePath?: string | null }): AppState {
const repoId = 'repo-1'
const worktreeId = `${repoId}::worktree-1`
return {
activeRepoId: repoId,
activeWorktreeId: args.worktreePath === undefined ? null : worktreeId,
repos:
args.repoPath === undefined
? []
: [
{
id: repoId,
path: args.repoPath
}
],
worktreesByRepo:
args.worktreePath === undefined
? {}
: {
[repoId]: [
{
id: worktreeId,
repoId,
path: args.worktreePath
}
]
}
} as AppState
}
describe('local preflight context', () => {
it('extracts WSL distro names from supported UNC forms', () => {
expect(getWslDistroFromPath(String.raw`\\wsl.localhost\Ubuntu\home\alice\repo`)).toBe('Ubuntu')
expect(getWslDistroFromPath(String.raw`\\wsl$\Debian\home\alice\repo`)).toBe('Debian')
expect(getWslDistroFromPath('/Users/alice/repo')).toBeNull()
})
it('returns a stable snapshot for repeated WSL selector reads', () => {
const state = makeState({
worktreePath: String.raw`\\wsl.localhost\Ubuntu\home\alice\repo`
})
const first = getLocalPreflightContext(state)
const second = getLocalPreflightContext(state)
expect(first).toBe(second)
expect(first).toEqual({ wslDistro: 'Ubuntu' })
expect(localPreflightContextKey(first)).toBe('wsl:Ubuntu')
})
it('reuses the same WSL snapshot across equivalent active repo and worktree paths', () => {
const fromRepo = getLocalPreflightContext(
makeState({
repoPath: String.raw`\\wsl.localhost\Ubuntu\home\alice\repo`
})
)
const fromWorktree = getLocalPreflightContext(
makeState({
repoPath: '/Users/alice/repo',
worktreePath: String.raw`\\wsl.localhost\Ubuntu\home\alice\repo`
})
)
const fromOtherDistro = getLocalPreflightContext(
makeState({
worktreePath: String.raw`\\wsl.localhost\Debian\home\alice\repo`
})
)
expect(fromRepo).toBe(fromWorktree)
expect(fromOtherDistro).not.toBe(fromRepo)
expect(fromOtherDistro).toEqual({ wslDistro: 'Debian' })
})
it('uses the stable host context for non-WSL paths', () => {
const state = makeState({ repoPath: '/Users/alice/repo' })
expect(getLocalPreflightContext(state)).toBeUndefined()
expect(getLocalPreflightContext(state)).toBeUndefined()
expect(localPreflightContextKey(getLocalPreflightContext(state))).toBe('host')
})
})
@@ -3,10 +3,25 @@ import { parseWslUncPath } from '../../../shared/wsl-paths'
export type LocalPreflightContext = { wslDistro?: string | null } | undefined
const wslPreflightContextsByDistro = new Map<string, NonNullable<LocalPreflightContext>>()
export function getWslDistroFromPath(path?: string | null): string | null {
return path ? (parseWslUncPath(path)?.distro ?? null) : null
}
function getWslPreflightContext(wslDistro: string): NonNullable<LocalPreflightContext> {
const cached = wslPreflightContextsByDistro.get(wslDistro)
if (cached) {
return cached
}
// Why: React/Zustand selectors must return a cached snapshot. A fresh object
// here triggers a useSyncExternalStore loop when Settings observes WSL repos.
const context = Object.freeze({ wslDistro })
wslPreflightContextsByDistro.set(wslDistro, context)
return context
}
export function getLocalPreflightContext(state: AppState): LocalPreflightContext {
const activeWorktree = state.activeWorktreeId
? Object.values(state.worktreesByRepo ?? {})
@@ -16,7 +31,7 @@ export function getLocalPreflightContext(state: AppState): LocalPreflightContext
const activePath =
activeWorktree?.path ?? (state.repos ?? []).find((repo) => repo.id === state.activeRepoId)?.path
const wslDistro = getWslDistroFromPath(activePath)
return wslDistro ? { wslDistro } : undefined
return wslDistro ? getWslPreflightContext(wslDistro) : undefined
}
export function localPreflightContextKey(context: LocalPreflightContext): string {