@illuma-ai/code-sandbox 1.2.2 → 1.3.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.
@@ -1,252 +1,111 @@
1
1
  /**
2
- * Terminal — Git-bash inspired terminal output panel.
3
- *
4
- * Renders process stdout/stderr with color-coded output:
5
- * - Commands ($ ...) get a green prompt + white command
6
- * - stderr lines get red text
7
- * - Success markers (checkmarks) get green
8
- * - Warnings get yellow
9
- * - Package names get purple
10
- * - URLs and paths get cyan
11
- * - Exit codes get colored by status
2
+ * Terminal — Clean monochrome terminal output panel with minimize toggle.
12
3
  *
13
4
  * Uses a simple div-based approach (not xterm.js) to keep the bundle lean.
14
5
  * Auto-scrolls to bottom as new output arrives.
6
+ * Minimize/expand state is controlled by the parent via props.
15
7
  */
16
8
 
17
9
  import React, { useEffect, useRef } from "react";
18
10
  import type { TerminalProps } from "../types";
19
11
 
20
- /** Classify a terminal output line for color coding */
21
- type LineType =
22
- | "prompt" // $ command
23
- | "stderr" // [stderr] ...
24
- | "success" // ✓ ...
25
- | "error" // ✗ ... or error messages
26
- | "warning" // warn / Warning
27
- | "info" // Installing, Dependencies, info lines
28
- | "exit-ok" // Process exited with code 0
29
- | "exit-fail" // Process exited with non-zero code
30
- | "dim" // Empty or separator lines
31
- | "normal"; // Default output
32
-
33
12
  /**
34
- * Classify a line of terminal output for styling.
35
- *
36
- * Pattern matching order matters — more specific patterns first.
37
- */
38
- function classifyLine(line: string): LineType {
39
- const trimmed = line.trim();
40
-
41
- // Empty or whitespace-only
42
- if (!trimmed) return "dim";
43
-
44
- // Explicit stderr prefix from runtime
45
- if (trimmed.startsWith("[stderr]")) return "stderr";
46
-
47
- // Command prompt ($ node server.js)
48
- if (/^\$\s/.test(trimmed)) return "prompt";
49
-
50
- // Success indicators
51
- if (/^\s*✓/.test(line) || /^\s*✔/.test(line) || /^done$/i.test(trimmed))
52
- return "success";
53
-
54
- // Error indicators
55
- if (
56
- /^\s*✗/.test(line) ||
57
- /^\s*✘/.test(line) ||
58
- /^error\b/i.test(trimmed) ||
59
- /^Error:/i.test(trimmed) ||
60
- /^TypeError:/i.test(trimmed) ||
61
- /^ReferenceError:/i.test(trimmed) ||
62
- /^SyntaxError:/i.test(trimmed) ||
63
- /ENOENT|EACCES|ECONNREFUSED/i.test(trimmed)
64
- )
65
- return "error";
66
-
67
- // Process exit
68
- if (/^Process exited with code\s+(\d+)/.test(trimmed)) {
69
- const code = trimmed.match(/code\s+(\d+)/)?.[1];
70
- return code === "0" ? "exit-ok" : "exit-fail";
71
- }
72
-
73
- // Warning
74
- if (
75
- /^warn\b/i.test(trimmed) ||
76
- /^warning\b/i.test(trimmed) ||
77
- /^Warning:/i.test(trimmed) ||
78
- /deprecated/i.test(trimmed)
79
- )
80
- return "warning";
81
-
82
- // Info / install progress
83
- if (
84
- /^Installing\b/i.test(trimmed) ||
85
- /^Dependencies\s+installed/i.test(trimmed) ||
86
- /^Listening\b/i.test(trimmed) ||
87
- /^Server\s+(running|started|listening)/i.test(trimmed) ||
88
- /^Starting\b/i.test(trimmed)
89
- )
90
- return "info";
91
-
92
- return "normal";
93
- }
94
-
95
- /** CSS class name for each line type */
96
- const LINE_CLASS: Record<LineType, string> = {
97
- prompt: "", // handled specially (split into prompt + command)
98
- stderr: "sb-term-stderr",
99
- success: "sb-term-success",
100
- error: "sb-term-stderr",
101
- warning: "sb-term-warning",
102
- info: "sb-term-info",
103
- "exit-ok": "sb-term-exit-ok",
104
- "exit-fail": "sb-term-exit-fail",
105
- dim: "sb-term-dim",
106
- normal: "sb-term-line",
107
- };
108
-
109
- /**
110
- * Render a single terminal line with appropriate styling.
111
- *
112
- * Prompt lines are split into a green "$ " prefix and white command text.
113
- * Stderr lines strip the "[stderr]" prefix and show in red.
114
- * Package install lines highlight the package name in purple.
115
- */
116
- function TerminalLine({ line }: { line: string }) {
117
- const type = classifyLine(line);
118
-
119
- // Prompt line: split into green prompt + white command
120
- if (type === "prompt") {
121
- const commandText = line.replace(/^\$\s*/, "");
122
- return (
123
- <div className="whitespace-pre-wrap">
124
- <span className="sb-term-prompt">{"$ "}</span>
125
- <span className="sb-term-cmd">{commandText}</span>
126
- </div>
127
- );
128
- }
129
-
130
- // Stderr: strip prefix, show in red
131
- if (type === "stderr") {
132
- const message = line.replace(/^\[stderr\]\s*/, "");
133
- return (
134
- <div className="whitespace-pre-wrap sb-term-stderr">
135
- <span className="sb-term-dim select-none">{"stderr "}</span>
136
- {message}
137
- </div>
138
- );
139
- }
140
-
141
- // Package install success: " ✓ express@4.18.0" → green check + purple pkg
142
- if (type === "success" && /✓\s+\S+@/.test(line)) {
143
- const match = line.match(/(.*✓\s*)(\S+@\S+)(.*)/);
144
- if (match) {
145
- return (
146
- <div className="whitespace-pre-wrap sb-term-success">
147
- {match[1]}
148
- <span className="sb-term-pkg">{match[2]}</span>
149
- {match[3]}
150
- </div>
151
- );
152
- }
153
- }
154
-
155
- // Info lines with port numbers: highlight the port
156
- if (type === "info" && /:\d{4}/.test(line)) {
157
- const parts = line.split(/(:\d{4,5})/);
158
- return (
159
- <div className="whitespace-pre-wrap sb-term-info">
160
- {parts.map((part, i) =>
161
- /^:\d{4,5}$/.test(part) ? (
162
- <span key={i} className="sb-term-path">
163
- {part}
164
- </span>
165
- ) : (
166
- <span key={i}>{part}</span>
167
- ),
168
- )}
169
- </div>
170
- );
171
- }
172
-
173
- return (
174
- <div className={`whitespace-pre-wrap ${LINE_CLASS[type]}`}>{line}</div>
175
- );
176
- }
177
-
178
- /**
179
- * Terminal component — renders process output in a git-bash inspired panel.
13
+ * Terminal component renders process output in a clean monochrome panel.
180
14
  *
181
15
  * Features:
182
- * - Color-coded output (commands, errors, warnings, info, packages)
183
- * - Monospace font stack (Cascadia Code → JetBrains Mono → Fira Code → fallback)
16
+ * - Monospace font with proper line spacing
184
17
  * - Auto-scroll to bottom on new output
18
+ * - Minimize/expand toggle via chevron in header (parent-controlled)
185
19
  * - Modern thin scrollbar matching the sandbox theme
186
- * - Dark background separate from editor for visual distinction
187
20
  */
