@startsimpli/ui 0.4.42 → 0.4.44
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/package.json +8 -4
- package/src/catalog/catalog.ts +29 -0
- package/src/components/__tests__/conversation-timeline.test.tsx +56 -0
- package/src/components/__tests__/diff-viewer.test.tsx +101 -0
- package/src/components/__tests__/session-components.test.tsx +184 -0
- package/src/components/diff/DiffViewer.tsx +235 -0
- package/src/components/diff/index.ts +2 -0
- package/src/components/index.ts +15 -0
- package/src/components/kanban/KanbanBoard.tsx +168 -59
- package/src/components/kanban/__tests__/kanban.test.tsx +64 -1
- package/src/components/kanban/index.ts +2 -2
- package/src/components/record-detail/RecordDetail.tsx +92 -0
- package/src/components/record-detail/__tests__/record-detail.test.tsx +42 -0
- package/src/components/record-detail/index.ts +2 -0
- package/src/components/session/AgentSessionConsole.tsx +200 -0
- package/src/components/session/ConversationTimeline.tsx +131 -0
- package/src/components/session/SessionCard.tsx +194 -0
- package/src/components/session/SessionList.tsx +271 -0
- package/src/components/session/SessionStatusBadge.tsx +55 -0
- package/src/components/session/index.ts +23 -0
- package/src/components/session/status.ts +80 -0
- package/src/components/session/types.ts +29 -0
- package/src/components/terminal/Terminal.tsx +369 -0
- package/src/components/terminal/index.ts +4 -0
- package/src/components/terminal/themes.ts +91 -0
- package/src/components/terminal/xterm.css +8 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import type { Terminal as XTerm } from 'xterm'
|
|
5
|
+
import type { FitAddon } from '@xterm/addon-fit'
|
|
6
|
+
import { AlertCircle, RefreshCw, TerminalSquare, Wifi, WifiOff } from 'lucide-react'
|
|
7
|
+
import { cn } from '../../lib/utils'
|
|
8
|
+
import { TERMINAL_THEMES, type TerminalStatus, type TerminalTheme } from './themes'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Imperative handle the PARENT uses to drive the terminal. The parent owns the
|
|
12
|
+
* transport (a websocket, an SSE stream, …) and pumps inbound bytes in via
|
|
13
|
+
* `write()` — the terminal never re-renders per byte.
|
|
14
|
+
*/
|
|
15
|
+
export interface TerminalHandle {
|
|
16
|
+
/** Write raw output (including ANSI escapes) to the terminal. */
|
|
17
|
+
write(data: string): void
|
|
18
|
+
/** Clear the screen + scrollback. */
|
|
19
|
+
clear(): void
|
|
20
|
+
/** Re-fit the terminal to its container. */
|
|
21
|
+
fit(): void
|
|
22
|
+
/** Move keyboard focus into the terminal. */
|
|
23
|
+
focus(): void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TerminalProps {
|
|
27
|
+
/** xterm colour theme. Defaults to `TERMINAL_THEMES.dark`. */
|
|
28
|
+
theme?: TerminalTheme
|
|
29
|
+
fontFamily?: string
|
|
30
|
+
fontSize?: number
|
|
31
|
+
scrollback?: number
|
|
32
|
+
/** Connection status shown in the chrome's indicator (purely cosmetic). */
|
|
33
|
+
status?: TerminalStatus
|
|
34
|
+
/** Fired with keystrokes/data typed into the terminal. Wire to your transport. */
|
|
35
|
+
onData?: (data: string) => void
|
|
36
|
+
/** Fired when the terminal is resized (after a fit). Report to the PTY. */
|
|
37
|
+
onResize?: (cols: number, rows: number) => void
|
|
38
|
+
/** Fired once the terminal is initialised, handing back the imperative handle. */
|
|
39
|
+
onReady?: (handle: TerminalHandle) => void
|
|
40
|
+
/** Fired when the reconnect button is clicked (only shown when provided). */
|
|
41
|
+
onReconnect?: () => void
|
|
42
|
+
/** Show the header chrome (title, status indicator, reconnect). Default true. */
|
|
43
|
+
showChrome?: boolean
|
|
44
|
+
className?: string
|
|
45
|
+
/** Optional text written to the terminal once, right after init. */
|
|
46
|
+
banner?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type Loggable = unknown
|
|
50
|
+
|
|
51
|
+
function StatusIndicator({ status }: { status: TerminalStatus }) {
|
|
52
|
+
switch (status) {
|
|
53
|
+
case 'connected':
|
|
54
|
+
return (
|
|
55
|
+
<span className="flex items-center gap-1.5 text-emerald-500" role="status">
|
|
56
|
+
<Wifi className="h-4 w-4" aria-hidden="true" />
|
|
57
|
+
<span className="text-xs">Connected</span>
|
|
58
|
+
</span>
|
|
59
|
+
)
|
|
60
|
+
case 'connecting':
|
|
61
|
+
return (
|
|
62
|
+
<span className="flex items-center gap-1.5 text-muted-foreground" role="status">
|
|
63
|
+
<RefreshCw className="h-4 w-4 animate-spin" aria-hidden="true" />
|
|
64
|
+
<span className="text-xs">Connecting…</span>
|
|
65
|
+
</span>
|
|
66
|
+
)
|
|
67
|
+
case 'error':
|
|
68
|
+
return (
|
|
69
|
+
<span className="flex items-center gap-1.5 text-red-500" role="status">
|
|
70
|
+
<AlertCircle className="h-4 w-4" aria-hidden="true" />
|
|
71
|
+
<span className="text-xs">Error</span>
|
|
72
|
+
</span>
|
|
73
|
+
)
|
|
74
|
+
case 'disconnected':
|
|
75
|
+
default:
|
|
76
|
+
return (
|
|
77
|
+
<span className="flex items-center gap-1.5 text-muted-foreground" role="status">
|
|
78
|
+
<WifiOff className="h-4 w-4" aria-hidden="true" />
|
|
79
|
+
<span className="text-xs">Disconnected</span>
|
|
80
|
+
</span>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A presentational xterm.js terminal. Generic — it knows nothing about the
|
|
87
|
+
* transport, the session model, or any app. The parent feeds output through the
|
|
88
|
+
* `TerminalHandle` (via `onReady`) and receives keystrokes/resizes through
|
|
89
|
+
* `onData`/`onResize`.
|
|
90
|
+
*
|
|
91
|
+
* The defensive init sequence (DOM-ready wait, render-service wait, safe fit, and
|
|
92
|
+
* a React-StrictMode double-init guard) is ported verbatim from dev-hq's
|
|
93
|
+
* TerminalInterface — it exists for real timing bugs, not decoration. xterm is
|
|
94
|
+
* loaded via dynamic import so this module is SSR- and test-safe (importing it
|
|
95
|
+
* never touches `xterm` at module-eval time).
|
|
96
|
+
*/
|
|
97
|
+
export function Terminal({
|
|
98
|
+
theme = TERMINAL_THEMES.dark,
|
|
99
|
+
fontFamily = 'Menlo, Monaco, "Courier New", monospace',
|
|
100
|
+
fontSize = 14,
|
|
101
|
+
scrollback = 10000,
|
|
102
|
+
status = 'disconnected',
|
|
103
|
+
onData,
|
|
104
|
+
onResize,
|
|
105
|
+
onReady,
|
|
106
|
+
onReconnect,
|
|
107
|
+
showChrome = true,
|
|
108
|
+
className,
|
|
109
|
+
banner,
|
|
110
|
+
}: TerminalProps) {
|
|
111
|
+
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
112
|
+
const xtermRef = React.useRef<XTerm | null>(null)
|
|
113
|
+
const fitAddonRef = React.useRef<FitAddon | null>(null)
|
|
114
|
+
const isInitializedRef = React.useRef(false) // guard against StrictMode double-init
|
|
115
|
+
const isMountedRef = React.useRef(true)
|
|
116
|
+
const resizeTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
117
|
+
|
|
118
|
+
// Keep callbacks in refs so the (sessionUuid-less) mount effect stays stable and
|
|
119
|
+
// xterm is never re-created just because a parent re-renders with a new closure.
|
|
120
|
+
const onDataRef = React.useRef(onData)
|
|
121
|
+
const onResizeRef = React.useRef(onResize)
|
|
122
|
+
const onReadyRef = React.useRef(onReady)
|
|
123
|
+
onDataRef.current = onData
|
|
124
|
+
onResizeRef.current = onResize
|
|
125
|
+
onReadyRef.current = onReady
|
|
126
|
+
|
|
127
|
+
const [errorMessage, setErrorMessage] = React.useState<string | null>(null)
|
|
128
|
+
|
|
129
|
+
// ---- defensive init helpers (ported from dev-hq, logging stripped) ----------
|
|
130
|
+
|
|
131
|
+
const waitForDOMReady = React.useCallback((): Promise<void> => {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
const container = containerRef.current
|
|
134
|
+
if (!container) {
|
|
135
|
+
reject(new Error('Terminal container ref not available'))
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
if (container.offsetWidth > 0 && container.offsetHeight > 0) {
|
|
139
|
+
resolve()
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
const ro = new ResizeObserver((entries) => {
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
|
|
145
|
+
ro.disconnect()
|
|
146
|
+
resolve()
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
ro.observe(container)
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
ro.disconnect()
|
|
154
|
+
reject(new Error('Container dimensions timeout'))
|
|
155
|
+
}, 5000)
|
|
156
|
+
})
|
|
157
|
+
}, [])
|
|
158
|
+
|
|
159
|
+
// Poll until xterm's render service exists and the terminal is attached. Never
|
|
160
|
+
// throws on timeout — the terminal is usable and fit() retries on resize.
|
|
161
|
+
const waitForRenderService = React.useCallback(async (terminal: XTerm): Promise<void> => {
|
|
162
|
+
const maxAttempts = 10
|
|
163
|
+
const delay = 50
|
|
164
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
165
|
+
if (!isMountedRef.current) throw new Error('Component unmounted during initialization')
|
|
166
|
+
try {
|
|
167
|
+
const term = terminal as unknown as { _core?: { _renderService?: Loggable; element?: Loggable } }
|
|
168
|
+
if (term._core?._renderService && term._core?.element) return
|
|
169
|
+
} catch {
|
|
170
|
+
/* render service not ready — keep polling */
|
|
171
|
+
}
|
|
172
|
+
await new Promise((r) => setTimeout(r, delay))
|
|
173
|
+
}
|
|
174
|
+
}, [])
|
|
175
|
+
|
|
176
|
+
const safeFit = React.useCallback((fitAddon: FitAddon): void => {
|
|
177
|
+
if (!isMountedRef.current || !containerRef.current) return
|
|
178
|
+
try {
|
|
179
|
+
fitAddon.fit()
|
|
180
|
+
} catch {
|
|
181
|
+
/* fit failures are timing-related; retries happen on resize */
|
|
182
|
+
}
|
|
183
|
+
}, [])
|
|
184
|
+
|
|
185
|
+
const handleResize = React.useCallback(() => {
|
|
186
|
+
if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current)
|
|
187
|
+
resizeTimeoutRef.current = setTimeout(() => {
|
|
188
|
+
if (fitAddonRef.current && xtermRef.current && containerRef.current && isMountedRef.current) {
|
|
189
|
+
try {
|
|
190
|
+
fitAddonRef.current.fit()
|
|
191
|
+
} catch {
|
|
192
|
+
/* ignore — retries on next resize */
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}, 150)
|
|
196
|
+
}, [])
|
|
197
|
+
|
|
198
|
+
// Mount / unmount: create + dispose xterm exactly once per mount.
|
|
199
|
+
React.useEffect(() => {
|
|
200
|
+
isMountedRef.current = true
|
|
201
|
+
let disposed = false
|
|
202
|
+
let cleanupCalled = false
|
|
203
|
+
|
|
204
|
+
const init = async () => {
|
|
205
|
+
if (!containerRef.current || xtermRef.current || isInitializedRef.current) return
|
|
206
|
+
isInitializedRef.current = true
|
|
207
|
+
try {
|
|
208
|
+
// Dynamic import keeps xterm out of the SSR/test module graph.
|
|
209
|
+
const [{ Terminal: XTermCtor }, { FitAddon: FitAddonCtor }] = await Promise.all([
|
|
210
|
+
import('xterm'),
|
|
211
|
+
import('@xterm/addon-fit'),
|
|
212
|
+
])
|
|
213
|
+
if (!isMountedRef.current || disposed) return
|
|
214
|
+
|
|
215
|
+
await waitForDOMReady()
|
|
216
|
+
if (!isMountedRef.current || disposed) return
|
|
217
|
+
|
|
218
|
+
const terminal = new XTermCtor({
|
|
219
|
+
cursorBlink: true,
|
|
220
|
+
fontSize,
|
|
221
|
+
fontFamily,
|
|
222
|
+
fontWeight: '400',
|
|
223
|
+
fontWeightBold: '700',
|
|
224
|
+
lineHeight: 1.2,
|
|
225
|
+
letterSpacing: 0,
|
|
226
|
+
theme,
|
|
227
|
+
scrollback,
|
|
228
|
+
allowProposedApi: true,
|
|
229
|
+
allowTransparency: true,
|
|
230
|
+
windowsMode: false,
|
|
231
|
+
})
|
|
232
|
+
const fitAddon = new FitAddonCtor()
|
|
233
|
+
terminal.loadAddon(fitAddon)
|
|
234
|
+
terminal.open(containerRef.current)
|
|
235
|
+
|
|
236
|
+
xtermRef.current = terminal
|
|
237
|
+
fitAddonRef.current = fitAddon
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
await waitForRenderService(terminal)
|
|
241
|
+
} catch {
|
|
242
|
+
/* continue — terminal still usable */
|
|
243
|
+
}
|
|
244
|
+
if (!isMountedRef.current || disposed) {
|
|
245
|
+
terminal.dispose()
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
safeFit(fitAddon)
|
|
250
|
+
|
|
251
|
+
terminal.onData((data: string) => onDataRef.current?.(data))
|
|
252
|
+
terminal.onResize(({ cols, rows }: { cols: number; rows: number }) =>
|
|
253
|
+
onResizeRef.current?.(cols, rows),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if (banner) terminal.writeln(banner)
|
|
257
|
+
|
|
258
|
+
const handle: TerminalHandle = {
|
|
259
|
+
write: (d) => terminal.write(d),
|
|
260
|
+
clear: () => terminal.clear(),
|
|
261
|
+
fit: () => safeFit(fitAddon),
|
|
262
|
+
focus: () => terminal.focus(),
|
|
263
|
+
}
|
|
264
|
+
onReadyRef.current?.(handle)
|
|
265
|
+
} catch (error) {
|
|
266
|
+
const err = error instanceof Error ? error : new Error('Failed to initialize terminal')
|
|
267
|
+
setErrorMessage(err.message)
|
|
268
|
+
isInitializedRef.current = false // allow retry
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Small delay so refs are set and the container is laid out.
|
|
273
|
+
const initTimer = setTimeout(init, 100)
|
|
274
|
+
|
|
275
|
+
return () => {
|
|
276
|
+
clearTimeout(initTimer)
|
|
277
|
+
if (cleanupCalled) return // StrictMode runs cleanup twice in dev
|
|
278
|
+
cleanupCalled = true
|
|
279
|
+
disposed = true
|
|
280
|
+
isMountedRef.current = false
|
|
281
|
+
if (resizeTimeoutRef.current) {
|
|
282
|
+
clearTimeout(resizeTimeoutRef.current)
|
|
283
|
+
resizeTimeoutRef.current = null
|
|
284
|
+
}
|
|
285
|
+
if (xtermRef.current) {
|
|
286
|
+
xtermRef.current.dispose()
|
|
287
|
+
xtermRef.current = null
|
|
288
|
+
}
|
|
289
|
+
fitAddonRef.current = null
|
|
290
|
+
isInitializedRef.current = false
|
|
291
|
+
}
|
|
292
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
293
|
+
}, [])
|
|
294
|
+
|
|
295
|
+
// Live-apply theme changes without re-creating the terminal.
|
|
296
|
+
React.useEffect(() => {
|
|
297
|
+
if (xtermRef.current) xtermRef.current.options.theme = theme
|
|
298
|
+
}, [theme])
|
|
299
|
+
|
|
300
|
+
React.useEffect(() => {
|
|
301
|
+
window.addEventListener('resize', handleResize)
|
|
302
|
+
return () => window.removeEventListener('resize', handleResize)
|
|
303
|
+
}, [handleResize])
|
|
304
|
+
|
|
305
|
+
const showReconnect = !!onReconnect && (status === 'disconnected' || status === 'error')
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div className={cn('flex h-full flex-col overflow-hidden rounded-lg border border-border bg-card', className)}>
|
|
309
|
+
{showChrome && (
|
|
310
|
+
<div className="flex flex-shrink-0 items-center justify-between border-b border-border bg-muted/40 px-4 py-2">
|
|
311
|
+
<div className="flex items-center gap-3">
|
|
312
|
+
<TerminalSquare className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
|
|
313
|
+
<span className="text-sm font-medium text-foreground">Terminal</span>
|
|
314
|
+
<StatusIndicator status={status} />
|
|
315
|
+
</div>
|
|
316
|
+
{showReconnect && (
|
|
317
|
+
<button
|
|
318
|
+
type="button"
|
|
319
|
+
onClick={onReconnect}
|
|
320
|
+
aria-label="Reconnect"
|
|
321
|
+
title="Reconnect"
|
|
322
|
+
className="inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
323
|
+
>
|
|
324
|
+
<RefreshCw className="h-4 w-4" aria-hidden="true" />
|
|
325
|
+
</button>
|
|
326
|
+
)}
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
|
|
330
|
+
{errorMessage && (
|
|
331
|
+
<div className="flex flex-shrink-0 items-center gap-2 border-b border-red-300 bg-red-50 px-4 py-2 text-sm text-red-700">
|
|
332
|
+
<AlertCircle className="h-4 w-4 flex-shrink-0" aria-hidden="true" />
|
|
333
|
+
<span>{errorMessage}</span>
|
|
334
|
+
</div>
|
|
335
|
+
)}
|
|
336
|
+
|
|
337
|
+
<div
|
|
338
|
+
ref={containerRef}
|
|
339
|
+
data-testid="terminal-container"
|
|
340
|
+
className="min-h-[300px] w-full flex-1 overflow-hidden"
|
|
341
|
+
style={{ backgroundColor: theme.background }}
|
|
342
|
+
/>
|
|
343
|
+
</div>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Skeleton placeholder shown while a terminal surface is loading. */
|
|
348
|
+
export function TerminalSkeleton({ className }: { className?: string }) {
|
|
349
|
+
return (
|
|
350
|
+
<div className={cn('overflow-hidden rounded-lg border border-border bg-card', className)}>
|
|
351
|
+
<div className="flex items-center justify-between border-b border-border bg-muted/40 px-4 py-2">
|
|
352
|
+
<div className="flex items-center gap-3">
|
|
353
|
+
<div className="h-4 w-4 animate-pulse rounded bg-muted" />
|
|
354
|
+
<div className="h-4 w-20 animate-pulse rounded bg-muted" />
|
|
355
|
+
<div className="h-4 w-24 animate-pulse rounded bg-muted" />
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
<div className="space-y-2 p-4" style={{ minHeight: 300 }}>
|
|
359
|
+
{Array.from({ length: 10 }).map((_, i) => (
|
|
360
|
+
<div
|
|
361
|
+
key={i}
|
|
362
|
+
className="h-4 animate-pulse rounded bg-muted"
|
|
363
|
+
style={{ width: `${((i * 37) % 40) + 55}%` }}
|
|
364
|
+
/>
|
|
365
|
+
))}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
)
|
|
369
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal colour themes (ported from dev-hq `types/terminal.ts`, stripped of the
|
|
3
|
+
* void/plasma/neural "Post-Interface" palette). These are plain xterm theme objects
|
|
4
|
+
* — a consumer can pass any object of the same shape to `<Terminal theme={...} />`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** An xterm.js-compatible colour theme. Matches xterm's `ITheme` shape. */
|
|
8
|
+
export interface TerminalTheme {
|
|
9
|
+
background: string
|
|
10
|
+
foreground: string
|
|
11
|
+
cursor: string
|
|
12
|
+
cursorAccent: string
|
|
13
|
+
selectionBackground: string
|
|
14
|
+
selectionForeground: string
|
|
15
|
+
// Standard ANSI colours
|
|
16
|
+
black: string
|
|
17
|
+
red: string
|
|
18
|
+
green: string
|
|
19
|
+
yellow: string
|
|
20
|
+
blue: string
|
|
21
|
+
magenta: string
|
|
22
|
+
cyan: string
|
|
23
|
+
white: string
|
|
24
|
+
// Bright ANSI colours
|
|
25
|
+
brightBlack: string
|
|
26
|
+
brightRed: string
|
|
27
|
+
brightGreen: string
|
|
28
|
+
brightYellow: string
|
|
29
|
+
brightBlue: string
|
|
30
|
+
brightMagenta: string
|
|
31
|
+
brightCyan: string
|
|
32
|
+
brightWhite: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A small map of neutral, framework-agnostic terminal themes. `dark` is the default
|
|
37
|
+
* (a classic VS Code-style dark palette) — the app-specific dev-hq palette is gone.
|
|
38
|
+
*/
|
|
39
|
+
export const TERMINAL_THEMES: { dark: TerminalTheme; light: TerminalTheme } = {
|
|
40
|
+
dark: {
|
|
41
|
+
background: '#1E1E1E',
|
|
42
|
+
foreground: '#D4D4D4',
|
|
43
|
+
cursor: '#FFFFFF',
|
|
44
|
+
cursorAccent: '#1E1E1E',
|
|
45
|
+
selectionBackground: '#264F78',
|
|
46
|
+
selectionForeground: '#FFFFFF',
|
|
47
|
+
black: '#000000',
|
|
48
|
+
red: '#CD3131',
|
|
49
|
+
green: '#0DBC79',
|
|
50
|
+
yellow: '#E5E510',
|
|
51
|
+
blue: '#2472C8',
|
|
52
|
+
magenta: '#BC3FBC',
|
|
53
|
+
cyan: '#11A8CD',
|
|
54
|
+
white: '#E5E5E5',
|
|
55
|
+
brightBlack: '#666666',
|
|
56
|
+
brightRed: '#F14C4C',
|
|
57
|
+
brightGreen: '#23D18B',
|
|
58
|
+
brightYellow: '#F5F543',
|
|
59
|
+
brightBlue: '#3B8EEA',
|
|
60
|
+
brightMagenta: '#D670D6',
|
|
61
|
+
brightCyan: '#29B8DB',
|
|
62
|
+
brightWhite: '#FFFFFF',
|
|
63
|
+
},
|
|
64
|
+
light: {
|
|
65
|
+
background: '#FFFFFF',
|
|
66
|
+
foreground: '#333333',
|
|
67
|
+
cursor: '#000000',
|
|
68
|
+
cursorAccent: '#FFFFFF',
|
|
69
|
+
selectionBackground: '#ADD6FF',
|
|
70
|
+
selectionForeground: '#000000',
|
|
71
|
+
black: '#000000',
|
|
72
|
+
red: '#CD3131',
|
|
73
|
+
green: '#00BC00',
|
|
74
|
+
yellow: '#949800',
|
|
75
|
+
blue: '#0451A5',
|
|
76
|
+
magenta: '#BC05BC',
|
|
77
|
+
cyan: '#0598BC',
|
|
78
|
+
white: '#555555',
|
|
79
|
+
brightBlack: '#666666',
|
|
80
|
+
brightRed: '#CD3131',
|
|
81
|
+
brightGreen: '#14CE14',
|
|
82
|
+
brightYellow: '#B5BA00',
|
|
83
|
+
brightBlue: '#0451A5',
|
|
84
|
+
brightMagenta: '#BC05BC',
|
|
85
|
+
brightCyan: '#0598BC',
|
|
86
|
+
brightWhite: '#A5A5A5',
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Connection state a consumer feeds into the terminal chrome's status indicator. */
|
|
91
|
+
export type TerminalStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export of xterm.js base styles so consumers can pull them from the package
|
|
3
|
+
* (`import '@startsimpli/ui/terminal/styles'`) instead of reaching into
|
|
4
|
+
* node_modules. `xterm` is a direct dependency of @startsimpli/ui, so this bare
|
|
5
|
+
* import resolves in any bundler that resolves CSS @import from node_modules
|
|
6
|
+
* (Next.js, Vite, webpack css-loader).
|
|
7
|
+
*/
|
|
8
|
+
@import 'xterm/css/xterm.css';
|