From 08fbe8405bc4421441a7ab0b17b7e35b461e698e Mon Sep 17 00:00:00 2001 From: mawkone Date: Thu, 11 Jun 2026 17:07:17 -0700 Subject: [PATCH] feat(preview): add 1-click start dev server button to empty state --- .../[projectId]/(home)/preview/page.tsx | 46 ++++++++++++++++--- .../[projectId]/dev-server/ensure/route.ts | 14 +++--- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx index 04c55d8..50518bb 100644 --- a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx +++ b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/preview/page.tsx @@ -174,6 +174,8 @@ export default function PreviewTab() { bridge.registerPreviewIframe(iframeDomRef.current, iframeSrc); }, [bridge, iframeSrc]); + const [isForceStarting, setIsForceStarting] = useState(false); + // Determine which empty state to show. const emptyContent = (() => { if (loading && !anatomy) return ; @@ -190,7 +192,11 @@ export default function PreviewTab() { /> ); } - if (ensureStatus === "calling" || ensureStatus === "starting") { + if ( + ensureStatus === "calling" || + ensureStatus === "starting" || + isForceStarting + ) { return ( ; + return ( + { + setIsForceStarting(true); + fetch( + `/api/projects/${projectId}/dev-server/ensure?forceStart=true`, + { + method: "POST", + }, + ).catch(() => setIsForceStarting(false)); + }} + /> + ); })(); return ( @@ -421,7 +439,7 @@ function FailedState({ ); } -function NotRunningState() { +function NotRunningState({ onStart }: { onStart: () => void }) { return (

Preview not running

No dev server found on port 3000.

-

- Ask the AI to start the dev server. -

+
); } diff --git a/vibn-frontend/app/api/projects/[projectId]/dev-server/ensure/route.ts b/vibn-frontend/app/api/projects/[projectId]/dev-server/ensure/route.ts index d2ad713..7ce681d 100644 --- a/vibn-frontend/app/api/projects/[projectId]/dev-server/ensure/route.ts +++ b/vibn-frontend/app/api/projects/[projectId]/dev-server/ensure/route.ts @@ -23,7 +23,7 @@ import { } from "@/lib/dev-container"; export async function POST( - _req: Request, + request: Request, { params }: { params: Promise<{ projectId: string }> }, ) { const { projectId } = await params; @@ -96,7 +96,10 @@ export async function POST( [projectId], ); - if (!last) { + const forceStart = + new URL(request.url).searchParams.get("forceStart") === "true"; + + if (!last && !forceStart) { return NextResponse.json({ status: "no_history" }); } @@ -111,13 +114,12 @@ export async function POST( } // 4. Fire restart in background — don't block the response. - // The probe (up to 300s) runs in background; anatomy polling at 5s - // will surface state='starting' immediately, then 'running' when ready. + // If forceStart is true but we have no history, default to Next.js start command. const restartOpts = { projectId: project.id, projectSlug: project.slug, - command: last.command, - port: last.port, + command: last?.command || "next dev -H 0.0.0.0 --no-turbopack", + port: last?.port || 3000, workspace, };