import { useEffect, useMemo, useRef, useState } from "react";
import type { VirtualFile } from "../hooks/useVirtualFS";
import { compileScript, isCompilable } from "../lib/bundler";
import type { Diagnostic } from "../hooks/usePreviewDiagnostics";
import { buildPreviewAuthScript } from "../lib/preview-auth";
import { svgToDataUrl } from "../lib/sticker";
interface PreviewFrameProps {
files: VirtualFile[];
version: number;
/**
* Hugging Face access token from the parent app. When set, the preview
* seeds it into the SDK's sessionStorage keys so a generated app's
* `mountHost()` / `connectToHost()` authenticates from cache and skips
* the OAuth round-trip (which can't complete in a srcDoc iframe). This is
* what lets the preview render the REAL host shell (top bar + picker)
* even without a live robot. See `lib/preview-auth.ts`.
*/
accessToken?: string | null;
/** HF username, seeded alongside the token so the host top bar shows it. */
userName?: string | null;
/**
* Called with the latest `docId` as soon as a new preview HTML document
* is prepared. The parent uses this to discard stale postMessage
* diagnostics from an older iframe that may still be flushing after a
* re-render.
*/
onDocReady?: (docId: number) => void;
/**
* Called with compile errors produced by esbuild-wasm BEFORE the iframe
* starts running. Runtime errors come through the `postMessage` channel
* instead and are collected by the parent's diagnostics hook.
*/
onCompileError?: (diagnostic: Diagnostic) => void;
}
/**
* Render the virtual FS inside a sandboxed iframe.
*
* Pipeline:
* 1. Take `index.html` from the virtual FS.
* 2. For each local ``,
);
} else {
const { file, line, column, text } = result.error;
onCompileError?.({
docId,
kind: "compile-error",
message: text,
file,
line,
col: column,
timestamp: Date.now(),
});
const escaped = JSON.stringify(
`[${file}:${line}:${column}] ${text}`,
);
replacements.set(
match,
``,
);
}
return;
}
if (normalized.endsWith(".js") || normalized.endsWith(".mjs")) {
replacements.set(
match,
``,
);
}
}),
);
const inlined = rawHtml.replace(assetRegex, (m) => replacements.get(m) ?? m);
// Seed the HF session into the SDK's storage keys so the generated app
// authenticates from cache and renders the real host shell in preview.
const tokenScript = buildPreviewAuthScript(accessToken, userName);
const reporterScript = ``;
// Reflect the REAL app icon in preview: if the VFS carries an `icon.svg`
// (agent-generated or user-provided), expose it as a data URL global the
// template reads for `mountHost({ appIconUrl })`. Without this the host top
// bar would fetch `/icon.svg` (the static default) and never show
// the generated one. Only emitted when an icon.svg actually exists, so it
// stays undefined otherwise and the app falls back to the default.
const iconContent = fileMap.get("icon.svg") ?? fileMap.get("./icon.svg");
const iconScript =
iconContent && /