import { normalizePath } from '@renderer/utils/pathNormalize'; import { isEphemeralProjectPath } from '@shared/utils/ephemeralProjectPath'; import type { DashboardRecentProjectFilesystemState, DashboardRecentProjectSource, } from '@features/recent-projects/contracts'; import type { ComboboxOption } from '@renderer/components/ui/combobox'; import type { Project } from '@shared/types'; export interface ProjectPathProject extends Project { discoverySource?: DashboardRecentProjectSource; filesystemState?: DashboardRecentProjectFilesystemState; } export interface ProjectPathOptionMeta extends Record { discoverySource?: DashboardRecentProjectSource; filesystemState?: DashboardRecentProjectFilesystemState; } function toProjectOption(project: ProjectPathProject): ComboboxOption { const option: ComboboxOption = { value: project.path, label: project.name, description: project.path, }; if (project.filesystemState === 'deleted') { option.disabled = true; } if (project.discoverySource !== undefined || project.filesystemState !== undefined) { const meta: ProjectPathOptionMeta = {}; if (project.discoverySource !== undefined) { meta.discoverySource = project.discoverySource; } if (project.filesystemState !== undefined) { meta.filesystemState = project.filesystemState; } option.meta = meta; } return option; } export function isSelectableProjectPathProject( project: Pick ): boolean { return !isEphemeralProjectPath(project.path) && project.filesystemState !== 'deleted'; } export function findProjectPathProjectByPath( projects: readonly ProjectPathProject[], projectPath: string ): ProjectPathProject | undefined { const normalizedPath = normalizePath(projectPath); return projects.find((project) => normalizePath(project.path) === normalizedPath); } export function isDeletedProjectPathSelection( projects: readonly ProjectPathProject[], projectPath: string ): boolean { return findProjectPathProjectByPath(projects, projectPath)?.filesystemState === 'deleted'; } /** * Collapse duplicate project entries that resolve to the same filesystem path. * This keeps combobox item values unique even when scanner sources overlap. */ export function buildProjectPathOptions( projects: ProjectPathProject[], preferredPath?: string ): ComboboxOption[] { const options: ComboboxOption[] = []; const optionIndexByNormalizedPath = new Map(); const normalizedPreferredPath = preferredPath ? normalizePath(preferredPath) : null; for (const project of projects) { if (isEphemeralProjectPath(project.path)) { continue; } const normalizedProjectPath = normalizePath(project.path); const existingIndex = optionIndexByNormalizedPath.get(normalizedProjectPath); if (existingIndex === undefined) { optionIndexByNormalizedPath.set(normalizedProjectPath, options.length); options.push(toProjectOption(project)); continue; } const shouldPreferCurrentOption = normalizedPreferredPath === normalizedProjectPath && project.path === preferredPath; if (shouldPreferCurrentOption) { options[existingIndex] = toProjectOption(project); } } return options; }