diff --git a/apps/app/package.json b/apps/app/package.json index 2838d2a8d..84c1e75dc 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -18,6 +18,7 @@ "test:events": "node scripts/events.mjs", "test:todos": "node scripts/todos.mjs", "test:permissions": "node scripts/permissions.mjs", + "test:workspace-path": "bun scripts/workspace-path.ts", "test:session-scope": "bun scripts/session-scope.ts", "test:session-switch": "node scripts/session-switch.mjs", "test:fs-engine": "node scripts/fs-engine.mjs", diff --git a/apps/app/scripts/workspace-path.ts b/apps/app/scripts/workspace-path.ts new file mode 100644 index 000000000..59fabc513 --- /dev/null +++ b/apps/app/scripts/workspace-path.ts @@ -0,0 +1,117 @@ +import assert from "node:assert/strict"; + +import { resolveCreatedLocalWorkspacePath } from "../src/app/lib/workspace-path.ts"; + +const winRaw = "C:/Users/Test/OpenWork/starter"; +const winCanonical = String.raw`C:\Users\Test\OpenWork\starter`; + +const results = { + ok: true, + steps: [] as Array>, +}; + +async function step(name: string, fn: () => void | Promise) { + results.steps.push({ name, status: "running" }); + const index = results.steps.length - 1; + + try { + await fn(); + results.steps[index] = { name, status: "ok" }; + } catch (error) { + results.ok = false; + results.steps[index] = { + name, + status: "error", + error: error instanceof Error ? error.message : String(error), + }; + throw error; + } +} + +try { + await step("prefers persisted local workspace path over raw picker path", () => { + assert.equal( + resolveCreatedLocalWorkspacePath({ + workspaceId: "ws_test", + fallbackPath: winRaw, + workspaces: [ + { + id: "ws_test", + name: "starter", + path: winCanonical, + preset: "starter", + workspaceType: "local", + remoteType: null, + baseUrl: null, + directory: null, + displayName: null, + openworkHostUrl: null, + openworkToken: null, + openworkWorkspaceId: null, + openworkWorkspaceName: null, + sandboxBackend: null, + sandboxRunId: null, + sandboxContainerName: null, + }, + ], + }), + winCanonical, + ); + }); + + await step("falls back to the original path when no workspace match exists", () => { + assert.equal( + resolveCreatedLocalWorkspacePath({ + workspaceId: "ws_missing", + fallbackPath: winRaw, + workspaces: [], + }), + winRaw, + ); + }); + + await step("ignores remote workspace rows when resolving a local workspace path", () => { + assert.equal( + resolveCreatedLocalWorkspacePath({ + workspaceId: "ws_test", + fallbackPath: winRaw, + workspaces: [ + { + id: "ws_test", + name: "starter", + path: "", + preset: "remote", + workspaceType: "remote", + remoteType: "openwork", + baseUrl: "https://example.com", + directory: winCanonical, + displayName: null, + openworkHostUrl: "https://example.com", + openworkToken: null, + openworkWorkspaceId: "ow_ws_test", + openworkWorkspaceName: "starter", + sandboxBackend: null, + sandboxRunId: null, + sandboxContainerName: null, + }, + ], + }), + winRaw, + ); + }); + + console.log(JSON.stringify(results, null, 2)); +} catch (error) { + results.ok = false; + console.error( + JSON.stringify( + { + ...results, + error: error instanceof Error ? error.message : String(error), + }, + null, + 2, + ), + ); + process.exitCode = 1; +} diff --git a/apps/app/src/app/context/workspace.ts b/apps/app/src/app/context/workspace.ts index aeebde2d3..61dab135d 100644 --- a/apps/app/src/app/context/workspace.ts +++ b/apps/app/src/app/context/workspace.ts @@ -71,6 +71,7 @@ import type { OpencodeConnectStatus, ProviderListItem } from "../types"; import { t, currentLocale } from "../../i18n"; import { filterProviderList, mapConfigProvidersToList } from "../utils/providers"; import { buildDefaultWorkspaceBlueprint, normalizeWorkspaceOpenworkConfig } from "../lib/workspace-blueprints"; +import { resolveCreatedLocalWorkspacePath } from "../lib/workspace-path"; import type { OpenworkServerStore } from "../connections/openwork-server-store"; export type WorkspaceStore = ReturnType; @@ -2185,6 +2186,11 @@ export function createWorkspaceStore(options: { } const nextSelectedId = createdWorkspaceId; + const createdWorkspacePath = resolveCreatedLocalWorkspacePath({ + workspaceId: nextSelectedId, + workspaces: ws.workspaces, + fallbackPath: resolvedFolder, + }); applyServerLocalWorkspaces(ws.workspaces, nextSelectedId); if (nextSelectedId) { const nextSelectedWorkspace = ws.workspaces.find((workspace) => workspace.id === nextSelectedId) ?? null; @@ -2200,15 +2206,15 @@ export function createWorkspaceStore(options: { setCreateWorkspaceOpen(false); - const opened = await activateFreshLocalWorkspace(nextSelectedId || null, resolvedFolder); + const opened = await activateFreshLocalWorkspace(nextSelectedId || null, createdWorkspacePath); if (!opened) { options.setPendingInitialSessionSelection?.(null); return false; } if (preset === "starter") { - const materialized = await materializeStarterSessions(resolvedFolder, name, preset); - const sessionsReady = await waitForWorkspaceSessionsReady(resolvedFolder); + const materialized = await materializeStarterSessions(createdWorkspacePath, name, preset); + const sessionsReady = await waitForWorkspaceSessionsReady(createdWorkspacePath); if (!sessionsReady) { throw new Error("Starter sessions did not finish loading for the new workspace."); } @@ -2225,7 +2231,7 @@ export function createWorkspaceStore(options: { } if (!nextSelectedId) { - await openEmptySession(resolvedFolder); + await openEmptySession(createdWorkspacePath); } return true; @@ -3270,11 +3276,16 @@ export function createWorkspaceStore(options: { setWorkspaces(ws.workspaces); const nextSelectedId = pickSelectedWorkspaceId(ws.workspaces, [resolveWorkspaceListSelectedId(ws)], ws); + const createdWorkspacePath = resolveCreatedLocalWorkspacePath({ + workspaceId: nextSelectedId, + workspaces: ws.workspaces, + fallbackPath: resolvedFolder, + }); syncSelectedWorkspaceId(nextSelectedId); setCreateWorkspaceOpen(false); setCreateRemoteWorkspaceOpen(false); - const opened = await activateFreshLocalWorkspace(nextSelectedId || null, resolvedFolder); + const opened = await activateFreshLocalWorkspace(nextSelectedId || null, createdWorkspacePath); if (!opened) { return; } diff --git a/apps/app/src/app/lib/workspace-path.ts b/apps/app/src/app/lib/workspace-path.ts new file mode 100644 index 000000000..10019ddce --- /dev/null +++ b/apps/app/src/app/lib/workspace-path.ts @@ -0,0 +1,15 @@ +import type { WorkspaceInfo } from "./tauri"; + +export function resolveCreatedLocalWorkspacePath(input: { + workspaceId?: string | null; + workspaces: WorkspaceInfo[]; + fallbackPath?: string | null; +}) { + const fallbackPath = input.fallbackPath?.trim() ?? ""; + const workspaceId = input.workspaceId?.trim() ?? ""; + if (!workspaceId) return fallbackPath; + + const created = input.workspaces.find((workspace) => workspace.id === workspaceId && workspace.workspaceType === "local"); + const path = created?.path?.trim() ?? ""; + return path || fallbackPath; +}