import { useEffect, useState } from "react"; import { AlertTriangle, RotateCcw, TimerReset } from "lucide-react"; import { healthApi, type DevServerHealthStatus } from "../api/health"; const RESTART_PENDING_RESET_MS = 30_000; function formatRelativeTimestamp(value: string | null): string | null { if (!value) return null; const timestamp = new Date(value).getTime(); if (Number.isNaN(timestamp)) return null; const deltaMs = Date.now() - timestamp; if (deltaMs < 60_000) return "just now"; const deltaMinutes = Math.round(deltaMs / 60_000); if (deltaMinutes < 60) return `${deltaMinutes}m ago`; const deltaHours = Math.round(deltaMinutes / 60); if (deltaHours < 24) return `${deltaHours}h ago`; const deltaDays = Math.round(deltaHours / 24); return `${deltaDays}d ago`; } function describeReason(devServer: DevServerHealthStatus): string { if (devServer.reason === "backend_changes_and_pending_migrations") { return "backend files changed and migrations are pending"; } if (devServer.reason === "pending_migrations") { return "pending migrations need a fresh boot"; } return "backend files changed since this server booted"; } export function DevRestartBanner({ devServer }: { devServer?: DevServerHealthStatus }) { const [restartPending, setRestartPending] = useState(false); useEffect(() => { if (!restartPending) return; const timeout = window.setTimeout(() => { setRestartPending(false); }, RESTART_PENDING_RESET_MS); return () => window.clearTimeout(timeout); }, [restartPending]); if (!devServer?.enabled || !devServer.restartRequired) return null; const currentDevServer = devServer; const changedAt = formatRelativeTimestamp(devServer.lastChangedAt); const sample = devServer.changedPathsSample.slice(0, 3); const activeRunLabel = `${devServer.activeRunCount} live run${ devServer.activeRunCount === 1 ? "" : "s" }`; async function requestRestartNow() { const warning = currentDevServer.activeRunCount > 0 ? `Restart Paperclip now? This may interrupt ${activeRunLabel}.` : "Restart Paperclip now?"; if (!window.confirm(warning)) return; setRestartPending(true); try { await healthApi.requestDevServerRestart(); } catch (error) { setRestartPending(false); window.alert(error instanceof Error ? error.message : "Failed to request restart"); } } return (
{describeReason(devServer)} {changedAt ? ` ยท updated ${changedAt}` : ""}
pnpm dev:once after the active work is safe to interrupt