mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-24 02:02:42 +00:00
fix(app): keep background workspaces hidden after reload (#7141)
* fix(app): preserve background workspace visibility after reload Persist the background flag and reject workspace rows that predate it so the existing cache repair clears their workspace cursor and refreshes the baseline. * test(app): make legacy workspace cache read paths explicit
This commit is contained in:
@@ -255,7 +255,93 @@ function deleteDirectory(cache: ReplicaCache, serverId: string): void {
|
||||
]);
|
||||
}
|
||||
|
||||
async function cacheWithoutBackgroundVisibility() {
|
||||
const storage = new MemoryStorage();
|
||||
const writer = createCache(storage);
|
||||
const checkpoint = {
|
||||
agents: { generation: "g", afterSeq: 12 },
|
||||
projects: { generation: "g", afterSeq: 4 },
|
||||
workspaces: { generation: "g", afterSeq: 8 },
|
||||
};
|
||||
const baseline = directory(checkpoint);
|
||||
commitDirectory(writer, SERVER_ID, baseline);
|
||||
writer.commitTimeline(SERVER_ID, "agent-1", timeline());
|
||||
await writer.flush();
|
||||
const key = `${SERVER_ID}:workspace:workspace-1`;
|
||||
const row = storage.rows.get(key)!;
|
||||
const payload = JSON.parse(row.payload) as Record<string, unknown>;
|
||||
delete payload.background;
|
||||
storage.rows.set(key, { ...row, payload: JSON.stringify(payload) });
|
||||
|
||||
return { storage, baseline, reader: createCache(storage) };
|
||||
}
|
||||
|
||||
async function expectLegacyWorkspaceRefresh({
|
||||
storage,
|
||||
baseline,
|
||||
reader,
|
||||
}: Awaited<ReturnType<typeof cacheWithoutBackgroundVisibility>>): Promise<void> {
|
||||
const restored = await reader.readDirectory(SERVER_ID);
|
||||
expect(restored.workspaces.size).toBe(0);
|
||||
expect(restored.checkpoint).toEqual({
|
||||
agents: baseline.checkpoint.agents,
|
||||
projects: baseline.checkpoint.projects,
|
||||
});
|
||||
expect(restored.agents).toEqual(baseline.agents);
|
||||
expect(restored.projects).toEqual(baseline.projects);
|
||||
expect(await reader.readTimeline(SERVER_ID, "agent-1")).toEqual(timeline());
|
||||
expect((await createCache(storage).readDirectory(SERVER_ID)).checkpoint).toEqual(
|
||||
restored.checkpoint,
|
||||
);
|
||||
|
||||
baseline.workspaces.set(
|
||||
"workspace-1",
|
||||
normalizeWorkspaceDescriptor({ ...workspacePayload(), background: true }),
|
||||
);
|
||||
reader.replaceDirectoryBaseline(SERVER_ID, baseline);
|
||||
await reader.flush();
|
||||
const refreshed = await createCache(storage).readDirectory(SERVER_ID);
|
||||
expect(refreshed.workspaces.get("workspace-1")?.background).toBe(true);
|
||||
expect(refreshed.checkpoint).toEqual(baseline.checkpoint);
|
||||
}
|
||||
|
||||
describe("ReplicaCache", () => {
|
||||
it.each([true, false, undefined])(
|
||||
"preserves background visibility (%s) when reopening a cached workspace",
|
||||
async (background) => {
|
||||
const storage = new MemoryStorage();
|
||||
const writer = createCache(storage);
|
||||
const baseline = directory();
|
||||
baseline.workspaces.set(
|
||||
"workspace-1",
|
||||
normalizeWorkspaceDescriptor({ ...workspacePayload(), background }),
|
||||
);
|
||||
commitDirectory(writer, SERVER_ID, baseline);
|
||||
await writer.flush();
|
||||
|
||||
const reader = createCache(storage);
|
||||
expect(
|
||||
(await reader.readDirectory(SERVER_ID)).workspaces.get("workspace-1")?.background,
|
||||
).toBe(background ?? false);
|
||||
expect((await reader.readWorkspace(SERVER_ID, "workspace-1"))?.workspace.background).toBe(
|
||||
background ?? false,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it("refetches workspaces cached without background visibility after a directory read", async () => {
|
||||
const cached = await cacheWithoutBackgroundVisibility();
|
||||
|
||||
await expectLegacyWorkspaceRefresh(cached);
|
||||
});
|
||||
|
||||
it("refetches workspaces cached without background visibility after a workspace read", async () => {
|
||||
const cached = await cacheWithoutBackgroundVisibility();
|
||||
|
||||
expect(await cached.reader.readWorkspace(SERVER_ID, "workspace-1")).toBeUndefined();
|
||||
await expectLegacyWorkspaceRefresh(cached);
|
||||
});
|
||||
|
||||
it("rejects pre-baseline-fix checkpoints without discarding cached rows or timelines", async () => {
|
||||
const storage = new MemoryStorage();
|
||||
const writer = createCache(storage);
|
||||
|
||||
@@ -306,6 +306,8 @@ const StoredWorkspaceSchema = z.strictObject({
|
||||
worktreeSlug: z.string().optional(),
|
||||
projectKind: z.enum(["git", "non_git", "directory"]),
|
||||
workspaceKind: z.enum(["directory", "local_checkout", "checkout", "worktree"]),
|
||||
// Require the flag so older rows that lost background visibility invalidate the workspace cursor.
|
||||
background: z.boolean(),
|
||||
name: z.string(),
|
||||
title: z.string().nullable(),
|
||||
pinnedAt: z.string().nullable(),
|
||||
@@ -698,6 +700,7 @@ function serializeWorkspace(workspace: WorkspaceDescriptor): StoredWorkspace {
|
||||
worktreeSlug: workspace.worktreeSlug,
|
||||
projectKind: workspace.projectKind,
|
||||
workspaceKind: workspace.workspaceKind,
|
||||
background: workspace.background ?? false,
|
||||
name: workspace.name,
|
||||
title: workspace.title ?? null,
|
||||
pinnedAt: workspace.pinnedAt ?? null,
|
||||
|
||||
Reference in New Issue
Block a user