import type { QueryClient } from "@tanstack/react-query"; import type { CheckoutStatusResponse, CheckoutStatusUpdate } from "@getpaseo/protocol/messages"; import equal from "fast-deep-equal/es6"; import { checkoutCommitsQueryKey, checkoutPrStatusQueryKey, checkoutStatusQueryKey, invalidatePrPaneTimelineForCheckout, } from "@/git/query-keys"; import { type CheckoutPrStatusPayload, normalizeCheckoutPrStatusPayload } from "@/git/pr-status"; import { resetDraftAgentCommandsForCheckout } from "@/agent-commands/cache"; export type CheckoutStatusPayload = CheckoutStatusResponse["payload"]; export type { CheckoutPrStatusPayload } from "@/git/pr-status"; export interface CheckoutStatusClient { getCheckoutStatus: (cwd: string) => Promise; } export async function fetchCheckoutStatus({ client, cwd, }: { client: CheckoutStatusClient; serverId: string; cwd: string; }): Promise { const payload = normalizeCheckoutBaseRef(await client.getCheckoutStatus(cwd)); return payload; } export async function ensureCheckoutStatus({ queryClient, client, serverId, cwd, }: { queryClient: QueryClient; client: CheckoutStatusClient; serverId: string; cwd: string; }): Promise { return await queryClient.fetchQuery({ queryKey: checkoutStatusQueryKey(serverId, cwd), queryFn: () => fetchCheckoutStatus({ client, serverId, cwd }), staleTime: Infinity, }); } export function applyCheckoutStatusUpdateFromEvent({ queryClient, serverId, message, }: { queryClient: QueryClient; serverId: string; message: CheckoutStatusUpdate; }): void { const payload = normalizeCheckoutBaseRef(message.payload); const prStatus = payload.prStatus ? normalizeCheckoutPrStatusPayload(payload.prStatus) : undefined; const cachePayload = prStatus ? { ...payload, prStatus } : payload; const previousStatus = queryClient.getQueryData( checkoutStatusQueryKey(serverId, payload.cwd), ); queryClient.setQueryData(checkoutStatusQueryKey(serverId, payload.cwd), cachePayload); if (previousStatus?.currentBranch !== payload.currentBranch) { void resetDraftAgentCommandsForCheckout(queryClient, { serverId, cwd: payload.cwd }); } void queryClient.invalidateQueries({ queryKey: checkoutCommitsQueryKey(serverId, payload.cwd), }); if (!prStatus) { return; } const previous = queryClient.getQueryData( checkoutPrStatusQueryKey(serverId, prStatus.cwd), ); queryClient.setQueryData(checkoutPrStatusQueryKey(serverId, prStatus.cwd), prStatus); // The PR activity timeline has no push channel; mark it stale when the pushed PR status // meaningfully changed. Active panes refetch immediately, evicted ones on next mount. if (hasPrStatusChanged(previous, prStatus)) { void invalidatePrPaneTimelineForCheckout(queryClient, { serverId, cwd: prStatus.cwd }); } } function normalizeCheckoutBaseRef(payload: T): T { // COMPAT(workspaceBaseBranch): hosts <= v0.11.2 only send the display ref; remove after 2027-10-14. return !payload.isGit || payload.baseRefExact === undefined ? payload : { ...payload, baseRef: payload.baseRefExact }; } // requestId changes on every emission and carries no PR state. function prStatusWithoutVolatileFields( prStatus: CheckoutPrStatusPayload, ): Omit { const { requestId: _requestId, ...rest } = prStatus; return rest; } function hasPrStatusChanged( previous: CheckoutPrStatusPayload | undefined, next: CheckoutPrStatusPayload, ): boolean { if (!previous) { return true; } return !equal(prStatusWithoutVolatileFields(previous), prStatusWithoutVolatileFields(next)); }