From 6aaaa4c807bf4f9c7f56852427dc7278243c2a46 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 25 Mar 2026 14:46:10 -0400 Subject: [PATCH] fix(app): normalize workspace keys for session restore --- packages/app/src/context/local.tsx | 5 +++-- packages/app/src/pages/layout/helpers.ts | 10 ++-------- packages/app/src/utils/persist.test.ts | 7 +++++++ packages/app/src/utils/persist.ts | 6 ++++-- packages/app/src/utils/workspace.ts | 7 +++++++ 5 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 packages/app/src/utils/workspace.ts diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx index 76d337c82b..715c84daf8 100644 --- a/packages/app/src/context/local.tsx +++ b/packages/app/src/context/local.tsx @@ -7,6 +7,7 @@ import { useModels } from "@/context/models" import { useProviders } from "@/hooks/use-providers" import { modelEnabled, modelProbe } from "@/testing/model-selection" import { Persist, persisted } from "@/utils/persist" +import { workspaceKey } from "@/utils/workspace" import { cycleModelVariant, getConfiguredAgentVariant, resolveModelVariant } from "./model-variant" import { useSDK } from "./sdk" import { useSync } from "./sync" @@ -26,7 +27,7 @@ type Saved = { const WORKSPACE_KEY = "__workspace__" const handoff = new Map() -const handoffKey = (dir: string, id: string) => `${dir}\n${id}` +const handoffKey = (dir: string, id: string) => `${workspaceKey(dir)}\n${id}` const migrate = (value: unknown) => { if (!value || typeof value !== "object") return { session: {} } @@ -364,7 +365,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ const next = clone(snapshot()) if (!next) return - if (dir === sdk.directory) { + if (workspaceKey(dir) === workspaceKey(sdk.directory)) { setSaved("session", session, next) setStore("draft", undefined) return diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index 226098c1cd..fc2141c404 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -1,19 +1,13 @@ import { getFilename } from "@opencode-ai/util/path" import { type Session } from "@opencode-ai/sdk/v2/client" +export { workspaceKey } from "@/utils/workspace" +import { workspaceKey } from "@/utils/workspace" type SessionStore = { session?: Session[] path: { directory: string } } -export const workspaceKey = (directory: string) => { - const value = directory.replaceAll("\\", "/") - const drive = value.match(/^([A-Za-z]:)\/+$/) - if (drive) return `${drive[1]}/` - if (/^\/+$/i.test(value)) return "/" - return value.replace(/\/+$/, "") -} - function sortSessions(now: number) { const oneMinuteAgo = now - 60 * 1000 return (a: Session, b: Session) => { diff --git a/packages/app/src/utils/persist.test.ts b/packages/app/src/utils/persist.test.ts index 673acd224d..983a3c135c 100644 --- a/packages/app/src/utils/persist.test.ts +++ b/packages/app/src/utils/persist.test.ts @@ -112,4 +112,11 @@ describe("persist localStorage resilience", () => { expect(result.endsWith(".dat")).toBeTrue() expect(/[:\\/]/.test(result)).toBeFalse() }) + + test("workspace storage treats slash variants as the same workspace", () => { + const a = persistTesting.workspaceStorage("C:\\Users\\foo\\bar\\") + const b = persistTesting.workspaceStorage("C:/Users/foo/bar") + + expect(a).toBe(b) + }) }) diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts index 3dcbeb7d36..0472feb894 100644 --- a/packages/app/src/utils/persist.ts +++ b/packages/app/src/utils/persist.ts @@ -1,4 +1,5 @@ import { Platform, usePlatform } from "@/context/platform" +import { workspaceKey } from "@/utils/workspace" import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage" import { checksum } from "@opencode-ai/util/encode" import { createResource, type Accessor } from "solid-js" @@ -209,8 +210,9 @@ function normalize(defaults: unknown, raw: string, migrate?: (value: unknown) => } function workspaceStorage(dir: string) { - const head = (dir.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-") - const sum = checksum(dir) ?? "0" + const key = workspaceKey(dir) + const head = (key.slice(0, 12) || "workspace").replace(/[^a-zA-Z0-9._-]/g, "-") + const sum = checksum(key) ?? "0" return `opencode.workspace.${head}.${sum}.dat` } diff --git a/packages/app/src/utils/workspace.ts b/packages/app/src/utils/workspace.ts new file mode 100644 index 0000000000..df8a7f5f2a --- /dev/null +++ b/packages/app/src/utils/workspace.ts @@ -0,0 +1,7 @@ +export const workspaceKey = (dir: string) => { + const value = dir.replaceAll("\\", "/") + const drive = value.match(/^([A-Za-z]:)\/+$/) + if (drive) return `${drive[1]}/` + if (/^\/+$/i.test(value)) return "/" + return value.replace(/\/+$/, "") +}