fix(app): treat workspace diff stats as authoritative
This commit is contained in:
@@ -34,7 +34,6 @@ import {
|
|||||||
type Agent,
|
type Agent,
|
||||||
type SessionState,
|
type SessionState,
|
||||||
type WorkspaceDescriptor,
|
type WorkspaceDescriptor,
|
||||||
mergeWorkspaceSnapshotWithExisting,
|
|
||||||
normalizeWorkspaceDescriptor,
|
normalizeWorkspaceDescriptor,
|
||||||
} from "@/stores/session-store";
|
} from "@/stores/session-store";
|
||||||
import { useDraftStore } from "@/stores/draft-store";
|
import { useDraftStore } from "@/stores/draft-store";
|
||||||
@@ -348,7 +347,6 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workspaces = new Map<string, WorkspaceDescriptor>();
|
const workspaces = new Map<string, WorkspaceDescriptor>();
|
||||||
const existingWorkspaces = useSessionStore.getState().sessions[serverId]?.workspaces;
|
|
||||||
let cursor: string | null = null;
|
let cursor: string | null = null;
|
||||||
let includeSubscribe = options?.subscribe ?? false;
|
let includeSubscribe = options?.subscribe ?? false;
|
||||||
|
|
||||||
@@ -364,13 +362,7 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
|||||||
|
|
||||||
for (const entry of payload.entries) {
|
for (const entry of payload.entries) {
|
||||||
const workspace = normalizeWorkspaceDescriptor(entry);
|
const workspace = normalizeWorkspaceDescriptor(entry);
|
||||||
workspaces.set(
|
workspaces.set(workspace.id, workspace);
|
||||||
workspace.id,
|
|
||||||
mergeWorkspaceSnapshotWithExisting({
|
|
||||||
incoming: workspace,
|
|
||||||
existing: existingWorkspaces?.get(workspace.id),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!payload.pageInfo.hasMore || !payload.pageInfo.nextCursor) {
|
if (!payload.pageInfo.hasMore || !payload.pageInfo.nextCursor) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo, useSyncExternalStore } from "react";
|
import { useCallback, useEffect, useMemo, useSyncExternalStore } from "react";
|
||||||
import type { WorkspaceDescriptorPayload } from "@server/shared/messages";
|
import type { WorkspaceDescriptorPayload } from "@server/shared/messages";
|
||||||
import {
|
import {
|
||||||
mergeWorkspaceSnapshotWithExisting,
|
|
||||||
normalizeWorkspaceDescriptor,
|
normalizeWorkspaceDescriptor,
|
||||||
useSessionStore,
|
useSessionStore,
|
||||||
type WorkspaceDescriptor,
|
type WorkspaceDescriptor,
|
||||||
@@ -274,7 +273,6 @@ export function useSidebarWorkspacesList(options?: {
|
|||||||
}
|
}
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const next = new Map<string, WorkspaceDescriptor>();
|
const next = new Map<string, WorkspaceDescriptor>();
|
||||||
const existingWorkspaces = useSessionStore.getState().sessions[serverId]?.workspaces;
|
|
||||||
let cursor: string | null = null;
|
let cursor: string | null = null;
|
||||||
try {
|
try {
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -284,13 +282,7 @@ export function useSidebarWorkspacesList(options?: {
|
|||||||
});
|
});
|
||||||
for (const entry of payload.entries) {
|
for (const entry of payload.entries) {
|
||||||
const workspace = toWorkspaceDescriptor(entry);
|
const workspace = toWorkspaceDescriptor(entry);
|
||||||
next.set(
|
next.set(workspace.id, workspace);
|
||||||
workspace.id,
|
|
||||||
mergeWorkspaceSnapshotWithExisting({
|
|
||||||
incoming: workspace,
|
|
||||||
existing: existingWorkspaces?.get(workspace.id),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (!payload.pageInfo.hasMore || !payload.pageInfo.nextCursor) {
|
if (!payload.pageInfo.hasMore || !payload.pageInfo.nextCursor) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import type { DaemonClient } from "@server/client/daemon-client";
|
|||||||
import type { WorkspaceDescriptorPayload } from "@server/shared/messages";
|
import type { WorkspaceDescriptorPayload } from "@server/shared/messages";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
mergeWorkspaceSnapshotWithExisting,
|
|
||||||
normalizeWorkspaceDescriptor,
|
normalizeWorkspaceDescriptor,
|
||||||
useSessionStore,
|
useSessionStore,
|
||||||
type WorkspaceDescriptor,
|
type WorkspaceDescriptor,
|
||||||
@@ -194,6 +193,25 @@ describe("mergeWorkspaces", () => {
|
|||||||
expect(after.workspaces.get("/repo/a")).not.toBe(beforeA);
|
expect(after.workspaces.get("/repo/a")).not.toBe(beforeA);
|
||||||
expect(after.workspaces.get("/repo/b")).toBe(beforeB);
|
expect(after.workspaces.get("/repo/b")).toBe(beforeB);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses incoming null diff stat as authoritative", () => {
|
||||||
|
const store = useSessionStore.getState();
|
||||||
|
initializeTestSession();
|
||||||
|
const workspace = createWorkspace({
|
||||||
|
id: "/repo/main",
|
||||||
|
diffStat: { additions: 2, deletions: 1 },
|
||||||
|
});
|
||||||
|
store.mergeWorkspaces("test-server", [workspace]);
|
||||||
|
const before = getTestSessionReferences();
|
||||||
|
|
||||||
|
store.mergeWorkspaces("test-server", [{ ...workspace, diffStat: null }]);
|
||||||
|
const after = getTestSessionReferences();
|
||||||
|
|
||||||
|
expect(after.sessions).not.toBe(before.sessions);
|
||||||
|
expect(after.session).not.toBe(before.session);
|
||||||
|
expect(after.workspaces).not.toBe(before.workspaces);
|
||||||
|
expect(after.workspaces.get(workspace.id)?.diffStat).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setWorkspaces", () => {
|
describe("setWorkspaces", () => {
|
||||||
@@ -215,25 +233,6 @@ describe("setWorkspaces", () => {
|
|||||||
expect(after.workspaces).toBe(before.workspaces);
|
expect(after.workspaces).toBe(before.workspaces);
|
||||||
expect(after.workspaces.get(workspace.id)).toBe(before.workspaces.get(workspace.id));
|
expect(after.workspaces.get(workspace.id)).toBe(before.workspaces.get(workspace.id));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stores replacement descriptors as-is when diff stat is cleared", () => {
|
|
||||||
const store = useSessionStore.getState();
|
|
||||||
initializeTestSession();
|
|
||||||
const workspace = createWorkspace({
|
|
||||||
id: "/repo/main",
|
|
||||||
diffStat: { additions: 2, deletions: 1 },
|
|
||||||
});
|
|
||||||
store.setWorkspaces("test-server", new Map([[workspace.id, workspace]]));
|
|
||||||
const before = getTestSessionReferences();
|
|
||||||
|
|
||||||
store.setWorkspaces("test-server", new Map([[workspace.id, { ...workspace, diffStat: null }]]));
|
|
||||||
const after = getTestSessionReferences();
|
|
||||||
|
|
||||||
expect(after.sessions).not.toBe(before.sessions);
|
|
||||||
expect(after.session).not.toBe(before.session);
|
|
||||||
expect(after.workspaces).not.toBe(before.workspaces);
|
|
||||||
expect(after.workspaces.get(workspace.id)?.diffStat).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("removeWorkspace", () => {
|
describe("removeWorkspace", () => {
|
||||||
@@ -277,34 +276,3 @@ describe("patchWorkspaceScripts", () => {
|
|||||||
expect(next.get(workspace.id)).toBe(workspace);
|
expect(next.get(workspace.id)).toBe(workspace);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("mergeWorkspaceSnapshotWithExisting", () => {
|
|
||||||
it("preserves the last known diff stat when a snapshot only has baseline null data", () => {
|
|
||||||
const existing = createWorkspace({
|
|
||||||
id: "/tmp/repo",
|
|
||||||
diffStat: { additions: 4, deletions: 2 },
|
|
||||||
});
|
|
||||||
const incoming = createWorkspace({
|
|
||||||
id: "/tmp/repo",
|
|
||||||
diffStat: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mergeWorkspaceSnapshotWithExisting({ incoming, existing })).toEqual({
|
|
||||||
...incoming,
|
|
||||||
diffStat: { additions: 4, deletions: 2 },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the incoming diff stat when the server provides a known value", () => {
|
|
||||||
const existing = createWorkspace({
|
|
||||||
id: "/tmp/repo",
|
|
||||||
diffStat: { additions: 4, deletions: 2 },
|
|
||||||
});
|
|
||||||
const incoming = createWorkspace({
|
|
||||||
id: "/tmp/repo",
|
|
||||||
diffStat: { additions: 0, deletions: 0 },
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mergeWorkspaceSnapshotWithExisting({ incoming, existing })).toEqual(incoming);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -145,21 +145,6 @@ export function normalizeWorkspaceDescriptor(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mergeWorkspaceSnapshotWithExisting(input: {
|
|
||||||
incoming: WorkspaceDescriptor;
|
|
||||||
existing?: WorkspaceDescriptor | null;
|
|
||||||
}): WorkspaceDescriptor {
|
|
||||||
const { incoming, existing } = input;
|
|
||||||
if (!existing || existing.id !== incoming.id) {
|
|
||||||
return incoming;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...incoming,
|
|
||||||
diffStat: incoming.diffStat ?? existing.diffStat,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function preserveWorkspaceDescriptorIdentity(
|
function preserveWorkspaceDescriptorIdentity(
|
||||||
incoming: WorkspaceDescriptor,
|
incoming: WorkspaceDescriptor,
|
||||||
existing?: WorkspaceDescriptor | null,
|
existing?: WorkspaceDescriptor | null,
|
||||||
@@ -1002,11 +987,7 @@ export const useSessionStore = create<SessionStore>()(
|
|||||||
let changed = false;
|
let changed = false;
|
||||||
for (const workspace of nextEntries) {
|
for (const workspace of nextEntries) {
|
||||||
const existing = next.get(workspace.id);
|
const existing = next.get(workspace.id);
|
||||||
const mergedWorkspace = mergeWorkspaceSnapshotWithExisting({
|
const nextWorkspace = preserveWorkspaceDescriptorIdentity(workspace, existing);
|
||||||
incoming: workspace,
|
|
||||||
existing,
|
|
||||||
});
|
|
||||||
const nextWorkspace = preserveWorkspaceDescriptorIdentity(mergedWorkspace, existing);
|
|
||||||
if (existing === nextWorkspace) {
|
if (existing === nextWorkspace) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user