fix(projects): resolve legacy mutation aliases

This commit is contained in:
Mohamed Boudra
2026-07-29 10:59:01 +00:00
parent 7e43d90aa5
commit 7b9787a75d
5 changed files with 31 additions and 12 deletions

View File

@@ -52,6 +52,19 @@ describe("project settings target", () => {
expect(first).not.toBe(second);
});
it("preserves whitespace in opaque project IDs", () => {
const withoutTrailingSpace = resolveHostProjectSettingsRouteKey({
serverId: "host-a",
projectId: "/repo/foo",
});
const withTrailingSpace = resolveHostProjectSettingsRouteKey({
serverId: "host-a",
projectId: "/repo/foo ",
});
expect(withTrailingSpace).not.toBe(withoutTrailingSpace);
});
it("prefers an online host for a generic grouped settings route", () => {
const groupedProject = {
projectKey: "remote:github.com/acme/app",

View File

@@ -9,8 +9,8 @@ export function resolveHostProjectSettingsRouteKey(host: {
serverId: string;
projectId?: string;
}): string | null {
const projectId = host.projectId?.trim();
if (!projectId) return null;
const projectId = host.projectId;
if (!projectId?.trim()) return null;
return frameHostProjectKey({ serverId: host.serverId, projectId });
}

View File

@@ -170,6 +170,7 @@ export function normalizeWorkspaceDescriptor(
return {
id: normalizeWorkspaceOpaqueId(payload.id) ?? payload.id,
projectId: payload.projectId,
// COMPAT(projectKey): added in v0.2.4 on 2026-07-29; remove after 2027-01-29.
projectKey: payload.projectKey ?? payload.project?.projectKey ?? null,
projectDisplayName: payload.projectDisplayName,
projectCustomName: payload.projectCustomName ?? null,

View File

@@ -125,6 +125,7 @@ import {
deriveWorkspaceDisplayName,
} from "./workspace-registry-model.js";
import { resolveWorkspaceIdForPath } from "./resolve-workspace-id-for-path.js";
import { resolveProjectReference } from "./project-reference.js";
import {
resolveProjectDisplayName,
resolveWorkspaceDisplayName,
@@ -2588,7 +2589,7 @@ export class Session {
);
try {
const existing = await this.projectRegistry.get(projectId);
const existing = await resolveProjectReference(projectId, this.projectRegistry);
if (!existing) {
this.emit({
type: "project.rename.response",
@@ -2632,7 +2633,7 @@ export class Session {
// resolved name lands in the UI immediately.
const workspaces = await this.workspaceRegistry.list();
const affectedWorkspaceIds = workspaces
.filter((workspace) => workspace.projectId === projectId)
.filter((workspace) => workspace.projectId === existing.projectId)
.map((workspace) => workspace.workspaceId);
if (affectedWorkspaceIds.length > 0) {
await this.emitWorkspaceUpdatesForWorkspaceIds(affectedWorkspaceIds);
@@ -2671,8 +2672,10 @@ export class Session {
this.sessionLogger.info({ projectId, requestId }, "session: project.remove.request");
try {
const project = await resolveProjectReference(projectId, this.projectRegistry);
const resolvedProjectId = project?.projectId ?? projectId;
const projectWorkspaces = (await this.workspaceRegistry.list()).filter(
(workspace) => workspace.projectId === projectId,
(workspace) => workspace.projectId === resolvedProjectId,
);
const activeWorkspaceIds = projectWorkspaces
.filter((workspace) => !workspace.archivedAt)
@@ -2700,7 +2703,7 @@ export class Session {
removedWorkspaceIds.push(workspaceId);
}
await this.projectRegistry.remove(projectId);
await this.projectRegistry.remove(resolvedProjectId);
} finally {
if (activeWorkspaceIds.length > 0) {
this.clearWorkspaceArchiving(activeWorkspaceIds);

View File

@@ -3406,6 +3406,7 @@ test("project.remove.request archives active workspaces and removes the project
});
const project = createPersistedProjectRecord({
projectId: "proj-remove-with-workspace",
projectKey: "remote:github.com/acme/remove-with-workspace",
rootPath: REPO_CWD,
kind: "git",
displayName: "repo",
@@ -3470,7 +3471,7 @@ test("project.remove.request archives active workspaces and removes the project
await session.handleMessage({
type: "project.remove.request",
projectId: project.projectId,
projectId: project.projectKey,
requestId: "req-remove-project",
});
@@ -3482,7 +3483,7 @@ test("project.remove.request archives active workspaces and removes the project
});
expect(findByType(emitted, "project.remove.response")?.payload).toEqual({
requestId: "req-remove-project",
projectId: project.projectId,
projectId: project.projectKey,
accepted: true,
removedWorkspaceIds: [workspace.workspaceId],
error: null,
@@ -3491,7 +3492,7 @@ test("project.remove.request archives active workspaces and removes the project
expect(workspaceUpdates.at(-1)?.payload).toEqual({
kind: "remove",
id: workspace.workspaceId,
removedProjectId: project.projectId,
removedProjectId: project.projectKey,
});
});
@@ -7392,7 +7393,8 @@ test("project.rename.request stores customName and emits an updated workspace de
);
const project = createPersistedProjectRecord({
projectId: "remote:github.com/acme/repo",
projectId: "prj_rename",
projectKey: "remote:github.com/acme/repo",
rootPath: REPO_CWD,
kind: "git",
displayName: "acme/repo",
@@ -7430,7 +7432,7 @@ test("project.rename.request stores customName and emits an updated workspace de
await session.handleMessage({
type: "project.rename.request",
projectId: project.projectId,
projectId: project.projectKey,
customName: " My Fork ",
requestId: "req-rename-1",
});
@@ -7438,7 +7440,7 @@ test("project.rename.request stores customName and emits an updated workspace de
const response = findByType(emitted, "project.rename.response");
expect(response?.payload).toEqual({
requestId: "req-rename-1",
projectId: project.projectId,
projectId: project.projectKey,
accepted: true,
customName: "My Fork",
error: null,