@illuma-ai/code-sandbox 1.0.0 → 1.2.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/src/types.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * @illuma-ai/code-sandbox — Type definitions
3
3
  *
4
4
  * Core types for the browser-native code sandbox.
5
- * Covers file maps, runtime states, git operations, and component props.
5
+ * Covers file maps, runtime states, imperative handle, and component props.
6
6
  */
7
7
 
8
8
  // ---------------------------------------------------------------------------
@@ -20,6 +20,78 @@ export interface FileNode {
20
20
  children?: FileNode[];
21
21
  }
22
22
 
23
+ /**
24
+ * Change status for a file relative to the baseline (original) snapshot.
25
+ *
26
+ * - `new`: File exists in current but not in original
27
+ * - `modified`: File exists in both but content differs
28
+ * - `deleted`: File exists in original but not in current
29
+ * - `unchanged`: File content is identical
30
+ */
31
+ export type FileChangeStatus = "new" | "modified" | "deleted" | "unchanged";
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Errors (structured error types for AI agent consumption)
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /**
38
+ * Categories of errors that can occur in the sandbox.
39
+ *
40
+ * - `process-stderr`: Output written to stderr by the Node.js process
41
+ * - `process-exit`: Process exited with a non-zero code
42
+ * - `runtime-exception`: Uncaught exception in the Node.js runtime (Nodepod)
43
+ * - `browser-error`: JavaScript error in the preview iframe (window.onerror)
44
+ * - `browser-unhandled-rejection`: Unhandled promise rejection in the iframe
45
+ * - `browser-console-error`: console.error() calls in the iframe
46
+ * - `compilation`: Syntax/import errors detected at module load time
47
+ * - `network`: Failed HTTP requests from the preview (fetch/XHR errors)
48
+ * - `boot`: Error during Nodepod boot, file writing, or dependency install
49
+ */
50
+ export type SandboxErrorCategory =
51
+ | "process-stderr"
52
+ | "process-exit"
53
+ | "runtime-exception"
54
+ | "browser-error"
55
+ | "browser-unhandled-rejection"
56
+ | "browser-console-error"
57
+ | "compilation"
58
+ | "network"
59
+ | "boot";
60
+
61
+ /**
62
+ * A structured error emitted by the sandbox.
63
+ *
64
+ * Designed for AI agent consumption — includes enough context for the
65
+ * agent to construct a targeted fix prompt (file path, line number,
66
+ * surrounding code, stack trace).
67
+ *
68
+ * @see Ranger's auto-fix: client/src/components/Artifacts/Artifacts.tsx
69
+ */
70
+ export interface SandboxError {
71
+ /** Unique ID for deduplication */
72
+ id: string;
73
+ /** Error category — determines how the agent should interpret it */
74
+ category: SandboxErrorCategory;
75
+ /** Human-readable error message (the "what") */
76
+ message: string;
77
+ /** Full stack trace if available */
78
+ stack?: string;
79
+ /** File path where the error occurred (relative to workdir) */
80
+ filePath?: string;
81
+ /** Line number in the file (1-indexed) */
82
+ line?: number;
83
+ /** Column number in the file (1-indexed) */
84
+ column?: number;
85
+ /** ISO 8601 timestamp */
86
+ timestamp: string;
87
+ /**
88
+ * Surrounding source code lines for context.
89
+ * Format: "lineNum: content" per line (e.g., "42: const x = foo();")
90
+ * Typically ~10 lines centered on the error line.
91
+ */
92
+ sourceContext?: string;
93
+ }
94
+
23
95
  // ---------------------------------------------------------------------------
24
96
  // Runtime (Nodepod lifecycle)
25
97
  // ---------------------------------------------------------------------------
@@ -64,12 +136,32 @@ export interface RuntimeState {
64
136
  terminalOutput: string[];
65
137
  /** Current files in the virtual FS (may differ from original after edits) */
66
138
  files: FileMap;
139
+ /**
140
+ * Baseline file snapshot — the files as they were before the latest
141
+ * update (via updateFiles). Used by the diff viewer to show what changed.
142
+ * Empty on first load (everything is "new"), populated after the first
143
+ * file update.
144
+ */
145
+ originalFiles: FileMap;
146
+ /**
147
+ * Per-file change status relative to originalFiles.
148
+ *
149
+ * Only includes files that have a non-"unchanged" status.
150
+ * Missing keys should be treated as "unchanged".
151
+ */
152
+ fileChanges: Record<string, FileChangeStatus>;
67
153
  /** Error message if status is 'error' */
68
154
  error: string | null;
155
+ /**
156
+ * All structured errors collected during this session.
157
+ * New errors are appended — the array grows monotonically.
158
+ * Use `errors[errors.length - 1]` to get the latest.
159
+ */
160
+ errors: SandboxError[];
69
161
  }
