@illuma-ai/code-sandbox 1.3.0 → 1.4.0
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.
- package/README.md +715 -0
- package/dist/components/Preview.d.ts +1 -1
- package/dist/index.cjs +32 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3188 -3129
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
- package/src/components/Preview.tsx +30 -1
- package/src/components/Workbench.tsx +1 -0
- package/src/hooks/useRuntime.ts +80 -16
- package/src/types.ts +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
# @illuma-ai/code-sandbox
|
|
2
|
+
|
|
3
|
+
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).
|
|
4
|
+
|
|
5
|
+
Run Express + React applications entirely in the browser. No server-side containers required.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Full IDE workbench: file tree, code editor, terminal, live preview
|
|
10
|
+
- Runs Node.js in the browser via Nodepod (SharedArrayBuffer + Service Worker)
|
|
11
|
+
- Monaco editor with syntax highlighting, diff viewer, and file tabs
|
|
12
|
+
- Structured error reporting with file path, line number, and source context
|
|
13
|
+
- Hot reload for CSS/HTML/static asset changes (no server restart)
|
|
14
|
+
- Imperative API for programmatic control (designed for AI agent integration)
|
|
15
|
+
- Built-in project templates (Express + React full-stack apps)
|
|
16
|
+
- Customizable via CSS custom properties
|
|
17
|
+
- Dark theme by default, Ranger/shadcn theme variable bridge
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @illuma-ai/code-sandbox
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Peer Dependencies
|
|
26
|
+
|
|
27
|
+
These must be installed by the consuming application:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install react react-dom @monaco-editor/react monaco-editor @xterm/xterm @xterm/addon-fit lucide-react
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Peer Dependency | Version |
|
|
34
|
+
| ---------------------- | ---------------------- |
|
|
35
|
+
| `react` | `^18.0.0 \|\| ^19.0.0` |
|
|
36
|
+
| `react-dom` | `^18.0.0 \|\| ^19.0.0` |
|
|
37
|
+
| `@monaco-editor/react` | `^4.6.0` |
|
|
38
|
+
| `monaco-editor` | `>=0.40.0` |
|
|
39
|
+
| `@xterm/xterm` | `^5.5.0 \|\| ^6.0.0` |
|
|
40
|
+
| `@xterm/addon-fit` | `^0.11.0` |
|
|
41
|
+
| `lucide-react` | `>=0.300.0` |
|
|
42
|
+
|
|
43
|
+
## Prerequisites
|
|
44
|
+
|
|
45
|
+
### COOP/COEP Headers
|
|
46
|
+
|
|
47
|
+
Nodepod requires `SharedArrayBuffer`, which is only available in cross-origin isolated contexts. Your dev server must send these headers:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
51
|
+
Cross-Origin-Embedder-Policy: credentialless
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Vite example:**
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// vite.config.ts
|
|
58
|
+
export default defineConfig({
|
|
59
|
+
server: {
|
|
60
|
+
headers: {
|
|
61
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
62
|
+
"Cross-Origin-Embedder-Policy": "credentialless",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Service Worker
|
|
69
|
+
|
|
70
|
+
Copy the Nodepod service worker file to your `public/` directory so it's served at `/__sw__.js`:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cp node_modules/@illuma-ai/nodepod/dist/sw.js public/__sw__.js
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Quick Start
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { useRef } from "react";
|
|
80
|
+
import { CodeSandbox } from "@illuma-ai/code-sandbox";
|
|
81
|
+
import type { CodeSandboxHandle } from "@illuma-ai/code-sandbox";
|
|
82
|
+
import "@illuma-ai/code-sandbox/styles.css";
|
|
83
|
+
|
|
84
|
+
function App() {
|
|
85
|
+
const ref = useRef<CodeSandboxHandle>(null);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<CodeSandbox
|
|
89
|
+
ref={ref}
|
|
90
|
+
template="fullstack-starter"
|
|
91
|
+
height="100vh"
|
|
92
|
+
onServerReady={(port, url) => console.log(`Ready on port ${port}`)}
|
|
93
|
+
onSandboxError={(err) => console.error(err.category, err.message)}
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### With Custom Files
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
const files = {
|
|
103
|
+
"package.json": JSON.stringify(
|
|
104
|
+
{
|
|
105
|
+
name: "my-app",
|
|
106
|
+
dependencies: { express: "^4.18.0" },
|
|
107
|
+
},
|
|
108
|
+
null,
|
|
109
|
+
2,
|
|
110
|
+
),
|
|
111
|
+
"server.js": `
|
|
112
|
+
const express = require("express");
|
|
113
|
+
const app = express();
|
|
114
|
+
app.get("/", (req, res) => res.send("<h1>Hello World</h1>"));
|
|
115
|
+
app.listen(3000, () => console.log("Running on port 3000"));
|
|
116
|
+
`,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
<CodeSandbox
|
|
120
|
+
ref={ref}
|
|
121
|
+
files={files}
|
|
122
|
+
entryCommand="node server.js"
|
|
123
|
+
port={3000}
|
|
124
|
+
height="100vh"
|
|
125
|
+
/>;
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### AI Agent Integration
|
|
129
|
+
|
|
130
|
+
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.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
const ref = useRef<CodeSandboxHandle>(null);
|
|
134
|
+
|
|
135
|
+
// Agent generates new files → push them into the sandbox
|
|
136
|
+
await ref.current.updateFiles(agentGeneratedFiles);
|
|
137
|
+
|
|
138
|
+
// Agent modifies a single file
|
|
139
|
+
await ref.current.updateFile("server.js", newServerCode);
|
|
140
|
+
|
|
141
|
+
// Read errors for the agent to self-correct
|
|
142
|
+
const errors = ref.current.getErrors();
|
|
143
|
+
// errors[0] → { category: "process-stderr", message: "...", filePath: "server.js", line: 42, sourceContext: "..." }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## API Reference
|
|
149
|
+
|
|
150
|
+
### `<CodeSandbox>` Component
|
|
151
|
+
|
|
152
|
+
The main component. Renders the full workbench (file tree, editor, terminal, preview). Uses `React.forwardRef` to expose the imperative handle.
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<CodeSandbox
|
|
156
|
+
ref={sandboxRef}
|
|
157
|
+
files={fileMap} // OR template="fullstack-starter"
|
|
158
|
+
entryCommand="node server.js"
|
|
159
|
+
port={3000}
|
|
160
|
+
env={{ NODE_ENV: "development" }}
|
|
161
|
+
height="100vh"
|
|
162
|
+
className="my-sandbox"
|
|
163
|
+
onFileChange={(path, content) => {}}
|
|
164
|
+
onServerReady={(port, url) => {}}
|
|
165
|
+
onProgress={(progress) => {}}
|
|
166
|
+
onError={(message) => {}}
|
|
167
|
+
onSandboxError={(error) => {}}
|
|
168
|
+
onFilesUpdated={(changes) => {}}
|
|
169
|
+
/>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### Props (`CodeSandboxProps`)
|
|
173
|
+
|
|
174
|
+
| Prop | Type | Default | Description |
|
|
175
|
+
| -------------- | ------------------------ | -------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
176
|
+
| `files` | `FileMap` | — | Initial file set. Pass a flat `Record<string, string>` mapping file paths to contents. |
|
|
177
|
+
| `template` | `string` | — | Use a built-in template instead of `files`. One of: `"express-react"`, `"fullstack-starter"`. |
|
|
178
|
+
| `entryCommand` | `string` | Inferred from `package.json` or `"node server.js"` | Shell command to start the dev server. |
|
|
179
|
+
| `port` | `number` | `3000` | Port the server listens on. |
|
|
180
|
+
| `env` | `Record<string, string>` | — | Environment variables passed to Node.js processes. |
|
|
181
|
+
| `height` | `string` | `"100vh"` | CSS height of the sandbox root element. |
|
|
182
|
+
| `className` | `string` | — | CSS class name for the root element. |
|
|
183
|
+
|
|
184
|
+
#### Callback Props
|
|
185
|
+
|
|
186
|
+
| Callback | Signature | Description |
|
|
187
|
+
| ---------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
188
|
+
| `onFileChange` | `(path: string, content: string) => void` | Fires when a file is modified in the editor. |
|
|
189
|
+
| `onServerReady` | `(port: number, url: string) => void` | Fires when the dev server is ready (initial boot or after restart). |
|
|
190
|
+
| `onProgress` | `(progress: BootProgress) => void` | Fires on boot progress changes. |
|
|
191
|
+
| `onError` | `(message: string) => void` | Fires on errors (legacy string format). |
|
|
192
|
+
| `onSandboxError` | `(error: SandboxError) => void` | Fires when a structured error is detected. **Primary error channel for AI agent integration.** |
|
|
193
|
+
| `onFilesUpdated` | `(changes: Record<string, FileChangeStatus>) => void` | Fires after `updateFiles()` completes (files written + server restarted). |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### `CodeSandboxHandle` (Imperative API)
|
|
198
|
+
|
|
199
|
+
Access via a React ref. This is the primary API for host applications to control the sandbox programmatically.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
const ref = useRef<CodeSandboxHandle>(null);
|
|
203
|
+
<CodeSandbox ref={ref} ... />
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Methods
|
|
207
|
+
|
|
208
|
+
##### `updateFiles(files, options?)`
|
|
209
|
+
|
|
210
|
+
Replace the entire file set. Diffs against current files, writes only changed files to the virtual FS, and restarts the server if anything changed.
|
|
211
|
+
|
|
212
|
+
The previous file set becomes `originalFiles` for diff tracking.
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
await ref.current.updateFiles(newFileMap);
|
|
216
|
+
await ref.current.updateFiles(newFileMap, { restartServer: false });
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Param | Type | Description |
|
|
220
|
+
| ----------------------- | --------- | ------------------------------------------------------------- |
|
|
221
|
+
| `files` | `FileMap` | Complete new file set. |
|
|
222
|
+
| `options.restartServer` | `boolean` | Whether to restart the server after writing. Default: `true`. |
|
|
223
|
+
|
|
224
|
+
**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.
|
|
225
|
+
|
|
226
|
+
##### `updateFile(path, content)`
|
|
227
|
+
|
|
228
|
+
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.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
await ref.current.updateFile("public/styles.css", newCss);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
##### `restart()`
|
|
235
|
+
|
|
236
|
+
Force restart the server process. Kills the current process and re-runs the entry command.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
await ref.current.restart();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
##### `getFiles()`
|
|
243
|
+
|
|
244
|
+
Returns the current `FileMap` (all files including edits).
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
const files: FileMap = ref.current.getFiles();
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
##### `getChangedFiles()`
|
|
251
|
+
|
|
252
|
+
Returns a `FileMap` containing only files that have been modified relative to `originalFiles`.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
const changed: FileMap = ref.current.getChangedFiles();
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
##### `getFileChanges()`
|
|
259
|
+
|
|
260
|
+
Returns the per-file change status map. Missing keys should be treated as `"unchanged"`.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
const changes: Record<string, FileChangeStatus> = ref.current.getFileChanges();
|
|
264
|
+
// { "server.js": "modified", "public/new-page.html": "new" }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
##### `getErrors()`
|
|
268
|
+
|
|
269
|
+
Returns all structured errors collected during this session. The array grows monotonically — new errors are appended.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const errors: SandboxError[] = ref.current.getErrors();
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
##### `getState()`
|
|
276
|
+
|
|
277
|
+
Returns the current `RuntimeState` snapshot.
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
const state: RuntimeState = ref.current.getState();
|
|
281
|
+
// state.status, state.previewUrl, state.terminalOutput, etc.
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### `SandboxError`
|
|
287
|
+
|
|
288
|
+
Structured error type designed for AI agent consumption. Includes enough context for the agent to construct a targeted fix prompt.
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
interface SandboxError {
|
|
292
|
+
id: string; // Unique ID for deduplication
|
|
293
|
+
category: SandboxErrorCategory; // Error type
|
|
294
|
+
message: string; // Human-readable error message
|
|
295
|
+
stack?: string; // Full stack trace
|
|
296
|
+
filePath?: string; // File path (relative to workdir)
|
|
297
|
+
line?: number; // Line number (1-indexed)
|
|
298
|
+
column?: number; // Column number (1-indexed)
|
|
299
|
+
timestamp: string; // ISO 8601 timestamp
|
|
300
|
+
sourceContext?: string; // ~10 surrounding source lines
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Error Categories (`SandboxErrorCategory`)
|
|
305
|
+
|
|
306
|
+
| Category | Description |
|
|
307
|
+
| ----------------------------- | -------------------------------------------------------------- |
|
|
308
|
+
| `process-stderr` | Output written to stderr by the Node.js process |
|
|
309
|
+
| `process-exit` | Process exited with a non-zero code |
|
|
310
|
+
| `runtime-exception` | Uncaught exception in the Node.js runtime |
|
|
311
|
+
| `browser-error` | JavaScript error in the preview iframe (`window.onerror`) |
|
|
312
|
+
| `browser-unhandled-rejection` | Unhandled promise rejection in the iframe |
|
|
313
|
+
| `browser-console-error` | `console.error()` calls in the iframe |
|
|
314
|
+
| `compilation` | Syntax/import errors at module load time |
|
|
315
|
+
| `network` | Failed HTTP requests from the preview (fetch/XHR) |
|
|
316
|
+
| `boot` | Error during Nodepod boot, file writing, or dependency install |
|
|
317
|
+
|
|
318
|
+
#### Source Context Format
|
|
319
|
+
|
|
320
|
+
When available, `sourceContext` contains lines formatted as `"lineNum: content"`:
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
38: const express = require("express");
|
|
324
|
+
39: const app = express();
|
|
325
|
+
40: const PORT = 3000;
|
|
326
|
+
41:
|
|
327
|
+
42: app.get("/api/data", (req, res) => {
|
|
328
|
+
43: res.json(undefinedVariable); // ← error here
|
|
329
|
+
44: });
|
|
330
|
+
45: app.listen(PORT);
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### `RuntimeState`
|
|
336
|
+
|
|
337
|
+
The full reactive state exposed by the runtime.
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
interface RuntimeState {
|
|
341
|
+
status: BootStage;
|
|
342
|
+
progress: BootProgress;
|
|
343
|
+
previewUrl: string | null;
|
|
344
|
+
terminalOutput: string[];
|
|
345
|
+
files: FileMap;
|
|
346
|
+
originalFiles: FileMap;
|
|
347
|
+
fileChanges: Record<string, FileChangeStatus>;
|
|
348
|
+
previewReloadKey: number;
|
|
349
|
+
error: string | null;
|
|
350
|
+
errors: SandboxError[];
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
| Field | Type | Description |
|
|
355
|
+
| ------------------ | ---------------------------------- | --------------------------------------------------------------------------- |
|
|
356
|
+
| `status` | `BootStage` | Current lifecycle stage. |
|
|
357
|
+
| `progress` | `BootProgress` | Current boot progress (stage, message, percent). |
|
|
358
|
+
| `previewUrl` | `string \| null` | URL for the preview iframe. `null` until server is ready. |
|
|
359
|
+
| `terminalOutput` | `string[]` | All terminal output lines (stdout + stderr). |
|
|
360
|
+
| `files` | `FileMap` | Current files in the virtual FS (including edits). |
|
|
361
|
+
| `originalFiles` | `FileMap` | Baseline files before the latest `updateFiles()`. Used for diffs. |
|
|
362
|
+
| `fileChanges` | `Record<string, FileChangeStatus>` | Per-file change status. Missing keys = `"unchanged"`. |
|
|
363
|
+
| `previewReloadKey` | `number` | Incremented on hot reload — triggers iframe refresh without server restart. |
|
|
364
|
+
| `error` | `string \| null` | Error message if status is `"error"`. |
|
|
365
|
+
| `errors` | `SandboxError[]` | All structured errors (grows monotonically). |
|
|
366
|
+
|
|
367
|
+
### `BootStage`
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
type BootStage =
|
|
371
|
+
| "initializing" // Nodepod.boot() in progress
|
|
372
|
+
| "writing-files" // Writing project files to virtual FS
|
|
373
|
+
| "installing" // npm install running
|
|
374
|
+
| "starting" // Entry command executing
|
|
375
|
+
| "ready" // Server is listening, preview iframe can load
|
|
376
|
+
| "error"; // Something failed
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### `BootProgress`
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
interface BootProgress {
|
|
383
|
+
stage: BootStage;
|
|
384
|
+
message: string;
|
|
385
|
+
percent: number; // 0-100, approximate
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### `FileChangeStatus`
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
type FileChangeStatus = "new" | "modified" | "deleted" | "unchanged";
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Hot Reload
|
|
398
|
+
|
|
399
|
+
When `updateFiles()` is called, the sandbox inspects which files changed:
|
|
400
|
+
|
|
401
|
+
- **Hot-reloadable extensions:** `.css`, `.html`, `.htm`, `.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.ico`, `.woff`, `.woff2`, `.ttf`, `.eot`
|
|
402
|
+
- If **all** changed files have hot-reloadable extensions → the preview iframe is soft-reloaded (`location.reload()`) without restarting the Node.js server process.
|
|
403
|
+
- If **any** changed file is JS/TS/JSON/etc. → the server process is killed and re-run (cold restart).
|
|
404
|
+
|
|
405
|
+
This makes CSS-only iterations instant.
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Built-in Templates
|
|
410
|
+
|
|
411
|
+
Use `template="name"` to bootstrap with a pre-built project.
|
|
412
|
+
|
|
413
|
+
### `express-react`
|
|
414
|
+
|
|
415
|
+
A lightweight Express + React app using JSONPlaceholder as a data source. React is loaded from CDN (no build step). Demonstrates posts/users CRUD views.
|
|
416
|
+
|
|
417
|
+
- **Entry command:** `node server.js`
|
|
418
|
+
- **Port:** 3000
|
|
419
|
+
- **Files:** 9 (server.js, public/index.html, styles, 5 React components)
|
|
420
|
+
|
|
421
|
+
### `fullstack-starter`
|
|
422
|
+
|
|
423
|
+
A comprehensive full-stack template with authentication, CRUD, charts, and theming.
|
|
424
|
+
|
|
425
|
+
- **Entry command:** `node server.js`
|
|
426
|
+
- **Port:** 3000
|
|
427
|
+
- **Files:** 35 (Express + sql.js + React + React Router + Auth + CRUD + Chart.js + Ranger theme variables)
|
|
428
|
+
- **Features:** Login/register, todo CRUD with SQLite (sql.js), doughnut chart, light/dark theme, responsive layout
|
|
429
|
+
|
|
430
|
+
### Template API
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
import { getTemplate, listTemplates } from "@illuma-ai/code-sandbox";
|
|
434
|
+
|
|
435
|
+
const names = listTemplates();
|
|
436
|
+
// ["express-react", "fullstack-starter"]
|
|
437
|
+
|
|
438
|
+
const tpl = getTemplate("fullstack-starter");
|
|
439
|
+
// { files: FileMap, entryCommand: string, port: number }
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Theming
|
|
445
|
+
|
|
446
|
+
### Sandbox Chrome (CSS Custom Properties)
|
|
447
|
+
|
|
448
|
+
The sandbox UI is themed via `--sb-*` CSS custom properties. Override them in your CSS to match your application:
|
|
449
|
+
|
|
450
|
+
```css
|
|
451
|
+
:root {
|
|
452
|
+
--sb-bg: #1e1e1e;
|
|
453
|
+
--sb-bg-alt: #252526;
|
|
454
|
+
--sb-bg-hover: #2a2d2e;
|
|
455
|
+
--sb-bg-active: #37373d;
|
|
456
|
+
--sb-sidebar: #252526;
|
|
457
|
+
--sb-editor: #1e1e1e;
|
|
458
|
+
--sb-terminal: #0d1117;
|
|
459
|
+
--sb-terminal-header: #161b22;
|
|
460
|
+
--sb-preview: #ffffff;
|
|
461
|
+
--sb-border: #3c3c3c;
|
|
462
|
+
--sb-text: #cccccc;
|
|
463
|
+
--sb-text-muted: #858585;
|
|
464
|
+
--sb-text-active: #ffffff;
|
|
465
|
+
--sb-accent: #007acc;
|
|
466
|
+
--sb-accent-hover: #1a8ad4;
|
|
467
|
+
--sb-success: #3fb950;
|
|
468
|
+
--sb-warning: #d29922;
|
|
469
|
+
--sb-error: #f85149;
|
|
470
|
+
--sb-info: #58a6ff;
|
|
471
|
+
--sb-scrollbar-thumb: rgba(255, 255, 255, 0.12);
|
|
472
|
+
--sb-scrollbar-thumb-hover: rgba(255, 255, 255, 0.25);
|
|
473
|
+
--sb-scrollbar-track: transparent;
|
|
474
|
+
--sb-scrollbar-width: 6px;
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Ranger Theme Bridge
|
|
479
|
+
|
|
480
|
+
The sandbox CSS also defines Ranger/shadcn 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 Ranger's UI.
|
|
481
|
+
|
|
482
|
+
Add the `.dark` class to an ancestor element to activate dark mode tokens.
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## Advanced Usage
|
|
487
|
+
|
|
488
|
+
### Individual Components
|
|
489
|
+
|
|
490
|
+
All sub-components are exported individually for custom layouts:
|
|
491
|
+
|
|
492
|
+
```tsx
|
|
493
|
+
import {
|
|
494
|
+
FileTree,
|
|
495
|
+
buildFileTree,
|
|
496
|
+
CodeEditor,
|
|
497
|
+
Terminal,
|
|
498
|
+
Preview,
|
|
499
|
+
BootOverlay,
|
|
500
|
+
ViewSlider,
|
|
501
|
+
} from "@illuma-ai/code-sandbox";
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
#### `<FileTree>`
|
|
505
|
+
|
|
506
|
+
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.
|
|
507
|
+
|
|
508
|
+
```tsx
|
|
509
|
+
import { FileTree, buildFileTree } from "@illuma-ai/code-sandbox";
|
|
510
|
+
|
|
511
|
+
const tree = buildFileTree(fileMap);
|
|
512
|
+
<FileTree
|
|
513
|
+
files={tree}
|
|
514
|
+
selectedFile="server.js"
|
|
515
|
+
onSelectFile={(path) => setSelected(path)}
|
|
516
|
+
fileChanges={{ "server.js": "modified" }}
|
|
517
|
+
/>;
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
#### `<CodeEditor>`
|
|
521
|
+
|
|
522
|
+
Monaco-based code editor with file tabs, diff mode toggle, and change status indicators on tabs.
|
|
523
|
+
|
|
524
|
+
```tsx
|
|
525
|
+
<CodeEditor
|
|
526
|
+
files={fileMap}
|
|
527
|
+
originalFiles={baselineFiles}
|
|
528
|
+
fileChanges={changes}
|
|
529
|
+
activeFile="server.js"
|
|
530
|
+
openFiles={["server.js", "public/index.html"]}
|
|
531
|
+
onSelectFile={(path) => {}}
|
|
532
|
+
onCloseFile={(path) => {}}
|
|
533
|
+
onFileChange={(path, content) => {}}
|
|
534
|
+
readOnly={false}
|
|
535
|
+
/>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
#### `<Terminal>`
|
|
539
|
+
|
|
540
|
+
Clean monochrome terminal output display with minimize/expand toggle and line count badge.
|
|
541
|
+
|
|
542
|
+
```tsx
|
|
543
|
+
<Terminal
|
|
544
|
+
output={terminalLines}
|
|
545
|
+
minimized={false}
|
|
546
|
+
onToggleMinimize={() => setMinimized(!minimized)}
|
|
547
|
+
/>
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
#### `<Preview>`
|
|
551
|
+
|
|
552
|
+
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.
|
|
553
|
+
|
|
554
|
+
```tsx
|
|
555
|
+
<Preview
|
|
556
|
+
url={previewUrl}
|
|
557
|
+
onRefresh={() => runtime.restart()}
|
|
558
|
+
onBrowserError={(error) => handleError(error)}
|
|
559
|
+
reloadKey={state.previewReloadKey}
|
|
560
|
+
/>
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
#### `<BootOverlay>`
|
|
564
|
+
|
|
565
|
+
Loading overlay with staged progress animation.
|
|
566
|
+
|
|
567
|
+
```tsx
|
|
568
|
+
<BootOverlay
|
|
569
|
+
progress={{
|
|
570
|
+
stage: "installing",
|
|
571
|
+
message: "Installing express...",
|
|
572
|
+
percent: 55,
|
|
573
|
+
}}
|
|
574
|
+
/>
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
#### `<ViewSlider>`
|
|
578
|
+
|
|
579
|
+
Pill-shaped toggle between Code and Preview views with Framer Motion animation.
|
|
580
|
+
|
|
581
|
+
```tsx
|
|
582
|
+
import type { WorkbenchView } from "@illuma-ai/code-sandbox";
|
|
583
|
+
|
|
584
|
+
<ViewSlider
|
|
585
|
+
activeView={view}
|
|
586
|
+
onViewChange={(v: WorkbenchView) => setView(v)}
|
|
587
|
+
/>;
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### `NodepodRuntime` Service
|
|
591
|
+
|
|
592
|
+
The framework-agnostic runtime service (no React dependency). Use directly for custom integrations.
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
import { NodepodRuntime } from "@illuma-ai/code-sandbox";
|
|
596
|
+
|
|
597
|
+
const runtime = new NodepodRuntime({
|
|
598
|
+
files: myFiles,
|
|
599
|
+
entryCommand: "node server.js",
|
|
600
|
+
port: 3000,
|
|
601
|
+
workdir: "/app",
|
|
602
|
+
env: { NODE_ENV: "production" },
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
runtime.setProgressCallback((progress) => console.log(progress));
|
|
606
|
+
runtime.setOutputCallback((line) => console.log(line));
|
|
607
|
+
runtime.setServerReadyCallback((port, url) => console.log("Ready:", url));
|
|
608
|
+
runtime.setErrorCallback((error) => console.error(error));
|
|
609
|
+
|
|
610
|
+
await runtime.boot();
|
|
611
|
+
|
|
612
|
+
// File operations
|
|
613
|
+
await runtime.writeFile("server.js", newContent);
|
|
614
|
+
const content = await runtime.readFile("server.js");
|
|
615
|
+
|
|
616
|
+
// State
|
|
617
|
+
const files = runtime.getCurrentFiles();
|
|
618
|
+
const changed = runtime.getChangedFiles();
|
|
619
|
+
const errors = runtime.getErrors();
|
|
620
|
+
const status = runtime.getStatus();
|
|
621
|
+
const previewUrl = runtime.getPreviewUrl();
|
|
622
|
+
|
|
623
|
+
// Lifecycle
|
|
624
|
+
await runtime.restart();
|
|
625
|
+
runtime.teardown();
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### `useRuntime` Hook
|
|
629
|
+
|
|
630
|
+
React hook wrapping `NodepodRuntime`. Powers the `CodeSandbox` component internally. Use for fully custom React UIs.
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
import { useRuntime } from "@illuma-ai/code-sandbox";
|
|
634
|
+
|
|
635
|
+
const {
|
|
636
|
+
state, // RuntimeState
|
|
637
|
+
selectedFile, // string | null
|
|
638
|
+
openFiles, // string[]
|
|
639
|
+
handleFileChange, // (path, content) => void
|
|
640
|
+
handleSelectFile, // (path) => void
|
|
641
|
+
handleCloseFile, // (path) => void
|
|
642
|
+
handleBrowserError, // (error: SandboxError) => void
|
|
643
|
+
restart, // () => Promise<void>
|
|
644
|
+
updateFiles, // (files, options?) => Promise<void>
|
|
645
|
+
updateFile, // (path, content) => Promise<void>
|
|
646
|
+
getFiles, // () => FileMap
|
|
647
|
+
getChangedFiles, // () => FileMap
|
|
648
|
+
getFileChanges, // () => Record<string, FileChangeStatus>
|
|
649
|
+
getErrors, // () => SandboxError[]
|
|
650
|
+
getState, // () => RuntimeState
|
|
651
|
+
} = useRuntime(props);
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## Nodepod Constraints
|
|
657
|
+
|
|
658
|
+
The sandbox runs on [Nodepod](https://www.npmjs.com/package/@illuma-ai/nodepod), a browser-native Node.js runtime. Key constraints:
|
|
659
|
+
|
|
660
|
+
- **No build tools:** Vite, Webpack, Next.js, etc. will not work. Use CDN-loaded libraries instead.
|
|
661
|
+
- **`express.static()` does not work.** Serve files manually via `fs.readFileSync()` + `res.send()`.
|
|
662
|
+
- **`res.sendFile()` does not work.** Same — use `fs.readFileSync()` + `res.send()`.
|
|
663
|
+
- **Module wrapper hides browser globals:** `document`, `window`, `location` are `undefined` in server-side code (as expected in Node.js).
|
|
664
|
+
- **Preview URL rewriting:** Nodepod returns `/__virtual__/{port}` URLs but the Service Worker expects `/__preview__/{port}/`. The sandbox handles this transparently.
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## All Exports
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
// Main component
|
|
672
|
+
export { CodeSandbox } from "@illuma-ai/code-sandbox";
|
|
673
|
+
|
|
674
|
+
// Individual components
|
|
675
|
+
export { FileTree, buildFileTree } from "@illuma-ai/code-sandbox";
|
|
676
|
+
export { CodeEditor } from "@illuma-ai/code-sandbox";
|
|
677
|
+
export { Terminal } from "@illuma-ai/code-sandbox";
|
|
678
|
+
export { Preview } from "@illuma-ai/code-sandbox";
|
|
679
|
+
export { BootOverlay } from "@illuma-ai/code-sandbox";
|
|
680
|
+
export { ViewSlider } from "@illuma-ai/code-sandbox";
|
|
681
|
+
|
|
682
|
+
// Services & hooks
|
|
683
|
+
export { NodepodRuntime } from "@illuma-ai/code-sandbox";
|
|
684
|
+
export { useRuntime } from "@illuma-ai/code-sandbox";
|
|
685
|
+
|
|
686
|
+
// Templates
|
|
687
|
+
export { getTemplate, listTemplates } from "@illuma-ai/code-sandbox";
|
|
688
|
+
|
|
689
|
+
// Types
|
|
690
|
+
export type {
|
|
691
|
+
FileMap,
|
|
692
|
+
FileNode,
|
|
693
|
+
FileChangeStatus,
|
|
694
|
+
SandboxError,
|
|
695
|
+
SandboxErrorCategory,
|
|
696
|
+
BootStage,
|
|
697
|
+
BootProgress,
|
|
698
|
+
RuntimeConfig,
|
|
699
|
+
RuntimeState,
|
|
700
|
+
CodeSandboxProps,
|
|
701
|
+
CodeSandboxHandle,
|
|
702
|
+
FileTreeProps,
|
|
703
|
+
CodeEditorProps,
|
|
704
|
+
TerminalProps,
|
|
705
|
+
PreviewProps,
|
|
706
|
+
BootOverlayProps,
|
|
707
|
+
WorkbenchView,
|
|
708
|
+
} from "@illuma-ai/code-sandbox";
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
## License
|
|
714
|
+
|
|
715
|
+
Proprietary - Illuma AI. See [LICENSE](./LICENSE) for details.
|