From e488df24a0dedcb7678ecce058846fa37e0daa32 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Fri, 2 May 2025 10:57:35 -0700 Subject: [PATCH] Hook into vite error overlay and show fixable in dyad (#71) --- scaffold/dyad-shim.js | 82 +++++++++++++++++++ .../preview_panel/PreviewIframe.tsx | 41 +++++++++- 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/scaffold/dyad-shim.js b/scaffold/dyad-shim.js index bb042a9..b892ef2 100644 --- a/scaffold/dyad-shim.js +++ b/scaffold/dyad-shim.js @@ -182,4 +182,86 @@ } sendSourcemappedErrorToParent(error, "unhandled-rejection"); }); + + (function watchForViteErrorOverlay() { + // --- Configuration for the observer --- + // We only care about direct children being added or removed. + const config = { + childList: true, // Observe additions/removals of child nodes + subtree: false, // IMPORTANT: Do *not* observe descendants, only direct children + }; + + // --- Callback function executed when mutations are observed --- + const observerCallback = function (mutationsList) { + // Iterate through all mutations that just occurred + for (const mutation of mutationsList) { + // We are only interested in nodes that were added + if (mutation.type === "childList" && mutation.addedNodes.length > 0) { + // Check each added node + for (const node of mutation.addedNodes) { + // Check if it's an ELEMENT_NODE (type 1) and has the correct ID + if ( + node.nodeType === Node.ELEMENT_NODE && + node.tagName === "vite-error-overlay".toUpperCase() + ) { + reportViteErrorOverlay(node); + } + } + } + } + }; + + function reportViteErrorOverlay(node) { + console.log(`Detected vite error overlay: ${node}`); + try { + window.parent.postMessage( + { + type: "build-error-report", + payload: { + message: node.shadowRoot.querySelector(".message").textContent, + file: node.shadowRoot.querySelector(".file").textContent, + frame: node.shadowRoot.querySelector(".frame").textContent, + }, + }, + PARENT_TARGET_ORIGIN + ); + } catch (error) { + console.error("Could not report vite error overlay", error); + } + } + + // --- Wait for DOM ready logic --- + if (document.readyState === "loading") { + // The document is still loading, wait for DOMContentLoaded + document.addEventListener("DOMContentLoaded", () => { + if (!document.body) { + console.error( + "document.body does not exist - something very weird happened" + ); + return; + } + + const node = document.body.querySelector("vite-error-overlay"); + if (node) { + reportViteErrorOverlay(node); + } + const observer = new MutationObserver(observerCallback); + observer.observe(document.body, config); + }); + console.log( + "Document loading, waiting for DOMContentLoaded to set up observer." + ); + } else { + if (!document.body) { + console.error( + "document.body does not exist - something very weird happened" + ); + return; + } + // The DOM is already interactive or complete + console.log("DOM already ready, setting up observer immediately."); + const observer = new MutationObserver(observerCallback); + observer.observe(document.body, config); + } + })(); })(); diff --git a/src/components/preview_panel/PreviewIframe.tsx b/src/components/preview_panel/PreviewIframe.tsx index 5d62c22..9d5d2c8 100644 --- a/src/components/preview_panel/PreviewIframe.tsx +++ b/src/components/preview_panel/PreviewIframe.tsx @@ -16,6 +16,7 @@ import { Sparkles, ChevronDown, Lightbulb, + ChevronRight, } from "lucide-react"; import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { IpcClient } from "@/ipc/ipc_client"; @@ -38,8 +39,19 @@ interface ErrorBannerProps { } const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => { + const [isCollapsed, setIsCollapsed] = useState(true); + if (!error) return null; + const getTruncatedError = () => { + const firstLine = error.split("\n")[0]; + const snippetLength = 200; + const snippet = error.substring(0, snippetLength); + return firstLine.length < snippet.length + ? firstLine + : snippet + (snippet.length === snippetLength ? "..." : ""); + }; + return (
{/* Close button in top left */} @@ -52,8 +64,17 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => { {/* Error message in the middle */}
-
- {error} +
setIsCollapsed(!isCollapsed)} + > + + {isCollapsed ? getTruncatedError() : error}
@@ -162,6 +183,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { | "window-error" | "unhandled-rejection" | "iframe-sourcemapped-error" + | "build-error-report" | "pushState" | "replaceState"; payload?: { @@ -169,6 +191,8 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { stack?: string; reason?: string; newUrl?: string; + file?: string; + frame?: string; }; }; @@ -195,6 +219,19 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => { timestamp: Date.now(), }, ]); + } else if (type === "build-error-report") { + console.debug(`Build error report: ${payload}`); + const errorMessage = `${payload?.message} from file ${payload?.file}.\n\nSource code:\n${payload?.frame}`; + setErrorMessage(errorMessage); + setAppOutput((prev) => [ + ...prev, + { + message: `Build error report: ${JSON.stringify(payload)}`, + type: "client-error", + appId: selectedAppId!, + timestamp: Date.now(), + }, + ]); } else if (type === "pushState" || type === "replaceState") { console.debug(`Navigation event: ${type}`, payload);