fix: Use SSE events instead of polling for preview status updates
httpBatchLink batches polling queries behind the long-running startPreview mutation, so refetchInterval never fires independently. Replace polling with preview: event invalidation via the existing useLiveUpdates SSE subscription — preview:building/ready/stopped/failed events now trigger listPreviews and getPreviewStatus invalidation.
This commit is contained in:
@@ -53,20 +53,14 @@ export function InitiativeReview({ initiativeId, onCompleted }: InitiativeReview
|
||||
const projectsQuery = trpc.getInitiativeProjects.useQuery({ initiativeId });
|
||||
const firstProjectId = projectsQuery.data?.[0]?.id ?? null;
|
||||
|
||||
const previewsQuery = trpc.listPreviews.useQuery(
|
||||
{ initiativeId },
|
||||
{ refetchInterval: 3000 },
|
||||
);
|
||||
const previewsQuery = trpc.listPreviews.useQuery({ initiativeId });
|
||||
const existingPreview = previewsQuery.data?.find(
|
||||
(p) => p.initiativeId === initiativeId,
|
||||
);
|
||||
const [activePreviewId, setActivePreviewId] = useState<string | null>(null);
|
||||
const previewStatusQuery = trpc.getPreviewStatus.useQuery(
|
||||
{ previewId: activePreviewId ?? existingPreview?.id ?? "" },
|
||||
{
|
||||
enabled: !!(activePreviewId ?? existingPreview?.id),
|
||||
refetchInterval: 3000,
|
||||
},
|
||||
{ enabled: !!(activePreviewId ?? existingPreview?.id) },
|
||||
);
|
||||
const preview = previewStatusQuery.data ?? existingPreview;
|
||||
|
||||
|
||||
@@ -45,14 +45,17 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) {
|
||||
|
||||
// Fetch phases for this initiative
|
||||
const phasesQuery = trpc.listPhases.useQuery({ initiativeId });
|
||||
const pendingReviewPhases = useMemo(
|
||||
() => (phasesQuery.data ?? []).filter((p) => p.status === "pending_review"),
|
||||
const reviewablePhases = useMemo(
|
||||
() => (phasesQuery.data ?? []).filter((p) => p.status === "pending_review" || p.status === "completed"),
|
||||
[phasesQuery.data],
|
||||
);
|
||||
|
||||
// Select first pending review phase
|
||||
// Select first pending review phase, falling back to completed phases
|
||||
const [selectedPhaseId, setSelectedPhaseId] = useState<string | null>(null);
|
||||
const activePhaseId = selectedPhaseId ?? pendingReviewPhases[0]?.id ?? null;
|
||||
const defaultPhaseId = reviewablePhases.find((p) => p.status === "pending_review")?.id ?? reviewablePhases[0]?.id ?? null;
|
||||
const activePhaseId = selectedPhaseId ?? defaultPhaseId;
|
||||
const activePhase = reviewablePhases.find((p) => p.id === activePhaseId);
|
||||
const isActivePhaseCompleted = activePhase?.status === "completed";
|
||||
|
||||
// Fetch projects for this initiative (needed for preview)
|
||||
const projectsQuery = trpc.getInitiativeProjects.useQuery({ initiativeId });
|
||||
@@ -78,20 +81,14 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) {
|
||||
);
|
||||
|
||||
// Preview state
|
||||
const previewsQuery = trpc.listPreviews.useQuery(
|
||||
{ initiativeId },
|
||||
{ refetchInterval: 3000 },
|
||||
);
|
||||
const previewsQuery = trpc.listPreviews.useQuery({ initiativeId });
|
||||
const existingPreview = previewsQuery.data?.find(
|
||||
(p) => p.phaseId === activePhaseId || p.initiativeId === initiativeId,
|
||||
);
|
||||
const [activePreviewId, setActivePreviewId] = useState<string | null>(null);
|
||||
const previewStatusQuery = trpc.getPreviewStatus.useQuery(
|
||||
{ previewId: activePreviewId ?? existingPreview?.id ?? "" },
|
||||
{
|
||||
enabled: !!(activePreviewId ?? existingPreview?.id),
|
||||
refetchInterval: 3000,
|
||||
},
|
||||
{ enabled: !!(activePreviewId ?? existingPreview?.id) },
|
||||
);
|
||||
const preview = previewStatusQuery.data ?? existingPreview;
|
||||
const sourceBranch = diffQuery.data?.sourceBranch ?? commitsQuery.data?.sourceBranch ?? "";
|
||||
@@ -263,7 +260,7 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) {
|
||||
|
||||
const activePhaseName =
|
||||
diffQuery.data?.phaseName ??
|
||||
pendingReviewPhases.find((p) => p.id === activePhaseId)?.name ??
|
||||
reviewablePhases.find((p) => p.id === activePhaseId)?.name ??
|
||||
"Phase";
|
||||
|
||||
// All files from the full branch diff (for sidebar file list)
|
||||
@@ -285,7 +282,7 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) {
|
||||
);
|
||||
}
|
||||
|
||||
if (pendingReviewPhases.length === 0) {
|
||||
if (reviewablePhases.length === 0) {
|
||||
return (
|
||||
<div className="flex h-64 items-center justify-center text-muted-foreground">
|
||||
<p>No phases pending review</p>
|
||||
@@ -297,8 +294,9 @@ export function ReviewTab({ initiativeId }: ReviewTabProps) {
|
||||
<div className="rounded-lg border border-border bg-card">
|
||||
{/* Header: phase selector + toolbar */}
|
||||
<ReviewHeader
|
||||
phases={pendingReviewPhases.map((p) => ({ id: p.id, name: p.name }))}
|
||||
phases={reviewablePhases.map((p) => ({ id: p.id, name: p.name, status: p.status }))}
|
||||
activePhaseId={activePhaseId}
|
||||
isReadOnly={isActivePhaseCompleted}
|
||||
onPhaseSelect={handlePhaseSelect}
|
||||
phaseName={activePhaseName}
|
||||
sourceBranch={sourceBranch}
|
||||
|
||||
@@ -33,6 +33,7 @@ function InitiativeDetailPage() {
|
||||
{ prefix: 'phase:', invalidate: ['listPhases', 'listTasks', 'listInitiativePhaseDependencies'] },
|
||||
{ prefix: 'agent:', invalidate: ['listAgents', 'getActiveRefineAgent'] },
|
||||
{ prefix: 'page:', invalidate: ['listPages', 'getPage', 'getRootPage'] },
|
||||
{ prefix: 'preview:', invalidate: ['listPreviews', 'getPreviewStatus'] },
|
||||
]);
|
||||
|
||||
// tRPC queries
|
||||
|
||||
Reference in New Issue
Block a user