@@ -267,7 +267,7 @@ jobs:
267267 - run : bun run --cwd packages/core build:hyperframes-runtime
268268 - name : Start studio and check for runtime errors
269269 run : |
270- # Start the studio dev server in the background
270+ # Start the studio Vite dev server (fast — no bundle step)
271271 bun run --filter '@hyperframes/studio' dev -- --port 5199 &
272272 SERVER_PID=$!
273273
@@ -283,8 +283,8 @@ jobs:
283283 exit 1
284284 fi
285285
286- # Load the studio in headless Chrome and capture console errors
287- # puppeteer is a dependency of @hyperframes/producer; resolve from there
286+ # Load the studio in headless Chrome with API mocking to trigger
287+ # the full splash→main transition (catches hooks-after-early-return bugs)
288288 cd packages/producer
289289 node --input-type=module <<'SMOKE_EOF'
290290 import puppeteer from "puppeteer";
@@ -298,18 +298,55 @@ jobs:
298298 page.on("console", (msg) => {
299299 if (msg.type() === "error") errors.push(msg.text());
300300 });
301- await page.goto("http://localhost:5199/", { waitUntil: "networkidle0", timeout: 30000 });
301+
302+ // Mock the project API so the studio transitions past the splash screen.
303+ // Without this, useServerConnection stays in "waiting" and the full React
304+ // tree (with all hooks) never renders — missing hooks-order violations.
305+ await page.setRequestInterception(true);
306+ page.on("request", (req) => {
307+ const url = req.url();
308+ if (url.includes("/api/projects")) {
309+ req.respond({
310+ status: 200,
311+ contentType: "application/json",
312+ body: JSON.stringify({ projects: [{ id: "smoke-test" }] }),
313+ });
314+ } else if (url.includes("/api/") && !url.includes("/__vite")) {
315+ req.respond({ status: 200, contentType: "application/json", body: "{}" });
316+ } else {
317+ req.continue();
318+ }
319+ });
320+
321+ await page.goto("http://localhost:5199/#project=smoke-test", {
322+ waitUntil: "networkidle0",
323+ timeout: 30000,
324+ });
325+ // Wait for React to render past splash into the full studio UI
302326 await new Promise((r) => setTimeout(r, 3000));
327+
328+ // Check for React error boundary (catches hooks violations, render crashes)
329+ const errorBoundary = await page.evaluate(() => {
330+ const text = document.body.innerText;
331+ if (text.includes("Something went wrong")) return text;
332+ return null;
333+ });
334+ if (errorBoundary) {
335+ errors.push("React error boundary triggered: " + errorBoundary);
336+ }
303337 await browser.close();
304338 const fatal = errors.filter(
305- (e) => !e.includes("favicon") && !e.includes("ERR_CONNECTION_REFUSED"),
339+ (e) =>
340+ !e.includes("favicon") &&
341+ !e.includes("ERR_CONNECTION_REFUSED") &&
342+ !e.includes("Failed to fetch"),
306343 );
307344 if (fatal.length > 0) {
308- console.error("FAIL: studio had runtime errors on load :");
345+ console.error("FAIL: studio had runtime errors:");
309346 for (const e of fatal) console.error(" •", e);
310347 process.exit(1);
311348 }
312- console.log("PASS: studio loaded without runtime errors");
349+ console.log("PASS: studio loaded and transitioned without runtime errors");
313350 SMOKE_EOF
314351
315352 kill $SERVER_PID 2>/dev/null || true
0 commit comments