diff --git a/packages/app/src/runtime/server/global-sync/utils.test.ts b/packages/app/src/runtime/server/global-sync/utils.test.ts index 7a29c194019..1c03224a2b8 100644 --- a/packages/app/src/runtime/server/global-sync/utils.test.ts +++ b/packages/app/src/runtime/server/global-sync/utils.test.ts @@ -1,6 +1,12 @@ import { describe, expect, test } from "bun:test" import type { AgentListOutput, ModelListOutput, Project, ProviderListOutput } from "@opencode-ai/client/promise" -import { directoryKey, normalizeAgentList, normalizeProjectInfo, normalizeProviderList } from "./utils" +import { + directoryKey, + normalizeAgentList, + normalizeProjectInfo, + normalizeProviderList, + updateProjectInfo, +} from "./utils" describe("normalizeAgentList", () => { test("adapts current agents to the app agent shape", () => { @@ -110,3 +116,34 @@ describe("directoryKey", () => { expect(String(directoryKey("/"))).toBe("/") }) }) + +describe("updateProjectInfo", () => { + test("applies saved metadata without losing workspace inventory", () => { + const update = { + id: "project", + canonical: "/repo", + name: "Repo", + icon: { color: "purple" }, + time: { created: 1, updated: 2 }, + sandboxes: ["/repo-sandbox"], + } satisfies Project + + expect( + updateProjectInfo( + { + ...update, + name: "Old name", + icon: { color: "gray" }, + worktree: "/old-repo", + worktrees: [{ directory: "/repo", strategy: "git" }], + }, + update, + ), + ).toMatchObject({ + name: "Repo", + icon: { color: "purple" }, + worktree: "/repo", + worktrees: [{ directory: "/repo", strategy: "git" }], + }) + }) +}) diff --git a/packages/app/src/runtime/server/global-sync/utils.ts b/packages/app/src/runtime/server/global-sync/utils.ts index a83ef47e9c9..2f68160fae7 100644 --- a/packages/app/src/runtime/server/global-sync/utils.ts +++ b/packages/app/src/runtime/server/global-sync/utils.ts @@ -137,3 +137,12 @@ export function normalizeProjectInfo(project: Project | CurrentProject): Project worktrees: "worktrees" in project ? project.worktrees : [{ directory: worktree }], } } + +export function updateProjectInfo(project: Project, update: CurrentProject): Project { + return { + ...project, + ...update, + worktree: update.canonical, + worktrees: project.worktrees, + } +} diff --git a/packages/app/src/runtime/server/sync.tsx b/packages/app/src/runtime/server/sync.tsx index d10c4c3658f..4499c79c6e0 100644 --- a/packages/app/src/runtime/server/sync.tsx +++ b/packages/app/src/runtime/server/sync.tsx @@ -11,7 +11,7 @@ import type { ProjectMeta } from "./global-sync/types" import { formatServerError } from "@/runtime/server/errors" import { queryOptions, useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/solid-query" import { createRefreshQueue } from "./global-sync/queue" -import { directoryKey } from "./global-sync/utils" +import { directoryKey, updateProjectInfo } from "./global-sync/utils" import { PathKey } from "@/workspaces/path-key" import type { ServerScope } from "@/runtime/server/scope" import { persisted } from "@/runtime/persistence/storage" @@ -215,8 +215,15 @@ export function createServerSyncContextInner(serverSDK: ServerSDK, data: Data) { return promise } + function applyProjectUpdate(update: Parameters[1]) { + setProjects((projects) => + projects.map((project) => (project.id === update.id ? updateProjectInfo(project, update) : project)), + ) + } + const unsub = serverSDK.event.listen((event) => { connection.handleEvent({ type: event.type }) + if (event.type === "project.updated") applyProjectUpdate(event.data) if (!event.location) { if (event.type === "config.updated" || event.type === "agent.updated" || event.type === "worktree.updated") @@ -243,6 +250,7 @@ export function createServerSyncContextInner(serverSDK: ServerSDK, data: Data) { }) const projectApi = { + update: applyProjectUpdate, meta(directory: string, patch: ProjectMeta) { children.projectMeta(directory, patch) }, diff --git a/packages/app/src/settings/workspaces/project-model.ts b/packages/app/src/settings/workspaces/project-model.ts index 22549861c8b..f63d6ae1c86 100644 --- a/packages/app/src/settings/workspaces/project-model.ts +++ b/packages/app/src/settings/workspaces/project-model.ts @@ -70,12 +70,13 @@ export function createEditProjectModel(props: { project: LocalProject; server: S const start = store.startup.trim() if (props.project.id && props.project.id !== "global") { - await serverCtx().sdk.api.project.update({ + const project = await serverCtx().sdk.api.project.update({ projectID: props.project.id, name, icon: { color: store.color ?? "", override: store.iconOverride ?? "" }, commands: { start }, }) + serverCtx().sync.project.update(project) dialog.close() return }