/** * F7 preview webview client (sealed sandbox, INV-21). Receives annotated HTML * from the extension host and swaps it in; runs mermaid over `.mermaid` blocks * (mermaid needs a DOM, so it runs here, not in the host). Bundled by esbuild as * a standalone IIFE → out/media/preview.js, so mermaid never enters the host * bundle. No network, no LLM. */ import mermaid from "mermaid"; // Imported so esbuild emits the sibling out/media/preview.css (the controller // links it into the sealed shell via asWebviewUri). import "./preview.css"; declare function acquireVsCodeApi(): { postMessage(m: unknown): void }; interface RenderMessage { type: "render"; mode: "changes" | "authorship"; html: string; epoch?: string; summary?: { added: number; removed: number; changed: number }; legend?: { claude: boolean; human: boolean }; } const vscodeApi = acquireVsCodeApi(); const body = document.getElementById("cw-body")!; const header = document.getElementById("cw-epoch")!; const summary = document.getElementById("cw-summary")!; const legend = document.getElementById("cw-legend")!; const segs = Array.from(document.querySelectorAll(".cw-seg")); for (const seg of segs) { seg.addEventListener("click", () => { vscodeApi.postMessage({ type: "setMode", mode: seg.dataset.mode }); }); } function themeFor(): "dark" | "default" { return document.body.classList.contains("vscode-dark") || document.body.classList.contains("vscode-high-contrast") ? "dark" : "default"; } async function renderMermaid(): Promise { const nodes = Array.from(body.querySelectorAll("pre.mermaid")); if (nodes.length === 0) return; mermaid.initialize({ startOnLoad: false, theme: themeFor(), securityLevel: "strict" }); try { await mermaid.run({ nodes }); } catch { // mermaid.run already marks failed nodes; ensure a visible chip per failure. for (const n of nodes) { if (!n.querySelector("svg")) n.setAttribute("data-cw-error", "true"); } } } window.addEventListener("message", (event: MessageEvent) => { const msg = event.data; if (msg?.type !== "render") return; body.innerHTML = msg.html; for (const seg of segs) seg.classList.toggle("cw-seg-on", seg.dataset.mode === msg.mode); const authorship = msg.mode === "authorship"; header.hidden = authorship; summary.hidden = authorship; legend.hidden = !authorship; if (authorship) { const parts: string[] = []; if (msg.legend?.claude) parts.push('Claude'); if (msg.legend?.human) parts.push('You'); legend.innerHTML = parts.join(" ") || "no attribution yet"; } else { header.textContent = `Track changes since ${msg.epoch ?? ""}`; summary.innerHTML = `+${(msg.summary?.added ?? 0) + (msg.summary?.changed ?? 0)} ` + `−${(msg.summary?.removed ?? 0) + (msg.summary?.changed ?? 0)}`; } void renderMermaid(); });