perf(app): preserve unchanged sidebar workspace projections

Reopening a retained sidebar rebuilt its entry collection and invalidated every project row despite unchanged workspace data. Preserve collection identity as well as row identity, while keeping membership and ordering changes observable.
This commit is contained in:
Mohamed Boudra
2026-10-16 15:18:08 +02:00
parent e53d6673b0
commit a3919faed4
2 changed files with 66 additions and 6 deletions
@@ -403,6 +403,57 @@ describe("shared sidebar workspace model", () => {
expect(model.projectNamesByViewKey).toEqual(new Map([["getpaseo/paseo", "getpaseo/paseo"]]));
});
it("preserves the collection when a retained sidebar reconnects to unchanged entries", () => {
const model = buildSidebarWorkspacePlacementModel({
projects: [project({ projectKey: "project", workspaceKeys: ["srv:one", "srv:two"] })],
});
const one = workspace({
id: "one",
name: "one",
projectId: "project",
projectDisplayName: "project",
});
const two = workspace({
id: "two",
name: "two",
projectId: "project",
projectDisplayName: "project",
});
const sessions = [
{
serverId: "srv",
workspaceAgentActivity: new Map(),
workspaces: new Map([
["one", one],
["two", two],
]),
},
];
const previousEntries = buildSidebarWorkspaceEntries({
placements: model.workspaces,
sessions,
});
expect(
buildSidebarWorkspaceEntries({
placements: model.workspaces,
sessions,
previousEntries,
}),
).toBe(previousEntries);
const reordered = buildSidebarWorkspaceEntries({
placements: model.workspaces.toReversed(),
sessions,
previousEntries,
});
expect([...reordered.keys()]).toEqual(["srv:two", "srv:one"]);
const removed = buildSidebarWorkspaceEntries({
placements: model.workspaces.slice(0, 1),
sessions,
previousEntries,
});
expect([...removed.keys()]).toEqual(["srv:one"]);
});
it("preserves unchanged row identities when another workspace updates", () => {
const model = buildSidebarWorkspacePlacementModel({
projects: [project({ projectKey: "project", workspaceKeys: ["srv:one", "srv:two"] })],
@@ -369,13 +369,15 @@ export function buildSidebarWorkspaceEntries(input: {
sessions: SidebarWorkspaceSession[];
pendingCreateAttempts?: Record<string, PendingCreateAttempt>;
previousEntries?: ReadonlyMap<string, SidebarWorkspaceEntry>;
}): Map<string, SidebarWorkspaceEntry> {
}): ReadonlyMap<string, SidebarWorkspaceEntry> {
if (input.placements.length === 0 || input.sessions.length === 0) {
return new Map();
}
const sessionByServerId = new Map(input.sessions.map((session) => [session.serverId, session]));
const entries = new Map<string, SidebarWorkspaceEntry>();
const previousKeys = input.previousEntries?.keys();
let unchanged = input.previousEntries !== undefined;
for (const placement of input.placements) {
const session = sessionByServerId.get(placement.serverId);
@@ -395,15 +397,22 @@ export function buildSidebarWorkspaceEntries(input: {
workspaceAgentActivity: session.workspaceAgentActivity,
});
const previousEntry = input.previousEntries?.get(placement.workspaceKey);
entries.set(
placement.workspaceKey,
const nextEntry =
previousEntry && areSidebarWorkspaceEntriesEqual(previousEntry, entry)
? previousEntry
: entry,
);
: entry;
entries.set(placement.workspaceKey, nextEntry);
unchanged =
previousKeys?.next().value === placement.workspaceKey &&
previousEntry === nextEntry &&
unchanged;
}
return entries;
// Resuming a retained sidebar must not invalidate its project/filter projections
// when the directory has not changed while it was hidden.
return unchanged && input.previousEntries?.size === entries.size
? input.previousEntries
: entries;
}
function areSidebarWorkspaceEntriesEqual(