fix(sync): show complete workspace and agent history (#2263)
Cached focused state was leaking into the sidebar before authoritative workspace hydration, while catch-up limits counted raw events instead of projected entries. Keep the focused cache fast, gate directory presentation on hydration, and preserve canonical cursors while paging projected history.
This commit is contained in:
@@ -4,6 +4,7 @@ import { useSidebarOrderStore } from "@/stores/sidebar-order-store";
|
||||
import {
|
||||
composeWorkspaceStructure,
|
||||
selectHasHydratedWorkspaces,
|
||||
selectHydratedWorkspaceServerIds,
|
||||
selectHasWorkspaces,
|
||||
selectProjectOrder,
|
||||
selectRecommendedProjectPaths,
|
||||
@@ -70,6 +71,14 @@ export function useHasHydratedWorkspaces(serverId: string | null): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function useHydratedWorkspaceServerIds(serverIds: string[]): string[] {
|
||||
return useStoreWithEqualityFn(
|
||||
useSessionStore,
|
||||
(state) => selectHydratedWorkspaceServerIds(state, serverIds),
|
||||
workspaceEqualityFns.deep,
|
||||
);
|
||||
}
|
||||
|
||||
export function useWorkspaceDirectory(
|
||||
serverId: string | null,
|
||||
workspaceId: string | null,
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
||||
import {
|
||||
composeWorkspaceStructure,
|
||||
selectHasWorkspaces,
|
||||
selectHydratedWorkspaceServerIds,
|
||||
selectProjectOrder,
|
||||
selectRecommendedProjectPaths,
|
||||
selectWorkspace,
|
||||
@@ -101,6 +102,75 @@ afterEach(() => {
|
||||
useSessionStore.getState().clearSession(SERVER_ID);
|
||||
});
|
||||
|
||||
describe("workspace replica authority", () => {
|
||||
it("keeps a cached workspace addressable without publishing it as an authoritative directory", () => {
|
||||
const cachedWorkspace = createWorkspace({ id: "cached-workspace" });
|
||||
initializeWorkspaces([cachedWorkspace]);
|
||||
|
||||
const cachedServerIds = selectHydratedWorkspaceServerIds(useSessionStore.getState(), [
|
||||
SERVER_ID,
|
||||
]);
|
||||
|
||||
expect(selectWorkspace(useSessionStore.getState(), SERVER_ID, cachedWorkspace.id)).toBe(
|
||||
cachedWorkspace,
|
||||
);
|
||||
expect(selectWorkspaceStructureProjects(useSessionStore.getState(), cachedServerIds)).toEqual(
|
||||
[],
|
||||
);
|
||||
|
||||
const authoritativeWorkspace = createWorkspace({
|
||||
id: "authoritative-workspace",
|
||||
projectId: "authoritative-project",
|
||||
});
|
||||
useSessionStore
|
||||
.getState()
|
||||
.setWorkspaces(SERVER_ID, new Map([[authoritativeWorkspace.id, authoritativeWorkspace]]));
|
||||
useSessionStore.getState().setHasHydratedWorkspaces(SERVER_ID, true);
|
||||
const hydratedServerIds = selectHydratedWorkspaceServerIds(useSessionStore.getState(), [
|
||||
SERVER_ID,
|
||||
]);
|
||||
|
||||
expect(
|
||||
selectWorkspaceStructureProjects(useSessionStore.getState(), hydratedServerIds).map(
|
||||
(project) => project.workspaceKeys,
|
||||
),
|
||||
).toEqual([[`${SERVER_ID}:${authoritativeWorkspace.id}`]]);
|
||||
});
|
||||
|
||||
it("publishes each host to the workspace directory independently", () => {
|
||||
const loadingServerId = "loading-server";
|
||||
const hydratedServerId = "hydrated-server";
|
||||
const loadingWorkspace = createWorkspace({ id: "loading-workspace" });
|
||||
const hydratedWorkspace = createWorkspace({
|
||||
id: "hydrated-workspace",
|
||||
projectId: "hydrated-project",
|
||||
});
|
||||
const state = {
|
||||
sessions: {
|
||||
[loadingServerId]: {
|
||||
hasHydratedWorkspaces: false,
|
||||
workspaces: new Map([[loadingWorkspace.id, loadingWorkspace]]),
|
||||
},
|
||||
[hydratedServerId]: {
|
||||
hasHydratedWorkspaces: true,
|
||||
workspaces: new Map([[hydratedWorkspace.id, hydratedWorkspace]]),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const directoryServerIds = selectHydratedWorkspaceServerIds(state, [
|
||||
loadingServerId,
|
||||
hydratedServerId,
|
||||
]);
|
||||
const directoryProjects = selectWorkspaceStructureProjects(state, directoryServerIds);
|
||||
|
||||
expect(directoryServerIds).toEqual([hydratedServerId]);
|
||||
expect(directoryProjects.map((project) => project.workspaceKeys)).toEqual([
|
||||
[`${hydratedServerId}:${hydratedWorkspace.id}`],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectWorkspace", () => {
|
||||
it("resolves a descriptor when the route id matches workspace identity but not the map key", () => {
|
||||
const workspace = createWorkspace({ id: "workspace-a" });
|
||||
|
||||
@@ -131,6 +131,13 @@ export function selectHasHydratedWorkspaces(
|
||||
return serverId ? (state.sessions[serverId]?.hasHydratedWorkspaces ?? false) : false;
|
||||
}
|
||||
|
||||
export function selectHydratedWorkspaceServerIds(
|
||||
state: SessionsSnapshot,
|
||||
serverIds: readonly string[],
|
||||
): string[] {
|
||||
return serverIds.filter((serverId) => state.sessions[serverId]?.hasHydratedWorkspaces === true);
|
||||
}
|
||||
|
||||
export function selectWorkspaceStructureProjects(
|
||||
state: SessionsSnapshot,
|
||||
serverIds: readonly string[],
|
||||
|
||||
Reference in New Issue
Block a user