Files
paseo/packages/app/src/runtime/directory-sync/workspace-replica.test.ts
T
Mohamed Boudra c46aee4e6a Revert #4421 and narrow the cache restore fix (#4436)
* Revert "Restore cached conversations before reconnecting (#4421)"

This reverts commit 1c51b52d07.

* fix(app): restore provider loading on native

* fix(app): preserve native navigators across appearance changes

Reapply the isolated appearance boundaries and native menu transform origin from #4421 so appearance hydration preserves native navigator and gesture host lifetimes.

* fix(app): retain cached baselines during live updates

Read the timeline concurrently with agent preparation and reconcile display-only cache rows beneath the live head using the existing reducer. Merge cached directory entries beneath live state while preserving the existing checkpoint race guard.
2026-09-07 19:18:54 +02:00

233 lines
7.9 KiB
TypeScript

import { expect, it } from "vitest";
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
import type { WorkspaceDescriptorPayload } from "@getpaseo/protocol/messages";
import {
normalizeProjectDescriptor,
normalizeWorkspaceDescriptor,
useSessionStore,
} from "@/stores/session-store";
import {
clearWorkspaceArchivePending,
markWorkspaceArchivePending,
} from "@/contexts/session-workspace-upserts";
import { WorkspaceDirectoryReplica } from "./workspace-replica";
function workspace(id: string, projectId = "project"): WorkspaceDescriptorPayload {
return {
id,
projectId,
projectDisplayName: projectId,
projectRootPath: `/repo/${projectId}`,
workspaceDirectory: `/repo/${projectId}/${id}`,
projectKind: "git",
workspaceKind: "worktree",
name: id,
title: id,
status: "done",
activityAt: null,
statusEnteredAt: null,
archivingAt: null,
diffStat: null,
scripts: [],
};
}
it("commits workspace and project-parent state with filtered removals", () => {
const serverId = "workspace-replica";
const store = useSessionStore.getState();
store.initializeSession(serverId, null as unknown as DaemonClient);
const replica = new WorkspaceDirectoryReplica(serverId);
const empty = normalizeProjectDescriptor({
projectId: "empty",
projectDisplayName: "Empty",
projectRootPath: "/repo/empty",
projectKind: "git",
});
replica.commitSnapshot(
{
workspaces: new Map([
["kept", normalizeWorkspaceDescriptor(workspace("kept"))],
["filtered", normalizeWorkspaceDescriptor(workspace("filtered", "filtered-project"))],
]),
projects: new Map([[empty.projectId, empty]]),
},
[{ kind: "remove", id: "filtered", removedProjectId: "filtered-project" }],
);
const session = useSessionStore.getState().sessions[serverId];
expect(Array.from(session?.workspaces.keys() ?? [])).toEqual(["kept"]);
expect(Array.from(session?.projects.keys() ?? [])).toEqual(["empty"]);
store.clearSession(serverId);
});
it("commits the authoritative snapshot before buffered project updates", () => {
const serverId = "project-update-replica";
const store = useSessionStore.getState();
store.initializeSession(serverId, null as unknown as DaemonClient);
const replica = new WorkspaceDirectoryReplica(serverId);
const attachedMain = normalizeWorkspaceDescriptor(workspace("attached-main", "attached"));
const attachedFeature = normalizeWorkspaceDescriptor(workspace("attached-feature", "attached"));
const removed = normalizeWorkspaceDescriptor(workspace("removed", "removed"));
const unrelated = normalizeWorkspaceDescriptor(workspace("unrelated", "unrelated"));
const staleAttachedProject = normalizeProjectDescriptor({
projectId: "attached",
projectDisplayName: "Stale attached project",
projectRootPath: "/repo/attached",
projectKind: "git",
});
const removedProject = normalizeProjectDescriptor({
projectId: "removed",
projectDisplayName: "Removed project",
projectRootPath: "/repo/removed",
projectKind: "git",
});
const unchangedEmptyProject = normalizeProjectDescriptor({
projectId: "unchanged-empty",
projectDisplayName: "Unchanged empty project",
projectRootPath: "/repo/unchanged-empty",
projectKind: "git",
});
replica.commitSnapshot(
{
workspaces: new Map([
[attachedMain.id, attachedMain],
[attachedFeature.id, attachedFeature],
[removed.id, removed],
[unrelated.id, unrelated],
]),
projects: new Map([
[staleAttachedProject.projectId, staleAttachedProject],
[removedProject.projectId, removedProject],
[unchangedEmptyProject.projectId, unchangedEmptyProject],
]),
},
[
{
kind: "upsert",
project: {
projectId: "attached",
projectKey: "remote:github.com/acme/attached",
projectDisplayName: "Renamed attached project",
projectCustomName: "Personal name",
projectRootPath: "/moved/attached",
projectKind: "directory",
},
},
{
kind: "upsert",
project: {
projectId: "new-empty",
projectDisplayName: "New empty project",
projectRootPath: "/repo/new-empty",
projectKind: "directory",
},
},
{ kind: "remove", projectId: "removed" },
],
);
const session = useSessionStore.getState().sessions[serverId];
expect(session?.workspaces.get(attachedMain.id)).toMatchObject({
projectDisplayName: "Renamed attached project",
projectCustomName: "Personal name",
projectRootPath: "/moved/attached",
projectKind: "directory",
});
expect(session?.workspaces.get(attachedFeature.id)).toMatchObject({
projectDisplayName: "Renamed attached project",
projectRootPath: "/moved/attached",
});
expect(session?.workspaces.has(removed.id)).toBe(false);
expect(session?.workspaces.get(unrelated.id)).toBe(unrelated);
expect(Array.from(session?.projects.keys() ?? [])).toEqual([
"attached",
"unchanged-empty",
"new-empty",
]);
expect(session?.projects.get("unchanged-empty")).toBe(unchangedEmptyProject);
expect(session?.projects.get("new-empty")).toMatchObject({
projectDisplayName: "New empty project",
projectRootPath: "/repo/new-empty",
});
store.clearSession(serverId);
});
it("preserves unchanged project identity when another project changes", () => {
const serverId = "project-identity";
const store = useSessionStore.getState();
store.initializeSession(serverId, null as unknown as DaemonClient);
const replica = new WorkspaceDirectoryReplica(serverId);
const first = normalizeProjectDescriptor({
projectId: "first",
projectDisplayName: "First",
projectRootPath: "/repo/first",
projectKind: "git",
});
const second = normalizeProjectDescriptor({
projectId: "second",
projectDisplayName: "Second",
projectRootPath: "/repo/second",
projectKind: "git",
});
replica.commitSnapshot(
{
workspaces: new Map(),
projects: new Map([
["first", first],
["second", second],
]),
},
[],
);
const previousSecond = useSessionStore.getState().sessions[serverId]?.projects.get("second");
replica.commitSnapshot(
{
workspaces: new Map(),
projects: new Map([
["first", { ...first, projectDisplayName: "Updated" }],
["second", { ...second }],
]),
},
[],
);
expect(useSessionStore.getState().sessions[serverId]?.projects.get("second")).toBe(
previousSecond,
);
store.clearSession(serverId);
});
it("does not invent a null-key project from a workspace update", () => {
const serverId = "workspace-before-project-update";
const store = useSessionStore.getState();
store.initializeSession(serverId, null as unknown as DaemonClient);
const replica = new WorkspaceDirectoryReplica(serverId);
replica.applyDelta({ kind: "upsert", workspace: workspace("main", "fresh-project") });
const session = useSessionStore.getState().sessions[serverId];
expect(session?.workspaces.has("main")).toBe(true);
expect(session?.projects.has("fresh-project")).toBe(false);
store.clearSession(serverId);
});
it("does not restore a targeted cached workspace while its archive is pending", () => {
const serverId = "cached-workspace-during-archive";
const workspaceId = "archived-workspace";
const store = useSessionStore.getState();
store.initializeSession(serverId, null as unknown as DaemonClient);
const replica = new WorkspaceDirectoryReplica(serverId);
markWorkspaceArchivePending({ serverId, workspaceId });
try {
replica.commitCachedWorkspace(normalizeWorkspaceDescriptor(workspace(workspaceId)), undefined);
expect(useSessionStore.getState().sessions[serverId]?.workspaces.has(workspaceId)).toBe(false);
} finally {
clearWorkspaceArchivePending({ serverId, workspaceId });
store.clearSession(serverId);
}
});