@illuma-ai/code-sandbox 1.4.0 → 1.4.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.
package/LICENSE CHANGED
@@ -1,15 +1,21 @@
1
- Copyright (c) 2026 Illuma AI. All rights reserved.
1
+ MIT License
2
2
 
3
- PROPRIETARY AND CONFIDENTIAL
3
+ Copyright (c) 2026 Illuma AI
4
4
 
5
- This software is the proprietary information of Illuma AI.
6
- Use is subject to license terms. Unauthorized copying of this software,
7
- via any medium, is strictly prohibited.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
8
11
 
9
- This software is provided "as is", without warranty of any kind, express or
10
- implied, including but not limited to the warranties of merchantability,
11
- fitness for a particular purpose and noninfringement. In no event shall the
12
- authors or copyright holders be liable for any claim, damages or other
13
- liability, whether in an action of contract, tort or otherwise, arising from,
14
- out of or in connection with the software or the use or other dealings in the
15
- software.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -712,4 +712,4 @@ export type {
712
712
 
713
713
  ## License
714
714
 
715
- Proprietary - Illuma AI. See [LICENSE](./LICENSE) for details.
715
+ MIT - See [LICENSE](./LICENSE) for details.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@illuma-ai/code-sandbox",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
- "license": "SEE LICENSE IN LICENSE",
5
+ "license": "MIT",
6
6
  "author": "Illuma AI (https://github.com/illuma-ai)",
7
7
  "publishConfig": {
8
8
  "access": "public"
@@ -24,8 +24,7 @@
24
24
  "./styles.css": "./dist/styles.css"
25
25
  },
26
26
  "files": [
27
- "dist",
28
- "src"
27
+ "dist"
29
28
  ],
30
29
  "scripts": {
31
30
  "dev": "vite",
@@ -1,145 +0,0 @@
1
- /**
2
- * BootOverlay — Loading animation displayed while Nodepod boots.
3
- *
4
- * Shows staged progress: initializing → writing files → installing → starting → ready.
5
- * Includes a progress bar and stage-specific messaging with a pulsing animation.
6
- */
7
-
8
- import React from "react";
9
- import type { BootOverlayProps } from "../types";
10
-
11
- /** Stage display configuration */
12
- const STAGE_CONFIG: Record<
13
- string,
14
- { icon: string; label: string; color: string }
15
- > = {
16
- initializing: {
17
- icon: "⚡",
18
- label: "Initializing Runtime",
19
- color: "text-blue-400",
20
- },
21
- "writing-files": {
22
- icon: "📁",
23
- label: "Writing Project Files",
24
- color: "text-yellow-400",
25
- },
26
- installing: {
27
- icon: "📦",
28
- label: "Installing Dependencies",
29
- color: "text-purple-400",
30
- },
31
- starting: {
32
- icon: "🚀",
33
- label: "Starting Application",
34
- color: "text-green-400",
35
- },
36
- ready: { icon: "✅", label: "Application Ready", color: "text-green-400" },
37
- error: { icon: "❌", label: "Error", color: "text-red-400" },
38
- };
39
-
40
- /**
41
- * Full-screen loading overlay with staged boot progress.
42
- *
43
- * Renders over the workbench while Nodepod boots. Shows:
44
- * - Current stage icon + label
45
- * - Progress bar with percentage
46
- * - Descriptive message of what's happening
47
- * - Pulsing dots animation
48
- */
49
- export function BootOverlay({ progress, className = "" }: BootOverlayProps) {
50
- const config = STAGE_CONFIG[progress.stage] || STAGE_CONFIG.initializing;
51
-
52
- if (progress.stage === "ready") {
53
- return null;
54
- }
55
-
56
- return (
57
- <div
58
- className={`absolute inset-0 z-50 flex items-center justify-center bg-sb-bg/95 backdrop-blur-sm ${className}`}
59
- >
60
- <div className="flex flex-col items-center gap-6 max-w-md px-8">
61
- {/* Animated icon */}
62
- <div className="text-5xl animate-bounce">{config.icon}</div>
63
-
64
- {/* Stage label */}
65
- <h2 className={`text-xl font-semibold ${config.color}`}>
66
- {config.label}
67
- {progress.stage !== "error" && <PulsingDots />}
68
- </h2>
69
-
70
- {/* Progress bar */}
71
- <div className="w-64 h-2 bg-sb-bg-active rounded-full overflow-hidden">
72
- <div
73
- className="h-full bg-sb-accent rounded-full transition-all duration-500 ease-out"
74
- style={{ width: `${progress.percent}%` }}
75
- />
76
- </div>
77
-
78
- {/* Message */}
79
- <p className="text-sm text-sb-text-muted text-center">
80
- {progress.message}
81
- </p>
82
-
83
- {/* Stage indicators */}
84
- <div className="flex items-center gap-2 mt-4">
85
- {["initializing", "writing-files", "installing", "starting"].map(
86
- (stage, i) => {
87
- const isCompleted = getStageOrder(progress.stage) > i;
88
- const isCurrent = progress.stage === stage;
89
-
90
- return (
91
- <React.Fragment key={stage}>
92
- <div
93
- className={`w-3 h-3 rounded-full transition-colors duration-300 ${
94
- isCompleted
95
- ? "bg-sb-success"
96
- : isCurrent
97
- ? "bg-sb-accent animate-pulse"
98
- : "bg-sb-bg-active"
99
- }`}
100
- />
101
- {i < 3 && (
102
- <div
103
- className={`w-8 h-0.5 transition-colors duration-300 ${
104
- isCompleted ? "bg-sb-success" : "bg-sb-bg-active"
105
- }`}
106
- />
107
- )}
108
- </React.Fragment>
109
- );
110
- },
111
- )}
112
- </div>
113
- </div>
114
- </div>
115
- );
116
- }
117
-
118
- /** Get numeric order of a boot stage for progress indicators */
119
- function getStageOrder(stage: string): number {
120
- const order = [
121
- "initializing",
122
- "writing-files",
123
- "installing",
124
- "starting",
125
- "ready",
126
- ];
127
- return order.indexOf(stage);
128
- }
129
-
130
- /** Animated "..." dots */
131
- function PulsingDots() {
132
- return (
133
- <span className="inline-flex ml-1">
134
- <span className="animate-pulse" style={{ animationDelay: "0ms" }}>
135
- .
136
- </span>
137
- <span className="animate-pulse" style={{ animationDelay: "200ms" }}>
138
- .
139
- </span>
140
- <span className="animate-pulse" style={{ animationDelay: "400ms" }}>
141
- .
142
- </span>
143
- </span>
144
- );
145
- }
@@ -1,298 +0,0 @@
1
- /**
2
- * CodeEditor — Monaco editor with file tabs, diff mode, and change indicators.
3
- *
4
- * Uses @monaco-editor/react for both the regular editor and diff editor.
5
- * Renders a tab bar of open files with change status indicators:
6
- * - Green dot: new file
7
- * - Orange dot: modified file
8
- * - Red dot: deleted file (shown as read-only)
9
- *
10
- * When diff mode is active, displays Monaco's side-by-side DiffEditor
11
- * comparing the original file content (left) with the current content (right).
12
- *
13
- * @see @monaco-editor/react DiffEditor: https://github.com/suren-atoyan/monaco-react
14
- */
15
-
16
- import React, { useCallback, useMemo, useState } from "react";
17
- import type { CodeEditorProps, FileChangeStatus } from "../types";
18
-
19
- // Lazy import Monaco to keep the bundle splittable
20
- let MonacoEditor: React.ComponentType<any> | null = null;
21
- let MonacoDiffEditor: React.ComponentType<any> | null = null;
22
- let monacoLoadPromise: Promise<void> | null = null;
23
-
24
- /**
25
- * Load Monaco editor (regular + diff) dynamically.
26
- * Returns both Editor and DiffEditor from @monaco-editor/react.
27
- */
28
- function loadMonaco(): Promise<void> {
29
- if (MonacoEditor && MonacoDiffEditor) return Promise.resolve();
30
- if (monacoLoadPromise) return monacoLoadPromise;
31
-
32
- monacoLoadPromise = import("@monaco-editor/react").then((mod) => {
33
- MonacoEditor = mod.default || mod.Editor;
34
- MonacoDiffEditor = mod.DiffEditor;
35
- });
36
-
37
- return monacoLoadPromise;
38
- }
39
-
40
- /**
41
- * Get Monaco language from file path.
42
- */
43
- function getLanguage(path: string): string {
44
- const ext = path.split(".").pop()?.toLowerCase() || "";
45
- const langMap: Record<string, string> = {
46
- js: "javascript",
47
- jsx: "javascript",
48
- ts: "typescript",
49
- tsx: "typescript",
50
- json: "json",
51
- html: "html",
52
- htm: "html",
53
- css: "css",
54
- scss: "scss",
55
- less: "less",
56
- md: "markdown",
57
- py: "python",
58
- rb: "ruby",
59
- go: "go",
60
- rs: "rust",
61
- yml: "yaml",
62
- yaml: "yaml",
63
- xml: "xml",
64
- sql: "sql",
65
- sh: "shell",
66
- bash: "shell",
67
- svg: "xml",
68
- };
69
- return langMap[ext] || "plaintext";
70
- }
71
-
72
- /** Color for each file change status indicator dot */
73
- const STATUS_COLORS: Record<FileChangeStatus, string> = {
74
- new: "#4ade80", // green-400
75
- modified: "#fb923c", // orange-400
76
- deleted: "#f87171", // red-400
77
- unchanged: "transparent",
78
- };
79
-
80
- /** Tooltip for each file change status */
81
- const STATUS_LABELS: Record<FileChangeStatus, string> = {
82
- new: "New file",
83
- modified: "Modified",
84
- deleted: "Deleted",
85
- unchanged: "",
86
- };
87
-
88
- /**
89
- * CodeEditor component — Monaco editor with file tabs, diff view, and
90
- * change status indicators.
91
- *
92
- * When `originalFiles` has content for the active file (meaning a prior
93
- * version exists), a "Diff" toggle button appears in the tab bar. Clicking
94
- * it switches to Monaco's DiffEditor showing the old (left) vs new (right).
95
- */
96
- export function CodeEditor({
97
- files,
98
- originalFiles,
99
- fileChanges,
100
- activeFile,
101
- openFiles,
102
- onSelectFile,
103
- onCloseFile,
104
- onFileChange,
105
- readOnly = false,
106
- }: CodeEditorProps) {
107
- const [loaded, setLoaded] = React.useState(!!MonacoEditor);
108
- const [diffMode, setDiffMode] = useState(false);
109
-
110
- // Load Monaco on mount
111
- React.useEffect(() => {
112
- if (!MonacoEditor) {
113
- loadMonaco().then(() => setLoaded(true));
114
- }
115
- }, []);
116
-
117
- const activeContent = activeFile ? files[activeFile] || "" : "";
118
- const activeOriginalContent = activeFile
119
- ? (originalFiles[activeFile] ?? "")
120
- : "";
121
- const language = activeFile ? getLanguage(activeFile) : "plaintext";
122
-
123
- /**
124
- * Whether the active file has a diff available.
125
- * A diff is available when:
126
- * - The file is marked as "modified" (content differs from original), OR
127
- * - The file is marked as "new" (no original — diff shows empty left side)
128
- */
129
- const hasDiff = activeFile
130
- ? fileChanges[activeFile] === "modified" ||
131
- fileChanges[activeFile] === "new"
132
- : false;
133
-
134
- // Auto-disable diff mode when switching to a file with no diff
135
- React.useEffect(() => {
136
- if (!hasDiff && diffMode) {
137
- setDiffMode(false);
138
- }
139
- }, [hasDiff, diffMode, activeFile]);
140
-
141
- const handleEditorChange = useCallback(
142
- (value: string | undefined) => {
143
- if (activeFile && value !== undefined) {
144
- onFileChange(activeFile, value);
145
- }
146
- },
147
- [activeFile, onFileChange],
148
- );
149
-
150
- /**
151
- * Handler for Monaco DiffEditor's modified content changes.
152
- * The DiffEditor fires `onMount` with the editor instance — we
153
- * listen to the modified editor's onDidChangeModelContent event.
154
- */
155
- const handleDiffEditorMount = useCallback(
156
- (editor: any) => {
157
- if (!editor || !activeFile) return;
158
- const modifiedEditor = editor.getModifiedEditor();
159
- modifiedEditor.onDidChangeModelContent(() => {
160
- const value = modifiedEditor.getValue();
161
- onFileChange(activeFile, value);
162
- });
163
- },
164
- [activeFile, onFileChange],
165
- );
166
-
167
- return (
168
- <div className="flex flex-col h-full bg-sb-editor">
169
- {/* Tab bar */}
170
- <div className="flex items-center bg-sb-bg-alt border-b border-sb-border overflow-x-auto">
171
- {openFiles.map((path) => {
172
- const fileName = path.split("/").pop() || path;
173
- const isActive = path === activeFile;
174
- const changeStatus: FileChangeStatus =
175
- fileChanges[path] || "unchanged";
176
-
177
- return (
178
- <div
179
- key={path}
180
- className={`group flex items-center gap-2 px-3 py-1.5 text-xs cursor-pointer border-r border-sb-border
181
- ${
182
- isActive
183
- ? "bg-sb-editor text-sb-text-active border-t-2 border-t-sb-accent"
184
- : "bg-sb-bg-alt text-sb-text-muted hover:bg-sb-bg-hover border-t-2 border-t-transparent"
185
- }`}
186
- onClick={() => onSelectFile(path)}
187
- >
188
- {/* Change status indicator dot */}
189
- {changeStatus !== "unchanged" && (
190
- <span
191
- className="w-2 h-2 rounded-full shrink-0"
192
- style={{ backgroundColor: STATUS_COLORS[changeStatus] }}
193
- title={STATUS_LABELS[changeStatus]}
194
- />
195
- )}
196
- <span className="truncate max-w-[120px]">{fileName}</span>
197
- <button
198
- className="opacity-0 group-hover:opacity-100 hover:text-sb-text-active ml-1 text-[10px]"
199
- onClick={(e) => {
200
- e.stopPropagation();
201
- onCloseFile(path);
202
- }}
203
- title="Close"
204
- >
205
-
206
- </button>
207
- </div>
208
- );
209
- })}
210
-
211
- {/* Spacer */}
212
- <div className="flex-1" />
213
-
214
- {/* Diff toggle button — only shown when a diff is available */}
215
- {hasDiff && (
216
- <button
217
- className={`shrink-0 px-2 py-1 mr-1 text-[10px] font-medium rounded transition-colors ${
218
- diffMode
219
- ? "bg-sb-accent text-white"
220
- : "text-sb-text-muted hover:text-sb-text-active hover:bg-sb-bg-hover"
221
- }`}
222
- onClick={() => setDiffMode((prev) => !prev)}
223
- title={diffMode ? "Switch to editor" : "Show diff view"}
224
- >
225
- {diffMode ? "Editor" : "Diff"}
226
- </button>
227
- )}
228
- </div>
229
-
230
- {/* Editor / DiffEditor / placeholder */}
231
- <div className="flex-1 overflow-hidden">
232
- {!activeFile && (
233
- <div className="flex items-center justify-center h-full text-sb-text-muted text-sm">
234
- Select a file to edit
235
- </div>
236
- )}
237
-
238
- {/* Diff mode: Monaco DiffEditor (side-by-side) */}
239
- {activeFile && loaded && diffMode && MonacoDiffEditor && (
240
- <MonacoDiffEditor
241
- height="100%"
242
- language={language}
243
- original={activeOriginalContent}
244
- modified={activeContent}
245
- theme="vs-dark"
246
- onMount={handleDiffEditorMount}
247
- options={{
248
- readOnly,
249
- minimap: { enabled: false },
250
- fontSize: 13,
251
- wordWrap: "on",
252
- scrollBeyondLastLine: false,
253
- padding: { top: 8 },
254
- renderWhitespace: "selection",
255
- automaticLayout: true,
256
- tabSize: 2,
257
- // DiffEditor-specific options
258
- renderSideBySide: true,
259
- enableSplitViewResizing: true,
260
- renderIndicators: true,
261
- renderMarginRevertIcon: false,
262
- originalEditable: false,
263
- }}
264
- />
265
- )}
266
-
267
- {/* Regular mode: Monaco Editor */}
268
- {activeFile && loaded && !diffMode && MonacoEditor && (
269
- <MonacoEditor
270
- height="100%"
271
- language={language}
272
- value={activeContent}
273
- onChange={handleEditorChange}
274
- theme="vs-dark"
275
- options={{
276
- readOnly,
277
- minimap: { enabled: false },
278
- fontSize: 13,
279
- lineNumbers: "on",
280
- wordWrap: "on",
281
- scrollBeyondLastLine: false,
282
- padding: { top: 8 },
283
- renderWhitespace: "selection",
284
- automaticLayout: true,
285
- tabSize: 2,
286
- }}
287
- />
288
- )}
289
-
290
- {activeFile && !loaded && (
291
- <div className="flex items-center justify-center h-full text-sb-text-muted text-sm">
292
- Loading editor...
293
- </div>
294
- )}
295
- </div>
296
- </div>
297
- );
298
- }