@openthink/ui-leaf 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/compile.ts","../src/internal/html.ts"],"sourcesContent":["// ui-leaf — Customizable browser views, on demand, for any CLI.\n// https://github.com/OpenThinkAi/ui-leaf\n\nimport { resolve } from \"node:path\";\nimport {\n startDevServer,\n type CspOption,\n type DevServerEvent,\n type DevServerEventListener,\n type MutationHandler,\n type Shell,\n} from \"./server.js\";\nimport type { BuildError } from \"./compile.js\";\n\nexport type { BuildError, CspOption, DevServerEvent, DevServerEventListener, MutationHandler, Shell };\n\nexport interface MountOptions {\n /** View name. Resolves to <viewsRoot>/<view>.tsx. */\n view: string;\n /**\n * JSON-serializable data passed to the view as a prop.\n *\n * Privacy note: the data is compiled into the HTML served at the mount URL\n * and held in memory for the mount lifetime. Any same-UID local process\n * that can reach `127.0.0.1:<port>` can fetch `GET /` and read it — the\n * per-launch token guards `/mutate` against drive-by cross-origin requests\n * in the browser, not against other processes on the machine. For PHI, PCI,\n * financial records, or anything where a same-UID local reader is in your\n * threat model, use `dataLoader` instead — the loader's return value is\n * served at a token-gated `/api/data` endpoint and never appears in the HTML.\n */\n data?: unknown;\n /**\n * Async function that supplies sensitive data to the view without\n * including it in the served HTML. When provided, the loader is called\n * once during mount setup; its resolved value is served at a token-gated\n * `GET /api/data` endpoint (same per-launch token as `/mutate`) and the\n * view fetches it on first render before calling `createRoot().render()`.\n * The data never appears in the compiled HTML.\n *\n * Use this instead of `data` for PHI, PCI, financial records, or anything\n * else where in-HTML data exposure is in your threat model.\n *\n * Error semantics: if the loader rejects, the rejection propagates to the\n * `mount()` caller (no automatic retry). Errors surface at mount time,\n * matching the synchronous `data` path's behavior.\n *\n * Mutual exclusion: passing both `data` and `dataLoader` throws at\n * mount time.\n */\n dataLoader?: () => Promise<unknown>;\n /**\n * Mutation handlers the view can call via mutate(name, args).\n * Each handler can self-type its args and return:\n *\n * mutations: {\n * recategorize: async (args: { id: string; category: string }) => {\n * await db.recategorize(args.id, args.category);\n * return { ok: true };\n * },\n * }\n *\n * Each request body is capped at 1 MiB; oversized POSTs are rejected\n * with a 400 and the view's mutate() promise rejects with a clear error.\n */\n // biome-ignore lint/suspicious/noExplicitAny: each handler has its own\n // arg/return types; the map can't share one shape.\n mutations?: Record<string, MutationHandler<any, any>>;\n /** Root directory holding view .tsx files. Defaults to <cwd>/views. */\n viewsRoot?: string;\n /** Browser tab title. Defaults to \"ui-leaf\". */\n title?: string;\n /**\n * Port to bind. Defaults to 5810 — unused by the major Node dev tools.\n * If the port is unavailable, ui-leaf bumps to the next free port and\n * the actual bound port is reflected on the returned `url` and `port`.\n * Pass `0` to let the OS pick a free port directly.\n * Override only if you need a stable URL (e.g. an external bookmark).\n */\n port?: number;\n /**\n * Open the browser when ready. Defaults to true. When false, mount()\n * returns the URL on its resolved value so the caller can drive a\n * headless browser, log the address, etc.\n */\n openBrowser?: boolean;\n /**\n * Browser shell. Defaults to \"tab\".\n *\n * - `\"tab\"` — open in the user's default browser as a regular tab.\n * Works everywhere; URL bar is visible.\n *\n * - `\"app\"` — try Chromium's `--app` mode for a chromeless window\n * (no URL bar, no tabs, looks like a desktop app). Available on\n * Chrome, Edge, and Brave. If no Chromium browser is installed,\n * ui-leaf falls back to \"tab\" with a stderr note. Safari and\n * Firefox always fall back.\n *\n * Pair with the share-link pattern (see \"Sharing views across users\"\n * in the README) when you want users to never see a localhost URL.\n */\n shell?: Shell;\n /**\n * Abort to close the dev server early. The returned `closed` promise\n * resolves either way; if you need to distinguish a signal-driven close\n * from a natural tab-close, check `signal.aborted` after the await.\n */\n signal?: AbortSignal;\n /**\n * Browser silence (ms) that triggers shutdown after the startup grace\n * window. Defaults to 75000 — chosen to survive a single browser\n * background-tab throttle (browsers clamp setInterval in hidden tabs to\n * roughly once per minute). Lower it if you want faster shutdown on tab\n * close; raise it if your debugger pauses the page or your machine\n * sleeps mid-session.\n */\n heartbeatTimeoutMs?: number;\n /**\n * Content-Security-Policy enforcement. Defaults to \"off\".\n *\n * - `\"off\"` — no CSP header sent. Views can fetch arbitrary URLs and\n * embed external resources freely. The data/mutations convention is\n * honor-system.\n *\n * - `\"strict\"` — ui-leaf sends a balanced preset: locks `connect-src`\n * to same-origin (the architectural lock — views cannot fetch\n * external APIs, so all data flows through `data` and `mutations`),\n * while permitting common needs (HTTPS images / fonts, inline\n * styles for React). View files can only *add* further restrictions\n * via meta tag, never remove them.\n *\n * - `string` — raw CSP header value for full control. Use when the\n * \"strict\" preset doesn't fit (e.g. you need `connect-src` to\n * include a Sentry endpoint).\n *\n * Trade-off: when set to \"strict\" or a custom string, a view file\n * cannot relax the policy at runtime. Switching back requires changing\n * the mount() call. That rigidity is a feature.\n */\n csp?: CspOption;\n /**\n * Extra hostnames accepted in the request `Host` and `Origin` headers\n * on top of the built-in loopback set (`localhost`, `127.0.0.1`, `[::1]`).\n *\n * The dev server gates every request on this set to defend against\n * DNS-rebinding attacks; non-matching requests get HTTP 403. Use this\n * escape hatch when you need to reach the dev server through a custom\n * `/etc/hosts` alias (e.g. `[\"my-app.local\"]`) or any other loopback\n * name. Hostnames are matched case-insensitively, port-agnostic.\n *\n * Be deliberate: any hostname you add becomes a viable DNS-rebinding\n * target. Don't add wildcards, public DNS names, or LAN hostnames you\n * don't fully control.\n */\n allowedHosts?: string[];\n /**\n * Suppress ui-leaf output to stdout. Default: false.\n *\n * When you drive `mount()` programmatically — e.g. as part of a Node\n * bridge for a non-Node CLI that's spawned ui-leaf as a subprocess —\n * stdout is usually reserved for a structured protocol (line-delimited\n * JSON, etc.). Setting `silent: true` redirects `process.stdout.write`\n * to `process.stderr` for the lifetime of the server, restored on close.\n *\n * Tradeoff: any other code in the same process that writes to stdout\n * during the server's lifetime is also redirected. Hold the captured\n * `process.stdout.write` reference yourself if you need to write to the\n * real stdout from the same process.\n */\n silent?: boolean;\n /**\n * Grace period (ms) after server start before the heartbeat watcher arms.\n * Cold-loading clients sometimes take a few seconds to send their first\n * heartbeat. Defaults to 30000.\n *\n * If no client connects within (startupGraceMs + heartbeatTimeoutMs),\n * the server shuts down on its own.\n */\n startupGraceMs?: number;\n}\n\nexport interface MountedView {\n /** URL the view is reachable at (http://127.0.0.1:<port>). */\n url: string;\n /** Bound port. Useful when port: 0 was requested. */\n port: number;\n /** Resolves when the view closes (heartbeat timeout) or close() is called. */\n closed: Promise<void>;\n /** Force-close the dev server early. */\n close: () => Promise<void>;\n /**\n * Replace in-memory data and notify all `data-updated` listeners.\n * Preserves in-page React state — no recompile.\n */\n update: (data: unknown) => void;\n /**\n * Swap the view source on the fly. Triggers a recompile; on success replaces\n * the served HTML and notifies all `view-swapped` listeners. On compile\n * failure the previous HTML is preserved. Returns compile errors if any.\n */\n swapView: (source: string) => Promise<BuildError[]>;\n /**\n * Atomically replace both data and view source. If compilation fails neither\n * takes effect. Returns compile errors if any.\n */\n patch: (data: unknown, source: string) => Promise<BuildError[]>;\n /**\n * Re-invoke the browser-open function to launch a fresh tab at the same URL.\n * Always opens a new tab — if one is already connected, a duplicate opens.\n */\n reopen: () => Promise<void>;\n /** Subscribe to a server-side event (data-updated | view-swapped). */\n on: (event: DevServerEvent, listener: DevServerEventListener) => void;\n /** Unsubscribe a previously-registered listener. */\n off: (event: DevServerEvent, listener: DevServerEventListener) => void;\n}\n\n/**\n * Mount a customizable browser view from a CLI. Spins up a local dev server\n * and renders the chosen view with the given data. Returns once the server\n * is ready; await `result.closed` to block until the user closes the\n * browser tab.\n *\n * Mutations triggered in the view are dispatched to the registered handlers\n * here; the view never reaches the CLI's backing API directly.\n *\n * Multi-tab note: if the user opens the served URL in additional tabs (or\n * duplicates the tab), each tab heartbeats independently and the server\n * stays alive while *any* tab is open. Closing the original tab does not\n * shut down the CLI if a duplicate is still loaded.\n *\n * Ctrl+C: this function installs SIGINT and SIGTERM handlers that close\n * the server before exiting.\n */\nexport async function mount(opts: MountOptions): Promise<MountedView> {\n const viewsRoot = opts.viewsRoot ?? resolve(process.cwd(), \"views\");\n\n const server = await startDevServer({\n view: opts.view,\n data: opts.data,\n dataLoader: opts.dataLoader,\n viewsRoot,\n mutations: opts.mutations,\n title: opts.title,\n port: opts.port,\n openBrowser: opts.openBrowser,\n shell: opts.shell,\n heartbeatTimeoutMs: opts.heartbeatTimeoutMs,\n startupGraceMs: opts.startupGraceMs,\n csp: opts.csp,\n allowedHosts: opts.allowedHosts,\n silent: opts.silent,\n });\n\n const onSignal = (signal: NodeJS.Signals): void => {\n void (async () => {\n await server.close();\n // Re-raise so default exit codes still apply.\n process.kill(process.pid, signal);\n })();\n };\n const sigint = (): void => onSignal(\"SIGINT\");\n const sigterm = (): void => onSignal(\"SIGTERM\");\n process.once(\"SIGINT\", sigint);\n process.once(\"SIGTERM\", sigterm);\n\n if (opts.signal) {\n if (opts.signal.aborted) {\n process.off(\"SIGINT\", sigint);\n process.off(\"SIGTERM\", sigterm);\n await server.close();\n return {\n url: server.url,\n port: server.port,\n closed: Promise.resolve(),\n close: server.close,\n update: server.update.bind(server),\n swapView: (source: string) => server.swapView(source),\n patch: (data: unknown, source: string) => server.patch(data, source),\n reopen: server.reopen.bind(server),\n on: server.on.bind(server),\n off: server.off.bind(server),\n };\n }\n opts.signal.addEventListener(\n \"abort\",\n () => void server.close(),\n { once: true },\n );\n }\n\n const closed = server.closed.finally(() => {\n process.off(\"SIGINT\", sigint);\n process.off(\"SIGTERM\", sigterm);\n });\n\n return {\n url: server.url,\n port: server.port,\n closed,\n close: server.close,\n update: server.update.bind(server),\n swapView: (source: string) => server.swapView(source),\n patch: (data: unknown, source: string) => server.patch(data, source),\n reopen: server.reopen.bind(server),\n on: server.on.bind(server),\n off: server.off.bind(server),\n };\n}\n","import { randomBytes, timingSafeEqual as nodeTimingSafeEqual } from \"node:crypto\";\nimport open, { apps } from \"open\";\nimport { compileView, compileSource } from \"./compile.js\";\n\n// Module-level stdout redirect state. Captured ONCE at module load so\n// concurrent silent: true mounts share the same \"original\" reference and\n// restore-order doesn't matter. Refcounted so the last close restores.\nconst ORIGINAL_STDOUT_WRITE = process.stdout.write.bind(process.stdout);\nlet stdoutRedirectCount = 0;\n\n/**\n * Redirect process.stdout.write to process.stderr until the returned\n * function is called. Safe under concurrent silent mounts.\n */\nfunction redirectStdoutToStderr(): () => void {\n stdoutRedirectCount++;\n if (stdoutRedirectCount === 1) {\n // biome-ignore lint/suspicious/noExplicitAny: stdout.write has overloaded\n // signatures; forward exactly what comes in.\n process.stdout.write = ((chunk: any, enc?: any, cb?: any) =>\n process.stderr.write(chunk, enc, cb)) as typeof process.stdout.write;\n }\n let released = false;\n return () => {\n if (released) return;\n released = true;\n stdoutRedirectCount--;\n if (stdoutRedirectCount === 0) {\n process.stdout.write = ORIGINAL_STDOUT_WRITE;\n }\n };\n}\n\nexport type MutationHandler<TArgs = unknown, TResult = unknown> = (\n args: TArgs,\n) => TResult | Promise<TResult>;\n\n// `(string & {})` preserves the \"off\" / \"strict\" autocomplete suggestions\n// while still allowing arbitrary CSP strings. Plain string would collapse\n// the union and lose IntelliSense for the literals.\nexport type CspOption = \"off\" | \"strict\" | (string & {});\n\nexport type Shell = \"tab\" | \"app\";\n\n/**\n * Try to open `url` in a Chromium browser's --app mode (chromeless window:\n * no URL bar, no tabs). Returns true if a Chromium browser was found and\n * launched, false if no Chromium variant is installed (caller should fall\n * back to the default-browser tab).\n */\nasync function openInAppMode(url: string): Promise<boolean> {\n // Order: most-common Chromium variants first.\n const candidates = [apps.chrome, apps.edge, apps.brave];\n for (const app of candidates) {\n try {\n await open(url, { app: { name: app, arguments: [`--app=${url}`] } });\n return true;\n } catch {\n // Try next candidate; `open` throws if the binary isn't installed.\n }\n }\n return false;\n}\n\n/**\n * Strict preset: locks `connect-src` to same-origin (the architectural\n * lock that forces views to route mutations through the CLI), while\n * permitting common needs (HTTPS images/fonts, inline styles for React).\n * A future v1.x mode could tighten script-src once usage patterns are known.\n */\nconst STRICT_CSP = [\n \"default-src 'self'\",\n \"connect-src 'self'\",\n \"img-src 'self' data: https:\",\n \"font-src 'self' https: data:\",\n \"style-src 'self' 'unsafe-inline'\",\n \"script-src 'self' 'unsafe-inline'\",\n].join(\"; \");\n\nfunction resolveCsp(opt: CspOption | undefined): string | null {\n if (!opt || opt === \"off\") return null;\n if (opt === \"strict\") return STRICT_CSP;\n return opt;\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n // Length check is not timing-safe but is fine — the token length is fixed\n // and known to attackers regardless. The byte compare must be timing-safe.\n if (a.length !== b.length) return false;\n return nodeTimingSafeEqual(Buffer.from(a, \"utf8\"), Buffer.from(b, \"utf8\"));\n}\n\nconst DEFAULT_LOOPBACK_HOSTNAMES = [\"127.0.0.1\", \"localhost\", \"::1\"] as const;\n\n// Extract the hostname portion of a Host header value, stripping the port.\n// IPv6 hosts arrive bracketed (`[::1]:5810`); plain hosts as `host:port`\n// or bare `host`. Returns lowercased hostname or null on shapes we don't\n// recognise (caller treats null as \"reject\").\nfunction parseHostHeader(value: string): string | null {\n const trimmed = value.trim();\n if (trimmed === \"\") return null;\n if (trimmed.startsWith(\"[\")) {\n const close = trimmed.indexOf(\"]\");\n if (close === -1) return null;\n return trimmed.slice(1, close).toLowerCase();\n }\n const colon = trimmed.indexOf(\":\");\n return (colon === -1 ? trimmed : trimmed.slice(0, colon)).toLowerCase();\n}\n\n// DNS-rebinding defence: every request must arrive with a Host header\n// pointing at one of the allowed names. Same gate applies to Origin when\n// the browser sends one. Absent Origin is fine — many legitimate\n// same-origin requests omit it. `Origin: null` is allowed because\n// sandboxed iframes and `file://` pages send it; the Host check still\n// constrains the network path so the Origin allowance isn't load-bearing.\nfunction isAllowedHost(value: string | undefined, allowed: Set<string>): boolean {\n const host = value === undefined ? null : parseHostHeader(value);\n return host !== null && allowed.has(host);\n}\n\nfunction isAllowedOrigin(value: string | undefined, allowed: Set<string>): boolean {\n if (value === undefined || value === \"\" || value === \"null\") return true;\n try {\n // WHATWG URL keeps the brackets on IPv6 hostnames (`[::1]`), but the\n // allow-list stores them stripped (matching parseHostHeader's output)\n // so origins and hosts compare consistently.\n let hostname = new URL(value).hostname.toLowerCase();\n if (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) {\n hostname = hostname.slice(1, -1);\n }\n return allowed.has(hostname);\n } catch {\n return false;\n }\n}\n\nexport interface DevServerOptions {\n view: string;\n data?: unknown;\n dataLoader?: () => Promise<unknown>;\n viewsRoot: string;\n // biome-ignore lint/suspicious/noExplicitAny: each handler has its own\n // arg/return types; the map can't share one shape.\n mutations?: Record<string, MutationHandler<any, any>>;\n /** Browser tab title. Defaults to \"ui-leaf\". */\n title?: string;\n port?: number;\n openBrowser?: boolean;\n /**\n * Browser shell. Defaults to \"tab\".\n *\n * - \"tab\" — open in user's default browser as a regular tab.\n * - \"app\" — try Chromium's --app mode (chromeless window). Falls back\n * to \"tab\" if no Chromium browser is installed (Chrome/Edge/Brave),\n * with a stderr note. Safari and Firefox always fall back.\n */\n shell?: Shell;\n /** Heartbeat-stop window in ms. Browser silence longer than this triggers shutdown. */\n heartbeatTimeoutMs?: number;\n /** Grace period after server start before the heartbeat watcher is armed. */\n startupGraceMs?: number;\n /** Content-Security-Policy enforcement. See MountOptions.csp. */\n csp?: CspOption;\n /**\n * Extra hostnames (beyond `localhost`, `127.0.0.1`, `[::1]`) accepted in\n * the request `Host` and `Origin` headers. Use to allow a custom\n * `/etc/hosts` alias or another loopback name; values are matched by\n * hostname only (port-agnostic). Anything outside this set + the\n * loopback defaults is rejected with HTTP 403 to defend against\n * DNS-rebinding attacks. Default: empty.\n */\n allowedHosts?: string[];\n /**\n * Suppress ui-leaf output to stdout. When true, process.stdout.write is\n * redirected to process.stderr for the lifetime of the server, restored\n * on close(). Use when driving mount() programmatically and stdout is\n * reserved for a structured protocol (e.g. line-delimited JSON).\n * Default: false.\n */\n silent?: boolean;\n /**\n * Test seam: replace the browser-open implementation. When provided,\n * called instead of `open(url)` for both the initial open and `reopen()`.\n * Never set this in production; use `openBrowser: false` instead.\n */\n _opener?: (url: string) => Promise<void>;\n}\n\nexport type DevServerEvent = \"data-updated\" | \"view-swapped\";\nexport type DevServerEventListener = () => void;\n\nexport interface DevServer {\n url: string;\n port: number;\n /** Resolves when the view is closed (heartbeat timeout) or close() is called. */\n closed: Promise<void>;\n close: () => Promise<void>;\n /**\n * Replace in-memory data and emit a `data-updated` event to all\n * registered listeners. Does not recompile the view.\n */\n update: (data: unknown) => void;\n /**\n * Recompile the view from an inline TSX source string and replace the\n * in-memory HTML. Emits `view-swapped` on success; preserves the previous\n * HTML on compile failure. Returns errors array (empty = success).\n */\n swapView: (source: string) => Promise<import(\"./compile.js\").BuildError[]>;\n /**\n * Atomically replace both data and view source. If compilation fails,\n * neither takes effect. Returns errors array (empty = success).\n */\n patch: (data: unknown, source: string) => Promise<import(\"./compile.js\").BuildError[]>;\n /**\n * Re-invoke the browser-open function to launch a fresh tab at the same URL.\n * Always opens a new tab — if one is already connected, a duplicate opens.\n */\n reopen: () => Promise<void>;\n /**\n * Subscribe to a server-side event. Listeners are called synchronously\n * after each mutation completes.\n *\n * Events:\n * \"data-updated\" — fired by update() and patch()\n * \"view-swapped\" — fired by swapView() and patch()\n */\n on: (event: DevServerEvent, listener: DevServerEventListener) => void;\n off: (event: DevServerEvent, listener: DevServerEventListener) => void;\n}\n\nexport async function startDevServer(opts: DevServerOptions): Promise<DevServer> {\n const {\n view,\n data,\n dataLoader,\n viewsRoot,\n mutations = {},\n title = \"ui-leaf\",\n port,\n openBrowser = true,\n shell = \"tab\",\n heartbeatTimeoutMs = 75_000,\n startupGraceMs = 30_000,\n csp,\n allowedHosts,\n silent = false,\n _opener,\n } = opts;\n const cspHeader = resolveCsp(csp);\n const allowedHostSet = new Set<string>(DEFAULT_LOOPBACK_HOSTNAMES);\n for (const h of allowedHosts ?? []) allowedHostSet.add(h.toLowerCase());\n const allowedHostList = [...allowedHostSet].join(\", \");\n\n // Programmatic consumers (esp. non-Node CLIs spawning ui-leaf as a\n // subprocess) often reserve stdout for a structured protocol. Redirect\n // process.stdout.write to stderr to catch anything that bypasses our\n // own output path.\n const restoreStdout: (() => void) | null = silent ? redirectStdoutToStderr() : null;\n\n try {\n if (view.includes(\"/\") || view.includes(\"\\\\\")) {\n throw new Error(\n `ui-leaf: view '${view}' must be a bare identifier with no path separators`,\n );\n }\n\n if (data !== undefined && dataLoader) {\n throw new Error(\"ui-leaf: pass data or dataLoader, not both\");\n }\n\n const token = randomBytes(32).toString(\"hex\");\n\n // Eagerly invoke the loader before starting the server. The resolved\n // value lives only in this closure — it is never written to disk. If the\n // loader rejects, the setup-failure catch below restores stdout before\n // re-throwing.\n let loadedData: unknown;\n if (dataLoader) {\n loadedData = await dataLoader();\n }\n\n // Compile the view once at mount time; hold the resulting HTML in memory.\n const result = await compileView({\n entry: view,\n viewsRoot,\n data: dataLoader ? null : data,\n title,\n csp: cspHeader ?? undefined,\n token,\n dataLoader: !!dataLoader,\n });\n\n if (result.errors.length > 0) {\n const msg = result.errors.map((e) => e.message).join(\"; \");\n throw new Error(`ui-leaf: view compilation failed: ${msg}`);\n }\n\n // Mutable view state: the / handler reads from this on every request.\n // update(), swapView(), patch() mutate it in place.\n const viewState = { html: result.html, data: dataLoader ? loadedData : data };\n\n // Minimal event broker. Pre-seeded so fireEvent's get() always returns a Set.\n const listeners = new Map<DevServerEvent, Set<DevServerEventListener>>([\n [\"data-updated\", new Set()],\n [\"view-swapped\", new Set()],\n ]);\n function fireEvent(event: DevServerEvent): void {\n for (const fn of listeners.get(event)!) fn();\n }\n\n let lastHeartbeatAt = Date.now();\n let closeRequested = false;\n let resolveClosed: () => void = () => {};\n const closed = new Promise<void>((r) => {\n resolveClosed = r;\n });\n\n const bunPort = port === undefined ? 5810 : port; // port: 0 → OS picks\n let actualPort = bunPort;\n // Auto-bump: if bunPort is busy, try bunPort+1 … up to MAX_PORT_ATTEMPTS.\n // port: 0 goes straight to Bun (OS assigns a free port; never EADDRINUSE).\n const server = (() => {\n const handler = (req: Request): Response | Promise<Response> => {\n const host = req.headers.get(\"host\") ?? undefined;\n const origin = req.headers.get(\"origin\") ?? undefined;\n\n // DNS-rebinding gate: reject any request (including WebSocket upgrade\n // attempts) that does not arrive with an allowed Host. When Origin is\n // present, it must also be in the allowed set.\n const hostOk = isAllowedHost(host, allowedHostSet);\n const originOk = isAllowedOrigin(origin, allowedHostSet);\n if (!hostOk || !originOk) {\n const offender = !hostOk\n ? `Host \"${host ?? \"(absent)\"}\"`\n : `Origin \"${origin}\"`;\n return new Response(\n `ui-leaf: refusing request with ${offender} — only the following hostnames are accepted to prevent DNS rebinding: ${allowedHostList}. Open the server at http://localhost:${actualPort}/ or http://127.0.0.1:${actualPort}/, or pass { allowedHosts: [\"my-alias\"] } to mount() to permit a custom alias.\\n`,\n { status: 403, headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } },\n );\n }\n\n const headers: Record<string, string> = {};\n if (cspHeader) {\n headers[\"Content-Security-Policy\"] = cspHeader;\n }\n\n const url = new URL(req.url);\n const path = url.pathname;\n const method = req.method;\n\n if (method === \"GET\" && path === \"/\") {\n return new Response(viewState.html, {\n status: 200,\n headers: { ...headers, \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n if (method === \"POST\" && path === \"/heartbeat\") {\n if (!checkAuth(req, token)) {\n return new Response(JSON.stringify({ error: \"unauthorized\" }), {\n status: 401,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n lastHeartbeatAt = Date.now();\n return new Response(\"\", { status: 204, headers });\n }\n\n if (method === \"POST\" && path === \"/mutate\") {\n if (!checkAuth(req, token)) {\n return new Response(JSON.stringify({ error: \"unauthorized\" }), {\n status: 401,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n return handleMutate(req, mutations, headers);\n }\n\n if (method === \"GET\" && path === \"/api/data\") {\n if (!dataLoader) {\n return new Response(JSON.stringify({ error: \"not found\" }), {\n status: 404,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n if (!checkAuth(req, token)) {\n return new Response(JSON.stringify({ error: \"unauthorized\" }), {\n status: 401,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n return new Response(JSON.stringify(viewState.data !== undefined ? viewState.data : null), {\n status: 200,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n\n return new Response(JSON.stringify({ error: \"not found\" }), {\n status: 404,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n };\n if (bunPort === 0) {\n return Bun.serve({ hostname: \"127.0.0.1\", port: 0, fetch: handler });\n }\n const MAX_PORT_ATTEMPTS = 10;\n for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {\n try {\n return Bun.serve({ hostname: \"127.0.0.1\", port: bunPort + i, fetch: handler });\n } catch (err) {\n const isAddrinuse = err instanceof Error && err.message.includes(\"EADDRINUSE\");\n if (!isAddrinuse || i === MAX_PORT_ATTEMPTS - 1) {\n if (isAddrinuse) {\n throw new Error(\n `ui-leaf: ports ${bunPort}–${bunPort + MAX_PORT_ATTEMPTS - 1} are all in use. Pass { port: 0 } to mount() for an OS-assigned port.`,\n );\n }\n throw err;\n }\n }\n }\n throw new Error(\"unreachable\"); // TypeScript: loop always returns or throws\n })();\n\n actualPort = server.port ?? bunPort;\n const url = `http://127.0.0.1:${actualPort}`;\n const startedAt = Date.now();\n\n let heartbeatWatcher: NodeJS.Timeout | undefined;\n\n const cleanup = async (): Promise<void> => {\n if (closeRequested) return;\n closeRequested = true;\n if (heartbeatWatcher) clearInterval(heartbeatWatcher);\n await server.stop(true);\n if (restoreStdout) restoreStdout();\n resolveClosed();\n };\n\n heartbeatWatcher = setInterval(() => {\n const now = Date.now();\n if (now - startedAt < startupGraceMs) return;\n if (now - lastHeartbeatAt > heartbeatTimeoutMs) {\n void cleanup();\n }\n }, 1000);\n\n // Browser-open implementation, or the test-seam override if one was supplied.\n const doOpen: () => Promise<void> = _opener\n ? () => _opener(url)\n : async () => {\n if (shell === \"app\") {\n const launched = await openInAppMode(url);\n if (!launched) {\n process.stderr.write(\n `ui-leaf: shell:\"app\" requested but no Chromium browser found; falling back to default browser tab.\\n`,\n );\n await open(url);\n }\n } else {\n await open(url);\n }\n };\n\n if (openBrowser) {\n await doOpen();\n }\n\n return {\n url,\n port: actualPort,\n closed,\n close: cleanup,\n on(event: DevServerEvent, listener: DevServerEventListener): void {\n listeners.get(event)?.add(listener);\n },\n off(event: DevServerEvent, listener: DevServerEventListener): void {\n listeners.get(event)?.delete(listener);\n },\n update(newData: unknown): void {\n viewState.data = newData;\n fireEvent(\"data-updated\");\n },\n async swapView(source: string): Promise<import(\"./compile.js\").BuildError[]> {\n const r = await compileSource({\n source,\n data: viewState.data,\n title,\n csp: cspHeader ?? undefined,\n token,\n });\n if (r.errors.length > 0) return r.errors;\n viewState.html = r.html;\n fireEvent(\"view-swapped\");\n return [];\n },\n async patch(newData: unknown, source: string): Promise<import(\"./compile.js\").BuildError[]> {\n // Compile first with newData so the HTML embeds the incoming data.\n const r = await compileSource({\n source,\n data: newData,\n title,\n csp: cspHeader ?? undefined,\n token,\n });\n if (r.errors.length > 0) return r.errors;\n // Only mutate state on compile success (atomicity guarantee).\n viewState.data = newData;\n viewState.html = r.html;\n fireEvent(\"data-updated\");\n fireEvent(\"view-swapped\");\n return [];\n },\n async reopen(): Promise<void> {\n await doOpen();\n },\n };\n } catch (err) {\n restoreStdout?.();\n throw err;\n }\n}\n\nfunction checkAuth(req: Request, token: string): boolean {\n const header = req.headers.get(\"authorization\") ?? \"\";\n const match = /^Bearer (.+)$/.exec(header);\n if (!match) return false;\n return timingSafeEqual(match[1]!, token);\n}\n\nasync function handleMutate(\n req: Request,\n mutations: Record<string, MutationHandler<any, any>>,\n headers: Record<string, string>,\n): Promise<Response> {\n // 1 MiB cap: Content-Length precheck short-circuits chunked / large bodies\n // before req.text() buffers them. req.text() still buffers the whole body\n // if Content-Length is absent or underreported — acceptable for this\n // loopback-only server, where the auth gate already runs first.\n const contentLength = req.headers.get(\"content-length\");\n if (contentLength && Number.parseInt(contentLength, 10) > 1024 * 1024) {\n return new Response(JSON.stringify({ error: \"request body exceeds 1 MiB limit\" }), {\n status: 400,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n\n let body: { name?: string; args?: unknown };\n try {\n const text = await req.text();\n if (text.length > 1024 * 1024) {\n return new Response(JSON.stringify({ error: \"request body exceeds 1 MiB limit\" }), {\n status: 400,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n body = (text ? JSON.parse(text) : undefined) as typeof body;\n } catch (err) {\n return new Response(\n JSON.stringify({ error: err instanceof Error ? err.message : \"bad request\" }),\n { status: 400, headers: { ...headers, \"Content-Type\": \"application/json\" } },\n );\n }\n\n const name = body?.name;\n if (typeof name !== \"string\" || name.length === 0) {\n return new Response(JSON.stringify({ error: \"missing mutation name\" }), {\n status: 400,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n\n if (!Object.hasOwn(mutations, name)) {\n return new Response(\n JSON.stringify({\n error: `ui-leaf: no mutation handler registered for '${name}'. Add it to the mutations: { } map passed to mount().`,\n }),\n { status: 404, headers: { ...headers, \"Content-Type\": \"application/json\" } },\n );\n }\n\n const handler = mutations[name]!;\n try {\n const result = await handler(body.args);\n return new Response(JSON.stringify(result ?? null), {\n status: 200,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n } catch (err) {\n return new Response(\n JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),\n { status: 500, headers: { ...headers, \"Content-Type\": \"application/json\" } },\n );\n }\n}\n","import { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { join, resolve, sep } from \"node:path\";\nimport { mkdtemp, rm, stat, writeFile } from \"node:fs/promises\";\nimport type { BunPlugin } from \"bun\";\nimport { escapeForScriptTag } from \"./internal/html.js\";\n\n// Resolve React imports at module load — works under bun test / bun run.\n// NOTE: under bun build --compile (binary mode), createRequire() resolves from\n// the binary's embedded virtual filesystem. AGT-131 (cross-compile script)\n// will need a Bun.build plugin or Bun embedded-files to ensure React is\n// reachable inside the compiled binary. Flagging here so AGT-131 is not blindsided.\nconst requireFromHere = createRequire(import.meta.url);\n\n// BunPlugin that rewrites bare react/react-dom imports to absolute paths\n// under ui-leaf's installed node_modules. Ensures the bundled view always\n// finds the same React instance regardless of the consumer's package-manager\n// hoisting, and prevents duplicate React instances across views.\nconst reactAliasPlugin: BunPlugin = {\n name: \"ui-leaf-react-alias\",\n setup(build) {\n // Matches: react, react/jsx-runtime, react/jsx-dev-runtime,\n // react-dom, react-dom/client, react-dom/profiling, etc.\n build.onResolve({ filter: /^react($|\\/|-dom($|\\/))/ }, (args) => {\n try {\n return { path: requireFromHere.resolve(args.path) };\n } catch {\n return {\n path: args.path,\n errors: [{ text: `ui-leaf: failed to resolve ${args.path}` }],\n };\n }\n });\n },\n};\n\nexport interface BuildError {\n file: string;\n line: number;\n column: number;\n message: string;\n}\n\nexport interface CompileOptions {\n /** View name or path relative to viewsRoot (e.g. \"dashboard\" or \"dashboard.tsx\"). */\n entry: string;\n /** Root directory holding .tsx view files. */\n viewsRoot: string;\n /** JSON-serializable data injected as window.__UI_LEAF__.data. Ignored when dataLoader is true. */\n data?: unknown;\n /** Browser tab title. Defaults to \"ui-leaf\". */\n title?: string;\n /**\n * Raw CSP string to emit as a <meta http-equiv=\"Content-Security-Policy\"> tag.\n * Undefined / absent means no CSP meta tag is emitted.\n */\n csp?: string;\n /**\n * Extra allowed hostnames (beyond loopback defaults). Accepted in the\n * option bag for API symmetry with DevServerOptions; has no compile-time\n * effect — the runtime DNS-rebinding gate lives in the server.\n */\n allowedHosts?: string[];\n /**\n * Per-launch auth token. When provided, included as window.__UI_LEAF__.token\n * so the browser-side bridge can authenticate /mutate and /heartbeat calls.\n */\n token?: string;\n /**\n * When true, generate an entry that fetches data from GET /api/data at\n * render time rather than reading it from window.__UI_LEAF__.data. The\n * compiled HTML bootstrap omits the data field (only token is included).\n * Use when data is sensitive and must not be written to the HTML file.\n */\n dataLoader?: boolean;\n}\n\n/**\n * Options for compiling an inline TSX source string.\n *\n * v1.0.0 constraint: `source` is treated as a self-contained TSX string.\n * Relative imports are not supported — the string has no filesystem context\n * to resolve them against. Bare-package imports (react, react-dom) work via\n * the react-alias plugin. This is the intended contract for IPC-driven\n * view hot-swaps (AI-generated self-contained components).\n */\nexport interface CompileSourceOptions {\n /** Raw TSX source string to compile. Must be a self-contained component. */\n source: string;\n /** JSON-serializable data injected as window.__UI_LEAF__.data. */\n data?: unknown;\n /** Browser tab title. Defaults to \"ui-leaf\". */\n title?: string;\n /** Raw CSP string. Undefined / absent means no CSP meta tag. */\n csp?: string;\n /** Per-launch auth token. */\n token?: string;\n}\n\nexport interface CompileResult {\n html: string;\n errors: BuildError[];\n}\n\n// Shared bridge injected into every compiled entry: mutation + heartbeat.\nconst SHARED_BRIDGE = `\nasync function mutate(name: string, args?: unknown): Promise<unknown> {\n const res = await fetch(\"/mutate\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(token ? { Authorization: \"Bearer \" + token } : {}),\n },\n body: JSON.stringify({ name, args }),\n });\n const text = await res.text().catch(() => \"\");\n if (!res.ok) {\n let detail = text;\n try {\n const parsed: unknown = text ? JSON.parse(text) : null;\n if (parsed !== null && typeof parsed === \"object\" && \"error\" in parsed && typeof (parsed as { error: unknown }).error === \"string\") {\n detail = (parsed as { error: string }).error;\n }\n } catch { /* keep raw text */ }\n throw new Error(\"ui-leaf: mutation '\" + name + \"' failed (\" + res.status + \"): \" + detail);\n }\n return text ? JSON.parse(text) : undefined;\n}\n\nasync function heartbeat(): Promise<void> {\n try {\n await fetch(\"/heartbeat\", {\n method: \"POST\",\n headers: token ? { Authorization: \"Bearer \" + token } : {},\n });\n } catch { /* server may have shut down; ignore */ }\n}\nsetInterval(heartbeat, 5000);\nheartbeat();`;\n\n/** Run Bun.build on `entryPath` and return the raw JS output or errors. */\nasync function runBunBuild(entryPath: string): Promise<{ js: string } | { errors: BuildError[] }> {\n let buildOutput: Awaited<ReturnType<typeof Bun.build>>;\n try {\n buildOutput = await Bun.build({\n entrypoints: [entryPath],\n target: \"browser\",\n format: \"esm\",\n minify: false,\n sourcemap: \"none\",\n plugins: [reactAliasPlugin],\n });\n } catch (err) {\n if (err instanceof AggregateError) {\n type BunBuildMsg = { message: string; position?: { file?: string; line?: number; column?: number } | null };\n const errors: BuildError[] = (err.errors as BunBuildMsg[]).map((e) => ({\n file: e.position?.file ?? \"<unknown>\",\n line: e.position?.line ?? 0,\n column: e.position?.column ?? 0,\n message: e.message,\n }));\n return { errors };\n }\n throw err;\n }\n const output = buildOutput.outputs[0];\n if (!output) {\n return {\n errors: [{ file: \"<unknown>\", line: 0, column: 0, message: \"ui-leaf: Bun.build produced no output\" }],\n };\n }\n return { js: await output.text() };\n}\n\n/** Assemble the final HTML page from compiled JS and options. */\nfunction assembleHtml(opts: {\n js: string;\n title: string;\n csp: string | undefined;\n data: unknown;\n token: string | undefined;\n dataLoader: boolean;\n}): string {\n const { js, title, csp, data, token, dataLoader } = opts;\n // Escape </script> sequences to prevent script-tag break-out.\n const safeJs = js.replace(/<\\/script>/gi, \"<\\\\/script>\");\n\n const titleEscaped = title\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n\n const cspMeta = csp\n ? ` <meta http-equiv=\"Content-Security-Policy\" content=\"${csp.replace(/&/g, \"&amp;\").replace(/\"/g, \"&quot;\")}\" />\\n`\n : \"\";\n\n // Double-stringify data: outer JSON.stringify produces a JSON string, then\n // escapeForScriptTag ensures </script> and U+2028/U+2029 can't break out.\n // Token is also passed through escapeForScriptTag so an arbitrary caller\n // providing a non-hex token can't break out of the inline script.\n const safeToken = token ? escapeForScriptTag(JSON.stringify(token)) : null;\n const tokenField = safeToken ? `, token: ${safeToken}` : \"\";\n const bootstrapValue = dataLoader\n ? `{ token: ${safeToken ?? '\"\"'} }`\n : `{ data: JSON.parse(${escapeForScriptTag(JSON.stringify(JSON.stringify(data ?? null)))})${tokenField} }`;\n\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>${titleEscaped}</title>\n${cspMeta} <!-- ui-leaf bootstrap -->\n <script>window.__UI_LEAF__ = ${bootstrapValue};</script>\n </head>\n <body>\n <div id=\"root\"></div>\n <script type=\"module\">${safeJs}</script>\n </body>\n</html>`;\n}\n\nexport async function compileView(opts: CompileOptions): Promise<CompileResult> {\n const {\n entry,\n viewsRoot,\n data,\n title = \"ui-leaf\",\n csp,\n // allowedHosts has no compile-time effect; accepted for API symmetry.\n allowedHosts: _allowedHosts,\n token,\n dataLoader = false,\n } = opts;\n\n const viewsRootAbs = resolve(viewsRoot);\n const hasExt = /\\.[a-z]+$/i.test(entry);\n const viewAbs = resolve(viewsRootAbs, hasExt ? entry : `${entry}.tsx`);\n if (!viewAbs.startsWith(viewsRootAbs + sep)) {\n return {\n html: \"\",\n errors: [\n {\n file: \"<unknown>\",\n line: 0,\n column: 0,\n message: `ui-leaf: view '${entry}' resolves outside viewsRoot`,\n },\n ],\n };\n }\n try {\n await stat(viewAbs);\n } catch {\n return {\n html: \"\",\n errors: [\n {\n file: viewAbs,\n line: 0,\n column: 0,\n message: `ui-leaf: view '${entry}' not found at ${viewAbs}`,\n },\n ],\n };\n }\n\n // Generate a temp entry that imports the resolved view, mounts React via\n // createRoot, and wires the mutation/heartbeat bridge.\n const tempDir = await mkdtemp(join(tmpdir(), \"ui-leaf-compile-\"));\n try {\n const entryPath = join(tempDir, \"entry.tsx\");\n\n const entryContent = dataLoader\n ? `import { createRoot } from \"react-dom/client\";\nimport View from ${JSON.stringify(viewAbs)};\n\nconst ctx = (globalThis as { __UI_LEAF__?: { token?: string } }).__UI_LEAF__ ?? {};\nconst token = ctx.token;\n${SHARED_BRIDGE}\n\nasync function bootstrap(): Promise<void> {\n const res = await fetch(\"/api/data\", {\n headers: token ? { Authorization: \"Bearer \" + token } : {},\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\"ui-leaf: /api/data fetch failed (\" + res.status + \"): \" + text);\n }\n const data = await res.json();\n const el = document.getElementById(\"root\");\n if (!el) throw new Error(\"ui-leaf: #root element missing\");\n createRoot(el).render(<View data={data} mutate={mutate} />);\n}\nbootstrap();\n`\n : `import { createRoot } from \"react-dom/client\";\nimport View from ${JSON.stringify(viewAbs)};\n\nconst ctx = (globalThis as { __UI_LEAF__?: { data?: unknown; token?: string } }).__UI_LEAF__ ?? {};\nconst data = ctx.data;\nconst token = ctx.token;\n${SHARED_BRIDGE}\n\nconst el = document.getElementById(\"root\");\nif (!el) throw new Error(\"ui-leaf: #root element missing\");\ncreateRoot(el).render(<View data={data} mutate={mutate} />);\n`;\n\n await writeFile(entryPath, entryContent);\n\n const buildResult = await runBunBuild(entryPath);\n if (\"errors\" in buildResult) return { html: \"\", errors: buildResult.errors };\n\n return {\n html: assembleHtml({ js: buildResult.js, title, csp, data, token, dataLoader }),\n errors: [],\n };\n } finally {\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Compile an inline TSX source string into a full HTML page.\n *\n * The source is treated as a self-contained component; relative imports are\n * not supported (v1.0.0 constraint — the string has no filesystem context).\n * Bare-package imports (react, react-dom) work via the react-alias plugin.\n */\nexport async function compileSource(opts: CompileSourceOptions): Promise<CompileResult> {\n const { source, data, title = \"ui-leaf\", csp, token } = opts;\n\n const tempDir = await mkdtemp(join(tmpdir(), \"ui-leaf-src-\"));\n try {\n // Write the caller's tsx as the view file, then write a thin entry wrapper.\n const viewPath = join(tempDir, \"view.tsx\");\n const entryPath = join(tempDir, \"entry.tsx\");\n\n await writeFile(viewPath, source);\n\n const entryContent = `import { createRoot } from \"react-dom/client\";\nimport View from ${JSON.stringify(viewPath)};\n\nconst ctx = (globalThis as { __UI_LEAF__?: { data?: unknown; token?: string } }).__UI_LEAF__ ?? {};\nconst data = ctx.data;\nconst token = ctx.token;\n${SHARED_BRIDGE}\n\nconst el = document.getElementById(\"root\");\nif (!el) throw new Error(\"ui-leaf: #root element missing\");\ncreateRoot(el).render(<View data={data} mutate={mutate} />);\n`;\n\n await writeFile(entryPath, entryContent);\n\n const buildResult = await runBunBuild(entryPath);\n if (\"errors\" in buildResult) return { html: \"\", errors: buildResult.errors };\n\n return {\n html: assembleHtml({ js: buildResult.js, title, csp, data, token, dataLoader: false }),\n errors: [],\n };\n } finally {\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n","export function escapeForScriptTag(json: string): string {\n // Defend against </script> break-out and U+2028/U+2029 line terminators\n // that JSON.stringify emits raw but JS string literals don't accept.\n return json\n .replace(/</g, \"\\\\u003c\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n"],"mappings":"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","names":["resolve","fireEvent","url","resolve"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/server.ts","../src/compile.ts","../src/internal/html.ts"],"sourcesContent":["// ui-leaf — Customizable browser views, on demand, for any CLI.\n// https://github.com/OpenThinkAi/ui-leaf\n\nimport { resolve } from \"node:path\";\nimport {\n startDevServer,\n type CloseReason,\n type CspOption,\n type DevServerEvent,\n type DevServerEventListener,\n type MutationHandler,\n type Shell,\n} from \"./server.js\";\nimport type { BuildError } from \"./compile.js\";\n\nexport type { BuildError, CloseReason, CspOption, DevServerEvent, DevServerEventListener, MutationHandler, Shell };\n\nexport interface MountOptions {\n /** View name. Resolves to <viewsRoot>/<view>.tsx. */\n view: string;\n /**\n * JSON-serializable data passed to the view as a prop.\n *\n * Privacy note: the data is compiled into the HTML served at the mount URL\n * and held in memory for the mount lifetime. Any same-UID local process\n * that can reach `127.0.0.1:<port>` can fetch `GET /` and read it — the\n * per-launch token guards `/mutate` against drive-by cross-origin requests\n * in the browser, not against other processes on the machine. For PHI, PCI,\n * financial records, or anything where a same-UID local reader is in your\n * threat model, use `dataLoader` instead — the loader's return value is\n * served at a token-gated `/api/data` endpoint and never appears in the HTML.\n */\n data?: unknown;\n /**\n * Async function that supplies sensitive data to the view without\n * including it in the served HTML. When provided, the loader is called\n * once during mount setup; its resolved value is served at a token-gated\n * `GET /api/data` endpoint (same per-launch token as `/mutate`) and the\n * view fetches it on first render before calling `createRoot().render()`.\n * The data never appears in the compiled HTML.\n *\n * Use this instead of `data` for PHI, PCI, financial records, or anything\n * else where in-HTML data exposure is in your threat model.\n *\n * Error semantics: if the loader rejects, the rejection propagates to the\n * `mount()` caller (no automatic retry). Errors surface at mount time,\n * matching the synchronous `data` path's behavior.\n *\n * Mutual exclusion: passing both `data` and `dataLoader` throws at\n * mount time.\n */\n dataLoader?: () => Promise<unknown>;\n /**\n * Mutation handlers the view can call via mutate(name, args).\n * Each handler can self-type its args and return:\n *\n * mutations: {\n * recategorize: async (args: { id: string; category: string }) => {\n * await db.recategorize(args.id, args.category);\n * return { ok: true };\n * },\n * }\n *\n * Each request body is capped at 1 MiB; oversized POSTs are rejected\n * with a 400 and the view's mutate() promise rejects with a clear error.\n */\n // biome-ignore lint/suspicious/noExplicitAny: each handler has its own\n // arg/return types; the map can't share one shape.\n mutations?: Record<string, MutationHandler<any, any>>;\n /** Root directory holding view .tsx files. Defaults to <cwd>/views. */\n viewsRoot?: string;\n /** Browser tab title. Defaults to \"ui-leaf\". */\n title?: string;\n /**\n * Port to bind. Defaults to 5810 — unused by the major Node dev tools.\n * If the port is unavailable, ui-leaf bumps to the next free port and\n * the actual bound port is reflected on the returned `url` and `port`.\n * Pass `0` to let the OS pick a free port directly.\n * Override only if you need a stable URL (e.g. an external bookmark).\n */\n port?: number;\n /**\n * Open the browser when ready. Defaults to true. When false, mount()\n * returns the URL on its resolved value so the caller can drive a\n * headless browser, log the address, etc.\n */\n openBrowser?: boolean;\n /**\n * Browser shell. Defaults to \"tab\".\n *\n * - `\"tab\"` — open in the user's default browser as a regular tab.\n * Works everywhere; URL bar is visible.\n *\n * - `\"app\"` — try Chromium's `--app` mode for a chromeless window\n * (no URL bar, no tabs, looks like a desktop app). Available on\n * Chrome, Edge, and Brave. If no Chromium browser is installed,\n * ui-leaf falls back to \"tab\" with a stderr note. Safari and\n * Firefox always fall back.\n *\n * Pair with the share-link pattern (see \"Sharing views across users\"\n * in the README) when you want users to never see a localhost URL.\n */\n shell?: Shell;\n /**\n * Abort to close the dev server early. The returned `closed` promise\n * resolves either way; if you need to distinguish a signal-driven close\n * from a natural tab-close, check `signal.aborted` after the await.\n */\n signal?: AbortSignal;\n /**\n * Browser silence (ms) after which the mount emits `disconnected`.\n * Defaults to 75000 — chosen to survive a single browser background-tab\n * throttle (browsers clamp setInterval to ~60s in hidden tabs). Lower it\n * for faster `disconnected` detection; raise it for sessions where the\n * page may pause (debugger, machine sleep). Note: this no longer controls\n * when the mount terminates — only when the `disconnected` event fires.\n */\n heartbeatTimeoutMs?: number;\n /**\n * Content-Security-Policy enforcement. Defaults to \"off\".\n *\n * - `\"off\"` — no CSP header sent. Views can fetch arbitrary URLs and\n * embed external resources freely. The data/mutations convention is\n * honor-system.\n *\n * - `\"strict\"` — ui-leaf sends a balanced preset: locks `connect-src`\n * to same-origin (the architectural lock — views cannot fetch\n * external APIs, so all data flows through `data` and `mutations`),\n * while permitting common needs (HTTPS images / fonts, inline\n * styles for React). View files can only *add* further restrictions\n * via meta tag, never remove them.\n *\n * - `string` — raw CSP header value for full control. Use when the\n * \"strict\" preset doesn't fit (e.g. you need `connect-src` to\n * include a Sentry endpoint).\n *\n * Trade-off: when set to \"strict\" or a custom string, a view file\n * cannot relax the policy at runtime. Switching back requires changing\n * the mount() call. That rigidity is a feature.\n */\n csp?: CspOption;\n /**\n * Extra hostnames accepted in the request `Host` and `Origin` headers\n * on top of the built-in loopback set (`localhost`, `127.0.0.1`, `[::1]`).\n *\n * The dev server gates every request on this set to defend against\n * DNS-rebinding attacks; non-matching requests get HTTP 403. Use this\n * escape hatch when you need to reach the dev server through a custom\n * `/etc/hosts` alias (e.g. `[\"my-app.local\"]`) or any other loopback\n * name. Hostnames are matched case-insensitively, port-agnostic.\n *\n * Be deliberate: any hostname you add becomes a viable DNS-rebinding\n * target. Don't add wildcards, public DNS names, or LAN hostnames you\n * don't fully control.\n */\n allowedHosts?: string[];\n /**\n * Suppress ui-leaf output to stdout. Default: false.\n *\n * When you drive `mount()` programmatically — e.g. as part of a Node\n * bridge for a non-Node CLI that's spawned ui-leaf as a subprocess —\n * stdout is usually reserved for a structured protocol (line-delimited\n * JSON, etc.). Setting `silent: true` redirects `process.stdout.write`\n * to `process.stderr` for the lifetime of the server, restored on close.\n *\n * Tradeoff: any other code in the same process that writes to stdout\n * during the server's lifetime is also redirected. Hold the captured\n * `process.stdout.write` reference yourself if you need to write to the\n * real stdout from the same process.\n */\n silent?: boolean;\n /**\n * Grace period (ms) after server start before the heartbeat watcher arms.\n * Cold-loading clients sometimes take a few seconds to send their first\n * heartbeat. Defaults to 30000.\n */\n startupGraceMs?: number;\n /**\n * Test seam: heartbeat watcher tick interval (ms). Defaults to 1000.\n * Never set this in production.\n */\n _heartbeatCheckIntervalMs?: number;\n}\n\nexport interface MountedView {\n /** URL the view is reachable at (http://127.0.0.1:<port>). */\n url: string;\n /** Bound port. Useful when port: 0 was requested. */\n port: number;\n /** Resolves with the close reason when the mount terminates. */\n closed: Promise<CloseReason>;\n /** Force-close the dev server early. */\n close: () => Promise<void>;\n /**\n * Replace in-memory data and notify all `data-updated` listeners.\n * Preserves in-page React state — no recompile.\n */\n update: (data: unknown) => void;\n /**\n * Swap the view source on the fly. Triggers a recompile; on success replaces\n * the served HTML and notifies all `view-swapped` listeners. On compile\n * failure the previous HTML is preserved. Returns compile errors if any.\n */\n swapView: (source: string) => Promise<BuildError[]>;\n /**\n * Atomically replace both data and view source. If compilation fails neither\n * takes effect. Returns compile errors if any.\n */\n patch: (data: unknown, source: string) => Promise<BuildError[]>;\n /**\n * Re-invoke the browser-open function to launch a fresh tab at the same URL.\n * Always opens a new tab — if one is already connected, a duplicate opens.\n */\n reopen: () => Promise<void>;\n /** Subscribe to a server-side event (data-updated | view-swapped). */\n on: (event: DevServerEvent, listener: DevServerEventListener) => void;\n /** Unsubscribe a previously-registered listener. */\n off: (event: DevServerEvent, listener: DevServerEventListener) => void;\n}\n\n/**\n * Mount a customizable browser view from a CLI. Spins up a local dev server\n * and renders the chosen view with the given data. Returns once the server\n * is ready; await `result.closed` to block until the mount terminates.\n *\n * Mutations triggered in the view are dispatched to the registered handlers\n * here; the view never reaches the CLI's backing API directly.\n *\n * **Lifecycle.** Browser tab close (heartbeat silence) emits a `disconnected`\n * event on `result` but does NOT resolve `closed` or stop the server. The\n * mount only terminates — and `closed` resolves — when you call\n * `result.close()`, receive SIGINT/SIGTERM, or an internal error occurs.\n * Listen for `disconnected` and call `result.close()` yourself if you want\n * fast shutdown on tab close.\n *\n * **Multi-tab note.** The heartbeat is a single high-water mark across all\n * open tabs; `disconnected` fires only when all tabs go silent. Closing one\n * tab while another is open emits no event.\n *\n * Ctrl+C: this function installs SIGINT and SIGTERM handlers that close\n * the server before exiting.\n */\nexport async function mount(opts: MountOptions): Promise<MountedView> {\n const viewsRoot = opts.viewsRoot ?? resolve(process.cwd(), \"views\");\n\n const server = await startDevServer({\n view: opts.view,\n data: opts.data,\n dataLoader: opts.dataLoader,\n viewsRoot,\n mutations: opts.mutations,\n title: opts.title,\n port: opts.port,\n openBrowser: opts.openBrowser,\n shell: opts.shell,\n heartbeatTimeoutMs: opts.heartbeatTimeoutMs,\n startupGraceMs: opts.startupGraceMs,\n csp: opts.csp,\n allowedHosts: opts.allowedHosts,\n silent: opts.silent,\n _heartbeatCheckIntervalMs: opts._heartbeatCheckIntervalMs,\n });\n\n const onSignal = (signal: NodeJS.Signals): void => {\n void (async () => {\n await server.close(\"signal\");\n // Re-raise so default exit codes still apply.\n process.kill(process.pid, signal);\n })();\n };\n const sigint = (): void => onSignal(\"SIGINT\");\n const sigterm = (): void => onSignal(\"SIGTERM\");\n process.once(\"SIGINT\", sigint);\n process.once(\"SIGTERM\", sigterm);\n\n if (opts.signal) {\n if (opts.signal.aborted) {\n process.off(\"SIGINT\", sigint);\n process.off(\"SIGTERM\", sigterm);\n await server.close();\n return {\n url: server.url,\n port: server.port,\n closed: Promise.resolve<CloseReason>(\"caller\"),\n close: () => server.close(),\n update: server.update.bind(server),\n swapView: (source: string) => server.swapView(source),\n patch: (data: unknown, source: string) => server.patch(data, source),\n reopen: server.reopen.bind(server),\n on: server.on.bind(server),\n off: server.off.bind(server),\n };\n }\n opts.signal.addEventListener(\n \"abort\",\n () => void server.close(),\n { once: true },\n );\n }\n\n const closed = server.closed.finally(() => {\n process.off(\"SIGINT\", sigint);\n process.off(\"SIGTERM\", sigterm);\n });\n\n return {\n url: server.url,\n port: server.port,\n closed,\n close: () => server.close(),\n update: server.update.bind(server),\n swapView: (source: string) => server.swapView(source),\n patch: (data: unknown, source: string) => server.patch(data, source),\n reopen: server.reopen.bind(server),\n on: server.on.bind(server),\n off: server.off.bind(server),\n };\n}\n","import { randomBytes, timingSafeEqual as nodeTimingSafeEqual } from \"node:crypto\";\nimport open, { apps } from \"open\";\nimport { compileView, compileSource } from \"./compile.js\";\nimport type { CloseReason } from \"./ipc.js\";\n\n// Module-level stdout redirect state. Captured ONCE at module load so\n// concurrent silent: true mounts share the same \"original\" reference and\n// restore-order doesn't matter. Refcounted so the last close restores.\nconst ORIGINAL_STDOUT_WRITE = process.stdout.write.bind(process.stdout);\nlet stdoutRedirectCount = 0;\n\n/**\n * Redirect process.stdout.write to process.stderr until the returned\n * function is called. Safe under concurrent silent mounts.\n */\nfunction redirectStdoutToStderr(): () => void {\n stdoutRedirectCount++;\n if (stdoutRedirectCount === 1) {\n // biome-ignore lint/suspicious/noExplicitAny: stdout.write has overloaded\n // signatures; forward exactly what comes in.\n process.stdout.write = ((chunk: any, enc?: any, cb?: any) =>\n process.stderr.write(chunk, enc, cb)) as typeof process.stdout.write;\n }\n let released = false;\n return () => {\n if (released) return;\n released = true;\n stdoutRedirectCount--;\n if (stdoutRedirectCount === 0) {\n process.stdout.write = ORIGINAL_STDOUT_WRITE;\n }\n };\n}\n\nexport type MutationHandler<TArgs = unknown, TResult = unknown> = (\n args: TArgs,\n) => TResult | Promise<TResult>;\n\n// `(string & {})` preserves the \"off\" / \"strict\" autocomplete suggestions\n// while still allowing arbitrary CSP strings. Plain string would collapse\n// the union and lose IntelliSense for the literals.\nexport type CspOption = \"off\" | \"strict\" | (string & {});\n\nexport type Shell = \"tab\" | \"app\";\n\n/**\n * Try to open `url` in a Chromium browser's --app mode (chromeless window:\n * no URL bar, no tabs). Returns true if a Chromium browser was found and\n * launched, false if no Chromium variant is installed (caller should fall\n * back to the default-browser tab).\n */\nasync function openInAppMode(url: string): Promise<boolean> {\n // Order: most-common Chromium variants first.\n const candidates = [apps.chrome, apps.edge, apps.brave];\n for (const app of candidates) {\n try {\n await open(url, { app: { name: app, arguments: [`--app=${url}`] } });\n return true;\n } catch {\n // Try next candidate; `open` throws if the binary isn't installed.\n }\n }\n return false;\n}\n\n/**\n * Strict preset: locks `connect-src` to same-origin (the architectural\n * lock that forces views to route mutations through the CLI), while\n * permitting common needs (HTTPS images/fonts, inline styles for React).\n * A future v1.x mode could tighten script-src once usage patterns are known.\n */\nconst STRICT_CSP = [\n \"default-src 'self'\",\n \"connect-src 'self'\",\n \"img-src 'self' data: https:\",\n \"font-src 'self' https: data:\",\n \"style-src 'self' 'unsafe-inline'\",\n \"script-src 'self' 'unsafe-inline'\",\n].join(\"; \");\n\nfunction resolveCsp(opt: CspOption | undefined): string | null {\n if (!opt || opt === \"off\") return null;\n if (opt === \"strict\") return STRICT_CSP;\n return opt;\n}\n\nfunction timingSafeEqual(a: string, b: string): boolean {\n // Length check is not timing-safe but is fine — the token length is fixed\n // and known to attackers regardless. The byte compare must be timing-safe.\n if (a.length !== b.length) return false;\n return nodeTimingSafeEqual(Buffer.from(a, \"utf8\"), Buffer.from(b, \"utf8\"));\n}\n\nconst DEFAULT_LOOPBACK_HOSTNAMES = [\"127.0.0.1\", \"localhost\", \"::1\"] as const;\n\n// Extract the hostname portion of a Host header value, stripping the port.\n// IPv6 hosts arrive bracketed (`[::1]:5810`); plain hosts as `host:port`\n// or bare `host`. Returns lowercased hostname or null on shapes we don't\n// recognise (caller treats null as \"reject\").\nfunction parseHostHeader(value: string): string | null {\n const trimmed = value.trim();\n if (trimmed === \"\") return null;\n if (trimmed.startsWith(\"[\")) {\n const close = trimmed.indexOf(\"]\");\n if (close === -1) return null;\n return trimmed.slice(1, close).toLowerCase();\n }\n const colon = trimmed.indexOf(\":\");\n return (colon === -1 ? trimmed : trimmed.slice(0, colon)).toLowerCase();\n}\n\n// DNS-rebinding defence: every request must arrive with a Host header\n// pointing at one of the allowed names. Same gate applies to Origin when\n// the browser sends one. Absent Origin is fine — many legitimate\n// same-origin requests omit it. `Origin: null` is allowed because\n// sandboxed iframes and `file://` pages send it; the Host check still\n// constrains the network path so the Origin allowance isn't load-bearing.\nfunction isAllowedHost(value: string | undefined, allowed: Set<string>): boolean {\n const host = value === undefined ? null : parseHostHeader(value);\n return host !== null && allowed.has(host);\n}\n\nfunction isAllowedOrigin(value: string | undefined, allowed: Set<string>): boolean {\n if (value === undefined || value === \"\" || value === \"null\") return true;\n try {\n // WHATWG URL keeps the brackets on IPv6 hostnames (`[::1]`), but the\n // allow-list stores them stripped (matching parseHostHeader's output)\n // so origins and hosts compare consistently.\n let hostname = new URL(value).hostname.toLowerCase();\n if (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) {\n hostname = hostname.slice(1, -1);\n }\n return allowed.has(hostname);\n } catch {\n return false;\n }\n}\n\nexport interface DevServerOptions {\n view: string;\n data?: unknown;\n dataLoader?: () => Promise<unknown>;\n viewsRoot: string;\n // biome-ignore lint/suspicious/noExplicitAny: each handler has its own\n // arg/return types; the map can't share one shape.\n mutations?: Record<string, MutationHandler<any, any>>;\n /** Browser tab title. Defaults to \"ui-leaf\". */\n title?: string;\n port?: number;\n openBrowser?: boolean;\n /**\n * Browser shell. Defaults to \"tab\".\n *\n * - \"tab\" — open in user's default browser as a regular tab.\n * - \"app\" — try Chromium's --app mode (chromeless window). Falls back\n * to \"tab\" if no Chromium browser is installed (Chrome/Edge/Brave),\n * with a stderr note. Safari and Firefox always fall back.\n */\n shell?: Shell;\n /**\n * Browser silence (ms) after which the mount transitions to disconnected.\n * The mount does NOT terminate on disconnect — only explicit close/signal/error does.\n */\n heartbeatTimeoutMs?: number;\n /** Grace period after server start before the heartbeat watcher is armed. */\n startupGraceMs?: number;\n /**\n * Test seam: interval (ms) for the heartbeat watcher tick. Defaults to 1000.\n * Lower values let tests observe disconnect transitions without sleeping ~1s.\n * Never set this in production.\n */\n _heartbeatCheckIntervalMs?: number;\n /** Content-Security-Policy enforcement. See MountOptions.csp. */\n csp?: CspOption;\n /**\n * Extra hostnames (beyond `localhost`, `127.0.0.1`, `[::1]`) accepted in\n * the request `Host` and `Origin` headers. Use to allow a custom\n * `/etc/hosts` alias or another loopback name; values are matched by\n * hostname only (port-agnostic). Anything outside this set + the\n * loopback defaults is rejected with HTTP 403 to defend against\n * DNS-rebinding attacks. Default: empty.\n */\n allowedHosts?: string[];\n /**\n * Suppress ui-leaf output to stdout. When true, process.stdout.write is\n * redirected to process.stderr for the lifetime of the server, restored\n * on close(). Use when driving mount() programmatically and stdout is\n * reserved for a structured protocol (e.g. line-delimited JSON).\n * Default: false.\n */\n silent?: boolean;\n /**\n * Test seam: replace the browser-open implementation. When provided,\n * called instead of `open(url)` for both the initial open and `reopen()`.\n * Never set this in production; use `openBrowser: false` instead.\n */\n _opener?: (url: string) => Promise<void>;\n}\n\nexport type { CloseReason };\n\nexport type DevServerEvent = \"data-updated\" | \"view-swapped\" | \"disconnected\" | \"reconnected\";\nexport type DevServerEventListener = () => void;\n\ntype ConnectionState = \"connecting\" | \"connected\" | \"disconnected\";\n\nexport interface DevServer {\n url: string;\n port: number;\n /** Resolves with the close reason when the mount terminates. */\n closed: Promise<CloseReason>;\n close: (reason?: CloseReason) => Promise<void>;\n /**\n * Replace in-memory data and emit a `data-updated` event to all\n * registered listeners. Does not recompile the view.\n */\n update: (data: unknown) => void;\n /**\n * Recompile the view from an inline TSX source string and replace the\n * in-memory HTML. Emits `view-swapped` on success; preserves the previous\n * HTML on compile failure. Returns errors array (empty = success).\n */\n swapView: (source: string) => Promise<import(\"./compile.js\").BuildError[]>;\n /**\n * Atomically replace both data and view source. If compilation fails,\n * neither takes effect. Returns errors array (empty = success).\n */\n patch: (data: unknown, source: string) => Promise<import(\"./compile.js\").BuildError[]>;\n /**\n * Re-invoke the browser-open function to launch a fresh tab at the same URL.\n * Always opens a new tab — if one is already connected, a duplicate opens.\n */\n reopen: () => Promise<void>;\n /**\n * Subscribe to a server-side event. Listeners are called synchronously\n * after each mutation completes.\n *\n * Events:\n * \"data-updated\" — fired by update() and patch()\n * \"view-swapped\" — fired by swapView() and patch()\n */\n on: (event: DevServerEvent, listener: DevServerEventListener) => void;\n off: (event: DevServerEvent, listener: DevServerEventListener) => void;\n}\n\nexport async function startDevServer(opts: DevServerOptions): Promise<DevServer> {\n const {\n view,\n data,\n dataLoader,\n viewsRoot,\n mutations = {},\n title = \"ui-leaf\",\n port,\n openBrowser = true,\n shell = \"tab\",\n heartbeatTimeoutMs = 75_000,\n startupGraceMs = 30_000,\n csp,\n allowedHosts,\n silent = false,\n _opener,\n _heartbeatCheckIntervalMs = 1000,\n } = opts;\n const cspHeader = resolveCsp(csp);\n const allowedHostSet = new Set<string>(DEFAULT_LOOPBACK_HOSTNAMES);\n for (const h of allowedHosts ?? []) allowedHostSet.add(h.toLowerCase());\n const allowedHostList = [...allowedHostSet].join(\", \");\n\n // Programmatic consumers (esp. non-Node CLIs spawning ui-leaf as a\n // subprocess) often reserve stdout for a structured protocol. Redirect\n // process.stdout.write to stderr to catch anything that bypasses our\n // own output path.\n const restoreStdout: (() => void) | null = silent ? redirectStdoutToStderr() : null;\n\n try {\n if (view.includes(\"/\") || view.includes(\"\\\\\")) {\n throw new Error(\n `ui-leaf: view '${view}' must be a bare identifier with no path separators`,\n );\n }\n\n if (data !== undefined && dataLoader) {\n throw new Error(\"ui-leaf: pass data or dataLoader, not both\");\n }\n\n const token = randomBytes(32).toString(\"hex\");\n\n // Eagerly invoke the loader before starting the server. The resolved\n // value lives only in this closure — it is never written to disk. If the\n // loader rejects, the setup-failure catch below restores stdout before\n // re-throwing.\n let loadedData: unknown;\n if (dataLoader) {\n loadedData = await dataLoader();\n }\n\n // Compile the view once at mount time; hold the resulting HTML in memory.\n const result = await compileView({\n entry: view,\n viewsRoot,\n data: dataLoader ? null : data,\n title,\n csp: cspHeader ?? undefined,\n token,\n dataLoader: !!dataLoader,\n });\n\n if (result.errors.length > 0) {\n const msg = result.errors.map((e) => e.message).join(\"; \");\n throw new Error(`ui-leaf: view compilation failed: ${msg}`);\n }\n\n // Mutable view state: the / handler reads from this on every request.\n // update(), swapView(), patch() mutate it in place.\n const viewState = { html: result.html, data: dataLoader ? loadedData : data };\n\n // Minimal event broker. Pre-seeded so fireEvent's get() always returns a Set.\n const listeners = new Map<DevServerEvent, Set<DevServerEventListener>>([\n [\"data-updated\", new Set()],\n [\"view-swapped\", new Set()],\n [\"disconnected\", new Set()],\n [\"reconnected\", new Set()],\n ]);\n function fireEvent(event: DevServerEvent): void {\n for (const fn of listeners.get(event)!) fn();\n }\n\n const sseClients = new Set<ReadableStreamDefaultController<Uint8Array>>();\n const sseEncoder = new TextEncoder();\n\n function broadcast(event: Record<string, unknown>): void {\n const frame = sseEncoder.encode(`data: ${JSON.stringify(event)}\\n\\n`);\n for (const controller of sseClients) {\n try {\n controller.enqueue(frame);\n } catch {\n sseClients.delete(controller);\n }\n }\n }\n\n let lastHeartbeatAt = Date.now();\n let closeRequested = false;\n let connectionState: ConnectionState = \"connecting\";\n let resolveClosed: (reason: CloseReason) => void = () => {};\n const closed = new Promise<CloseReason>((r) => {\n resolveClosed = r;\n });\n\n const bunPort = port === undefined ? 5810 : port; // port: 0 → OS picks\n let actualPort = bunPort;\n\n const handler = (req: Request): Response | Promise<Response> => {\n const host = req.headers.get(\"host\") ?? undefined;\n const origin = req.headers.get(\"origin\") ?? undefined;\n\n // DNS-rebinding gate: reject any request (including WebSocket upgrade\n // attempts) that does not arrive with an allowed Host. When Origin is\n // present, it must also be in the allowed set.\n const hostOk = isAllowedHost(host, allowedHostSet);\n const originOk = isAllowedOrigin(origin, allowedHostSet);\n if (!hostOk || !originOk) {\n const offender = !hostOk\n ? `Host \"${host ?? \"(absent)\"}\"`\n : `Origin \"${origin}\"`;\n return new Response(\n `ui-leaf: refusing request with ${offender} — only the following hostnames are accepted to prevent DNS rebinding: ${allowedHostList}. Open the server at http://localhost:${actualPort}/ or http://127.0.0.1:${actualPort}/, or pass { allowedHosts: [\"my-alias\"] } to mount() to permit a custom alias.\\n`,\n { status: 403, headers: { \"Content-Type\": \"text/plain; charset=utf-8\" } },\n );\n }\n\n const headers: Record<string, string> = {};\n if (cspHeader) {\n headers[\"Content-Security-Policy\"] = cspHeader;\n }\n\n const url = new URL(req.url);\n const path = url.pathname;\n const method = req.method;\n\n if (method === \"GET\" && path === \"/\") {\n return new Response(viewState.html, {\n status: 200,\n headers: { ...headers, \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n\n if (method === \"POST\" && path === \"/heartbeat\") {\n if (!checkAuth(req, token)) {\n return new Response(\"\", { status: 401, headers });\n }\n lastHeartbeatAt = Date.now();\n if (connectionState === \"disconnected\") {\n connectionState = \"connected\";\n fireEvent(\"reconnected\");\n } else if (connectionState === \"connecting\") {\n connectionState = \"connected\";\n }\n return new Response(\"\", { status: 204, headers });\n }\n\n if (method === \"POST\" && path === \"/mutate\") {\n if (!checkAuth(req, token)) {\n return new Response(\"\", { status: 401, headers });\n }\n return handleMutate(req, mutations, headers);\n }\n\n if (method === \"GET\" && path === \"/api/data\") {\n if (!dataLoader) {\n return new Response(JSON.stringify({ error: \"not found\" }), {\n status: 404,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n if (!checkAuth(req, token)) {\n return new Response(\"\", { status: 401, headers });\n }\n return new Response(JSON.stringify(viewState.data !== undefined ? viewState.data : null), {\n status: 200,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n\n if (method === \"GET\" && path === \"/events\") {\n if (!checkAuth(req, token)) {\n return new Response(\"\", { status: 401, headers });\n }\n let sseController!: ReadableStreamDefaultController<Uint8Array>;\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n sseController = controller;\n sseClients.add(controller);\n // Enqueue an SSE comment immediately so Bun flushes response headers\n // before any broadcast event arrives (empty streams block header send).\n controller.enqueue(sseEncoder.encode(\": connected\\n\\n\"));\n req.signal?.addEventListener(\"abort\", () => {\n sseClients.delete(sseController);\n try { sseController.close(); } catch { /* already closed */ }\n });\n },\n cancel() {\n sseClients.delete(sseController);\n },\n });\n return new Response(stream, {\n status: 200,\n headers: {\n ...headers,\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n },\n });\n }\n\n return new Response(JSON.stringify({ error: \"not found\" }), {\n status: 404,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n };\n\n let heartbeatWatcher: NodeJS.Timeout | undefined;\n\n // `bunServer` is assigned immediately after this declaration by the IIFE\n // below. The `!` assertion is safe: cleanup is never called during server\n // construction, only after the server is running.\n let bunServer!: ReturnType<typeof Bun.serve>;\n\n const cleanup = async (reason: CloseReason): Promise<void> => {\n if (closeRequested) return;\n closeRequested = true;\n if (heartbeatWatcher) clearInterval(heartbeatWatcher);\n broadcast({ type: \"closing\", reason });\n for (const controller of sseClients) {\n try { controller.close(); } catch { /* already closed */ }\n }\n sseClients.clear();\n // Graceful stop: waits for in-flight writes (including the closing SSE\n // event) to flush before tearing down TCP connections.\n await bunServer.stop();\n if (restoreStdout) restoreStdout();\n resolveClosed(reason);\n };\n\n // Auto-bump: if bunPort is busy, try bunPort+1 … up to MAX_PORT_ATTEMPTS.\n // port: 0 goes straight to Bun (OS assigns a free port; never EADDRINUSE).\n // The Bun error callback fires for socket errors AND for unhandled throws in\n // the fetch handler. Either case routes through cleanup(\"error\") so the mount\n // terminates cleanly rather than hanging. This means a single buggy request\n // handler is fatal — intentional: unhandled errors indicate broken invariants.\n const serverErrorHandler = (_err: Error): Response => {\n void cleanup(\"error\");\n return new Response(JSON.stringify({ error: \"internal server error\" }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n };\n bunServer = (() => {\n if (bunPort === 0) {\n return Bun.serve({ hostname: \"127.0.0.1\", port: 0, fetch: handler, error: serverErrorHandler, idleTimeout: 0 });\n }\n const MAX_PORT_ATTEMPTS = 10;\n for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {\n try {\n return Bun.serve({ hostname: \"127.0.0.1\", port: bunPort + i, fetch: handler, error: serverErrorHandler, idleTimeout: 0 });\n } catch (err) {\n const isAddrinuse = err instanceof Error && err.message.includes(\"EADDRINUSE\");\n if (!isAddrinuse || i === MAX_PORT_ATTEMPTS - 1) {\n if (isAddrinuse) {\n throw new Error(\n `ui-leaf: ports ${bunPort}–${bunPort + MAX_PORT_ATTEMPTS - 1} are all in use. Pass { port: 0 } to mount() for an OS-assigned port.`,\n );\n }\n throw err;\n }\n }\n }\n throw new Error(\"unreachable\"); // TypeScript: loop always returns or throws\n })();\n actualPort = bunServer.port ?? bunPort;\n const url = `http://127.0.0.1:${actualPort}`;\n const startedAt = Date.now();\n\n heartbeatWatcher = setInterval(() => {\n if (closeRequested) return;\n const now = Date.now();\n if (now - startedAt < startupGraceMs) return;\n if (now - lastHeartbeatAt > heartbeatTimeoutMs) {\n if (connectionState !== \"disconnected\") {\n connectionState = \"disconnected\";\n fireEvent(\"disconnected\");\n }\n }\n }, _heartbeatCheckIntervalMs);\n\n // The URL passed to the browser includes the token as a hash fragment so it\n // is never sent to the server (browsers strip fragments before HTTP requests).\n // The public `url` returned to consumers stays fragment-free.\n const openUrl = `${url}/#token=${token}`;\n\n // Browser-open implementation, or the test-seam override if one was supplied.\n const doOpen: () => Promise<void> = _opener\n ? () => _opener(openUrl)\n : async () => {\n if (shell === \"app\") {\n const launched = await openInAppMode(openUrl);\n if (!launched) {\n process.stderr.write(\n `ui-leaf: shell:\"app\" requested but no Chromium browser found; falling back to default browser tab.\\n`,\n );\n await open(openUrl);\n }\n } else {\n await open(openUrl);\n }\n };\n\n if (openBrowser) {\n await doOpen();\n }\n\n return {\n url,\n port: actualPort,\n closed,\n close: (reason: CloseReason = \"caller\") => cleanup(reason),\n on(event: DevServerEvent, listener: DevServerEventListener): void {\n listeners.get(event)?.add(listener);\n },\n off(event: DevServerEvent, listener: DevServerEventListener): void {\n listeners.get(event)?.delete(listener);\n },\n update(newData: unknown): void {\n viewState.data = newData;\n broadcast({ type: \"data-updated\", data: newData });\n fireEvent(\"data-updated\");\n },\n async swapView(source: string): Promise<import(\"./compile.js\").BuildError[]> {\n const r = await compileSource({\n source,\n data: viewState.data,\n title,\n csp: cspHeader ?? undefined,\n token,\n });\n if (r.errors.length > 0) return r.errors;\n viewState.html = r.html;\n broadcast({ type: \"view-swapped\" });\n fireEvent(\"view-swapped\");\n return [];\n },\n async patch(newData: unknown, source: string): Promise<import(\"./compile.js\").BuildError[]> {\n // Compile first with newData so the HTML embeds the incoming data.\n const r = await compileSource({\n source,\n data: newData,\n title,\n csp: cspHeader ?? undefined,\n token,\n });\n if (r.errors.length > 0) return r.errors;\n // Only mutate state on compile success (atomicity guarantee).\n viewState.data = newData;\n viewState.html = r.html;\n broadcast({ type: \"data-updated\", data: newData });\n broadcast({ type: \"view-swapped\" });\n fireEvent(\"data-updated\");\n fireEvent(\"view-swapped\");\n return [];\n },\n async reopen(): Promise<void> {\n await doOpen();\n },\n };\n } catch (err) {\n restoreStdout?.();\n throw err;\n }\n}\n\n// Custom header (not Authorization: Bearer) so any cross-origin fetch triggers\n// a CORS preflight, which browsers block for non-same-origin callers without\n// an explicit CORS allow list. This closes the simple-form-POST / no-preflight\n// attack vector against the localhost dev server.\nfunction checkAuth(req: Request, token: string): boolean {\n const value = req.headers.get(\"x-ui-leaf-token\") ?? \"\";\n if (!value) return false;\n return timingSafeEqual(value, token);\n}\n\nasync function handleMutate(\n req: Request,\n mutations: Record<string, MutationHandler<any, any>>,\n headers: Record<string, string>,\n): Promise<Response> {\n // 1 MiB cap: Content-Length precheck short-circuits chunked / large bodies\n // before req.text() buffers them. req.text() still buffers the whole body\n // if Content-Length is absent or underreported — acceptable for this\n // loopback-only server, where the auth gate already runs first.\n const contentLength = req.headers.get(\"content-length\");\n if (contentLength && Number.parseInt(contentLength, 10) > 1024 * 1024) {\n return new Response(JSON.stringify({ error: \"request body exceeds 1 MiB limit\" }), {\n status: 400,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n\n let body: { name?: string; args?: unknown };\n try {\n const text = await req.text();\n if (text.length > 1024 * 1024) {\n return new Response(JSON.stringify({ error: \"request body exceeds 1 MiB limit\" }), {\n status: 400,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n body = (text ? JSON.parse(text) : undefined) as typeof body;\n } catch (err) {\n return new Response(\n JSON.stringify({ error: err instanceof Error ? err.message : \"bad request\" }),\n { status: 400, headers: { ...headers, \"Content-Type\": \"application/json\" } },\n );\n }\n\n const name = body?.name;\n if (typeof name !== \"string\" || name.length === 0) {\n return new Response(JSON.stringify({ error: \"missing mutation name\" }), {\n status: 400,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n }\n\n if (!Object.hasOwn(mutations, name)) {\n return new Response(\n JSON.stringify({\n error: `ui-leaf: no mutation handler registered for '${name}'. Add it to the mutations: { } map passed to mount().`,\n }),\n { status: 404, headers: { ...headers, \"Content-Type\": \"application/json\" } },\n );\n }\n\n const handler = mutations[name]!;\n try {\n const result = await handler(body.args);\n return new Response(JSON.stringify(result ?? null), {\n status: 200,\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n });\n } catch (err) {\n return new Response(\n JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),\n { status: 500, headers: { ...headers, \"Content-Type\": \"application/json\" } },\n );\n }\n}\n","import { createRequire } from \"node:module\";\nimport { tmpdir } from \"node:os\";\nimport { join, resolve, sep } from \"node:path\";\nimport { mkdtemp, rm, stat, writeFile } from \"node:fs/promises\";\nimport type { BunPlugin } from \"bun\";\nimport { escapeForScriptTag } from \"./internal/html.js\";\n\n// Resolve React imports at module load — works under bun test / bun run.\n// NOTE: under bun build --compile (binary mode), createRequire() resolves from\n// the binary's embedded virtual filesystem. AGT-131 (cross-compile script)\n// will need a Bun.build plugin or Bun embedded-files to ensure React is\n// reachable inside the compiled binary. Flagging here so AGT-131 is not blindsided.\nconst requireFromHere = createRequire(import.meta.url);\n\n// BunPlugin that rewrites bare react/react-dom imports to absolute paths\n// under ui-leaf's installed node_modules. Ensures the bundled view always\n// finds the same React instance regardless of the consumer's package-manager\n// hoisting, and prevents duplicate React instances across views.\nconst reactAliasPlugin: BunPlugin = {\n name: \"ui-leaf-react-alias\",\n setup(build) {\n // Matches: react, react/jsx-runtime, react/jsx-dev-runtime,\n // react-dom, react-dom/client, react-dom/profiling, etc.\n build.onResolve({ filter: /^react($|\\/|-dom($|\\/))/ }, (args) => {\n try {\n return { path: requireFromHere.resolve(args.path) };\n } catch {\n return {\n path: args.path,\n errors: [{ text: `ui-leaf: failed to resolve ${args.path}` }],\n };\n }\n });\n },\n};\n\nexport interface BuildError {\n file: string;\n line: number;\n column: number;\n message: string;\n}\n\nexport interface CompileOptions {\n /** View name or path relative to viewsRoot (e.g. \"dashboard\" or \"dashboard.tsx\"). */\n entry: string;\n /** Root directory holding .tsx view files. */\n viewsRoot: string;\n /** JSON-serializable data injected as window.__UI_LEAF__.data. Ignored when dataLoader is true. */\n data?: unknown;\n /** Browser tab title. Defaults to \"ui-leaf\". */\n title?: string;\n /**\n * Raw CSP string to emit as a <meta http-equiv=\"Content-Security-Policy\"> tag.\n * Undefined / absent means no CSP meta tag is emitted.\n */\n csp?: string;\n /**\n * Extra allowed hostnames (beyond loopback defaults). Accepted in the\n * option bag for API symmetry with DevServerOptions; has no compile-time\n * effect — the runtime DNS-rebinding gate lives in the server.\n */\n allowedHosts?: string[];\n /**\n * Per-launch auth token. Accepted for API symmetry with DevServerOptions;\n * the token is no longer embedded in HTML — it is delivered via the URL\n * fragment and read by the inline bootstrap script.\n * @deprecated No-op since v1.0.0 — token delivery is handled by startDevServer.\n */\n token?: string;\n /**\n * When true, generate an entry that fetches data from GET /api/data at\n * render time rather than reading it from window.__UI_LEAF__.data. The\n * compiled HTML bootstrap omits the data field (only token is included).\n * Use when data is sensitive and must not be written to the HTML file.\n */\n dataLoader?: boolean;\n}\n\n/**\n * Options for compiling an inline TSX source string.\n *\n * v1.0.0 constraint: `source` is treated as a self-contained TSX string.\n * Relative imports are not supported — the string has no filesystem context\n * to resolve them against. Bare-package imports (react, react-dom) work via\n * the react-alias plugin. This is the intended contract for IPC-driven\n * view hot-swaps (AI-generated self-contained components).\n */\nexport interface CompileSourceOptions {\n /** Raw TSX source string to compile. Must be a self-contained component. */\n source: string;\n /** JSON-serializable data injected as window.__UI_LEAF__.data. */\n data?: unknown;\n /** Browser tab title. Defaults to \"ui-leaf\". */\n title?: string;\n /** Raw CSP string. Undefined / absent means no CSP meta tag. */\n csp?: string;\n /**\n * Per-launch auth token. Accepted for API symmetry; not embedded in HTML —\n * see CompileOptions.token.\n * @deprecated No-op since v1.0.0.\n */\n token?: string;\n}\n\nexport interface CompileResult {\n html: string;\n errors: BuildError[];\n}\n\n// Friendly message rendered when the page is reloaded without the token fragment.\nconst SESSION_ENDED_HTML =\n '<div style=\"font-family:sans-serif;padding:2em;color:#555\"><p>Session ended — re-launch the CLI to continue.</p></div>';\n\n// Overlay rendered when the mount terminates. v1.x extension point: replaceable\n// via a consumer-supplied template slot (deferred per plan-approval decision).\nconst CLOSED_OVERLAY_HTML =\n '<div style=\"font-family:sans-serif;padding:2em;color:#555\"><p>This view has closed.</p></div>';\n\n// Shared bridge injected into every compiled entry: mutation + heartbeat.\nconst SHARED_BRIDGE = `\nasync function mutate(name: string, args?: unknown): Promise<unknown> {\n const res = await fetch(\"/mutate\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(token ? { \"X-UI-Leaf-Token\": token } : {}),\n },\n body: JSON.stringify({ name, args }),\n });\n const text = await res.text().catch(() => \"\");\n if (!res.ok) {\n let detail = text;\n try {\n const parsed: unknown = text ? JSON.parse(text) : null;\n if (parsed !== null && typeof parsed === \"object\" && \"error\" in parsed && typeof (parsed as { error: unknown }).error === \"string\") {\n detail = (parsed as { error: string }).error;\n }\n } catch { /* keep raw text */ }\n throw new Error(\"ui-leaf: mutation '\" + name + \"' failed (\" + res.status + \"): \" + detail);\n }\n return text ? JSON.parse(text) : undefined;\n}\n\nasync function heartbeat(): Promise<void> {\n try {\n await fetch(\"/heartbeat\", {\n method: \"POST\",\n headers: token ? { \"X-UI-Leaf-Token\": token } : {},\n });\n } catch { /* server may have shut down; ignore */ }\n}\nsetInterval(heartbeat, 5000);\nheartbeat();\n\nfunction subscribeEvents(onEvent: (ev: { type: string; [k: string]: unknown }) => void): void {\n let delay = 250;\n const budget = 30_000;\n const started = Date.now();\n let done = false;\n\n async function connect(): Promise<void> {\n try {\n const res = await fetch(\"/events\", {\n headers: token ? { \"X-UI-Leaf-Token\": token } : {},\n });\n if (!res.ok || !res.body) throw new Error(\"bad status \" + res.status);\n delay = 250;\n const reader = res.body.getReader();\n const dec = new TextDecoder(\"utf-8\");\n let buf = \"\";\n while (true) {\n const { done: streamDone, value } = await reader.read();\n if (streamDone) break;\n buf += dec.decode(value, { stream: true });\n let idx: number;\n while ((idx = buf.indexOf(\"\\\\n\\\\n\")) !== -1) {\n const chunk = buf.slice(0, idx);\n buf = buf.slice(idx + 2);\n for (const line of chunk.split(\"\\\\n\")) {\n if (line.startsWith(\"data:\")) {\n try {\n const ev = JSON.parse(line.slice(5).trimStart()) as { type: string; [k: string]: unknown };\n if (ev.type === \"closing\") done = true;\n onEvent(ev);\n } catch { /* skip malformed event */ }\n }\n }\n }\n if (done) return;\n }\n } catch {\n if (done) return;\n }\n if (done) return;\n if (Date.now() - started > budget) {\n onEvent({ type: \"closing\", reason: \"error\" });\n return;\n }\n await new Promise<void>((r) => setTimeout(r, delay));\n delay = Math.min(delay * 2, 5_000);\n void connect();\n }\n\n void connect();\n}`;\n\n/** Run Bun.build on `entryPath` and return the raw JS output or errors. */\nasync function runBunBuild(entryPath: string): Promise<{ js: string } | { errors: BuildError[] }> {\n let buildOutput: Awaited<ReturnType<typeof Bun.build>>;\n try {\n buildOutput = await Bun.build({\n entrypoints: [entryPath],\n target: \"browser\",\n format: \"esm\",\n minify: false,\n sourcemap: \"none\",\n plugins: [reactAliasPlugin],\n });\n } catch (err) {\n if (err instanceof AggregateError) {\n type BunBuildMsg = { message: string; position?: { file?: string; line?: number; column?: number } | null };\n const errors: BuildError[] = (err.errors as BunBuildMsg[]).map((e) => ({\n file: e.position?.file ?? \"<unknown>\",\n line: e.position?.line ?? 0,\n column: e.position?.column ?? 0,\n message: e.message,\n }));\n return { errors };\n }\n throw err;\n }\n const output = buildOutput.outputs[0];\n if (!output) {\n return {\n errors: [{ file: \"<unknown>\", line: 0, column: 0, message: \"ui-leaf: Bun.build produced no output\" }],\n };\n }\n return { js: await output.text() };\n}\n\n/** Assemble the final HTML page from compiled JS and options. */\nfunction assembleHtml(opts: {\n js: string;\n title: string;\n csp: string | undefined;\n data: unknown;\n dataLoader: boolean;\n}): string {\n const { js, title, csp, data, dataLoader } = opts;\n // Escape </script> sequences to prevent script-tag break-out.\n const safeJs = js.replace(/<\\/script>/gi, \"<\\\\/script>\");\n\n const titleEscaped = title\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n\n const cspMeta = csp\n ? ` <meta http-equiv=\"Content-Security-Policy\" content=\"${csp.replace(/&/g, \"&amp;\").replace(/\"/g, \"&quot;\")}\" />\\n`\n : \"\";\n\n // Double-stringify data: outer JSON.stringify produces a JSON string, then\n // escapeForScriptTag ensures </script> and U+2028/U+2029 can't break out.\n const dataInit = dataLoader\n ? \"window.__UI_LEAF__ = {};\"\n : `window.__UI_LEAF__ = { data: JSON.parse(${escapeForScriptTag(JSON.stringify(JSON.stringify(data ?? null)))}) };`;\n\n // Bootstrap: reads token from URL fragment, stashes it on __UI_LEAF__.token,\n // then immediately clears the fragment from the URL bar so the token is\n // never visible in history. On reload (fragment gone), sets sessionEnded so\n // the bundled module can render a friendly recovery message instead of\n // attempting unauthenticated fetches.\n // decodeURIComponent is wrapped in try/catch: a malformed %-sequence would\n // otherwise throw and kill the bootstrap silently; the catch falls through\n // to sessionEnded so the user gets the recovery screen instead of a blank page.\n const bootstrapScript = `${dataInit}\n(function(){var m=/[#&]token=([^&#]*)/.exec(window.location.hash);if(m){try{window.__UI_LEAF__.token=decodeURIComponent(m[1]);history.replaceState(null,\"\",window.location.pathname+window.location.search);}catch(e){window.__UI_LEAF__.sessionEnded=true;}}else{window.__UI_LEAF__.sessionEnded=true;}})();`;\n\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>${titleEscaped}</title>\n${cspMeta} <!-- ui-leaf bootstrap -->\n <script>${bootstrapScript}</script>\n </head>\n <body>\n <div id=\"root\"></div>\n <script type=\"module\">${safeJs}</script>\n </body>\n</html>`;\n}\n\nexport async function compileView(opts: CompileOptions): Promise<CompileResult> {\n const {\n entry,\n viewsRoot,\n data,\n title = \"ui-leaf\",\n csp,\n // allowedHosts and token have no compile-time effect; accepted for API symmetry.\n allowedHosts: _allowedHosts,\n token: _token,\n dataLoader = false,\n } = opts;\n\n const viewsRootAbs = resolve(viewsRoot);\n const hasExt = /\\.[a-z]+$/i.test(entry);\n const viewAbs = resolve(viewsRootAbs, hasExt ? entry : `${entry}.tsx`);\n if (!viewAbs.startsWith(viewsRootAbs + sep)) {\n return {\n html: \"\",\n errors: [\n {\n file: \"<unknown>\",\n line: 0,\n column: 0,\n message: `ui-leaf: view '${entry}' resolves outside viewsRoot`,\n },\n ],\n };\n }\n try {\n await stat(viewAbs);\n } catch {\n return {\n html: \"\",\n errors: [\n {\n file: viewAbs,\n line: 0,\n column: 0,\n message: `ui-leaf: view '${entry}' not found at ${viewAbs}`,\n },\n ],\n };\n }\n\n // Generate a temp entry that imports the resolved view, mounts React via\n // createRoot, and wires the mutation/heartbeat bridge.\n const tempDir = await mkdtemp(join(tmpdir(), \"ui-leaf-compile-\"));\n try {\n const entryPath = join(tempDir, \"entry.tsx\");\n\n const entryContent = dataLoader\n ? `import { createRoot } from \"react-dom/client\";\nimport View from ${JSON.stringify(viewAbs)};\n\nconst ctx = (globalThis as { __UI_LEAF__?: { token?: string; sessionEnded?: boolean } }).__UI_LEAF__ ?? {};\nconst token = ctx.token;\n\nif (ctx.sessionEnded) {\n const root = document.getElementById(\"root\");\n if (root) root.innerHTML = ${JSON.stringify(SESSION_ENDED_HTML)};\n} else {\n${SHARED_BRIDGE}\n\n async function bootstrap(): Promise<void> {\n const res = await fetch(\"/api/data\", {\n headers: token ? { \"X-UI-Leaf-Token\": token } : {},\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(\"ui-leaf: /api/data fetch failed (\" + res.status + \"): \" + text);\n }\n let currentData: unknown = await res.json();\n const el = document.getElementById(\"root\");\n if (!el) throw new Error(\"ui-leaf: #root element missing\");\n const root = createRoot(el);\n root.render(<View data={currentData} mutate={mutate} />);\n subscribeEvents((ev) => {\n if (ev.type === \"data-updated\") {\n currentData = ev.data;\n root.render(<View data={currentData} mutate={mutate} />);\n } else if (ev.type === \"view-swapped\") {\n window.location.reload();\n } else if (ev.type === \"closing\") {\n el.innerHTML = ${JSON.stringify(CLOSED_OVERLAY_HTML)};\n }\n });\n }\n bootstrap();\n}\n`\n : `import { createRoot } from \"react-dom/client\";\nimport View from ${JSON.stringify(viewAbs)};\n\nconst ctx = (globalThis as { __UI_LEAF__?: { data?: unknown; token?: string; sessionEnded?: boolean } }).__UI_LEAF__ ?? {};\nconst token = ctx.token;\n\nif (ctx.sessionEnded) {\n const root = document.getElementById(\"root\");\n if (root) root.innerHTML = ${JSON.stringify(SESSION_ENDED_HTML)};\n} else {\n let currentData: unknown = ctx.data;\n${SHARED_BRIDGE}\n\n const el = document.getElementById(\"root\");\n if (!el) throw new Error(\"ui-leaf: #root element missing\");\n const root = createRoot(el);\n root.render(<View data={currentData} mutate={mutate} />);\n subscribeEvents((ev) => {\n if (ev.type === \"data-updated\") {\n currentData = ev.data;\n root.render(<View data={currentData} mutate={mutate} />);\n } else if (ev.type === \"view-swapped\") {\n window.location.reload();\n } else if (ev.type === \"closing\") {\n el.innerHTML = ${JSON.stringify(CLOSED_OVERLAY_HTML)};\n }\n });\n}\n`;\n\n await writeFile(entryPath, entryContent);\n\n const buildResult = await runBunBuild(entryPath);\n if (\"errors\" in buildResult) return { html: \"\", errors: buildResult.errors };\n\n return {\n html: assembleHtml({ js: buildResult.js, title, csp, data, dataLoader }),\n errors: [],\n };\n } finally {\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Compile an inline TSX source string into a full HTML page.\n *\n * The source is treated as a self-contained component; relative imports are\n * not supported (v1.0.0 constraint — the string has no filesystem context).\n * Bare-package imports (react, react-dom) work via the react-alias plugin.\n */\nexport async function compileSource(opts: CompileSourceOptions): Promise<CompileResult> {\n const { source, data, title = \"ui-leaf\", csp, token: _token } = opts;\n\n const tempDir = await mkdtemp(join(tmpdir(), \"ui-leaf-src-\"));\n try {\n // Write the caller's tsx as the view file, then write a thin entry wrapper.\n const viewPath = join(tempDir, \"view.tsx\");\n const entryPath = join(tempDir, \"entry.tsx\");\n\n await writeFile(viewPath, source);\n\n const entryContent = `import { createRoot } from \"react-dom/client\";\nimport View from ${JSON.stringify(viewPath)};\n\nconst ctx = (globalThis as { __UI_LEAF__?: { data?: unknown; token?: string; sessionEnded?: boolean } }).__UI_LEAF__ ?? {};\nconst token = ctx.token;\n\nif (ctx.sessionEnded) {\n const root = document.getElementById(\"root\");\n if (root) root.innerHTML = ${JSON.stringify(SESSION_ENDED_HTML)};\n} else {\n let currentData: unknown = ctx.data;\n${SHARED_BRIDGE}\n\n const el = document.getElementById(\"root\");\n if (!el) throw new Error(\"ui-leaf: #root element missing\");\n const root = createRoot(el);\n root.render(<View data={currentData} mutate={mutate} />);\n subscribeEvents((ev) => {\n if (ev.type === \"data-updated\") {\n currentData = ev.data;\n root.render(<View data={currentData} mutate={mutate} />);\n } else if (ev.type === \"view-swapped\") {\n window.location.reload();\n } else if (ev.type === \"closing\") {\n el.innerHTML = ${JSON.stringify(CLOSED_OVERLAY_HTML)};\n }\n });\n}\n`;\n\n await writeFile(entryPath, entryContent);\n\n const buildResult = await runBunBuild(entryPath);\n if (\"errors\" in buildResult) return { html: \"\", errors: buildResult.errors };\n\n return {\n html: assembleHtml({ js: buildResult.js, title, csp, data, dataLoader: false }),\n errors: [],\n };\n } finally {\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n","export function escapeForScriptTag(json: string): string {\n // Defend against </script> break-out and U+2028/U+2029 line terminators\n // that JSON.stringify emits raw but JS string literals don't accept.\n return json\n .replace(/</g, \"\\\\u003c\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n"],"mappings":"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","names":["resolve","fireEvent","broadcast","url","resolve"]}
@@ -1,7 +1,7 @@
1
1
  type MutationHandler<TArgs = unknown, TResult = unknown> = (args: TArgs) => TResult | Promise<TResult>;
2
2
  type CspOption = "off" | "strict" | (string & {});
3
3
  type Shell = "tab" | "app";
4
- type DevServerEvent = "data-updated" | "view-swapped";
4
+ type DevServerEvent = "data-updated" | "view-swapped" | "disconnected" | "reconnected";
5
5
  type DevServerEventListener = () => void;
6
6
 
7
7
  export type { CspOption as C, DevServerEvent as D, MutationHandler as M, Shell as S, DevServerEventListener as a };
@@ -1,4 +1,4 @@
1
- export { M as MutationHandler } from './server-Bp6cms3O.js';
1
+ export { M as MutationHandler } from './server-3vbR-tuu.js';
2
2
 
3
3
  type Mutate = <TResult = unknown>(name: string, args?: unknown) => Promise<TResult>;
4
4
  interface ViewProps<TData = unknown> {