188
- export function Terminal({ output, className = "" }: TerminalProps) {
21
+ export function Terminal({
22
+ output,
23
+ className = "",
24
+ minimized = false,
25
+ onToggleMinimize,
26
+ }: TerminalProps) {
189
27
  const containerRef = useRef<HTMLDivElement>(null);
190
28
 
191
29
  // Auto-scroll to bottom when new output arrives
192
30
  useEffect(() => {
193
31
  const el = containerRef.current;
194
- if (el) {
32
+ if (el && !minimized) {
195
33
  el.scrollTop = el.scrollHeight;
196
34
  }
197
- }, [output.length]);
35
+ }, [output.length, minimized]);
198
36
 
199
37
  return (
200
38
  <div
201
- className={`flex flex-col h-full ${className}`}
39
+ className={`flex flex-col ${minimized ? "" : "h-full"} ${className}`}
202
40
  style={{ background: "var(--sb-terminal)" }}
203
41
  >
204
- {/* Header — styled like a real terminal tab bar */}
42
+ {/* Header */}
205
43
  <div
206
- className="flex items-center gap-2 px-3 py-1.5 border-b border-sb-border shrink-0"
44
+ className="flex items-center gap-2 px-3 py-1 border-t border-sb-border shrink-0 select-none cursor-pointer"
207
45
  style={{ background: "var(--sb-terminal-header)" }}
46
+ onClick={onToggleMinimize}
208
47
  >
209
- {/* Traffic light dots (decorative) */}
210
- <div className="flex items-center gap-1.5 mr-1">
211
- <span
212
- className="w-2.5 h-2.5 rounded-full"
213
- style={{ background: "#f85149" }}
214
- />
215
- <span
216
- className="w-2.5 h-2.5 rounded-full"
217
- style={{ background: "#d29922" }}
218
- />
219
- <span
220
- className="w-2.5 h-2.5 rounded-full"
221
- style={{ background: "#3fb950" }}
48
+ {/* Chevron toggle */}
49
+ <svg
50
+ width="14"
51
+ height="14"
52
+ viewBox="0 0 16 16"
53
+ fill="none"
54
+ className="shrink-0 transition-transform duration-150"
55
+ style={{
56
+ transform: minimized ? "rotate(-90deg)" : "rotate(0deg)",
57
+ color: "var(--sb-text-muted)",
58
+ }}
59
+ >
60
+ <path
61
+ d="M4 6l4 4 4-4"
62
+ stroke="currentColor"
63
+ strokeWidth="1.5"
64
+ strokeLinecap="round"
65
+ strokeLinejoin="round"
222
66
  />
223
- </div>
67
+ </svg>
224
68
  <span
225
69
  className="text-[11px] font-medium tracking-wider"
226
70
  style={{ color: "var(--sb-text-muted)" }}
227
71
  >
228
72
  TERMINAL
229
73
  </span>
230
- {/* Shell indicator */}
231
- <span className="text-[10px] ml-auto" style={{ color: "#484f58" }}>
232
- bash
233
- </span>
234
- </div>
235
-
236
- {/* Output area */}
237
- <div
238
- ref={containerRef}
239
- className="flex-1 overflow-auto px-3.5 py-2.5 sb-terminal-output overscroll-contain"
240
- >
241
- {output.length === 0 ? (
242
- <div className="flex items-center gap-2 sb-term-dim">
243
- <span className="sb-term-prompt">$</span>
244
- <span className="animate-pulse">_</span>
245
- </div>
246
- ) : (
247
- output.map((line, i) => <TerminalLine key={i} line={line} />)
74
+ {/* Line count badge */}
75
+ {output.length > 0 && (
76
+ <span
77
+ className="text-[10px] ml-auto tabular-nums"
78
+ style={{ color: "var(--sb-text-muted)", opacity: 0.6 }}
79
+ >
80
+ {output.length} lines
81
+ </span>
248
82
  )}
249
83
  </div>
84
+
85
+ {/* Output area — hidden when minimized */}
86
+ {!minimized && (
87
+ <div
88
+ ref={containerRef}
89
+ className="flex-1 overflow-auto px-3.5 py-2 sb-terminal-output overscroll-contain min-h-0"
90
+ >
91
+ {output.length === 0 ? (
92
+ <div style={{ color: "var(--sb-text-muted)" }}>
93
+ <span>$ </span>
94
+ <span className="animate-pulse">_</span>
95
+ </div>
96
+ ) : (
97
+ output.map((line, i) => (
98
+ <div
99
+ key={i}
100
+ className="whitespace-pre-wrap"
101
+ style={{ color: "var(--sb-text)" }}
102
+ >
103
+ {line}
104
+ </div>
105
+ ))
106
+ )}
107
+ </div>
108
+ )}
250
109
  </div>
251
110
  );
252
111
  }
@@ -303,6 +303,7 @@ interface CodeViewProps {
303
303
  /**
304
304
  * CodeView — FileTree (left) + Editor (top-right) + Terminal (bottom-right).
305
305
  * Uses Allotment for resizable split panes.
306
+ * Terminal can be minimized to just a header bar.
306
307
  * Responsive: file tree shrinks on narrow containers.
307
308
  */
308
309
  function CodeView({
@@ -317,6 +318,8 @@ function CodeView({
317
318
  onCloseFile,
318
319
  onFileChange,
319
320
  }: CodeViewProps) {
321
+ const [terminalMinimized, setTerminalMinimized] = useState(false);
322
+
320
323
  return (
321
324
  <div className="h-full w-full">
322
325
  <Allotment>
@@ -332,24 +335,45 @@ function CodeView({
332
335
 
333
336
  {/* Editor + Terminal — right */}
334
337
  <Allotment.Pane>
335
- <Allotment vertical>
336
- <Allotment.Pane preferredSize="70%">
337
- <CodeEditor
338
- files={files}
339
- originalFiles={originalFiles}
340
- fileChanges={fileChanges}
341
- activeFile={selectedFile}
342
- openFiles={openFiles}
343
- onSelectFile={onSelectFile}
344
- onCloseFile={onCloseFile}
345
- onFileChange={onFileChange}
346
- />
347
- </Allotment.Pane>
338
+ <div className="h-full flex flex-col">
339
+ {/* Editor takes remaining space */}
340
+ <div
341
+ className={
342
+ terminalMinimized ? "flex-1 min-h-0" : "flex-1 min-h-0"
343
+ }
344
+ style={{ height: terminalMinimized ? "100%" : "70%" }}
345
+ >
346
+ <div className="h-full">
347
+ <CodeEditor
348
+ files={files}
349
+ originalFiles={originalFiles}
350
+ fileChanges={fileChanges}
351
+ activeFile={selectedFile}
352
+ openFiles={openFiles}
353
+ onSelectFile={onSelectFile}
354
+ onCloseFile={onCloseFile}
355
+ onFileChange={onFileChange}
356
+ />
357
+ </div>
358
+ </div>
348
359
 
349
- <Allotment.Pane preferredSize="30%" minSize={60}>
350
- <Terminal output={terminalOutput} />
351
- </Allotment.Pane>
352
- </Allotment>
360
+ {/* Terminal — collapsible */}
361
+ {terminalMinimized ? (
362
+ <Terminal
363
+ output={terminalOutput}
364
+ minimized={true}
365
+ onToggleMinimize={() => setTerminalMinimized(false)}
366
+ />
367
+ ) : (
368
+ <div style={{ height: "30%", minHeight: 60 }}>
369
+ <Terminal
370
+ output={terminalOutput}
371
+ minimized={false}
372
+ onToggleMinimize={() => setTerminalMinimized(true)}
373
+ />
374
+ </div>
375
+ )}
376
+ </div>
353
377
  </Allotment.Pane>
354
378
  </Allotment>
355
379
  </div>
package/src/styles.css CHANGED
@@ -67,7 +67,7 @@
67
67
  }
68
68
 
69
69
  /* ---------------------------------------------------------------------------
70
- * Terminal theme git-bash inspired colors and styling
70
+ * Terminal — clean monochrome styling
71
71
  * --------------------------------------------------------------------------- */
72
72
 
73
73
  .sb-terminal-output {
@@ -81,47 +81,6 @@
81
81
  -moz-osx-font-smoothing: grayscale;
82
82
  }
83
83
 
84
- /* Terminal line colors via data attribute */
85
- .sb-term-line {
86
- color: #c9d1d9;
87
- }
88
- .sb-term-prompt {
89
- color: var(--sb-success);
90
- font-weight: 600;
91
- }
92
- .sb-term-cmd {
93
- color: #f0f6fc;
94
- font-weight: 500;
95
- }
96
- .sb-term-stderr {
97
- color: var(--sb-error);
98
- }
99
- .sb-term-success {
100
- color: var(--sb-success);
101
- }
102
- .sb-term-warning {
103
- color: var(--sb-warning);
104
- }
105
- .sb-term-info {
106
- color: var(--sb-info);
107
- }
108
- .sb-term-dim {
109
- color: #484f58;
110
- }
111
- .sb-term-path {
112
- color: var(--sb-cyan);
113
- }
114
- .sb-term-pkg {
115
- color: var(--sb-magenta);
116
- }
117
- .sb-term-exit-ok {
118
- color: var(--sb-success);
119
- }
120
- .sb-term-exit-fail {
121
- color: var(--sb-error);
122
- font-weight: 600;
123
- }
124
-
125
84
  /*
126
85
  * Ranger Theme Variables — bridged from ranger/client/src/style.css.
127
86
  *
package/src/types.ts CHANGED
@@ -343,6 +343,10 @@ export interface CodeEditorProps {
343
343
  export interface TerminalProps {
344
344
  output: string[];
345
345
  className?: string;
346
+ /** Whether the terminal is collapsed to just its header bar */
347
+ minimized?: boolean;
348
+ /** Called when the user clicks the minimize/expand toggle */
349
+ onToggleMinimize?: () => void;
346
350
  }
347
351
 
348
352
  export interface PreviewProps {