Fix project icon cache reuse in New Workspace (#3600)
* fix(app): reuse project icon cache in new workspace New Workspace omitted each host placement's effective icon revision, so its icon queries could not share the sidebar's memory or persistent cache entries. Carry that revision through the target projection. * refactor(app): centralize project icon targets Project icon consumers constructed equivalent cache targets independently. One target factory now carries each host placement revision through the sidebar, New Workspace, Projects, and Project Settings. * test(app): inline project icon fixture
This commit is contained in:
@@ -3,12 +3,18 @@ import { createProjectViewKey } from "@/projects/workspace-structure";
|
||||
import type { ProjectDescriptor, WorkspaceDescriptor } from "@/stores/session-store";
|
||||
import { buildProjects, getProjectSummaryForHostProject } from "./projects";
|
||||
|
||||
function descriptor(id: string, key: string, root: string): ProjectDescriptor {
|
||||
function descriptor(
|
||||
id: string,
|
||||
key: string,
|
||||
root: string,
|
||||
iconRevision?: string,
|
||||
): ProjectDescriptor {
|
||||
return {
|
||||
projectId: id,
|
||||
projectKey: key,
|
||||
projectDisplayName: "acme/app",
|
||||
projectCustomName: null,
|
||||
projectIconRevision: iconRevision,
|
||||
projectRootPath: root,
|
||||
projectKind: "git",
|
||||
};
|
||||
@@ -42,14 +48,14 @@ describe("buildProjects", () => {
|
||||
serverId: "host-a",
|
||||
serverName: "Host A",
|
||||
isOnline: true,
|
||||
projects: [descriptor("prj_a", key, "/a/app")],
|
||||
projects: [descriptor("prj_a", key, "/a/app", "revision-a")],
|
||||
workspaces: [workspace("ws-a", "prj_a", "/a/app")],
|
||||
},
|
||||
{
|
||||
serverId: "host-b",
|
||||
serverName: "Host B",
|
||||
isOnline: false,
|
||||
projects: [descriptor("prj_b", key, "/b/app")],
|
||||
projects: [descriptor("prj_b", key, "/b/app", "revision-b")],
|
||||
workspaces: [workspace("ws-b", "prj_b", "/b/app")],
|
||||
},
|
||||
],
|
||||
@@ -62,8 +68,16 @@ describe("buildProjects", () => {
|
||||
onlineHostCount: 1,
|
||||
totalWorkspaceCount: 2,
|
||||
hosts: [
|
||||
expect.objectContaining({ serverId: "host-a", projectId: "prj_a" }),
|
||||
expect.objectContaining({ serverId: "host-b", projectId: "prj_b" }),
|
||||
expect.objectContaining({
|
||||
serverId: "host-a",
|
||||
projectId: "prj_a",
|
||||
iconRevision: "revision-a",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
serverId: "host-b",
|
||||
projectId: "prj_b",
|
||||
iconRevision: "revision-b",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -25,6 +25,7 @@ export interface ProjectHostEntry {
|
||||
gitRuntime?: WorkspaceDescriptor["gitRuntime"];
|
||||
githubRuntime?: WorkspaceDescriptor["githubRuntime"];
|
||||
customIconRevision?: string | null;
|
||||
iconRevision?: string;
|
||||
}
|
||||
|
||||
export interface ProjectSummary {
|
||||
@@ -87,6 +88,7 @@ interface HostGroup {
|
||||
isOnline: boolean;
|
||||
workspaces: WorkspaceDescriptor[];
|
||||
customIconRevision?: string | null;
|
||||
iconRevision?: string;
|
||||
// Repo root for a project parent that has no workspaces yet. Without it the
|
||||
// host's repoRoot resolves to "" and the project reads as non-editable.
|
||||
fallbackRepoRoot: string;
|
||||
@@ -159,6 +161,7 @@ function toHostEntry(group: HostGroup): ProjectHostEntry {
|
||||
gitRuntime: canonical?.gitRuntime,
|
||||
githubRuntime: canonical?.githubRuntime,
|
||||
customIconRevision: canonical?.projectCustomIconRevision ?? group.customIconRevision,
|
||||
iconRevision: group.iconRevision,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -223,6 +226,7 @@ function addHostProjects(
|
||||
isOnline: host.isOnline,
|
||||
workspaces: [],
|
||||
customIconRevision: placement.customIconRevision,
|
||||
iconRevision: placement.iconRevision,
|
||||
fallbackRepoRoot: repoRootByProjectId.get(projectId) ?? "",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -232,7 +232,12 @@ describe("buildSidebarProjectRowModel", () => {
|
||||
project({
|
||||
viewKey: '["placement","host-b","project-b"]',
|
||||
hosts: [
|
||||
{ serverId: "host-b", iconWorkingDir: "/repo/b", worktreeSupport: "supported" as const },
|
||||
{
|
||||
serverId: "host-b",
|
||||
iconWorkingDir: "/repo/b",
|
||||
worktreeSupport: "supported" as const,
|
||||
iconRevision: "effective-revision",
|
||||
},
|
||||
],
|
||||
}),
|
||||
]);
|
||||
@@ -242,6 +247,7 @@ describe("buildSidebarProjectRowModel", () => {
|
||||
serverId: "host-b",
|
||||
projectId: "project-host-b",
|
||||
iconWorkingDir: "/repo/b",
|
||||
iconRevision: "effective-revision",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SidebarProjectEntry } from "@/hooks/use-sidebar-workspaces-list";
|
||||
import { createProjectIconTarget, type ProjectIconTarget } from "@/projects/icon-target";
|
||||
|
||||
export interface SidebarProjectHostTarget {
|
||||
serverId: string;
|
||||
@@ -53,16 +54,17 @@ export function resolveSidebarProjectIconTarget(
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface SidebarProjectIconTarget extends SidebarProjectHostTarget {
|
||||
projectViewKey: string;
|
||||
}
|
||||
export type SidebarProjectIconTarget = ProjectIconTarget;
|
||||
|
||||
export function resolveSidebarProjectIconTargets(
|
||||
projects: readonly SidebarProjectEntry[],
|
||||
): SidebarProjectIconTarget[] {
|
||||
return projects.flatMap((project) => {
|
||||
const target = resolveSidebarProjectIconTarget(project);
|
||||
return target ? [{ projectViewKey: project.viewKey, ...target }] : [];
|
||||
const iconTarget = target
|
||||
? createProjectIconTarget({ projectViewKey: project.viewKey, placement: target })
|
||||
: null;
|
||||
return iconTarget ? [iconTarget] : [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user