diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 829e6a396..0ee412286 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -2441,11 +2441,12 @@ export class Session { const trimmed = customName?.trim() ?? ""; const nextCustomName = trimmed.length === 0 ? null : trimmed; - await this.projectRegistry.upsert({ + const updated = { ...existing, customName: nextCustomName, updatedAt: new Date().toISOString(), - }); + }; + await this.projectRegistry.upsert(updated); this.emit({ type: "project.rename.response", @@ -2458,6 +2459,10 @@ export class Session { }, }); + // Emit a project.update so clients that track the project as an empty + // project (no workspaces yet) receive the resolved name immediately. + this.emitProjectUpdate({ kind: "upsert", project: updated }); + // Re-emit descriptors for every workspace under this project so the new // resolved name lands in the UI immediately. const workspaces = await this.workspaceRegistry.list(); diff --git a/packages/server/src/server/session.workspaces.test.ts b/packages/server/src/server/session.workspaces.test.ts index cd2b94e17..2b38acba4 100644 --- a/packages/server/src/server/session.workspaces.test.ts +++ b/packages/server/src/server/session.workspaces.test.ts @@ -13,6 +13,7 @@ import path from "node:path"; import { afterEach, expect, test, vi } from "vitest"; import { z } from "zod"; +import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities"; import { createTestLogger } from "../test-utils/test-logger.js"; import { Session } from "./session.js"; import type { SessionOptions } from "./session.js"; @@ -157,6 +158,7 @@ interface SessionTestAccess { workspaceIds: Iterable, options?: { skipReconcile?: boolean }, ): Promise; + updateClientCapabilities(capabilities: Record | null): void; emit(message: unknown): void; onMessage(message: unknown): void; paseoHome: string; @@ -7055,6 +7057,42 @@ test("project.rename.request stores customName and emits an updated workspace de }); }); +test("project.rename.request updates a project with no workspaces", async () => { + const emitted: SessionOutboundMessage[] = []; + const session = asTestSession( + createSessionForWorkspaceTests({ onMessage: (message) => emitted.push(message) }), + ); + session.updateClientCapabilities({ [CLIENT_CAPS.projectUpdates]: true }); + + const project = createPersistedProjectRecord({ + projectId: "prj_empty", + rootPath: REPO_CWD, + kind: "non_git", + displayName: "repo", + createdAt: "2026-07-20T12:00:00.000Z", + updatedAt: "2026-07-20T12:00:00.000Z", + }); + session.projectRegistry.get = async () => project; + session.projectRegistry.upsert = async () => project; + session.workspaceRegistry.list = async () => []; + + await session.handleMessage({ + type: "project.rename.request", + projectId: project.projectId, + customName: "Renamed empty project", + requestId: "req-rename-empty", + }); + + expect(findByType(emitted, "project.update")?.payload).toMatchObject({ + kind: "upsert", + project: { + projectId: project.projectId, + projectDisplayName: "Renamed empty project", + projectCustomName: "Renamed empty project", + }, + }); +}); + test("project.rename.request with whitespace-only customName clears the override", async () => { const emitted: SessionOutboundMessage[] = []; const session = asTestSession(