Hook into vite error overlay and show fixable in dyad (#71)

This commit is contained in:
Will Chen
2025-05-02 10:57:35 -07:00
committed by GitHub
parent c069599de4
commit e488df24a0
2 changed files with 121 additions and 2 deletions

View File

@@ -182,4 +182,86 @@
} }
sendSourcemappedErrorToParent(error, "unhandled-rejection"); 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);
}
})();
})(); })();

View File

@@ -16,6 +16,7 @@ import {
Sparkles, Sparkles,
ChevronDown, ChevronDown,
Lightbulb, Lightbulb,
ChevronRight,
} from "lucide-react"; } from "lucide-react";
import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
@@ -38,8 +39,19 @@ interface ErrorBannerProps {
} }
const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => { const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => {
const [isCollapsed, setIsCollapsed] = useState(true);
if (!error) return null; 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 ( return (
<div className="absolute top-2 left-2 right-2 z-10 bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800 rounded-md shadow-sm p-2"> <div className="absolute top-2 left-2 right-2 z-10 bg-red-50 dark:bg-red-950 border border-red-200 dark:border-red-800 rounded-md shadow-sm p-2">
{/* Close button in top left */} {/* Close button in top left */}
@@ -52,8 +64,17 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => {
{/* Error message in the middle */} {/* Error message in the middle */}
<div className="px-6 py-1 text-sm"> <div className="px-6 py-1 text-sm">
<div className="text-red-700 dark:text-red-300 text-wrap font-mono whitespace-pre-wrap break-words text-xs"> <div
{error} className="text-red-700 dark:text-red-300 text-wrap font-mono whitespace-pre-wrap break-words text-xs cursor-pointer flex gap-1 items-start"
onClick={() => setIsCollapsed(!isCollapsed)}
>
<ChevronRight
size={14}
className={`mt-0.5 transform transition-transform ${
isCollapsed ? "" : "rotate-90"
}`}
/>
{isCollapsed ? getTruncatedError() : error}
</div> </div>
</div> </div>
@@ -162,6 +183,7 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
| "window-error" | "window-error"
| "unhandled-rejection" | "unhandled-rejection"
| "iframe-sourcemapped-error" | "iframe-sourcemapped-error"
| "build-error-report"
| "pushState" | "pushState"
| "replaceState"; | "replaceState";
payload?: { payload?: {
@@ -169,6 +191,8 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
stack?: string; stack?: string;
reason?: string; reason?: string;
newUrl?: string; newUrl?: string;
file?: string;
frame?: string;
}; };
}; };
@@ -195,6 +219,19 @@ export const PreviewIframe = ({ loading }: { loading: boolean }) => {
timestamp: Date.now(), 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") { } else if (type === "pushState" || type === "replaceState") {
console.debug(`Navigation event: ${type}`, payload); console.debug(`Navigation event: ${type}`, payload);