npm package: @illuma-ai/code-sandbox
Description: Browser-native code sandbox with file tree, editor, terminal, and live preview powered by Nodepod
Author: Illuma AI
Latest version: 1.34.0

# @illuma-ai/code-sandbox

A browser-native code sandbox with file tree, Monaco editor, terminal, and live preview — powered by [Nodepod](https://www.npmjs.com/package/@illuma-ai/nodepod).

Run Express + React applications entirely in the browser. No server-side containers required.

## Features

- Full IDE workbench: file tree, code editor, terminal, live preview
- Runs Node.js in the browser via Nodepod (SharedArrayBuffer + Service Worker)
- Monaco editor with syntax highlighting, diff viewer, and file tabs
- Structured error reporting with file path, line number, and source context
- Hot reload for CSS/HTML/static asset changes (no server restart)
- Imperative API for programmatic control (designed for AI agent integration)
- Built-in project templates (Express + React full-stack apps)
- Customizable via CSS custom properties
- Dark theme by default, with shadcn-compatible theme variable bridge

## Installation

```bash
npm install @illuma-ai/code-sandbox
```

### Peer Dependencies

These must be installed by the consuming application:

```bash
npm install react react-dom @monaco-editor/react monaco-editor allotment framer-motion
```

| Peer Dependency        | Version                             |
| ---------------------- | ----------------------------------- |
| `react`                | `^18.0.0 \|\| ^19.0.0`              |
| `react-dom`            | `^18.0.0 \|\| ^19.0.0`              |
| `@monaco-editor/react` | `^4.6.0`                            |
| `monaco-editor`        | `>=0.40.0`                          |
| `allotment`            | `^1.20.0`                           |
| `framer-motion`        | `^10.0.0 \|\| ^11.0.0 \|\| ^12.0.0` |

## Prerequisites

### COOP/COEP Headers

Nodepod requires `SharedArrayBuffer`, which is only available in cross-origin isolated contexts. Your dev server must send these headers:

```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: credentialless
```

**Vite example:**

```ts
// vite.config.ts
export default defineConfig({
  server: {
    headers: {
      "Cross-Origin-Opener-Policy": "same-origin",
      "Cross-Origin-Embedder-Policy": "credentialless",
    },
  },
});
```

### Service Worker

Copy the Nodepod service worker file to your `public/` directory so it's served at `/__sw__.js`:

```bash
cp node_modules/@illuma-ai/nodepod/dist/sw.js public/__sw__.js
```

## Quick Start

```tsx
import { useRef } from "react";
import { CodeSandbox } from "@illuma-ai/code-sandbox";
import type { CodeSandboxHandle } from "@illuma-ai/code-sandbox";
import "@illuma-ai/code-sandbox/styles.css";

function App() {
  const ref = useRef<CodeSandboxHandle>(null);

  return (
    <CodeSandbox
      ref={ref}
      template="fullstack-starter"
      height="100vh"
      onServerReady={(port, url) => console.log(`Ready on port ${port}`)}
      onSandboxError={(err) => console.error(err.category, err.message)}
    />
  );
}
```

### With Custom Files

```tsx
const files = {
  "package.json": JSON.stringify(
    {
      name: "my-app",
      dependencies: { express: "^4.18.0" },
    },
    null,
    2,
  ),
  "server.js": `
    const express = require("express");
    const app = express();
    app.get("/", (req, res) => res.send("<h1>Hello World</h1>"));
    app.listen(3000, () => console.log("Running on port 3000"));
  `,
};

<CodeSandbox
  ref={ref}
  files={files}
  entryCommand="node server.js"
  port={3000}
  height="100vh"
/>;
```

### AI Agent Integration

The sandbox is designed as a "dumb renderer" — the host application pushes files in and reads state out. The user does not edit code in the sandbox; an AI agent writes code, and the host pushes it via the imperative API.

```tsx
const ref = useRef<CodeSandboxHandle>(null);

// Agent starts editing — enable diff view + lock editor
ref.current.setDiffMode(true);

// Agent generates new files → push them into the sandbox
await ref.current.updateFiles(agentGeneratedFiles);

// Agent modifies a single file (surgical — no server restart)
await ref.current.updateFile("server.js", newServerCode);

// Agent deletes a file
await ref.current.deleteFile("old-route.js");

// Read errors for the agent to self-correct
const errors = ref.current.getErrors();
// errors[0] → { category: "process-stderr", message: "...", filePath: "server.js", line: 42, sourceContext: "..." }

// Agent done — disable diff view + unlock editor
ref.current.setDiffMode(false);
```

---

## API Reference

### `<CodeSandbox>` Component

The main component. Renders the full workbench (file tree, editor, terminal, preview). Uses `React.forwardRef` to expose the imperative handle.

```tsx
<CodeSandbox
  ref={sandboxRef}
  files={fileMap} // OR template="fullstack-starter"
  entryCommand="node server.js"
  port={3000}
  env={{ NODE_ENV: "development" }}
  height="100vh"
  className="my-sandbox"
  onFileChange={(path, content) => {}}
  onServerReady={(port, url) => {}}
  onProgress={(progress) => {}}
  onError={(message) => {}}
  onSandboxError={(error) => {}}
  onFilesUpdated={(changes) => {}}
/>
```

#### Props (`CodeSandboxProps`)

| Prop           | Type                     | Default                                            | Description                                                                                   |
| -------------- | ------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `files`        | `FileMap`                | —                                                  | Initial file set. Pass a flat `Record<string, string>` mapping file paths to contents.        |
| `template`     | `string`                 | —                                                  | Use a built-in template instead of `files`. One of: `"express-react"`, `"fullstack-starter"`. |
| `entryCommand` | `string`                 | Inferred from `package.json` or `"node server.js"` | Shell command to start the dev server.                                                        |
| `port`         | `number`                 | `3000`                                             | Port the server listens on.                                                                   |
| `env`          | `Record<string, string>` | —                                                  | Environment variables passed to Node.js processes.                                            |
| `height`       | `string`                 | `"100vh"`                                          | CSS height of the sandbox root element.                                                       |
| `className`    | `string`                 | —                                                  | CSS class name for the root element.                                                          |
| `diffMode`     | `boolean`                | —                                                  | Externally controlled diff mode. When `true`, the editor shows Monaco's side-by-side DiffEditor for files that have changes. When `undefined`, the user controls diff via the built-in Diff button. |
| `readOnly`     | `boolean`                | `false`                                            | Make the editor read-only. When `true`, only the agent can modify files via the imperative handle. Typical pattern: set both `readOnly` and `diffMode` to `true` while the agent is editing, then flip both off when done. |

#### Callback Props

| Callback         | Signature                                             | Description                                                                                    |
| ---------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `onFileChange`   | `(path: string, content: string) => void`             | Fires when a file is modified in the editor.                                                   |
| `onServerReady`  | `(port: number, url: string) => void`                 | Fires when the dev server is ready (initial boot or after restart).                            |
| `onProgress`     | `(progress: BootProgress) => void`                    | Fires on boot progress changes.                                                                |
| `onError`        | `(message: string) => void`                           | Fires on errors (legacy string format).                                                        |
| `onSandboxError` | `(error: SandboxError) => void`                       | Fires when a structured error is detected. **Primary error channel for AI agent integration.** |
| `onFilesUpdated` | `(changes: Record<string, FileChangeStatus>) => void` | Fires after `updateFiles()` completes (files written + server restarted).                      |

---

### `CodeSandboxHandle` (Imperative API)

Access via a React ref. This is the primary API for host applications to control the sandbox programmatically.

```tsx
const ref = useRef<CodeSandboxHandle>(null);
<CodeSandbox ref={ref} ... />
```

#### Methods

##### `updateFiles(files, options?)`

Replace the entire file set. Diffs against current files, writes only changed files to the virtual FS, and restarts the server if anything changed.

By default, the previous file set becomes `originalFiles` for diff tracking. Use `keepBaseline` to preserve the original baseline during incremental updates (e.g., streaming AI agent edits).

```ts
// Standard update — resets diff baseline
await ref.current.updateFiles(newFileMap);

// Skip server restart
await ref.current.updateFiles(newFileMap, { restartServer: false });

// Incremental update — preserves diff baseline for accumulated diffs
await ref.current.updateFiles(newFileMap, { keepBaseline: true, restartServer: false });
```

| Param                   | Type      | Default | Description                                                   |
| ----------------------- | --------- | ------- | ------------------------------------------------------------- |
| `files`                 | `FileMap` | —       | Complete new file set.                                        |
| `options.restartServer` | `boolean` | `true`  | Whether to restart the server after writing.                  |
| `options.keepBaseline`  | `boolean` | `false` | When `true`, preserves `originalFiles` so diffs accumulate against the initial state rather than resetting on each update. Ideal for streaming AI edits where you want to show total changes. |

**Hot reload vs cold restart:** If ALL changed files are hot-reloadable (CSS, HTML, images, fonts), the sandbox refreshes the preview iframe without restarting the Node.js server. If ANY changed file is JS/TS/JSON, the server process is killed and re-run.

**AI agent editing pattern:**

```tsx
// 1. Agent starts editing — switch to code view, enable diff mode
ref.current.setView("code");

// 2. Agent edits files one at a time — push each surgically
//    updateFile() does NOT restart the server, diffs accumulate
await ref.current.updateFile("routes/todos.js", newContent);
ref.current.openFile("routes/todos.js", 42); // scroll to changed line

// 2b. Agent deletes a file
await ref.current.deleteFile("routes/deprecated.js");

// 3. Agent finishes — reconciliation with restart
await ref.current.updateFiles(finalFiles); // resets baseline, restarts server
ref.current.setView("preview"); // switch back to see the running app
```

##### `updateFile(path, content)`

Update a single file. Writes to the virtual FS and updates state. Does **not** restart the server — call `restart()` manually if needed, or use `updateFiles()` for bulk updates with auto-restart.

```ts
await ref.current.updateFile("public/styles.css", newCss);
```

##### `deleteFile(path)`

Delete a file from the sandbox. Removes it from the virtual FS and marks it as `"deleted"` in `fileChanges` for diff tracking. If the file was open in the editor, it is automatically closed.

```ts
await ref.current.deleteFile("routes/old-api.js");
// fileChanges → { "routes/old-api.js": "deleted" }
```

Files that existed in `originalFiles` are tracked as `"deleted"` (shown with red indicator in file tree). Files that were added after boot and then deleted are simply removed from tracking.

##### `restart()`

Force restart the server process. Kills the current process and re-runs the entry command.

```ts
await ref.current.restart();
```

##### `getFiles()`

Returns the current `FileMap` (all files including edits).

```ts
const files: FileMap = ref.current.getFiles();
```

##### `getChangedFiles()`

Returns a `FileMap` containing only files that have been modified relative to `originalFiles`.

```ts
const changed: FileMap = ref.current.getChangedFiles();
```

##### `getFileChanges()`

Returns the per-file change status map. Missing keys should be treated as `"unchanged"`.

```ts
const changes: Record<string, FileChangeStatus> = ref.current.getFileChanges();
// { "server.js": "modified", "public/new-page.html": "new" }
```

##### `getErrors()`

Returns all structured errors collected during this session. The array grows monotonically — new errors are appended.

```ts
const errors: SandboxError[] = ref.current.getErrors();
```

##### `getState()`

Returns the current `RuntimeState` snapshot.

```ts
const state: RuntimeState = ref.current.getState();
// state.status, state.previewUrl, state.terminalOutput, etc.
```

##### `setDiffMode(enabled)`

Enable or disable diff mode in the editor. When enabled, files with changes show Monaco's side-by-side DiffEditor comparing `originalFiles` (left) with current files (right).

Use this to show diffs in real-time while the AI agent is editing, then disable when the agent finishes.

```ts
// Agent starts editing
ref.current.setDiffMode(true);

// Push agent edits — user sees diffs live
await ref.current.updateFiles(agentEditedFiles);

// Agent done — back to normal editor
ref.current.setDiffMode(false);
```

> **Tip:** Combine with the `readOnly` prop to lock the editor during agent edits:
> ```tsx
> <CodeSandbox ref={ref} readOnly={agentEditing} diffMode={agentEditing} ... />
> ```

##### `setView(view)`

Programmatically switch between `"code"`, `"preview"`, and `"swagger"` views.

```ts
ref.current.setView("code");    // Show editor + file tree
ref.current.setView("preview"); // Show live preview iframe
ref.current.setView("swagger"); // Show auto-generated API documentation
```

Both views are always mounted (no destroy/recreate). Switching uses opacity + pointer-events for instant toggling without losing state.

##### `openFile(path)`

Open a specific file in the editor and switch to code view. Useful for highlighting agent-edited files.

```ts
ref.current.openFile("routes/todos.js");
// → File opens in editor, code view activated
```

##### `getConsoleLogs()`

Returns all console log entries captured from the preview iframe (console.log, info, warn, error).

```ts
const logs: ConsoleEntry[] = ref.current.getConsoleLogs();
```

##### `clearConsoleLogs()`

Clear all captured console log entries.

```ts
ref.current.clearConsoleLogs();
```

---

### `SandboxError`

Structured error type designed for AI agent consumption. Includes enough context for the agent to construct a targeted fix prompt.

```ts
interface SandboxError {
  id: string; // Unique ID for deduplication
  category: SandboxErrorCategory; // Error type
  message: string; // Human-readable error message
  stack?: string; // Full stack trace
  filePath?: string; // File path (relative to workdir)
  line?: number; // Line number (1-indexed)
  column?: number; // Column number (1-indexed)
  timestamp: string; // ISO 8601 timestamp
  sourceContext?: string; // ~10 surrounding source lines
}
```

#### Error Categories (`SandboxErrorCategory`)

| Category                      | Description                                                    |
| ----------------------------- | -------------------------------------------------------------- |
| `process-stderr`              | Output written to stderr by the Node.js process                |
| `process-exit`                | Process exited with a non-zero code                            |
| `runtime-exception`           | Uncaught exception in the Node.js runtime                      |
| `browser-error`               | JavaScript error in the preview iframe (`window.onerror`)      |
| `browser-unhandled-rejection` | Unhandled promise rejection in the iframe                      |
| `browser-console-error`       | `console.error()` calls in the iframe                          |
| `compilation`                 | Syntax/import errors at module load time                       |
| `network`                     | Failed HTTP requests from the preview (fetch/XHR)              |
| `boot`                        | Error during Nodepod boot, file writing, or dependency install |

#### Source Context Format

When available, `sourceContext` contains lines formatted as `"lineNum: content"`:

```
38: const express = require("express");
39: const app = express();
40: const PORT = 3000;
41:
42: app.get("/api/data", (req, res) => {
43:   res.json(undefinedVariable);    // ← error here
44: });
45: app.listen(PORT);
```

---

### `RuntimeState`

The full reactive state exposed by the runtime.

```ts
interface RuntimeState {
  status: BootStage;
  progress: BootProgress;
  previewUrl: string | null;
  terminalOutput: string[];
  files: FileMap;
  originalFiles: FileMap;
  fileChanges: Record<string, FileChangeStatus>;
  previewReloadKey: number;
  error: string | null;
  errors: SandboxError[];
}
```

| Field              | Type                               | Description                                                                 |
| ------------------ | ---------------------------------- | --------------------------------------------------------------------------- |
| `status`           | `BootStage`                        | Current lifecycle stage.                                                    |
| `progress`         | `BootProgress`                     | Current boot progress (stage, message, percent).                            |
| `previewUrl`       | `string \| null`                   | URL for the preview iframe. `null` until server is ready.                   |
| `terminalOutput`   | `string[]`                         | All terminal output lines (stdout + stderr).                                |
| `files`            | `FileMap`                          | Current files in the virtual FS (including edits).                          |
| `originalFiles`    | `FileMap`                          | Baseline files before the latest `updateFiles()`. Used for diffs.           |
| `fileChanges`      | `Record<string, FileChangeStatus>` | Per-file change status. Missing keys = `"unchanged"`.                       |
| `previewReloadKey` | `number`                           | Incremented on hot reload — triggers iframe refresh without server restart. |
| `error`            | `string \| null`                   | Error message if status is `"error"`.                                       |
| `errors`           | `SandboxError[]`                   | All structured errors (grows monotonically).                                |

### `BootStage`

```ts
type BootStage =
  | "initializing" // Nodepod.boot() in progress
  | "writing-files" // Writing project files to virtual FS
  | "installing" // npm install running
  | "starting" // Entry command executing
  | "ready" // Server is listening, preview iframe can load
  | "error"; // Something failed
```

### `BootProgress`

```ts
interface BootProgress {
  stage: BootStage;
  message: string;
  percent: number; // 0-100, approximate
}
```

### `FileChangeStatus`

```ts
type FileChangeStatus = "new" | "modified" | "deleted" | "unchanged";
```

---

## Hot Reload

When `updateFiles()` is called, the sandbox inspects which files changed:

- **Hot-reloadable extensions:** `.css`, `.html`, `.htm`, `.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.ico`, `.woff`, `.woff2`, `.ttf`, `.eot`
- If **all** changed files have hot-reloadable extensions → the preview iframe is soft-reloaded (`location.reload()`) without restarting the Node.js server process.
- If **any** changed file is JS/TS/JSON/etc. → the server process is killed and re-run (cold restart).

This makes CSS-only iterations instant.

---

## Built-in Templates

Use `template="name"` to bootstrap with a pre-built project.

### `express-react`

A lightweight Express + React app using JSONPlaceholder as a data source. React is loaded from CDN (no build step). Demonstrates posts/users CRUD views.

- **Entry command:** `node server.js`
- **Port:** 3000
- **Files:** 9 (server.js, public/index.html, styles, 5 React components)

### `fullstack-starter`

A comprehensive full-stack template with authentication, CRUD, charts, and theming.

- **Entry command:** `node server.js`
- **Port:** 3000
- **Files:** 35 (Express + sql.js + React + React Router + Auth + CRUD + Chart.js + shadcn theme variables)
- **Features:** Login/register, todo CRUD with SQLite (sql.js), doughnut chart, light/dark theme, responsive layout

### Template API

```ts
import { getTemplate, listTemplates } from "@illuma-ai/code-sandbox";

const names = listTemplates();
// ["express-react", "fullstack-starter"]

const tpl = getTemplate("fullstack-starter");
// { files: FileMap, entryCommand: string, port: number }
```

---

## Theming

### Sandbox Chrome (CSS Custom Properties)

The sandbox UI is themed via `--sb-*` CSS custom properties. Override them in your CSS to match your application:

```css
:root {
  --sb-bg: #1e1e1e;
  --sb-bg-alt: #252526;
  --sb-bg-hover: #2a2d2e;
  --sb-bg-active: #37373d;
  --sb-sidebar: #252526;
  --sb-editor: #1e1e1e;
  --sb-terminal: #0d1117;
  --sb-terminal-header: #161b22;
  --sb-preview: #ffffff;
  --sb-border: #3c3c3c;
  --sb-text: #cccccc;
  --sb-text-muted: #858585;
  --sb-text-active: #ffffff;
  --sb-accent: #007acc;
  --sb-accent-hover: #1a8ad4;
  --sb-success: #3fb950;
  --sb-warning: #d29922;
  --sb-error: #f85149;
  --sb-info: #58a6ff;
  --sb-scrollbar-thumb: rgba(255, 255, 255, 0.12);
  --sb-scrollbar-thumb-hover: rgba(255, 255, 255, 0.25);
  --sb-scrollbar-track: transparent;
  --sb-scrollbar-width: 6px;
}
```

### Theme Variable Bridge

The sandbox CSS also defines shadcn-compatible design tokens (`--text-primary`, `--surface-primary`, `--border-light`, `--chart-1` through `--chart-5`, etc.) in both light and dark variants. These are available inside the preview iframe content for seamless visual integration with your application's UI.

Add the `.dark` class to an ancestor element to activate dark mode tokens.

---

## Advanced Usage

### Individual Components

All sub-components are exported individually for custom layouts:

```tsx
import {
  FileTree,
  buildFileTree,
  CodeEditor,
  Terminal,
  Preview,
  BootOverlay,
  ViewSlider,
} from "@illuma-ai/code-sandbox";
```

#### `<FileTree>`

Renders a collapsible file tree with per-extension SVG icons (19 distinct types: JS, TS, JSX, TSX, JSON, HTML, CSS, SCSS, MD, PY, RB, GO, RS, YML, ENV, SH, SQL, SVG, Lock) and change status indicators.

```tsx
import { FileTree, buildFileTree } from "@illuma-ai/code-sandbox";

const tree = buildFileTree(fileMap);
<FileTree
  files={tree}
  selectedFile="server.js"
  onSelectFile={(path) => setSelected(path)}
  fileChanges={{ "server.js": "modified" }}
/>;
```

#### `<CodeEditor>`

Monaco-based code editor with file tabs, diff mode toggle, and change status indicators on tabs.

```tsx
<CodeEditor
  files={fileMap}
  originalFiles={baselineFiles}
  fileChanges={changes}
  activeFile="server.js"
  openFiles={["server.js", "public/index.html"]}
  onSelectFile={(path) => {}}
  onCloseFile={(path) => {}}
  onFileChange={(path, content) => {}}
  readOnly={false}
  diffMode={false}
/>
```

#### `<Terminal>`

Clean monochrome terminal output display with minimize/expand toggle and line count badge.

```tsx
<Terminal
  output={terminalLines}
  minimized={false}
  onToggleMinimize={() => setMinimized(!minimized)}
/>
```

#### `<Preview>`

Live preview iframe with JavaScript error capture (injects `window.onerror`, `unhandledrejection`, and `console.error` listeners). URL bar displays friendly `localhost:3000/` instead of internal paths.

```tsx
<Preview
  url={previewUrl}
  onRefresh={() => runtime.restart()}
  onBrowserError={(error) => handleError(error)}
  reloadKey={state.previewReloadKey}
/>
```

#### `<BootOverlay>`

Loading overlay with staged progress animation.

```tsx
<BootOverlay
  progress={{
    stage: "installing",
    message: "Installing express...",
    percent: 55,
  }}
/>
```

#### `<ViewSlider>`

Icon-based tab toggle for Code, Preview, and API Docs views with Framer Motion animation. Icons expand to show labels on hover.

```tsx
import type { WorkbenchView } from "@illuma-ai/code-sandbox";
// WorkbenchView = "code" | "preview" | "swagger"

<ViewSlider
  selected={view}
  onSelect={(v: WorkbenchView) => setView(v)}
/>;
```

### `NodepodRuntime` Service

The framework-agnostic runtime service (no React dependency). Use directly for custom integrations.

```ts
import { NodepodRuntime } from "@illuma-ai/code-sandbox";

const runtime = new NodepodRuntime({
  files: myFiles,
  entryCommand: "node server.js",
  port: 3000,
  workdir: "/app",
  env: { NODE_ENV: "production" },
});

runtime.setProgressCallback((progress) => console.log(progress));
runtime.setOutputCallback((line) => console.log(line));
runtime.setServerReadyCallback((port, url) => console.log("Ready:", url));
runtime.setErrorCallback((error) => console.error(error));

await runtime.boot();

// File operations
await runtime.writeFile("server.js", newContent);
await runtime.deleteFile("old-route.js");
const content = await runtime.readFile("server.js");

// State
const files = runtime.getCurrentFiles();
const changed = runtime.getChangedFiles();
const errors = runtime.getErrors();
const status = runtime.getStatus();
const previewUrl = runtime.getPreviewUrl();

// Lifecycle
await runtime.restart();
runtime.teardown();
```

### `useRuntime` Hook

React hook wrapping `NodepodRuntime`. Powers the `CodeSandbox` component internally. Use for fully custom React UIs.

```ts
import { useRuntime } from "@illuma-ai/code-sandbox";

const {
  state, // RuntimeState
  selectedFile, // string | null
  openFiles, // string[]
  handleFileChange, // (path, content) => void
  handleSelectFile, // (path) => void
  handleCloseFile, // (path) => void
  handleBrowserError, // (error: SandboxError) => void
  restart, // () => Promise<void>
  updateFiles, // (files, options?) => Promise<void>
  updateFile, // (path, content) => Promise<void>
  deleteFile, // (path) => Promise<void>
  getFiles, // () => FileMap
  getChangedFiles, // () => FileMap
  getFileChanges, // () => Record<string, FileChangeStatus>
  getErrors, // () => SandboxError[]
  getState, // () => RuntimeState
} = useRuntime(props);
```

---

## Nodepod Constraints

The sandbox runs on [Nodepod](https://www.npmjs.com/package/@illuma-ai/nodepod), a browser-native Node.js runtime. Key constraints:

- **No build tools:** Vite, Webpack, Next.js, etc. will not work. Use CDN-loaded libraries instead.
- **`express.static()` does not work.** Serve files manually via `fs.readFileSync()` + `res.send()`.
- **`res.sendFile()` does not work.** Same — use `fs.readFileSync()` + `res.send()`.
- **Module wrapper hides browser globals:** `document`, `window`, `location` are `undefined` in server-side code (as expected in Node.js).
- **Preview URL rewriting:** Nodepod returns `/__virtual__/{port}` URLs but the Service Worker expects `/__preview__/{port}/`. The sandbox handles this transparently.

---

## All Exports

```ts
// Main component
export { CodeSandbox } from "@illuma-ai/code-sandbox";

// Individual components
export { FileTree, buildFileTree } from "@illuma-ai/code-sandbox";
export { CodeEditor } from "@illuma-ai/code-sandbox";
export { Terminal } from "@illuma-ai/code-sandbox";
export { Preview } from "@illuma-ai/code-sandbox";
export { BootOverlay } from "@illuma-ai/code-sandbox";
export { ViewSlider } from "@illuma-ai/code-sandbox";

// Services & hooks
export { NodepodRuntime } from "@illuma-ai/code-sandbox";
export { useRuntime } from "@illuma-ai/code-sandbox";

// Templates
export { getTemplate, listTemplates } from "@illuma-ai/code-sandbox";

// Types
export type {
  FileMap,
  FileNode,
  FileChangeStatus,
  SandboxError,
  SandboxErrorCategory,
  SandboxErrorSeverity,
  ConsoleEntry,
  ConsoleLevel,
  BootStage,
  BootProgress,
  RuntimeConfig,
  RuntimeState,
  CodeSandboxProps,
  CodeSandboxHandle,
  FileTreeProps,
  CodeEditorProps,
  TerminalProps,
  PreviewProps,
  BootOverlayProps,
  WorkbenchView,
} from "@illuma-ai/code-sandbox";
```

---

## License

MIT - See [LICENSE](./LICENSE) for details.