diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 5199e5a26be..48fd53c2074 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -90,6 +90,25 @@ export function pruneSessionKeys(input: { .slice(input.max) } +type RootResult = { + root: string + project?: { + id?: string + worktree?: string + vcs?: string + sandboxes?: string[] + } +} + +const norm = (dir: string) => { + const drive = dir.match(/^([A-Za-z]:)[\\/]+$/) + if (drive) return `${drive[1].toLowerCase()}/` + if (/^[\\/]+$/.test(dir)) return "/" + const key = dir.replace(/[\\/]+$/, "").replaceAll("\\", "/") + if (/^[A-Za-z]:\//.test(key) || key.startsWith("//")) return key.toLowerCase() + return key +} + function nextSessionTabsForOpen(current: SessionTabs | undefined, tab: string): SessionTabs { const all = current?.all ?? [] if (tab === "review") return { all: all.filter((x) => x !== "review"), active: tab } @@ -457,9 +476,30 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( return directory } + const resolveRoot = (directory: string): Promise => { + return globalSdk + .createClient({ directory, throwOnError: true }) + .project.current() + .then((x) => { + const project = x.data + return { + root: project?.worktree && project.id !== "global" ? project.worktree : directory, + project: project + ? { + id: project.id, + worktree: project.worktree, + vcs: project.vcs, + sandboxes: project.sandboxes, + } + : undefined, + } + }) + .catch(() => ({ root: directory })) + } + createEffect(() => { const projects = server.projects.list() - const seen = new Set(projects.map((project) => project.worktree)) + const seen = new Set(projects.map((project) => norm(project.worktree))) batch(() => { for (const project of projects) { @@ -468,9 +508,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( server.projects.close(project.worktree) - if (!seen.has(root)) { + const key = norm(root) + if (!seen.has(key)) { server.projects.open(root) - seen.add(root) + seen.add(key) } if (project.expanded) server.projects.expand(root) @@ -565,11 +606,18 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, projects: { list, - open(directory: string) { - const root = rootFor(directory) - if (server.projects.list().find((x) => x.worktree === root)) return + resolve(directory: string) { + return resolveRoot(directory) + }, + async open(directory: string, resolved?: RootResult) { + const value = resolved ?? (await resolveRoot(directory)) + const root = value.root + const key = norm(root) + const exists = server.projects.list().some((x) => norm(x.worktree) === key) + if (exists) return value globalSync.project.loadSessions(root) server.projects.open(root) + return value }, close(directory: string) { server.projects.close(directory) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 052a03c5491..50e857f1c0b 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1262,20 +1262,28 @@ export default function Layout(props: ParentProps) { navigateWithSidebarReset(`/${base64Encode(session.directory)}/session/${session.id}`) } - function openProject(directory: string, navigate = true) { - layout.projects.open(directory) - if (navigate) navigateToProject(directory) + async function openProject(directory: string, navigate = true) { + const match = layout.projects.list().find((item) => workspaceKey(item.worktree) === workspaceKey(directory)) + if (match) { + if (navigate) await navigateToProject(match.worktree) + return match.worktree + } + + const { root, project } = await layout.projects.resolve(directory) + await layout.projects.open(directory, { root, project }) + if (navigate) await navigateToProject(root) + return root } const handleDeepLinks = (urls: string[]) => { if (!server.isLocal()) return for (const directory of collectOpenProjectDeepLinks(urls)) { - openProject(directory) + void openProject(directory) } for (const link of collectNewSessionDeepLinks(urls)) { - openProject(link.directory, false) + void openProject(link.directory, false) const slug = base64Encode(link.directory) if (link.prompt) { setSessionHandoff(slug, { prompt: link.prompt }) @@ -1355,14 +1363,13 @@ export default function Layout(props: ParentProps) { const showEditProjectDialog = (project: LocalProject) => dialog.show(() => ) async function chooseProject() { - function resolve(result: string | string[] | null) { + async function resolve(result: string | string[] | null) { if (Array.isArray(result)) { - for (const directory of result) { - openProject(directory, false) - } - navigateToProject(result[0]) + const roots = await Promise.all(result.map((directory) => openProject(directory, false))) + if (!roots[0]) return + await navigateToProject(roots[0]) } else if (result) { - openProject(result) + await openProject(result) } } @@ -1371,11 +1378,11 @@ export default function Layout(props: ParentProps) { title: language.t("command.project.open"), multiple: true, }) - resolve(result) + void resolve(result) } else { dialog.show( - () => , - () => resolve(null), + () => void resolve(result)} />, + () => void resolve(null), ) } }