fix(preview): ignore stale ghost dev servers in auto-restarter; cap elapsed timer

This commit is contained in:
2026-06-11 11:20:19 -07:00
parent d165ab9de1
commit ca0ae32a21
2 changed files with 22 additions and 6 deletions

View File

@@ -19,7 +19,7 @@ function sandboxIframe(src: string, origin: string): boolean {
}
}
/** Elapsed time since an ISO string, formatted as "1m 23s". */
/** Elapsed time since an ISO string, formatted as "1m 23s". Capped at 59m 59s. */
function useElapsed(sinceIso: string | undefined) {
const [elapsed, setElapsed] = useState("");
useEffect(() => {
@@ -29,7 +29,11 @@ function useElapsed(sinceIso: string | undefined) {
if (ms < 0) return;
const s = Math.floor(ms / 1000);
const m = Math.floor(s / 60);
setElapsed(m > 0 ? `${m}m ${s % 60}s` : `${s}s`);
if (m > 59) {
setElapsed("> 1h");
} else {
setElapsed(m > 0 ? `${m}m ${s % 60}s` : `${s}s`);
}
};
update();
const id = setInterval(update, 1000);
@@ -56,8 +60,14 @@ export default function PreviewTab() {
(p) => p.port === 3000 && p.state === "running",
);
// Also track a starting entry so we show the warm-up state instead of blank.
// Ignore ghosts older than 15 minutes.
const primaryStarting = !primaryRunning
? previews.find((p) => p.port === 3000 && p.state === "starting")
? previews.find(
(p) =>
p.port === 3000 &&
p.state === "starting" &&
Date.now() - new Date(p.startedAt).getTime() < 15 * 60 * 1000,
)
: undefined;
// Derive in-flight / recently-failed build from prod apps.

View File

@@ -53,7 +53,7 @@ export async function POST(
return NextResponse.json({ error: "Project not found" }, { status: 404 });
}
// 1. Is a dev server already running or starting?
// 1. Is a dev server already running or starting on the primary port?
const running = await queryOne<{
id: string;
state: string;
@@ -63,7 +63,12 @@ export async function POST(
}>(
`SELECT id, state, preview_url, command, port
FROM fs_dev_servers
WHERE project_id = $1 AND state IN ('running', 'starting')
WHERE project_id = $1
AND port = 3000
AND (
state = 'running' OR
(state = 'starting' AND started_at > NOW() - INTERVAL '15 minutes')
)
ORDER BY started_at DESC LIMIT 1`,
[projectId],
);
@@ -78,6 +83,7 @@ export async function POST(
}
// 2. Do we have a previous config to restart from?
// (Limit to port 3000 since that's what the preview pane embeds)
const last = await queryOne<{
command: string;
port: number;
@@ -85,7 +91,7 @@ export async function POST(
}>(
`SELECT command, port, preview_url
FROM fs_dev_servers
WHERE project_id = $1
WHERE project_id = $1 AND port = 3000
ORDER BY started_at DESC LIMIT 1`,
[projectId],
);