From 4d659064efa4779a43bceba1b9136221a77db7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Sun, 29 Mar 2026 08:16:05 +0200 Subject: [PATCH] fix(studio): show clear error when render server is unreachable (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Surfaces error message when render fails due to producer server not running - Two cases covered: initial POST failure and SSE connection drop - Failed jobs now show the error reason in red text in the Renders panel ## Test plan - [x] Start studio without producer server - [x] Click render → should show "Could not reach render server" in red - [x] Start render then kill producer → should show "Connection lost" 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .../components/renders/RenderQueueItem.tsx | 4 ++ .../src/components/renders/useRenderQueue.ts | 46 ++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/studio/src/components/renders/RenderQueueItem.tsx b/packages/studio/src/components/renders/RenderQueueItem.tsx index 467c85de0..a0925ef57 100644 --- a/packages/studio/src/components/renders/RenderQueueItem.tsx +++ b/packages/studio/src/components/renders/RenderQueueItem.tsx @@ -78,6 +78,10 @@ export const RenderQueueItem = memo(function RenderQueueItem({ )} + {job.status === "failed" && job.error && ( + {job.error} + )} + {job.status !== "rendering" && ( {formatTimeAgo(job.createdAt)} )} diff --git a/packages/studio/src/components/renders/useRenderQueue.ts b/packages/studio/src/components/renders/useRenderQueue.ts index 92e810648..190079c5f 100644 --- a/packages/studio/src/components/renders/useRenderQueue.ts +++ b/packages/studio/src/components/renders/useRenderQueue.ts @@ -5,6 +5,7 @@ export interface RenderJob { status: "rendering" | "complete" | "failed" | "cancelled"; progress: number; stage?: string; + error?: string; filename: string; createdAt: number; durationMs?: number; @@ -62,12 +63,37 @@ export function useRenderQueue(projectId: string | null) { if (!projectId) return; const startTime = Date.now(); - const res = await fetch(`/api/projects/${projectId}/render`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ fps, quality, format }), - }); - if (!res.ok) return; + let res: Response; + try { + res = await fetch(`/api/projects/${projectId}/render`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ fps, quality, format }), + }); + } catch { + const failedJob: RenderJob = { + id: crypto.randomUUID(), + status: "failed", + progress: 0, + error: "Could not reach render server. Use `hyperframes render` from the CLI instead.", + filename: "Export failed", + createdAt: startTime, + }; + setJobs((prev) => [...prev, failedJob]); + return; + } + if (!res.ok) { + const failedJob: RenderJob = { + id: crypto.randomUUID(), + status: "failed", + progress: 0, + error: `Server error (${res.status}). Check the terminal for details.`, + filename: "Export failed", + createdAt: startTime, + }; + setJobs((prev) => [...prev, failedJob]); + return; + } const { jobId } = await res.json(); const ext = format === "webm" ? ".webm" : ".mp4"; @@ -119,7 +145,13 @@ export function useRenderQueue(projectId: string | null) { es.close(); setJobs((prev) => prev.map((j) => - j.id === jobId && j.status === "rendering" ? { ...j, status: "failed" } : j, + j.id === jobId && j.status === "rendering" + ? { + ...j, + status: "failed" as const, + error: "Connection lost. Is the render server running?", + } + : j, ), ); activeJobRef.current = null;