70
162
 
71
163
  // ---------------------------------------------------------------------------
72
- // Git (GitHub API integration)
164
+ // Git (GitHub API integration — utility, NOT used internally by sandbox)
73
165
  // ---------------------------------------------------------------------------
74
166
 
75
167
  export interface GitHubRepo {
@@ -105,17 +197,118 @@ export interface GitPRResult {
105
197
  branch: string;
106
198
  }
107
199
 
200
+ // ---------------------------------------------------------------------------
201
+ // Imperative Handle
202
+ // ---------------------------------------------------------------------------
203
+
204
+ /**
205
+ * Imperative handle exposed by CodeSandbox via React.forwardRef.
206
+ *
207
+ * This is the primary API for host applications (like Ranger) to
208
+ * control the sandbox programmatically. The sandbox is a "dumb renderer"
209
+ * — the host pushes files in and reads state out.
210
+ *
211
+ * @example
212
+ * ```tsx
213
+ * const ref = useRef<CodeSandboxHandle>(null);
214
+ *
215
+ * // Push files from any source (GitHub, DB, S3, agent output)
216
+ * ref.current?.updateFiles(fileMap);
217
+ *
218
+ * // Push a single file (e.g., agent modified one file)
219
+ * ref.current?.updateFile('server.js', newContent);
220
+ *
221
+ * // Read current state
222
+ * const files = ref.current?.getFiles();
223
+ * const changes = ref.current?.getChangedFiles();
224
+ * const errors = ref.current?.getErrors();
225
+ * ```
226
+ */
227
+ export interface CodeSandboxHandle {
228
+ /**
229
+ * Replace the entire file set. Diffs against current files, writes only
230
+ * changed files to Nodepod FS, and restarts the server if anything changed.
231
+ *
232
+ * The previous file set becomes `originalFiles` for diff tracking.
233
+ *
234
+ * @param files - Complete new file set
235
+ * @param options - Optional: restartServer (default true)
236
+ */
237
+ updateFiles: (
238
+ files: FileMap,
239
+ options?: { restartServer?: boolean },
240
+ ) => Promise<void>;
241
+
242
+ /**
243
+ * Update a single file. Writes to Nodepod FS and updates state.
244
+ * Does NOT restart the server — call `restart()` manually if needed,
245
+ * or use `updateFiles()` for bulk updates with auto-restart.
246
+ *
247
+ * @param path - File path (e.g., "server.js", "public/index.html")
248
+ * @param content - New file content
249
+ */
250
+ updateFile: (path: string, content: string) => Promise<void>;
251
+
252
+ /**
253
+ * Force restart the server process. Kills the current process
254
+ * and re-runs the entry command.
255
+ */
256
+ restart: () => Promise<void>;
257
+
258
+ /**
259
+ * Get the current file map (all files including edits).
260
+ */
261
+ getFiles: () => FileMap;
262
+
263
+ /**
264
+ * Get only files that have been modified relative to originalFiles.
265
+ * Returns a FileMap containing only the changed files.
266
+ */
267
+ getChangedFiles: () => FileMap;
268
+
269
+ /**
270
+ * Get the per-file change status map (new/modified/deleted).
271
+ * Missing keys should be treated as "unchanged".
272
+ */
273
+ getFileChanges: () => Record<string, FileChangeStatus>;
274
+
275
+ /**
276
+ * Get all structured errors collected during this session.
277
+ */
278
+ getErrors: () => SandboxError[];
279
+
280
+ /**
281
+ * Get the current runtime state snapshot.
282
+ */
283
+ getState: () => RuntimeState;
284
+ }
285
+
108
286
  // ---------------------------------------------------------------------------
109
287
  // Component Props
110
288
  // ---------------------------------------------------------------------------
111
289
 
290
+ /**
291
+ * Props for the CodeSandbox component.
292
+ *
293
+ * The sandbox is a pure renderer — it receives files via props and
294
+ * exposes an imperative handle for programmatic updates. It does NOT
295
+ * interact with any storage backend (GitHub, S3, DB) directly.
296
+ *
297
+ * File sources (for initial load, pick ONE):
298
+ * - `files` — pass a FileMap directly
299
+ * - `template` — use a built-in template name (e.g., "fullstack-starter")
300
+ *
301
+ * For live updates after mount, use the imperative handle:
302
+ * ```tsx
303
+ * const ref = useRef<CodeSandboxHandle>(null);
304
+ * <CodeSandbox ref={ref} files={initialFiles} />
305
+ * // Later:
306
+ * ref.current.updateFiles(newFiles);
307
+ * ```
308
+ */
112
309
  export interface CodeSandboxProps {
113
- /** Pass files directly */
310
+ /** Pass files directly (from any source — GitHub, DB, S3, agent, etc.) */
114
311
  files?: FileMap;
115
- /** OR load from GitHub */
116
- github?: GitHubRepo;
117
- /** GitHub personal access token (for private repos and write-back) */
118
- gitToken?: string;
119
312
  /** OR use a built-in template name */
120
313
  template?: string;
121
314
  /** Command to start the dev server (default inferred from package.json or template) */
@@ -126,14 +319,30 @@ export interface CodeSandboxProps {
126
319
  env?: Record<string, string>;
127
320
 
128
321
  // Callbacks
129
- /** Fires when a file is modified in the editor */
322
+ /** Fires when a file is modified in the editor by the user */
130
323
  onFileChange?: (path: string, content: string) => void;
131
- /** Fires when the dev server is ready */
324
+ /** Fires when the dev server is ready (initial boot or after restart) */
132
325
  onServerReady?: (port: number, url: string) => void;
133
326
  /** Fires on boot progress changes */
134
327
  onProgress?: (progress: BootProgress) => void;
135
- /** Fires on errors */
328
+ /** Fires on errors (legacy — simple string message) */
136
329
  onError?: (error: string) => void;
330
+ /**
331
+ * Fires when a structured error is detected.
332
+ *
333
+ * This is the primary error channel for AI agent integration.
334
+ * Errors include file path, line number, source context, and category
335
+ * so the agent can construct a targeted fix prompt.
336
+ *
337
+ * Max 2 auto-fix attempts per error is recommended (see Ranger pattern).
338
+ */
339
+ onSandboxError?: (error: SandboxError) => void;
340
+ /**
341
+ * Fires after updateFiles() completes (files written + server restarted).
342
+ * Useful for the host to know when the sandbox has finished processing
343
+ * a file push.
344
+ */
345
+ onFilesUpdated?: (fileChanges: Record<string, FileChangeStatus>) => void;
137
346
 
138
347
  // Layout
139
348
  /** CSS class name for the root element */
@@ -146,6 +355,8 @@ export interface FileTreeProps {
146
355
  files: FileNode[];
147
356
  selectedFile: string | null;
148
357
  onSelectFile: (path: string) => void;
358
+ /** Per-file change status for visual indicators (colored dots) */
359
+ fileChanges?: Record<string, FileChangeStatus>;
149
360
  onCreateFile?: (path: string) => void;
150
361
  onCreateFolder?: (path: string) => void;
151
362
  onDeleteFile?: (path: string) => void;
@@ -154,6 +365,10 @@ export interface FileTreeProps {
154
365
 
155
366
  export interface CodeEditorProps {
156
367
  files: FileMap;
368
+ /** Baseline files for diff comparison (empty = no diff available) */
369
+ originalFiles: FileMap;
370
+ /** Per-file change status for tab indicators */
371
+ fileChanges: Record<string, FileChangeStatus>;
157
372
  activeFile: string | null;
158
373
  openFiles: string[];
159
374
  onSelectFile: (path: string) => void;
@@ -171,6 +386,8 @@ export interface PreviewProps {
171
386
  url: string | null;
172
387
  className?: string;
173
388
  onRefresh?: () => void;
389
+ /** Called when a JavaScript error occurs inside the preview iframe */
390
+ onBrowserError?: (error: SandboxError) => void;
174
391
  }
175
392
 
176
393
  export interface BootOverlayProps {