mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-10 10:25:03 +00:00
Hiding the Changes panel releases its diff subscription. Reopening it starts a new stream, and since #8907 a new stream first publishes a loading snapshot (entries, no bodies) before its first revision commits. That snapshot replaced the settled diff in the query cache, so the panel showed the full loading spinner for about a second and then repainted the same diff. A loading snapshot now only counts on the first load. Once a diff has settled, shareCheckoutDiff keeps it until the new stream's ready revision lands, which replaces it in one step when the content changed.
985 lines
33 KiB
TypeScript
985 lines
33 KiB
TypeScript
import type { DiffState, OwnedSubscription, SubscriptionObserver } from "@getpaseo/client";
|
|
import { QueryClient, QueryObserver, skipToken } from "@tanstack/react-query";
|
|
import { describe, expect, it } from "vitest";
|
|
import type { MutableDaemonConfig, SessionOutboundMessage } from "@getpaseo/protocol/messages";
|
|
import { deriveCheckoutDiffResult, type CheckoutDiffSnapshot } from "@/git/diff-snapshot";
|
|
import { shareCheckoutDiff } from "@/git/diff-sharing";
|
|
import { checkoutDiffQueryKey } from "@/git/query-keys";
|
|
import { buildTerminalsQueryKey } from "@/screens/workspace/terminals/state";
|
|
import { daemonConfigQueryKey } from "@/data/daemon-config";
|
|
import { daemonPairingOfferQueryKey } from "@/data/daemon-pairing";
|
|
import { providersSnapshotQueryKey } from "@/data/providers-snapshot";
|
|
import {
|
|
checkoutDiffPushRoute,
|
|
invalidateServerDataQueriesAfterReconnect,
|
|
mountServerDataPushRouter,
|
|
workspaceTerminalsPushRoute,
|
|
} from "@/data/push-router";
|
|
|
|
type ProvidersSnapshotUpdateMessage = Extract<
|
|
SessionOutboundMessage,
|
|
{ type: "providers_snapshot_update" }
|
|
>;
|
|
type CheckoutDiffUpdateMessage = Extract<SessionOutboundMessage, { type: "checkout_diff_update" }>;
|
|
type SubscribeCheckoutDiffResponseMessage = Extract<
|
|
SessionOutboundMessage,
|
|
{ type: "subscribe_checkout_diff_response" }
|
|
>;
|
|
type StatusMessage = Extract<SessionOutboundMessage, { type: "status" }>;
|
|
type TerminalsChangedMessage = Extract<SessionOutboundMessage, { type: "terminals_changed" }>;
|
|
type RouterMessage =
|
|
| ProvidersSnapshotUpdateMessage
|
|
| CheckoutDiffUpdateMessage
|
|
| SubscribeCheckoutDiffResponseMessage
|
|
| StatusMessage
|
|
| TerminalsChangedMessage;
|
|
type RouterMessageType = RouterMessage["type"];
|
|
type RouterHandler = (message: RouterMessage) => void;
|
|
type RouterClient = Parameters<typeof mountServerDataPushRouter>[0]["client"];
|
|
type DiffFile = SubscribeCheckoutDiffResponseMessage["payload"]["files"][number];
|
|
|
|
const daemonConfig: MutableDaemonConfig = {
|
|
relay: { enabled: false },
|
|
mcp: { injectIntoAgents: true },
|
|
browserTools: { enabled: false },
|
|
providers: {},
|
|
metadataGeneration: { providers: [] },
|
|
autoArchiveAfterMerge: false,
|
|
enableTerminalAgentHooks: false,
|
|
appendSystemPrompt: "",
|
|
};
|
|
|
|
function createFakeClient(config: { rejectCheckoutDiffSubscribe?: boolean } = {}): {
|
|
client: RouterClient;
|
|
emit: <K extends RouterMessageType>(message: Extract<RouterMessage, { type: K }>) => void;
|
|
subscribeCheckoutDiffCalls: Array<{
|
|
cwd: string;
|
|
compare: { mode: "uncommitted" | "base" | "all"; baseRef?: string; ignoreWhitespace?: boolean };
|
|
subscriptionId: string;
|
|
}>;
|
|
unsubscribeCheckoutDiffCalls: string[];
|
|
subscribeTerminalCalls: Array<{ cwd?: string; workspaceId?: string }>;
|
|
unsubscribeTerminalCalls: Array<{ cwd?: string; workspaceId?: string }>;
|
|
} {
|
|
const handlers: Record<RouterMessageType, RouterHandler[]> = {
|
|
providers_snapshot_update: [],
|
|
checkout_diff_update: [],
|
|
subscribe_checkout_diff_response: [],
|
|
status: [],
|
|
terminals_changed: [],
|
|
};
|
|
const subscribeCheckoutDiffCalls: Array<{
|
|
cwd: string;
|
|
compare: { mode: "uncommitted" | "base" | "all"; baseRef?: string; ignoreWhitespace?: boolean };
|
|
subscriptionId: string;
|
|
}> = [];
|
|
const unsubscribeCheckoutDiffCalls: string[] = [];
|
|
const subscribeTerminalCalls: Array<{ cwd?: string; workspaceId?: string }> = [];
|
|
const unsubscribeTerminalCalls: Array<{ cwd?: string; workspaceId?: string }> = [];
|
|
|
|
function on<K extends RouterMessageType>(
|
|
type: K,
|
|
handler: (message: Extract<RouterMessage, { type: K }>) => void,
|
|
): () => void {
|
|
const routerHandler: RouterHandler = (message) => {
|
|
if (message.type === type) {
|
|
handler(message as Extract<RouterMessage, { type: K }>);
|
|
}
|
|
};
|
|
handlers[type].push(routerHandler);
|
|
return () => {
|
|
handlers[type] = handlers[type].filter((candidate) => candidate !== routerHandler);
|
|
};
|
|
}
|
|
|
|
function emit<K extends RouterMessageType>(message: Extract<RouterMessage, { type: K }>): void {
|
|
for (const handler of handlers[message.type]) {
|
|
handler(message);
|
|
}
|
|
}
|
|
|
|
function observe<T extends { subscriptionId: string }>(
|
|
snapshot: T,
|
|
types: RouterMessageType[],
|
|
released: () => void,
|
|
matches: (message: RouterMessage) => boolean = () => true,
|
|
): OwnedSubscription<T> {
|
|
const observers = new Set<SubscriptionObserver<T>>();
|
|
let active = true;
|
|
const cleanups = types.map((type) =>
|
|
on(type, (message) => {
|
|
if (!matches(message)) return;
|
|
for (const observer of observers) {
|
|
if (message.type === "subscribe_checkout_diff_response")
|
|
observer.snapshot({ ...snapshot, ...message.payload });
|
|
else observer.update(message);
|
|
}
|
|
}),
|
|
);
|
|
return {
|
|
subscriptionId: snapshot.subscriptionId,
|
|
ready: Promise.resolve(snapshot),
|
|
subscribe: (observer) => {
|
|
observers.add(observer);
|
|
observer.snapshot(snapshot);
|
|
return () => {
|
|
observers.delete(observer);
|
|
};
|
|
},
|
|
release: async () => {
|
|
if (!active) return;
|
|
active = false;
|
|
cleanups.forEach((cleanup) => cleanup());
|
|
observers.clear();
|
|
released();
|
|
},
|
|
};
|
|
}
|
|
|
|
return {
|
|
client: {
|
|
async getProvidersSnapshot() {
|
|
throw new Error("Unexpected snapshot pull");
|
|
},
|
|
observeEvents: () =>
|
|
observe(
|
|
{ subscriptionId: "events", requestId: "events", events: [] },
|
|
["status", "providers_snapshot_update"],
|
|
() => {},
|
|
),
|
|
observeDiff(input) {
|
|
const cwd = input.cwd;
|
|
let mode: "uncommitted" | "base" | "all" = "uncommitted";
|
|
if (input.head.kind === "rev") mode = "base";
|
|
else if (input.mergeBase) mode = "all";
|
|
const compare = {
|
|
mode,
|
|
...(input.base.kind === "rev" && input.base.rev !== "HEAD"
|
|
? { baseRef: input.base.rev }
|
|
: {}),
|
|
ignoreWhitespace: input.ignoreWhitespace,
|
|
};
|
|
const subscriptionId = `server-diff-${subscribeCheckoutDiffCalls.length + 1}`;
|
|
subscribeCheckoutDiffCalls.push({ cwd, compare, subscriptionId });
|
|
const handle = observe<SubscribeCheckoutDiffResponseMessage["payload"]>(
|
|
{ subscriptionId, cwd, files: [], error: null, requestId: "subscribe-checkout-diff" },
|
|
["checkout_diff_update", "subscribe_checkout_diff_response"],
|
|
() => unsubscribeCheckoutDiffCalls.push(subscriptionId),
|
|
(message) =>
|
|
"subscriptionId" in message.payload &&
|
|
message.payload.subscriptionId === subscriptionId,
|
|
);
|
|
const modern: OwnedSubscription<DiffState> = {
|
|
...handle,
|
|
ready: Promise.resolve({
|
|
phase: "ready",
|
|
revision: 1,
|
|
resolved: { baseSha: "", headSha: "", mergeBaseSha: "" },
|
|
files: [],
|
|
diffTooLarge: false,
|
|
subscriptionId,
|
|
}),
|
|
subscribe: (observer) =>
|
|
handle.subscribe({
|
|
snapshot: (payload) =>
|
|
observer.snapshot({
|
|
phase: "ready",
|
|
revision: 1,
|
|
resolved: { baseSha: "", headSha: "", mergeBaseSha: "" },
|
|
files: payload.files,
|
|
diffTooLarge: payload.diffTooLarge === true,
|
|
subscriptionId,
|
|
}),
|
|
update: (message) => {
|
|
if (message.type === "checkout_diff_update")
|
|
observer.snapshot({
|
|
phase: "ready",
|
|
revision: 2,
|
|
resolved: { baseSha: "", headSha: "", mergeBaseSha: "" },
|
|
files: message.payload.files,
|
|
diffTooLarge: message.payload.diffTooLarge === true,
|
|
subscriptionId,
|
|
});
|
|
},
|
|
}),
|
|
};
|
|
if (!config.rejectCheckoutDiffSubscribe) return modern;
|
|
return {
|
|
...modern,
|
|
subscribe: (observer) => {
|
|
queueMicrotask(() => observer.error?.(new Error("subscribe failed")));
|
|
return () => {};
|
|
},
|
|
};
|
|
},
|
|
observeTerminals(input) {
|
|
const { signal: _signal, ...query } = input;
|
|
subscribeTerminalCalls.push(query);
|
|
return observe(
|
|
{
|
|
...query,
|
|
subscriptionId: `terminal-${subscribeTerminalCalls.length}`,
|
|
requestId: "terminals",
|
|
terminals: [],
|
|
},
|
|
["terminals_changed"],
|
|
() => unsubscribeTerminalCalls.push(query),
|
|
(message) => message.type === "terminals_changed" && message.payload.cwd === query.cwd,
|
|
);
|
|
},
|
|
},
|
|
emit,
|
|
subscribeCheckoutDiffCalls,
|
|
unsubscribeCheckoutDiffCalls,
|
|
subscribeTerminalCalls,
|
|
unsubscribeTerminalCalls,
|
|
};
|
|
}
|
|
|
|
/** Matches the real diff observation: nothing is delivered until the stream publishes. */
|
|
type SubscribedDiffState = DiffState & { subscriptionId: string };
|
|
|
|
function createDiffStreamClient() {
|
|
const subscriptions: Array<{ publish(state: DiffState): void }> = [];
|
|
const noSubscription = {
|
|
subscriptionId: "none",
|
|
ready: Promise.resolve(undefined as never),
|
|
subscribe: () => () => {},
|
|
release: async () => {},
|
|
};
|
|
const client: RouterClient = {
|
|
async getProvidersSnapshot() {
|
|
throw new Error("Unexpected snapshot pull");
|
|
},
|
|
observeEvents: () => noSubscription,
|
|
observeTerminals: () => noSubscription,
|
|
observeDiff() {
|
|
const observers = new Set<SubscriptionObserver<SubscribedDiffState>>();
|
|
let released = false;
|
|
const subscriptionId = `diff-${subscriptions.length + 1}`;
|
|
subscriptions.push({
|
|
publish: (state) => {
|
|
if (released) return;
|
|
for (const observer of observers) observer.snapshot({ ...state, subscriptionId });
|
|
},
|
|
});
|
|
return {
|
|
subscriptionId,
|
|
ready: new Promise<never>(() => {}),
|
|
subscribe: (observer: SubscriptionObserver<SubscribedDiffState>) => {
|
|
observers.add(observer);
|
|
return () => observers.delete(observer);
|
|
},
|
|
release: async () => {
|
|
released = true;
|
|
},
|
|
};
|
|
},
|
|
};
|
|
return { client, subscriptions, latest: () => subscriptions[subscriptions.length - 1]! };
|
|
}
|
|
|
|
function providerUpdate(generatedAt: string): ProvidersSnapshotUpdateMessage {
|
|
return {
|
|
type: "providers_snapshot_update",
|
|
payload: {
|
|
entries: [{ provider: "codex", status: "ready", enabled: true, models: [] }],
|
|
generatedAt,
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("server data push router", () => {
|
|
it("keeps a visible diff subscribed when a hidden panel switches to the same comparison", () => {
|
|
const queryClient = new QueryClient();
|
|
const fake = createFakeClient();
|
|
const serverId = "server-1";
|
|
const cwd = "/repo";
|
|
const options = (mode: "base" | "uncommitted", enabled: boolean) => {
|
|
const queryKey = checkoutDiffQueryKey(
|
|
serverId,
|
|
cwd,
|
|
mode,
|
|
mode === "base" ? "main" : undefined,
|
|
);
|
|
return {
|
|
queryKey,
|
|
queryFn: skipToken as typeof skipToken,
|
|
gcTime: Infinity,
|
|
meta: checkoutDiffPushRoute({
|
|
enabled,
|
|
serverId,
|
|
cwd,
|
|
subscriptionId: `checkoutDiff:${JSON.stringify(queryKey)}`,
|
|
compare: { mode, ...(mode === "base" ? { baseRef: "main" } : {}) },
|
|
}),
|
|
};
|
|
};
|
|
const visible = new QueryObserver(queryClient, options("base", true));
|
|
const hidden = new QueryObserver(queryClient, options("base", false));
|
|
const unsubscribeVisible = visible.subscribe(() => {});
|
|
const unsubscribeHidden = hidden.subscribe(() => {});
|
|
const unmount = mountServerDataPushRouter({ queryClient, client: fake.client, serverId });
|
|
try {
|
|
visible.setOptions(options("uncommitted", true));
|
|
hidden.setOptions(options("uncommitted", false));
|
|
const subscription = fake.subscribeCheckoutDiffCalls.findLast(
|
|
(call) => call.compare.mode === "uncommitted",
|
|
)!;
|
|
const files = [
|
|
{
|
|
path: "README.md",
|
|
isNew: false,
|
|
isDeleted: false,
|
|
additions: 1,
|
|
deletions: 0,
|
|
hunks: [],
|
|
},
|
|
];
|
|
fake.emit({
|
|
type: "checkout_diff_update",
|
|
payload: { subscriptionId: subscription.subscriptionId, cwd, files, error: null },
|
|
});
|
|
expect(queryClient.getQueryData(options("uncommitted", true).queryKey)).toMatchObject({
|
|
files,
|
|
});
|
|
expect(fake.unsubscribeCheckoutDiffCalls).not.toContain(subscription.subscriptionId);
|
|
|
|
unsubscribeVisible();
|
|
expect(fake.unsubscribeCheckoutDiffCalls).toContain(subscription.subscriptionId);
|
|
} finally {
|
|
unsubscribeVisible();
|
|
unsubscribeHidden();
|
|
unmount();
|
|
queryClient.clear();
|
|
}
|
|
});
|
|
|
|
it("retains equal diff files without reconstructing changed file bodies", () => {
|
|
const queryClient = new QueryClient();
|
|
const fake = createFakeClient();
|
|
const serverId = "diff-sharing";
|
|
const cwd = "/repo";
|
|
const queryKey = checkoutDiffQueryKey(serverId, cwd, "uncommitted", undefined, false);
|
|
const subscriptionId = "diff-sharing";
|
|
const observer = new QueryObserver(queryClient, {
|
|
queryKey,
|
|
queryFn: skipToken,
|
|
meta: checkoutDiffPushRoute({
|
|
enabled: true,
|
|
serverId,
|
|
cwd,
|
|
subscriptionId,
|
|
compare: { mode: "uncommitted" },
|
|
}),
|
|
});
|
|
const unsubscribe = observer.subscribe(() => {});
|
|
const unmount = mountServerDataPushRouter({ queryClient, client: fake.client, serverId });
|
|
type Payload = SubscribeCheckoutDiffResponseMessage["payload"];
|
|
const files: Payload["files"] = ["a.ts", "b.ts"].map((path) => ({
|
|
path,
|
|
isNew: true,
|
|
isDeleted: false,
|
|
additions: 2,
|
|
deletions: 0,
|
|
hunks: [
|
|
{
|
|
oldStart: 0,
|
|
oldCount: 0,
|
|
newStart: 1,
|
|
newCount: 2,
|
|
lines: [
|
|
{
|
|
type: "add",
|
|
content: "const answer = 42",
|
|
tokens: [
|
|
{ text: "const", style: "keyword" },
|
|
{ text: " answer = 42", style: null },
|
|
],
|
|
},
|
|
{ type: "add", content: "answer", tokens: [{ text: "answer", style: null }] },
|
|
],
|
|
},
|
|
],
|
|
}));
|
|
const publish = (incomingFiles: Payload["files"], requestId: string) => {
|
|
fake.emit({
|
|
type: "subscribe_checkout_diff_response",
|
|
payload: {
|
|
subscriptionId: fake.subscribeCheckoutDiffCalls[0]!.subscriptionId,
|
|
cwd,
|
|
files: incomingFiles,
|
|
requestId,
|
|
error: null,
|
|
},
|
|
});
|
|
return queryClient.getQueryData<Payload>(queryKey)!;
|
|
};
|
|
try {
|
|
const first = publish(files, "first");
|
|
const same = publish(structuredClone(files), "reopen");
|
|
expect(same.files).toBe(first.files);
|
|
expect(same.requestId).toBe("diff:1:ready");
|
|
const changed = structuredClone(files);
|
|
changed[0]!.hunks[0]!.lines[0]!.tokens![0]!.style = "variable";
|
|
const next = publish(changed, "edit");
|
|
expect(next.files[1]).toBe(first.files[1]);
|
|
expect(next.files[0]).toBe(changed[0]);
|
|
expect(next.files[0]!.hunks[0]!.lines[0]!.tokens![0]!.style).toBe("variable");
|
|
expect(publish([], "deleted").files).toEqual([]);
|
|
} finally {
|
|
unsubscribe();
|
|
unmount();
|
|
queryClient.clear();
|
|
}
|
|
});
|
|
|
|
describe("a diff reopened from a retained panel", () => {
|
|
const serverId = "server-1";
|
|
const cwd = "/repo";
|
|
const queryKey = checkoutDiffQueryKey(serverId, cwd, "uncommitted", undefined, false);
|
|
|
|
function diffFile(content: string): DiffFile {
|
|
return {
|
|
path: "README.md",
|
|
isNew: false,
|
|
isDeleted: false,
|
|
additions: 1,
|
|
deletions: 0,
|
|
hunks: [
|
|
{
|
|
oldStart: 1,
|
|
oldCount: 1,
|
|
newStart: 1,
|
|
newCount: 1,
|
|
lines: [{ type: "add", content, tokens: [{ text: content, style: null }] }],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
function loadingState(revision: number): DiffState {
|
|
return {
|
|
phase: "loading",
|
|
revision,
|
|
entries: [
|
|
{
|
|
path: "README.md",
|
|
oldPath: null,
|
|
change: "modified",
|
|
content: "text",
|
|
additions: 1,
|
|
deletions: 0,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
function readyState(revision: number, files: DiffFile[]): DiffState {
|
|
return {
|
|
phase: "ready",
|
|
revision,
|
|
resolved: { baseSha: "", headSha: "", mergeBaseSha: "" },
|
|
files,
|
|
diffTooLarge: false,
|
|
};
|
|
}
|
|
|
|
function mountChangesPanel(fake: ReturnType<typeof createDiffStreamClient>) {
|
|
const queryClient = new QueryClient();
|
|
const route = (enabled: boolean) => ({
|
|
queryKey,
|
|
queryFn: skipToken as typeof skipToken,
|
|
gcTime: Infinity,
|
|
structuralSharing: shareCheckoutDiff,
|
|
meta: checkoutDiffPushRoute({
|
|
enabled,
|
|
serverId,
|
|
cwd,
|
|
subscriptionId: `checkoutDiff:${JSON.stringify(queryKey)}`,
|
|
compare: { mode: "uncommitted", ignoreWhitespace: false },
|
|
}),
|
|
});
|
|
const observer = new QueryObserver(queryClient, route(true));
|
|
const unsubscribeObserver = observer.subscribe(() => {});
|
|
const unmountRouter = mountServerDataPushRouter({
|
|
queryClient,
|
|
client: fake.client,
|
|
serverId,
|
|
});
|
|
return {
|
|
hide: () => observer.setOptions(route(false)),
|
|
show: () => observer.setOptions(route(true)),
|
|
// What the Changes pane renders from.
|
|
diff: () =>
|
|
deriveCheckoutDiffResult(
|
|
queryClient.getQueryData<CheckoutDiffSnapshot>(queryKey) ?? null,
|
|
),
|
|
unmount: () => {
|
|
unsubscribeObserver();
|
|
unmountRouter();
|
|
queryClient.clear();
|
|
},
|
|
};
|
|
}
|
|
|
|
it("keeps the painted diff through the reload when the daemon returns the same content", () => {
|
|
const fake = createDiffStreamClient();
|
|
const panel = mountChangesPanel(fake);
|
|
try {
|
|
fake.latest().publish(readyState(1, [diffFile("same")]));
|
|
const painted = panel.diff().files;
|
|
expect(fake.subscriptions).toHaveLength(1);
|
|
|
|
panel.hide();
|
|
panel.show();
|
|
expect(fake.subscriptions).toHaveLength(2);
|
|
|
|
fake.latest().publish(loadingState(1));
|
|
expect(panel.diff()).toMatchObject({ isLoading: false, entries: null });
|
|
expect(panel.diff().files).toBe(painted);
|
|
|
|
fake.latest().publish(readyState(1, [diffFile("same")]));
|
|
expect(panel.diff().isLoading).toBe(false);
|
|
expect(panel.diff().files).toBe(painted);
|
|
} finally {
|
|
panel.unmount();
|
|
}
|
|
});
|
|
|
|
it("replaces the painted diff in one step when the daemon returns changed content", () => {
|
|
const fake = createDiffStreamClient();
|
|
const panel = mountChangesPanel(fake);
|
|
try {
|
|
fake.latest().publish(readyState(1, [diffFile("before")]));
|
|
const painted = panel.diff().files;
|
|
|
|
panel.hide();
|
|
panel.show();
|
|
fake.latest().publish(loadingState(1));
|
|
expect(panel.diff().isLoading).toBe(false);
|
|
expect(panel.diff().files).toBe(painted);
|
|
|
|
const changed = [diffFile("after")];
|
|
fake.latest().publish(readyState(1, changed));
|
|
expect(panel.diff().isLoading).toBe(false);
|
|
expect(panel.diff().files).toEqual(changed);
|
|
} finally {
|
|
panel.unmount();
|
|
}
|
|
});
|
|
|
|
it("reports loading with the announced entries when no content has been painted", () => {
|
|
const fake = createDiffStreamClient();
|
|
const panel = mountChangesPanel(fake);
|
|
try {
|
|
expect(panel.diff().isLoading).toBe(true);
|
|
|
|
fake.latest().publish(loadingState(1));
|
|
expect(panel.diff()).toMatchObject({ isLoading: true, files: [] });
|
|
expect(panel.diff().entries).toHaveLength(1);
|
|
|
|
fake.latest().publish(readyState(1, [diffFile("first")]));
|
|
expect(panel.diff()).toMatchObject({ isLoading: false, entries: null });
|
|
expect(panel.diff().files).toHaveLength(1);
|
|
} finally {
|
|
panel.unmount();
|
|
}
|
|
});
|
|
});
|
|
|
|
it("routes provider snapshot and daemon config payloads until detached", async () => {
|
|
const queryClient = new QueryClient();
|
|
const fake = createFakeClient();
|
|
const serverId = "server-1";
|
|
const pairingOfferKey = daemonPairingOfferQueryKey(serverId);
|
|
queryClient.setQueryData(pairingOfferKey, { relayEnabled: true, url: "https://pairing" });
|
|
const unmount = mountServerDataPushRouter({ client: fake.client, queryClient, serverId });
|
|
|
|
fake.emit(providerUpdate("2026-01-01T00:00:00.000Z"));
|
|
fake.emit({
|
|
type: "status",
|
|
payload: { status: "daemon_config_changed", config: daemonConfig },
|
|
});
|
|
|
|
await expect
|
|
.poll(() => queryClient.getQueryData(providersSnapshotQueryKey(serverId)))
|
|
.toEqual({
|
|
entries: [{ provider: "codex", status: "ready", enabled: true, models: [] }],
|
|
generatedAt: "2026-01-01T00:00:00.000Z",
|
|
requestId: "providers_snapshot_update",
|
|
});
|
|
expect(queryClient.getQueryData(daemonConfigQueryKey(serverId))).toEqual(daemonConfig);
|
|
expect(queryClient.getQueryState(pairingOfferKey)?.isInvalidated).toBe(true);
|
|
|
|
unmount();
|
|
fake.emit(providerUpdate("2026-01-01T00:00:01.000Z"));
|
|
|
|
await expect
|
|
.poll(() => queryClient.getQueryData(providersSnapshotQueryKey(serverId)))
|
|
.toEqual({
|
|
entries: [{ provider: "codex", status: "ready", enabled: true, models: [] }],
|
|
generatedAt: "2026-01-01T00:00:00.000Z",
|
|
requestId: "providers_snapshot_update",
|
|
});
|
|
});
|
|
|
|
it.each(["base", "all"] as const)("routes %s diff snapshots and updates", (mode) => {
|
|
const queryClient = new QueryClient();
|
|
const fake = createFakeClient();
|
|
const serverId = "server-1";
|
|
const cwd = "/repo";
|
|
const queryKey = checkoutDiffQueryKey(serverId, cwd, mode, "main", true);
|
|
const subscriptionId = `checkoutDiff:${JSON.stringify(queryKey)}`;
|
|
const observer = new QueryObserver(queryClient, {
|
|
queryKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
meta: checkoutDiffPushRoute({
|
|
enabled: true,
|
|
serverId,
|
|
subscriptionId,
|
|
cwd,
|
|
compare: { mode, baseRef: "main", ignoreWhitespace: true },
|
|
}),
|
|
});
|
|
const unsubscribeObserver = observer.subscribe(() => undefined);
|
|
const unmount = mountServerDataPushRouter({ client: fake.client, queryClient, serverId });
|
|
|
|
expect(fake.subscribeCheckoutDiffCalls).toHaveLength(1);
|
|
const serverSubscriptionId = fake.subscribeCheckoutDiffCalls[0]!.subscriptionId;
|
|
expect(fake.subscribeCheckoutDiffCalls).toEqual([
|
|
{
|
|
cwd,
|
|
compare: { mode, baseRef: "main", ignoreWhitespace: true },
|
|
subscriptionId: serverSubscriptionId,
|
|
},
|
|
]);
|
|
|
|
fake.emit({
|
|
type: "subscribe_checkout_diff_response",
|
|
payload: {
|
|
subscriptionId: serverSubscriptionId,
|
|
cwd,
|
|
files: [],
|
|
error: null,
|
|
diffTooLarge: true,
|
|
requestId: "diff-1",
|
|
},
|
|
});
|
|
|
|
expect(queryClient.getQueryData(queryKey)).toEqual({
|
|
cwd,
|
|
files: [],
|
|
error: null,
|
|
diffTooLarge: true,
|
|
requestId: "diff:1:ready",
|
|
});
|
|
|
|
fake.emit({
|
|
type: "checkout_diff_update",
|
|
payload: {
|
|
subscriptionId: serverSubscriptionId,
|
|
cwd,
|
|
files: [],
|
|
error: null,
|
|
diffTooLarge: true,
|
|
},
|
|
});
|
|
|
|
expect(queryClient.getQueryData(queryKey)).toEqual({
|
|
cwd,
|
|
files: [],
|
|
error: null,
|
|
diffTooLarge: true,
|
|
requestId: "diff:2:ready",
|
|
});
|
|
|
|
unsubscribeObserver();
|
|
|
|
expect(fake.unsubscribeCheckoutDiffCalls).toEqual([serverSubscriptionId]);
|
|
|
|
unmount();
|
|
});
|
|
|
|
it("does not retry failed subscriptions on unrelated cache events", async () => {
|
|
const queryClient = new QueryClient();
|
|
const fake = createFakeClient({ rejectCheckoutDiffSubscribe: true });
|
|
const serverId = "server-1";
|
|
const cwd = "/repo";
|
|
const queryKey = checkoutDiffQueryKey(serverId, cwd, "base", "main", true);
|
|
const subscriptionId = `checkoutDiff:${JSON.stringify(queryKey)}`;
|
|
const observer = new QueryObserver(queryClient, {
|
|
queryKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
meta: checkoutDiffPushRoute({
|
|
enabled: true,
|
|
serverId,
|
|
subscriptionId,
|
|
cwd,
|
|
compare: { mode: "base", baseRef: "main", ignoreWhitespace: true },
|
|
}),
|
|
});
|
|
const unsubscribeObserver = observer.subscribe(() => undefined);
|
|
const unmount = mountServerDataPushRouter({ client: fake.client, queryClient, serverId });
|
|
|
|
expect(fake.subscribeCheckoutDiffCalls).toHaveLength(1);
|
|
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
|
|
queryClient.setQueryData(["unrelated"], "value");
|
|
|
|
expect(fake.subscribeCheckoutDiffCalls).toHaveLength(1);
|
|
|
|
unsubscribeObserver();
|
|
unmount();
|
|
});
|
|
|
|
it("subscribes active terminal queries and filters terminal pushes by workspace", () => {
|
|
const queryClient = new QueryClient();
|
|
const fake = createFakeClient();
|
|
const serverId = "server-1";
|
|
const cwd = "/repo";
|
|
const workspaceId = "workspace-a";
|
|
const queryKey = buildTerminalsQueryKey(serverId, cwd, workspaceId);
|
|
const observer = new QueryObserver(queryClient, {
|
|
queryKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
meta: workspaceTerminalsPushRoute({
|
|
enabled: true,
|
|
serverId,
|
|
cwd,
|
|
workspaceId,
|
|
}),
|
|
});
|
|
const unsubscribeObserver = observer.subscribe(() => undefined);
|
|
const unmount = mountServerDataPushRouter({ client: fake.client, queryClient, serverId });
|
|
|
|
expect(fake.subscribeTerminalCalls).toEqual([{ cwd, workspaceId }]);
|
|
|
|
fake.emit({
|
|
type: "terminals_changed",
|
|
payload: {
|
|
cwd,
|
|
terminals: [
|
|
{ id: "terminal-a", name: "Main", workspaceId },
|
|
{ id: "terminal-b", name: "Sibling", workspaceId: "workspace-b" },
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(queryClient.getQueryData(queryKey)).toEqual({
|
|
cwd,
|
|
terminals: [{ id: "terminal-a", name: "Main", workspaceId }],
|
|
requestId: expect.stringMatching(/^terminals-changed-/),
|
|
});
|
|
|
|
unsubscribeObserver();
|
|
|
|
expect(fake.unsubscribeTerminalCalls).toEqual([{ cwd, workspaceId }]);
|
|
|
|
unmount();
|
|
});
|
|
|
|
it("keeps stable handles while invalidating caches after reconnect", () => {
|
|
const queryClient = new QueryClient();
|
|
const fake = createFakeClient();
|
|
const serverId = "server-1";
|
|
const cwd = "/repo";
|
|
const workspaceId = "workspace-a";
|
|
const checkoutDiffKey = checkoutDiffQueryKey(serverId, cwd, "base", "main", true);
|
|
const checkoutDiffSubscriptionId = `checkoutDiff:${JSON.stringify(checkoutDiffKey)}`;
|
|
const terminalKey = buildTerminalsQueryKey(serverId, cwd, workspaceId);
|
|
const checkoutDiffObserver = new QueryObserver(queryClient, {
|
|
queryKey: checkoutDiffKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
meta: checkoutDiffPushRoute({
|
|
enabled: true,
|
|
serverId,
|
|
subscriptionId: checkoutDiffSubscriptionId,
|
|
cwd,
|
|
compare: { mode: "base", baseRef: "main", ignoreWhitespace: true },
|
|
}),
|
|
});
|
|
const terminalObserver = new QueryObserver(queryClient, {
|
|
queryKey: terminalKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
meta: workspaceTerminalsPushRoute({
|
|
enabled: true,
|
|
serverId,
|
|
cwd,
|
|
workspaceId,
|
|
}),
|
|
});
|
|
const unsubscribeCheckoutDiffObserver = checkoutDiffObserver.subscribe(() => undefined);
|
|
const unsubscribeTerminalObserver = terminalObserver.subscribe(() => undefined);
|
|
const unmount = mountServerDataPushRouter({ client: fake.client, queryClient, serverId });
|
|
const plainCheckoutDiffObserver = new QueryObserver(queryClient, {
|
|
queryKey: checkoutDiffKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
});
|
|
const plainTerminalObserver = new QueryObserver(queryClient, {
|
|
queryKey: terminalKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
});
|
|
const unsubscribePlainCheckoutDiffObserver = plainCheckoutDiffObserver.subscribe(
|
|
() => undefined,
|
|
);
|
|
const unsubscribePlainTerminalObserver = plainTerminalObserver.subscribe(() => undefined);
|
|
|
|
invalidateServerDataQueriesAfterReconnect({ queryClient, serverId });
|
|
|
|
expect(fake.subscribeCheckoutDiffCalls).toEqual([
|
|
{
|
|
cwd,
|
|
compare: { mode: "base", baseRef: "main", ignoreWhitespace: true },
|
|
subscriptionId: "server-diff-1",
|
|
},
|
|
]);
|
|
expect(fake.subscribeTerminalCalls).toEqual([{ cwd, workspaceId }]);
|
|
|
|
fake.emit({
|
|
type: "terminals_changed",
|
|
payload: {
|
|
cwd,
|
|
terminals: [
|
|
{ id: "terminal-a", name: "Main", workspaceId },
|
|
{ id: "terminal-b", name: "Sibling", workspaceId: "workspace-b" },
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(queryClient.getQueryData(terminalKey)).toEqual({
|
|
cwd,
|
|
terminals: [{ id: "terminal-a", name: "Main", workspaceId }],
|
|
requestId: expect.stringMatching(/^terminals-changed-/),
|
|
});
|
|
|
|
unsubscribePlainCheckoutDiffObserver();
|
|
unsubscribePlainTerminalObserver();
|
|
unsubscribeCheckoutDiffObserver();
|
|
unsubscribeTerminalObserver();
|
|
unmount();
|
|
});
|
|
|
|
it("routes terminal pushes after another observer attaches without push metadata", () => {
|
|
const queryClient = new QueryClient();
|
|
const fake = createFakeClient();
|
|
const serverId = "server-1";
|
|
const cwd = "/repo";
|
|
const workspaceId = "workspace-a";
|
|
const queryKey = buildTerminalsQueryKey(serverId, cwd, workspaceId);
|
|
const pushObserver = new QueryObserver(queryClient, {
|
|
queryKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
meta: workspaceTerminalsPushRoute({
|
|
enabled: true,
|
|
serverId,
|
|
cwd,
|
|
workspaceId,
|
|
}),
|
|
});
|
|
const unsubscribePushObserver = pushObserver.subscribe(() => undefined);
|
|
const unmount = mountServerDataPushRouter({ client: fake.client, queryClient, serverId });
|
|
expect(fake.subscribeTerminalCalls).toEqual([{ cwd, workspaceId }]);
|
|
|
|
const plainObserver = new QueryObserver(queryClient, {
|
|
queryKey,
|
|
queryFn: skipToken,
|
|
enabled: true,
|
|
gcTime: Infinity,
|
|
staleTime: Infinity,
|
|
});
|
|
const unsubscribePlainObserver = plainObserver.subscribe(() => undefined);
|
|
|
|
fake.emit({
|
|
type: "terminals_changed",
|
|
payload: {
|
|
cwd,
|
|
terminals: [
|
|
{
|
|
id: "terminal-a",
|
|
name: "Main",
|
|
workspaceId,
|
|
activity: { state: "idle", attentionReason: "needs_input", changedAt: 1 },
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(queryClient.getQueryData(queryKey)).toEqual({
|
|
cwd,
|
|
terminals: [
|
|
{
|
|
id: "terminal-a",
|
|
name: "Main",
|
|
workspaceId,
|
|
activity: { state: "idle", attentionReason: "needs_input", changedAt: 1 },
|
|
},
|
|
],
|
|
requestId: expect.stringMatching(/^terminals-changed-/),
|
|
});
|
|
expect(fake.unsubscribeTerminalCalls).toEqual([]);
|
|
|
|
unsubscribePlainObserver();
|
|
unsubscribePushObserver();
|
|
unmount();
|
|
});
|
|
|
|
it("invalidates only the reconnect-repair scopes for one server", () => {
|
|
const queryClient = new QueryClient();
|
|
const serverId = "server-1";
|
|
const otherServerId = "server-2";
|
|
const providerKey = providersSnapshotQueryKey(serverId);
|
|
const daemonConfigKey = daemonConfigQueryKey(serverId);
|
|
const pairingOfferKey = daemonPairingOfferQueryKey(serverId);
|
|
const diffKey = checkoutDiffQueryKey(serverId, "/repo", "uncommitted", undefined, false);
|
|
const terminalKey = buildTerminalsQueryKey(serverId, "/repo", "workspace-a");
|
|
const otherProviderKey = providersSnapshotQueryKey(otherServerId);
|
|
|
|
queryClient.setQueryData(providerKey, { entries: [], generatedAt: "now", requestId: "p" });
|
|
queryClient.setQueryData(daemonConfigKey, daemonConfig);
|
|
queryClient.setQueryData(pairingOfferKey, { relayEnabled: false, url: "" });
|
|
queryClient.setQueryData(diffKey, { cwd: "/repo", files: [], error: null, requestId: "d" });
|
|
queryClient.setQueryData(terminalKey, { cwd: "/repo", terminals: [], requestId: "t" });
|
|
queryClient.setQueryData(otherProviderKey, {
|
|
entries: [],
|
|
generatedAt: "now",
|
|
requestId: "other",
|
|
});
|
|
|
|
invalidateServerDataQueriesAfterReconnect({ queryClient, serverId });
|
|
|
|
expect(queryClient.getQueryState(providerKey)?.isInvalidated).toBe(true);
|
|
expect(queryClient.getQueryState(daemonConfigKey)?.isInvalidated).toBe(true);
|
|
expect(queryClient.getQueryState(pairingOfferKey)?.isInvalidated).toBe(true);
|
|
expect(queryClient.getQueryState(diffKey)?.isInvalidated).toBe(true);
|
|
expect(queryClient.getQueryState(terminalKey)?.isInvalidated).toBe(true);
|
|
expect(queryClient.getQueryState(otherProviderKey)?.isInvalidated).toBe(false);
|
|
});
|
|
});
|