diff --git a/src/renderer/src/components/task-page-initial-selection-scaling.test.tsx b/src/renderer/src/components/task-page-initial-selection-scaling.test.tsx new file mode 100644 index 00000000000..84b8cb044e1 --- /dev/null +++ b/src/renderer/src/components/task-page-initial-selection-scaling.test.tsx @@ -0,0 +1,88 @@ +import { describe, expect, it } from 'vitest' +import { renderToString } from 'react-dom/server' +import type { Repo } from '../../../shared/repo-types' +import type { TaskPageStoreBindingsModel } from './use-task-page-store-bindings' +import { useTaskPageRepoSelection } from './use-task-page-repo-selection' + +function resolveSelection(repos: Repo[], persisted: string[], preferred?: string) { + let selected: readonly string[] = [] + function Probe() { + const model = { + repos, + settings: { defaultRepoSelection: persisted }, + pageData: { preselectedRepoId: preferred }, + linearStatus: {}, + jiraStatus: {}, + preflightStatus: null + } as unknown as TaskPageStoreBindingsModel + useTaskPageRepoSelection(model) + selected = [ + ...(model as TaskPageStoreBindingsModel & { resolvedInitialSelection: ReadonlySet }) + .resolvedInitialSelection + ] + return null + } + renderToString() + return selected +} + +describe('task page initial repo selection', () => { + it('normalizes persisted selections without a repository scan for every stored ID', () => { + let reads = 0 + const repos: Repo[] = Array.from({ length: 1000 }, (_, index) => ({ + get id() { + reads++ + return `repo-${index}` + }, + path: `/repos/${index}`, + displayName: `Repo ${index}`, + badgeColor: '', + addedAt: index, + kind: 'git' + })) + const persisted = Array.from({ length: 1000 }, (_, index) => `repo-${index}`) + expect(resolveSelection(repos, persisted)).toEqual(persisted) + expect(reads).toBeLessThan(40_000) + }) + + it('retains preferred selection, missing-ID fallback, and explicit empty defaults', () => { + const repos: Repo[] = ['a', 'b'].map((id) => ({ + id, + path: `/repos/${id}`, + displayName: id, + badgeColor: '', + addedAt: 0, + kind: 'git' + })) + expect(resolveSelection(repos, ['b'], 'a')).toEqual(['a']) + expect(resolveSelection(repos, ['missing'])).toEqual(['a', 'b']) + expect(resolveSelection(repos, [])).toEqual(['a', 'b']) + expect(resolveSelection(repos, ['missing', 'b', 'b'])).toEqual(['b']) + }) + + it('ignores persisted IDs for ineligible repos, including folder workspaces', () => { + const repos: Repo[] = [ + { + id: 'tracked', + path: '/repos/tracked', + displayName: 'tracked', + badgeColor: '', + addedAt: 0, + kind: 'git' + }, + { + id: 'folder', + path: '/repos/folder', + displayName: 'folder', + badgeColor: '', + addedAt: 1, + kind: 'folder' + } + ] + + // A folder workspace is never task-eligible, so a stored selection naming only one must fall + // through to the automatic default rather than rendering an empty picker. + expect(resolveSelection(repos, ['folder'])).toEqual(['tracked']) + expect(resolveSelection(repos, ['folder', 'tracked'])).toEqual(['tracked']) + }) +}) diff --git a/src/renderer/src/components/use-task-page-repo-selection.ts b/src/renderer/src/components/use-task-page-repo-selection.ts index 194f0db8d93..c3895139746 100644 --- a/src/renderer/src/components/use-task-page-repo-selection.ts +++ b/src/renderer/src/components/use-task-page-repo-selection.ts @@ -51,11 +51,7 @@ export function useTaskPageRepoSelection(model: TaskPageStoreBindingsModel) { } const persisted = settings?.defaultRepoSelection if (Array.isArray(persisted)) { - const filtered = persisted.filter((id) => eligibleRepos.some((r) => r.id === id)) - if (filtered.length > 0) { - return normalizeTaskRepoSelection(eligibleRepos, new Set(filtered)) - } - // Why: empty after filtering (all persisted repos removed) falls through to the automatic default so the page never renders an empty selection. + return normalizeTaskRepoSelection(eligibleRepos, new Set(persisted)) } return getDefaultTaskRepoSelection(eligibleRepos) }, [eligibleRepos, pageData.preselectedRepoId, settings?.defaultRepoSelection])