Files
paseo/packages/app/src/hooks/use-checkout-pr-status-query.ts
T
Mohamed Boudra 8b1fbb7b86 Merge branch 'main' into dev
Resolve 38 conflicts preserving both sides' work:
- Server: DB-based registries (dev) + WorkspaceGitService/DaemonConfigStore (main)
- App: script proxy/voice MCP (dev) + pin/hide tabs/bootstrap boundary (main)
- Shared: WorkspaceScriptPayload (dev) + git/GitHub runtime schemas (main)
- Deleted workspace-registry-bootstrap.ts (dev's DB approach supersedes)
2026-04-14 03:05:45 +07:00

126 lines
3.5 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import type { CheckoutPrStatusResponse } from "@server/shared/messages";
const CHECKOUT_PR_STATUS_STALE_TIME = 20_000;
const WORKSPACE_PR_HINT_REFETCH_INTERVAL = 60_000;
function checkoutPrStatusQueryKey(serverId: string, cwd: string) {
return ["checkoutPrStatus", serverId, cwd] as const;
}
interface UseCheckoutPrStatusQueryOptions {
serverId: string;
cwd: string;
enabled?: boolean;
}
export type CheckoutPrStatusPayload = CheckoutPrStatusResponse["payload"];
export interface PrHint {
url: string;
number: number;
state: "open" | "merged" | "closed";
checks?: Array<{ name: string; status: string; url: string | null }>;
checksStatus?: "none" | "pending" | "success" | "failure";
reviewDecision?: "approved" | "changes_requested" | "pending" | null;
}
function parsePullRequestNumber(url: string): number | null {
try {
const pathname = new URL(url).pathname;
const match = pathname.match(/\/pull\/(\d+)(?:\/|$)/);
if (!match) {
return null;
}
const number = Number.parseInt(match[1], 10);
return Number.isFinite(number) ? number : null;
} catch {
return null;
}
}
function selectWorkspacePrHint(payload: CheckoutPrStatusPayload): PrHint | null {
const status = payload.status;
if (!status?.url) {
return null;
}
const number = parsePullRequestNumber(status.url);
if (number === null) {
return null;
}
return {
url: status.url,
number,
state:
status.isMerged || status.state === "merged"
? "merged"
: status.state === "open"
? "open"
: "closed",
checks: status.checks,
checksStatus: status.checksStatus as PrHint["checksStatus"],
reviewDecision: status.reviewDecision as PrHint["reviewDecision"],
};
}
export function useCheckoutPrStatusQuery({
serverId,
cwd,
enabled = true,
}: UseCheckoutPrStatusQueryOptions) {
const client = useHostRuntimeClient(serverId);
const isConnected = useHostRuntimeIsConnected(serverId);
const query = useQuery({
queryKey: checkoutPrStatusQueryKey(serverId, cwd),
queryFn: async () => {
if (!client) {
throw new Error("Daemon client not available");
}
return await client.checkoutPrStatus(cwd);
},
enabled: !!client && isConnected && !!cwd && enabled,
staleTime: CHECKOUT_PR_STATUS_STALE_TIME,
refetchInterval: 15_000,
});
return {
status: query.data?.status ?? null,
githubFeaturesEnabled: query.data?.githubFeaturesEnabled ?? true,
payloadError: query.data?.error ?? null,
isLoading: query.isLoading,
isFetching: query.isFetching,
isError: query.isError,
error: query.error,
refresh: query.refetch,
};
}
export function useWorkspacePrHint({
serverId,
cwd,
enabled = true,
}: UseCheckoutPrStatusQueryOptions): PrHint | null {
const client = useHostRuntimeClient(serverId);
const isConnected = useHostRuntimeIsConnected(serverId);
const query = useQuery<CheckoutPrStatusPayload, Error, PrHint | null>({
queryKey: checkoutPrStatusQueryKey(serverId, cwd),
queryFn: async () => {
if (!client) {
throw new Error("Daemon client not available");
}
return await client.checkoutPrStatus(cwd);
},
enabled: !!client && isConnected && !!cwd && enabled,
staleTime: CHECKOUT_PR_STATUS_STALE_TIME,
refetchInterval: WORKSPACE_PR_HINT_REFETCH_INTERVAL,
select: selectWorkspacePrHint,
});
return query.data ?? null;
}