From 35b2c399dc6c37d0b0eb5d960aa2b933a2af8f7d Mon Sep 17 00:00:00 2001 From: natewill <50088025+natewill@users.noreply.github.com> Date: Sun, 8 Mar 2026 23:24:46 -0400 Subject: [PATCH 1/4] fix(app): auto-enable workspaces when opening sandbox --- packages/app/src/pages/layout.tsx | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 9c359aafbdae..2f1c0e729f75 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1238,7 +1238,49 @@ export default function Layout(props: ParentProps) { navigateWithSidebarReset(`/${base64Encode(session.directory)}/session/${session.id}`) } + function autoEnableWorkspacesIfSandbox(directory: string) { + const dir = workspaceKey(directory) + const project = layout.projects + .list() + .find( + (item) => workspaceKey(item.worktree) === dir || item.sandboxes?.some((entry) => workspaceKey(entry) === dir), + ) + + if (project?.vcs === "git") { + const root = workspaceKey(project.worktree) + const sandbox = project.sandboxes?.some((item) => workspaceKey(item) === dir) + if (root === dir) return + if (sandbox) { + const enabled = layout.sidebar.workspaces(project.worktree)() + if (!enabled) { + layout.sidebar.setWorkspaces(project.worktree, true) + return + } + return + } + } + + if (project && project.vcs && project.vcs !== "git") return + + void globalSDK + .createClient({ directory, throwOnError: true }) + .project.current() + .then((x) => x.data) + .then((item) => { + if (!item) return + if (item.vcs !== "git") return + const root = workspaceKey(item.worktree) + const sandbox = item.sandboxes?.some((entry) => workspaceKey(entry) === dir) + if (root === dir) return + if (!sandbox) return + if (layout.sidebar.workspaces(item.worktree)()) return + layout.sidebar.setWorkspaces(item.worktree, true) + }) + .catch(() => undefined) + } + function openProject(directory: string, navigate = true) { + autoEnableWorkspacesIfSandbox(directory) layout.projects.open(directory) if (navigate) navigateToProject(directory) } From c176a63eb2ee3d75b4e619191fb1a1753a3d918f Mon Sep 17 00:00:00 2001 From: natewill <50088025+natewill@users.noreply.github.com> Date: Sun, 8 Mar 2026 23:47:38 -0400 Subject: [PATCH 2/4] refactor(app): use project resolve for sandbox auto-enable --- packages/app/src/context/layout.tsx | 34 +++++++++++++++++ packages/app/src/pages/layout.tsx | 57 +++++++++-------------------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 5199e5a26be4..d3c474429679 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -90,6 +90,16 @@ export function pruneSessionKeys(input: { .slice(input.max) } +type RootResult = { + root: string + project?: { + id?: string + worktree?: string + vcs?: string + sandboxes?: string[] + } +} + 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,6 +467,27 @@ 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)) @@ -565,6 +596,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, projects: { list, + resolve(directory: string) { + return resolveRoot(directory) + }, open(directory: string) { const root = rootFor(directory) if (server.projects.list().find((x) => x.worktree === root)) return diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 2f1c0e729f75..0de4371afdec 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1238,49 +1238,28 @@ export default function Layout(props: ParentProps) { navigateWithSidebarReset(`/${base64Encode(session.directory)}/session/${session.id}`) } - function autoEnableWorkspacesIfSandbox(directory: string) { + function autoEnableWorkspacesIfSandbox( + directory: string, + root: string, + project?: { vcs?: string; sandboxes?: string[] }, + ) { + if (project?.vcs !== "git") return const dir = workspaceKey(directory) - const project = layout.projects - .list() - .find( - (item) => workspaceKey(item.worktree) === dir || item.sandboxes?.some((entry) => workspaceKey(entry) === dir), - ) - - if (project?.vcs === "git") { - const root = workspaceKey(project.worktree) - const sandbox = project.sandboxes?.some((item) => workspaceKey(item) === dir) - if (root === dir) return - if (sandbox) { - const enabled = layout.sidebar.workspaces(project.worktree)() - if (!enabled) { - layout.sidebar.setWorkspaces(project.worktree, true) - return - } - return - } - } - - if (project && project.vcs && project.vcs !== "git") return - - void globalSDK - .createClient({ directory, throwOnError: true }) - .project.current() - .then((x) => x.data) - .then((item) => { - if (!item) return - if (item.vcs !== "git") return - const root = workspaceKey(item.worktree) - const sandbox = item.sandboxes?.some((entry) => workspaceKey(entry) === dir) - if (root === dir) return - if (!sandbox) return - if (layout.sidebar.workspaces(item.worktree)()) return - layout.sidebar.setWorkspaces(item.worktree, true) - }) - .catch(() => undefined) + const key = workspaceKey(root) + if (dir === key) return + const sandbox = project.sandboxes?.some((item) => workspaceKey(item) === dir) + if (!sandbox) return + if (layout.sidebar.workspaces(root)()) return + layout.sidebar.setWorkspaces(root, true) } function openProject(directory: string, navigate = true) { - autoEnableWorkspacesIfSandbox(directory) + void layout.projects + .resolve(directory) + .then((result) => { + autoEnableWorkspacesIfSandbox(directory, result.root, result.project) + }) + .catch(() => undefined) layout.projects.open(directory) if (navigate) navigateToProject(directory) } From fa625ab6e0938a448a43fd53d065042bd6d700a0 Mon Sep 17 00:00:00 2001 From: natewill <50088025+natewill@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:55:10 -0400 Subject: [PATCH 3/4] fix(app): normalize project root comparisons for dedupe --- packages/app/src/context/layout.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index d3c474429679..4cf5233d7169 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -100,6 +100,15 @@ type RootResult = { } } +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 } @@ -490,7 +499,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( 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) { @@ -499,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) @@ -601,7 +611,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( }, open(directory: string) { const root = rootFor(directory) - if (server.projects.list().find((x) => x.worktree === root)) return + const key = norm(root) + const exists = server.projects.list().some((x) => norm(x.worktree) === key) + if (exists) return globalSync.project.loadSessions(root) server.projects.open(root) }, From 2849df0fab35966750236d82e01a2a4fd565b314 Mon Sep 17 00:00:00 2001 From: natewill <50088025+natewill@users.noreply.github.com> Date: Mon, 9 Mar 2026 01:11:41 -0400 Subject: [PATCH 4/4] refactor(app): align open-project flow across PR branches --- packages/app/src/context/layout.tsx | 8 +++--- packages/app/src/pages/layout.tsx | 42 +++++++++++++++-------------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 4cf5233d7169..48fd53c20741 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -609,13 +609,15 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( resolve(directory: string) { return resolveRoot(directory) }, - open(directory: string) { - const root = rootFor(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 + 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 0de4371afdec..cd679740fc81 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1253,26 +1253,29 @@ export default function Layout(props: ParentProps) { layout.sidebar.setWorkspaces(root, true) } - function openProject(directory: string, navigate = true) { - void layout.projects - .resolve(directory) - .then((result) => { - autoEnableWorkspacesIfSandbox(directory, result.root, result.project) - }) - .catch(() => undefined) - 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) + autoEnableWorkspacesIfSandbox(directory, root, project) + 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 }) @@ -1352,14 +1355,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) } } @@ -1368,11 +1370,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), ) } }