// ── Dev-mode error overlay ───────────────────────────────────────────────────── // // A full-screen, Vite/Next-style overlay for unexpected errors thrown by a Flow action. The // server attaches the error's message + stack + action to the error frame ONLY under the dev // worker (never in production), so the mere presence of a stack means "show the overlay". Purely // presentational + dismissible; the component's patch still applies underneath, so dismissing // returns you to a live page. export interface OverlayError { message: string; name?: string | undefined; stack?: string | undefined; action?: string | undefined; component?: string | undefined; } const OVERLAY_ID = "flow-error-overlay"; function _esc(s: unknown): string { return String(s ?? "").replace( /[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]!, ); } /** Render each stack line, dimming framework/node_modules frames so app frames stand out. @internal */ export function _formatStack(stack: string): string { return stack .split("\n") .slice(1) // first line is "Name: message", already shown as the heading .map((line) => { const dim = /node_modules|node:internal|\/flow\/src\//.test(line); return `
${_esc(line.trim())}
`; }) .join(""); } /** Show (or replace) the dev error overlay for `err`. Idempotent — re-showing replaces content. */ export function showErrorOverlay(err: OverlayError): void { if (typeof document === "undefined") return; hideErrorOverlay(); const root = document.createElement("div"); root.id = OVERLAY_ID; const shadow = root.attachShadow({ mode: "open" }); const where = [ err.action ? `action ${_esc(err.action)}` : "", err.component ? `in ${_esc(err.component)}` : "", ] .filter(Boolean) .join(" "); shadow.innerHTML = `
${_esc(err.name || "Error")} ${where ? `
${where}
` : ""}
${_esc(err.message)}
${err.stack ? `
${_formatStack(err.stack)}
` : ""}
Flow dev error · fix the action and save, or dismiss (Esc)
`; document.body.appendChild(root); const back = shadow.querySelector(".flow-eo-back")!; back.addEventListener("click", (e) => { // Dismiss on backdrop click or the Dismiss button; keep clicks inside the card. if (e.target === back || (e.target as HTMLElement).closest("[data-eo-dismiss]")) hideErrorOverlay(); }); document.addEventListener("keydown", _onKey); } function _onKey(e: KeyboardEvent): void { if (e.key === "Escape") hideErrorOverlay(); } /** Remove the overlay if present. */ export function hideErrorOverlay(): void { if (typeof document === "undefined") return; document.getElementById(OVERLAY_ID)?.remove(); document.removeEventListener("keydown", _onKey); }