fix(studio): show clear error when render server is unreachable (#116)

## 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)
This commit is contained in:
Miguel Ángel
2026-03-29 08:16:05 +02:00
committed by GitHub
parent 05f28d9ba4
commit 4d659064ef
2 changed files with 43 additions and 7 deletions
@@ -78,6 +78,10 @@ export const RenderQueueItem = memo(function RenderQueueItem({
</div>
)}
{job.status === "failed" && job.error && (
<span className="text-[9px] text-red-400 mt-0.5 block">{job.error}</span>
)}
{job.status !== "rendering" && (
<span className="text-[9px] text-neutral-600">{formatTimeAgo(job.createdAt)}</span>
)}
@@ -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;