Fix renaming projects before their first workspace (#2252)

* fix: emit project.update on rename so empty projects update

The rename handler only re-emitted workspace descriptors, which left
empty projects (no workspaces yet) showing the stale name on the client.

* test(projects): cover renaming empty projects

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
Alberto De Agostini
2026-07-20 23:43:05 +02:00
committed by GitHub
parent ddb6d97bf0
commit aa6384babd
2 changed files with 45 additions and 2 deletions

View File

@@ -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();

View File

@@ -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<string>,
options?: { skipReconcile?: boolean },
): Promise<void>;
updateClientCapabilities(capabilities: Record<string, unknown> | 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(