division-ds 0.4.0 → 0.5.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/AGENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Division DS: instructions for coding agents
|
|
2
2
|
|
|
3
|
-
A React 19 + Tailwind 4 + Radix design system:
|
|
3
|
+
A React 19 + Tailwind 4 + Radix design system: 75 components, tokens in three layers, light and
|
|
4
4
|
dark themes, WCAG 2.2 AA. This file is the entry point. It is short on purpose; everything it
|
|
5
5
|
points to is generated from the code, so it is current.
|
|
6
6
|
|
|
@@ -51,7 +51,7 @@ installs these rules into that project's `AGENTS.md`, `GEMINI.md` and Cursor rul
|
|
|
51
51
|
`.claude/skills/` (Claude Code).
|
|
52
52
|
|
|
53
53
|
An agent that supports MCP can have the docs and the same checks as tools instead:
|
|
54
|
-
`claude mcp add division-ds -- npx -y division-ds@0.
|
|
54
|
+
`claude mcp add division-ds -- npx -y division-ds@0.4.1 mcp` (`list_components`, `get_component`,
|
|
55
55
|
`search`, `get_tokens`, `validate_code`, `check_contrast`, `get_add_command`,
|
|
56
56
|
`get_review_checklist`).
|
|
57
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "division-ds",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Checks a project that uses Division DS components (raw colours, invented tokens, touching buttons, unknown props), and serves the docs and the same checks to agents over MCP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Victor Ilioiu",
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# Terminal
|
|
2
|
+
|
|
3
|
+
> A live terminal session in the page, built on xterm.js. It renders the output you write to it and reports what the person types. It knows nothing about the connection: your app owns that.
|
|
4
|
+
|
|
5
|
+
- **Best for:** A live, interactive process that needs real terminal behaviour: colour, cursor movement, full-screen programs.
|
|
6
|
+
- **Not for:** Static code or a finished log: use CodeBlock. A conversation shown as messages: use ChatThread.
|
|
7
|
+
- Organism. Source: `src/components/organisms/terminal/Terminal.tsx`
|
|
8
|
+
- Rendered docs page: `/components/terminal`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Terminal } from 'src/components/organisms/terminal/Terminal'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file. An install has no index barrels: import each component from its own file, never from `components/molecules`.
|
|
17
|
+
|
|
18
|
+
## Requires
|
|
19
|
+
|
|
20
|
+
- Import it from its own file and load the screen that uses it with React.lazy. It adds about 100 kB of JavaScript after gzip (about 380 kB before) and 1 kB of CSS, so it is not in the organisms barrel.
|
|
21
|
+
- A height. It is 24rem by default; set another with className (h-[32rem]), or flex-1 inside a column.
|
|
22
|
+
|
|
23
|
+
## Props
|
|
24
|
+
|
|
25
|
+
| Prop | Type | Default | Description |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| `label` | `string` | | Required. What this session is ("Build agent"). Names the terminal, its input and its status announcements. |
|
|
28
|
+
| `status` | `'connecting' \| 'live' \| 'reconnecting' \| 'exited' \| 'error'` | | Required. connecting and reconnecting are info, live is ok, exited is neutral for code 0 and hostile otherwise, error is hostile. Buffered output stays visible in all of them. |
|
|
29
|
+
| `exitCode` | `number` | | With status exited: shown beside the word, and a non-zero code turns it hostile. |
|
|
30
|
+
| `statusMessage` | `string` | | Detail beside the status, and announced with it: the reason for an error, the attempt while reconnecting. |
|
|
31
|
+
| `readOnly` | `boolean` | `false` | Blocks input and never fires onData. Selecting and copying keep working, and Tab and Escape leave the terminal as they do anywhere else. |
|
|
32
|
+
| `announceOutput` | `boolean` | `false` | Reads output aloud through xterm’s live region, up to 20 lines at a time. For a single session a screen reader user is working in. Leave it off when several terminals share a page. |
|
|
33
|
+
| `onData` | `(data: string) => void` | | What the person typed or pasted, plus the replies a program asks the terminal for. Send it to the process. |
|
|
34
|
+
| `onResize` | `(size: { cols: number; rows: number }) => void` | | The new grid size, debounced by 100ms and only when cols or rows changed. It fires once after mounting. |
|
|
35
|
+
| `scrollback` | `number` | `5000` | Lines kept above the screen. xterm’s own default is 1000, which a busy agent passes in seconds. |
|
|
36
|
+
| `className` | `string` | | Merged onto the frame. Set the height here. |
|
|
37
|
+
| `ref` | `Ref<TerminalHandle>` | | The handle with the four methods below. |
|
|
38
|
+
| `write (TerminalHandle)` | `(data: string \| Uint8Array, callback?: () => void) => void` | | Sends output to the terminal, without a React render. The callback runs when it has been parsed: use it for flow control, because xterm throws data away past 50 MB waiting. |
|
|
39
|
+
| `clear (TerminalHandle)` | `() => void` | | Clears the screen and the scrollback, keeping the line the cursor is on. |
|
|
40
|
+
| `focus (TerminalHandle)` | `() => void` | | Moves keyboard focus into the terminal. |
|
|
41
|
+
| `fit (TerminalHandle)` | `() => void` | | Fits the grid to the frame now. Resizing already does this. |
|
|
42
|
+
|
|
43
|
+
## Types and helpers
|
|
44
|
+
|
|
45
|
+
Exported beside the component, from the same file. Import a type with `import type { ... }`.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
export type TerminalStatus = 'connecting' | 'live' | 'reconnecting' | 'exited' | 'error'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
export type TerminalHandle = {
|
|
53
|
+
/** Sends output to the terminal, without a React render. The callback runs once it is parsed: use it for flow control. */
|
|
54
|
+
write(data: string | Uint8Array, callback?: () => void): void
|
|
55
|
+
/** Clears the screen and the scrollback, keeping the cursor's line. */
|
|
56
|
+
clear(): void
|
|
57
|
+
/** Moves keyboard focus into the terminal. */
|
|
58
|
+
focus(): void
|
|
59
|
+
/** Fits the grid to the container now. Resizing already does this; use it after a change no resize reports. */
|
|
60
|
+
fit(): void
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Examples
|
|
65
|
+
|
|
66
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
67
|
+
|
|
68
|
+
### Connecting
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<Terminal label="Build agent" status="connecting" className="h-40 max-w-3xl" />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Live, with a simulated stream
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const ESC = '\u001b'
|
|
78
|
+
|
|
79
|
+
const paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`
|
|
80
|
+
|
|
81
|
+
const COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]
|
|
82
|
+
.map(code => paint(code, `${code} `.padEnd(4)))
|
|
83
|
+
.join('')
|
|
84
|
+
|
|
85
|
+
const TRANSCRIPT = [
|
|
86
|
+
paint(1, 'Sample session, written by this page'),
|
|
87
|
+
'┌──────────────────────────────┐',
|
|
88
|
+
`│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,
|
|
89
|
+
'└──────────────────────────────┘',
|
|
90
|
+
COLOURS,
|
|
91
|
+
'Docs at https://example.com/terminal',
|
|
92
|
+
'',
|
|
93
|
+
].join('\r\n')
|
|
94
|
+
|
|
95
|
+
const STREAM = ['compiling', 'linking', 'checking types', 'running tests', 'writing bundle']
|
|
96
|
+
|
|
97
|
+
function LiveDemo() {
|
|
98
|
+
const term = useRef<TerminalHandle>(null)
|
|
99
|
+
const [streaming, setStreaming] = useState(false)
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
term.current?.write(`${TRANSCRIPT}Type here, or start the stream.\r\n$ `)
|
|
103
|
+
}, [])
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!streaming) return
|
|
107
|
+
let line = 0
|
|
108
|
+
const timer = window.setInterval(() => {
|
|
109
|
+
const step = STREAM[line % STREAM.length]
|
|
110
|
+
const code = 31 + (line % 6)
|
|
111
|
+
term.current?.write(`${paint(code, `[${String(line + 1).padStart(3, '0')}]`)} ${step}\r\n`)
|
|
112
|
+
line += 1
|
|
113
|
+
if (line === 40) setStreaming(false)
|
|
114
|
+
}, 120)
|
|
115
|
+
return () => window.clearInterval(timer)
|
|
116
|
+
}, [streaming])
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div className="flex max-w-3xl flex-col gap-3">
|
|
120
|
+
<Terminal
|
|
121
|
+
ref={term}
|
|
122
|
+
label="Sample build"
|
|
123
|
+
status="live"
|
|
124
|
+
className="h-80"
|
|
125
|
+
onData={data => term.current?.write(data === '\r' ? '\r\n$ ' : data)}
|
|
126
|
+
/>
|
|
127
|
+
<div className="flex flex-wrap gap-3">
|
|
128
|
+
<Button variant="secondary" onClick={() => setStreaming(on => !on)}>{streaming ? 'Stop the stream' : 'Start the stream'}</Button>
|
|
129
|
+
<Button variant="secondary" onClick={() => term.current?.clear()}>Clear</Button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
<LiveDemo />
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Read only: select and copy, but no typing
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
const ESC = '\u001b'
|
|
142
|
+
|
|
143
|
+
const paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`
|
|
144
|
+
|
|
145
|
+
const COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]
|
|
146
|
+
.map(code => paint(code, `${code} `.padEnd(4)))
|
|
147
|
+
.join('')
|
|
148
|
+
|
|
149
|
+
const TRANSCRIPT = [
|
|
150
|
+
paint(1, 'Sample session, written by this page'),
|
|
151
|
+
'┌──────────────────────────────┐',
|
|
152
|
+
`│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,
|
|
153
|
+
'└──────────────────────────────┘',
|
|
154
|
+
COLOURS,
|
|
155
|
+
'Docs at https://example.com/terminal',
|
|
156
|
+
'',
|
|
157
|
+
].join('\r\n')
|
|
158
|
+
|
|
159
|
+
function SampleTerminal(props: Omit<TerminalProps, 'ref'>) {
|
|
160
|
+
const term = useRef<TerminalHandle>(null)
|
|
161
|
+
useEffect(() => term.current?.write(TRANSCRIPT), [])
|
|
162
|
+
return <Terminal ref={term} className="h-64 max-w-3xl" {...props} />
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
<SampleTerminal label="Deploy log" status="live" readOnly />
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Reconnecting, with the output kept
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
const ESC = '\u001b'
|
|
172
|
+
|
|
173
|
+
const paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`
|
|
174
|
+
|
|
175
|
+
const COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]
|
|
176
|
+
.map(code => paint(code, `${code} `.padEnd(4)))
|
|
177
|
+
.join('')
|
|
178
|
+
|
|
179
|
+
const TRANSCRIPT = [
|
|
180
|
+
paint(1, 'Sample session, written by this page'),
|
|
181
|
+
'┌──────────────────────────────┐',
|
|
182
|
+
`│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,
|
|
183
|
+
'└──────────────────────────────┘',
|
|
184
|
+
COLOURS,
|
|
185
|
+
'Docs at https://example.com/terminal',
|
|
186
|
+
'',
|
|
187
|
+
].join('\r\n')
|
|
188
|
+
|
|
189
|
+
function SampleTerminal(props: Omit<TerminalProps, 'ref'>) {
|
|
190
|
+
const term = useRef<TerminalHandle>(null)
|
|
191
|
+
useEffect(() => term.current?.write(TRANSCRIPT), [])
|
|
192
|
+
return <Terminal ref={term} className="h-64 max-w-3xl" {...props} />
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
<SampleTerminal label="Test agent" status="reconnecting" statusMessage="Attempt 2 of 5" />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Exited with code 0
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
const ESC = '\u001b'
|
|
202
|
+
|
|
203
|
+
const paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`
|
|
204
|
+
|
|
205
|
+
const COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]
|
|
206
|
+
.map(code => paint(code, `${code} `.padEnd(4)))
|
|
207
|
+
.join('')
|
|
208
|
+
|
|
209
|
+
const TRANSCRIPT = [
|
|
210
|
+
paint(1, 'Sample session, written by this page'),
|
|
211
|
+
'┌──────────────────────────────┐',
|
|
212
|
+
`│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,
|
|
213
|
+
'└──────────────────────────────┘',
|
|
214
|
+
COLOURS,
|
|
215
|
+
'Docs at https://example.com/terminal',
|
|
216
|
+
'',
|
|
217
|
+
].join('\r\n')
|
|
218
|
+
|
|
219
|
+
function SampleTerminal(props: Omit<TerminalProps, 'ref'>) {
|
|
220
|
+
const term = useRef<TerminalHandle>(null)
|
|
221
|
+
useEffect(() => term.current?.write(TRANSCRIPT), [])
|
|
222
|
+
return <Terminal ref={term} className="h-64 max-w-3xl" {...props} />
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
<SampleTerminal label="Migration" status="exited" exitCode={0} readOnly />
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Exited with an error code
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
const ESC = '\u001b'
|
|
232
|
+
|
|
233
|
+
const paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`
|
|
234
|
+
|
|
235
|
+
const COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]
|
|
236
|
+
.map(code => paint(code, `${code} `.padEnd(4)))
|
|
237
|
+
.join('')
|
|
238
|
+
|
|
239
|
+
const TRANSCRIPT = [
|
|
240
|
+
paint(1, 'Sample session, written by this page'),
|
|
241
|
+
'┌──────────────────────────────┐',
|
|
242
|
+
`│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,
|
|
243
|
+
'└──────────────────────────────┘',
|
|
244
|
+
COLOURS,
|
|
245
|
+
'Docs at https://example.com/terminal',
|
|
246
|
+
'',
|
|
247
|
+
].join('\r\n')
|
|
248
|
+
|
|
249
|
+
function SampleTerminal(props: Omit<TerminalProps, 'ref'>) {
|
|
250
|
+
const term = useRef<TerminalHandle>(null)
|
|
251
|
+
useEffect(() => term.current?.write(TRANSCRIPT), [])
|
|
252
|
+
return <Terminal ref={term} className="h-64 max-w-3xl" {...props} />
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
<SampleTerminal label="Type check" status="exited" exitCode={2} readOnly />
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Connection error
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
const ESC = '\u001b'
|
|
262
|
+
|
|
263
|
+
const paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`
|
|
264
|
+
|
|
265
|
+
const COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]
|
|
266
|
+
.map(code => paint(code, `${code} `.padEnd(4)))
|
|
267
|
+
.join('')
|
|
268
|
+
|
|
269
|
+
const TRANSCRIPT = [
|
|
270
|
+
paint(1, 'Sample session, written by this page'),
|
|
271
|
+
'┌──────────────────────────────┐',
|
|
272
|
+
`│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,
|
|
273
|
+
'└──────────────────────────────┘',
|
|
274
|
+
COLOURS,
|
|
275
|
+
'Docs at https://example.com/terminal',
|
|
276
|
+
'',
|
|
277
|
+
].join('\r\n')
|
|
278
|
+
|
|
279
|
+
function SampleTerminal(props: Omit<TerminalProps, 'ref'>) {
|
|
280
|
+
const term = useRef<TerminalHandle>(null)
|
|
281
|
+
useEffect(() => term.current?.write(TRANSCRIPT), [])
|
|
282
|
+
return <Terminal ref={term} className="h-64 max-w-3xl" {...props} />
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
<SampleTerminal label="Review agent" status="error" statusMessage="The connection was refused." readOnly />
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Switching theme updates the colours and keeps the buffer
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
const ESC = '\u001b'
|
|
292
|
+
|
|
293
|
+
const paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`
|
|
294
|
+
|
|
295
|
+
const COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]
|
|
296
|
+
.map(code => paint(code, `${code} `.padEnd(4)))
|
|
297
|
+
.join('')
|
|
298
|
+
|
|
299
|
+
const TRANSCRIPT = [
|
|
300
|
+
paint(1, 'Sample session, written by this page'),
|
|
301
|
+
'┌──────────────────────────────┐',
|
|
302
|
+
`│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,
|
|
303
|
+
'└──────────────────────────────┘',
|
|
304
|
+
COLOURS,
|
|
305
|
+
'Docs at https://example.com/terminal',
|
|
306
|
+
'',
|
|
307
|
+
].join('\r\n')
|
|
308
|
+
|
|
309
|
+
function SampleTerminal(props: Omit<TerminalProps, 'ref'>) {
|
|
310
|
+
const term = useRef<TerminalHandle>(null)
|
|
311
|
+
useEffect(() => term.current?.write(TRANSCRIPT), [])
|
|
312
|
+
return <Terminal ref={term} className="h-64 max-w-3xl" {...props} />
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
<div className="flex max-w-3xl flex-col gap-3">
|
|
316
|
+
<SampleTerminal label="Theme check" status="live" readOnly />
|
|
317
|
+
<div className="flex flex-wrap gap-3">
|
|
318
|
+
<Button variant="secondary" onClick={() => setTheme('light')}>Light theme</Button>
|
|
319
|
+
<Button variant="secondary" onClick={() => setTheme('dark')}>Dark theme</Button>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Keyboard
|
|
325
|
+
|
|
326
|
+
| Keys | Result |
|
|
327
|
+
|---|---|
|
|
328
|
+
| `Tab` | From the page, moves into the terminal. Inside it, goes to the program, unless readOnly is set, when it moves on. |
|
|
329
|
+
| `Ctrl` `Shift` `L` | Moves focus out of the terminal to the next tab stop. Tab and Escape belong to the program, so this is the way out. |
|
|
330
|
+
| `Ctrl` `C` | With text selected, copies it and clears the selection. With nothing selected, goes to the program as an interrupt. |
|
|
331
|
+
| `Ctrl` `Shift` `C` | Copies the selection and keeps it. |
|
|
332
|
+
| `Ctrl` `V` | Pastes. Ctrl+Shift+V does the same. A program that wants a literal Ctrl+V does not get it. |
|
|
333
|
+
|
|
334
|
+
## Do
|
|
335
|
+
|
|
336
|
+
- Give each terminal a label that says which session it is, so a page of them can be told apart.
|
|
337
|
+
- Set readOnly when nobody can usefully type: a finished process, a session someone else owns.
|
|
338
|
+
- Say why in statusMessage when status is error.
|
|
339
|
+
- Use write’s callback to pause the source when output arrives faster than the page can draw it.
|
|
340
|
+
|
|
341
|
+
## Don't
|
|
342
|
+
|
|
343
|
+
- Don’t import it from the organisms barrel, and don’t load it on a screen that does not show it.
|
|
344
|
+
- Don’t put a terminal inside another bordered panel; it brings its own frame.
|
|
345
|
+
- Don’t keep output in React state to pass down. Write it through the ref.
|
|
346
|
+
- Don’t expect the status to block input by itself: set readOnly when a process has exited.
|
|
347
|
+
|
|
348
|
+
## Common mistakes
|
|
349
|
+
|
|
350
|
+
Wrong:
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
<Terminal label="Agent" status="live" output={text} />
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Right:
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
const term = useRef<TerminalHandle>(null)
|
|
360
|
+
const socket = useRef<WebSocket>(null)
|
|
361
|
+
const [status, setStatus] = useState<TerminalStatus>('connecting')
|
|
362
|
+
|
|
363
|
+
useEffect(() => {
|
|
364
|
+
const ws = new WebSocket(url)
|
|
365
|
+
ws.binaryType = 'arraybuffer'
|
|
366
|
+
socket.current = ws
|
|
367
|
+
ws.onopen = () => setStatus('live')
|
|
368
|
+
ws.onmessage = event =>
|
|
369
|
+
term.current?.write(typeof event.data === 'string' ? event.data : new Uint8Array(event.data))
|
|
370
|
+
ws.onclose = () => setStatus('reconnecting')
|
|
371
|
+
return () => ws.close()
|
|
372
|
+
}, [url])
|
|
373
|
+
|
|
374
|
+
<Terminal
|
|
375
|
+
ref={term}
|
|
376
|
+
label="Build agent"
|
|
377
|
+
status={status}
|
|
378
|
+
onData={data => socket.current?.send(data)}
|
|
379
|
+
onResize={size => socket.current?.send(JSON.stringify({ type: 'resize', ...size }))}
|
|
380
|
+
/>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Output goes through the ref, so a fast stream never re-renders React. The connection lives in your app, not in the component.
|
|
384
|
+
|
|
385
|
+
Wrong:
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
<Suspense fallback={null}><LazyTerminal ref={term} ... /></Suspense> // with the socket in this component's effect
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Right:
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
const Screen = lazy(() => import('./AgentScreen')) // AgentScreen imports Terminal directly and owns the socket
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
If only the Terminal is lazy, your effect runs while the ref is still empty and the first output is lost. Lazy-load the screen that holds both.
|
|
398
|
+
|
|
399
|
+
## Anatomy
|
|
400
|
+
|
|
401
|
+
- **Frame**: A Panel on surface.base, which is the surface all sixteen ANSI colours are measured on. It shows the focus ring while the terminal has focus.
|
|
402
|
+
- **Title bar**: The label, the status Badge with a word and a tone, a Read only Badge when set, statusMessage, and the shortcut that leaves the terminal.
|
|
403
|
+
- **Status announcement**: A visually hidden status region. It says only the label and the status, so a screen reader hears changes and not output.
|
|
404
|
+
- **Grid**: xterm.js in the mono font, fitted to the frame. Colours are read from tokens at runtime and updated when the theme changes, keeping the buffer.
|
|
405
|
+
- **Screen reader rows**: xterm’s accessibility tree: the visible rows as a list a screen reader can browse. Scrolling the terminal changes which rows it holds.
|
|
@@ -12041,6 +12041,242 @@
|
|
|
12041
12041
|
}
|
|
12042
12042
|
],
|
|
12043
12043
|
"closed": true
|
|
12044
|
+
},
|
|
12045
|
+
{
|
|
12046
|
+
"name": "Terminal",
|
|
12047
|
+
"title": "Terminal",
|
|
12048
|
+
"tier": "organisms",
|
|
12049
|
+
"slug": "terminal",
|
|
12050
|
+
"summary": "A live terminal session in the page, built on xterm.js. It renders the output you write to it and reports what the person types. It knows nothing about the connection: your app owns that.",
|
|
12051
|
+
"useWhen": "A live, interactive process that needs real terminal behaviour: colour, cursor movement, full-screen programs.",
|
|
12052
|
+
"notFor": "Static code or a finished log: use CodeBlock. A conversation shown as messages: use ChatThread.",
|
|
12053
|
+
"source": "src/components/organisms/terminal/Terminal.tsx",
|
|
12054
|
+
"docs": "/docs/components/terminal.md",
|
|
12055
|
+
"imports": [
|
|
12056
|
+
{
|
|
12057
|
+
"names": [
|
|
12058
|
+
"Terminal"
|
|
12059
|
+
],
|
|
12060
|
+
"from": "src/components/organisms/terminal/Terminal"
|
|
12061
|
+
}
|
|
12062
|
+
],
|
|
12063
|
+
"requires": [
|
|
12064
|
+
"Import it from its own file and load the screen that uses it with React.lazy. It adds about 100 kB of JavaScript after gzip (about 380 kB before) and 1 kB of CSS, so it is not in the organisms barrel.",
|
|
12065
|
+
"A height. It is 24rem by default; set another with className (h-[32rem]), or flex-1 inside a column."
|
|
12066
|
+
],
|
|
12067
|
+
"props": [
|
|
12068
|
+
{
|
|
12069
|
+
"name": "label",
|
|
12070
|
+
"type": "string",
|
|
12071
|
+
"description": "Required. What this session is (\"Build agent\"). Names the terminal, its input and its status announcements."
|
|
12072
|
+
},
|
|
12073
|
+
{
|
|
12074
|
+
"name": "status",
|
|
12075
|
+
"type": "'connecting' | 'live' | 'reconnecting' | 'exited' | 'error'",
|
|
12076
|
+
"description": "Required. connecting and reconnecting are info, live is ok, exited is neutral for code 0 and hostile otherwise, error is hostile. Buffered output stays visible in all of them."
|
|
12077
|
+
},
|
|
12078
|
+
{
|
|
12079
|
+
"name": "exitCode",
|
|
12080
|
+
"type": "number",
|
|
12081
|
+
"description": "With status exited: shown beside the word, and a non-zero code turns it hostile."
|
|
12082
|
+
},
|
|
12083
|
+
{
|
|
12084
|
+
"name": "statusMessage",
|
|
12085
|
+
"type": "string",
|
|
12086
|
+
"description": "Detail beside the status, and announced with it: the reason for an error, the attempt while reconnecting."
|
|
12087
|
+
},
|
|
12088
|
+
{
|
|
12089
|
+
"name": "readOnly",
|
|
12090
|
+
"type": "boolean",
|
|
12091
|
+
"default": "false",
|
|
12092
|
+
"description": "Blocks input and never fires onData. Selecting and copying keep working, and Tab and Escape leave the terminal as they do anywhere else."
|
|
12093
|
+
},
|
|
12094
|
+
{
|
|
12095
|
+
"name": "announceOutput",
|
|
12096
|
+
"type": "boolean",
|
|
12097
|
+
"default": "false",
|
|
12098
|
+
"description": "Reads output aloud through xterm’s live region, up to 20 lines at a time. For a single session a screen reader user is working in. Leave it off when several terminals share a page."
|
|
12099
|
+
},
|
|
12100
|
+
{
|
|
12101
|
+
"name": "onData",
|
|
12102
|
+
"type": "(data: string) => void",
|
|
12103
|
+
"description": "What the person typed or pasted, plus the replies a program asks the terminal for. Send it to the process."
|
|
12104
|
+
},
|
|
12105
|
+
{
|
|
12106
|
+
"name": "onResize",
|
|
12107
|
+
"type": "(size: { cols: number; rows: number }) => void",
|
|
12108
|
+
"description": "The new grid size, debounced by 100ms and only when cols or rows changed. It fires once after mounting."
|
|
12109
|
+
},
|
|
12110
|
+
{
|
|
12111
|
+
"name": "scrollback",
|
|
12112
|
+
"type": "number",
|
|
12113
|
+
"default": "5000",
|
|
12114
|
+
"description": "Lines kept above the screen. xterm’s own default is 1000, which a busy agent passes in seconds."
|
|
12115
|
+
},
|
|
12116
|
+
{
|
|
12117
|
+
"name": "className",
|
|
12118
|
+
"type": "string",
|
|
12119
|
+
"description": "Merged onto the frame. Set the height here."
|
|
12120
|
+
},
|
|
12121
|
+
{
|
|
12122
|
+
"name": "ref",
|
|
12123
|
+
"type": "Ref<TerminalHandle>",
|
|
12124
|
+
"description": "The handle with the four methods below."
|
|
12125
|
+
},
|
|
12126
|
+
{
|
|
12127
|
+
"name": "write (TerminalHandle)",
|
|
12128
|
+
"type": "(data: string | Uint8Array, callback?: () => void) => void",
|
|
12129
|
+
"description": "Sends output to the terminal, without a React render. The callback runs when it has been parsed: use it for flow control, because xterm throws data away past 50 MB waiting."
|
|
12130
|
+
},
|
|
12131
|
+
{
|
|
12132
|
+
"name": "clear (TerminalHandle)",
|
|
12133
|
+
"type": "() => void",
|
|
12134
|
+
"description": "Clears the screen and the scrollback, keeping the line the cursor is on."
|
|
12135
|
+
},
|
|
12136
|
+
{
|
|
12137
|
+
"name": "focus (TerminalHandle)",
|
|
12138
|
+
"type": "() => void",
|
|
12139
|
+
"description": "Moves keyboard focus into the terminal."
|
|
12140
|
+
},
|
|
12141
|
+
{
|
|
12142
|
+
"name": "fit (TerminalHandle)",
|
|
12143
|
+
"type": "() => void",
|
|
12144
|
+
"description": "Fits the grid to the frame now. Resizing already does this."
|
|
12145
|
+
}
|
|
12146
|
+
],
|
|
12147
|
+
"api": [
|
|
12148
|
+
{
|
|
12149
|
+
"name": "TerminalStatus",
|
|
12150
|
+
"kind": "type",
|
|
12151
|
+
"code": "export type TerminalStatus = 'connecting' | 'live' | 'reconnecting' | 'exited' | 'error'",
|
|
12152
|
+
"from": "src/components/organisms/terminal/Terminal"
|
|
12153
|
+
},
|
|
12154
|
+
{
|
|
12155
|
+
"name": "TerminalHandle",
|
|
12156
|
+
"kind": "type",
|
|
12157
|
+
"code": "export type TerminalHandle = {\n /** Sends output to the terminal, without a React render. The callback runs once it is parsed: use it for flow control. */\n write(data: string | Uint8Array, callback?: () => void): void\n /** Clears the screen and the scrollback, keeping the cursor's line. */\n clear(): void\n /** Moves keyboard focus into the terminal. */\n focus(): void\n /** Fits the grid to the container now. Resizing already does this; use it after a change no resize reports. */\n fit(): void\n}",
|
|
12158
|
+
"from": "src/components/organisms/terminal/Terminal"
|
|
12159
|
+
}
|
|
12160
|
+
],
|
|
12161
|
+
"keyboard": [
|
|
12162
|
+
{
|
|
12163
|
+
"keys": [
|
|
12164
|
+
"Tab"
|
|
12165
|
+
],
|
|
12166
|
+
"does": "From the page, moves into the terminal. Inside it, goes to the program, unless readOnly is set, when it moves on."
|
|
12167
|
+
},
|
|
12168
|
+
{
|
|
12169
|
+
"keys": [
|
|
12170
|
+
"Ctrl",
|
|
12171
|
+
"Shift",
|
|
12172
|
+
"L"
|
|
12173
|
+
],
|
|
12174
|
+
"does": "Moves focus out of the terminal to the next tab stop. Tab and Escape belong to the program, so this is the way out."
|
|
12175
|
+
},
|
|
12176
|
+
{
|
|
12177
|
+
"keys": [
|
|
12178
|
+
"Ctrl",
|
|
12179
|
+
"C"
|
|
12180
|
+
],
|
|
12181
|
+
"does": "With text selected, copies it and clears the selection. With nothing selected, goes to the program as an interrupt."
|
|
12182
|
+
},
|
|
12183
|
+
{
|
|
12184
|
+
"keys": [
|
|
12185
|
+
"Ctrl",
|
|
12186
|
+
"Shift",
|
|
12187
|
+
"C"
|
|
12188
|
+
],
|
|
12189
|
+
"does": "Copies the selection and keeps it."
|
|
12190
|
+
},
|
|
12191
|
+
{
|
|
12192
|
+
"keys": [
|
|
12193
|
+
"Ctrl",
|
|
12194
|
+
"V"
|
|
12195
|
+
],
|
|
12196
|
+
"does": "Pastes. Ctrl+Shift+V does the same. A program that wants a literal Ctrl+V does not get it."
|
|
12197
|
+
}
|
|
12198
|
+
],
|
|
12199
|
+
"dos": [
|
|
12200
|
+
"Give each terminal a label that says which session it is, so a page of them can be told apart.",
|
|
12201
|
+
"Set readOnly when nobody can usefully type: a finished process, a session someone else owns.",
|
|
12202
|
+
"Say why in statusMessage when status is error.",
|
|
12203
|
+
"Use write’s callback to pause the source when output arrives faster than the page can draw it."
|
|
12204
|
+
],
|
|
12205
|
+
"donts": [
|
|
12206
|
+
"Don’t import it from the organisms barrel, and don’t load it on a screen that does not show it.",
|
|
12207
|
+
"Don’t put a terminal inside another bordered panel; it brings its own frame.",
|
|
12208
|
+
"Don’t keep output in React state to pass down. Write it through the ref.",
|
|
12209
|
+
"Don’t expect the status to block input by itself: set readOnly when a process has exited."
|
|
12210
|
+
],
|
|
12211
|
+
"pitfalls": [
|
|
12212
|
+
{
|
|
12213
|
+
"wrong": "<Terminal label=\"Agent\" status=\"live\" output={text} />",
|
|
12214
|
+
"right": "const term = useRef<TerminalHandle>(null)\nconst socket = useRef<WebSocket>(null)\nconst [status, setStatus] = useState<TerminalStatus>('connecting')\n\nuseEffect(() => {\n const ws = new WebSocket(url)\n ws.binaryType = 'arraybuffer'\n socket.current = ws\n ws.onopen = () => setStatus('live')\n ws.onmessage = event =>\n term.current?.write(typeof event.data === 'string' ? event.data : new Uint8Array(event.data))\n ws.onclose = () => setStatus('reconnecting')\n return () => ws.close()\n}, [url])\n\n<Terminal\n ref={term}\n label=\"Build agent\"\n status={status}\n onData={data => socket.current?.send(data)}\n onResize={size => socket.current?.send(JSON.stringify({ type: 'resize', ...size }))}\n/>",
|
|
12215
|
+
"why": "Output goes through the ref, so a fast stream never re-renders React. The connection lives in your app, not in the component."
|
|
12216
|
+
},
|
|
12217
|
+
{
|
|
12218
|
+
"wrong": "<Suspense fallback={null}><LazyTerminal ref={term} ... /></Suspense> // with the socket in this component's effect",
|
|
12219
|
+
"right": "const Screen = lazy(() => import('./AgentScreen')) // AgentScreen imports Terminal directly and owns the socket",
|
|
12220
|
+
"why": "If only the Terminal is lazy, your effect runs while the ref is still empty and the first output is lost. Lazy-load the screen that holds both."
|
|
12221
|
+
}
|
|
12222
|
+
],
|
|
12223
|
+
"anatomy": [
|
|
12224
|
+
{
|
|
12225
|
+
"part": "Frame",
|
|
12226
|
+
"description": "A Panel on surface.base, which is the surface all sixteen ANSI colours are measured on. It shows the focus ring while the terminal has focus."
|
|
12227
|
+
},
|
|
12228
|
+
{
|
|
12229
|
+
"part": "Title bar",
|
|
12230
|
+
"description": "The label, the status Badge with a word and a tone, a Read only Badge when set, statusMessage, and the shortcut that leaves the terminal."
|
|
12231
|
+
},
|
|
12232
|
+
{
|
|
12233
|
+
"part": "Status announcement",
|
|
12234
|
+
"description": "A visually hidden status region. It says only the label and the status, so a screen reader hears changes and not output."
|
|
12235
|
+
},
|
|
12236
|
+
{
|
|
12237
|
+
"part": "Grid",
|
|
12238
|
+
"description": "xterm.js in the mono font, fitted to the frame. Colours are read from tokens at runtime and updated when the theme changes, keeping the buffer."
|
|
12239
|
+
},
|
|
12240
|
+
{
|
|
12241
|
+
"part": "Screen reader rows",
|
|
12242
|
+
"description": "xterm’s accessibility tree: the visible rows as a list a screen reader can browse. Scrolling the terminal changes which rows it holds."
|
|
12243
|
+
}
|
|
12244
|
+
],
|
|
12245
|
+
"examples": [
|
|
12246
|
+
{
|
|
12247
|
+
"caption": "Connecting",
|
|
12248
|
+
"source": "<Terminal label=\"Build agent\" status=\"connecting\" className=\"h-40 max-w-3xl\" />"
|
|
12249
|
+
},
|
|
12250
|
+
{
|
|
12251
|
+
"caption": "Live, with a simulated stream",
|
|
12252
|
+
"source": "const ESC = '\\u001b'\n\nconst paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`\n\nconst COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]\n .map(code => paint(code, `${code} `.padEnd(4)))\n .join('')\n\nconst TRANSCRIPT = [\n paint(1, 'Sample session, written by this page'),\n '┌──────────────────────────────┐',\n `│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,\n '└──────────────────────────────┘',\n COLOURS,\n 'Docs at https://example.com/terminal',\n '',\n].join('\\r\\n')\n\nconst STREAM = ['compiling', 'linking', 'checking types', 'running tests', 'writing bundle']\n\nfunction LiveDemo() {\n const term = useRef<TerminalHandle>(null)\n const [streaming, setStreaming] = useState(false)\n\n useEffect(() => {\n term.current?.write(`${TRANSCRIPT}Type here, or start the stream.\\r\\n$ `)\n }, [])\n\n useEffect(() => {\n if (!streaming) return\n let line = 0\n const timer = window.setInterval(() => {\n const step = STREAM[line % STREAM.length]\n const code = 31 + (line % 6)\n term.current?.write(`${paint(code, `[${String(line + 1).padStart(3, '0')}]`)} ${step}\\r\\n`)\n line += 1\n if (line === 40) setStreaming(false)\n }, 120)\n return () => window.clearInterval(timer)\n }, [streaming])\n\n return (\n <div className=\"flex max-w-3xl flex-col gap-3\">\n <Terminal\n ref={term}\n label=\"Sample build\"\n status=\"live\"\n className=\"h-80\"\n onData={data => term.current?.write(data === '\\r' ? '\\r\\n$ ' : data)}\n />\n <div className=\"flex flex-wrap gap-3\">\n <Button variant=\"secondary\" onClick={() => setStreaming(on => !on)}>{streaming ? 'Stop the stream' : 'Start the stream'}</Button>\n <Button variant=\"secondary\" onClick={() => term.current?.clear()}>Clear</Button>\n </div>\n </div>\n )\n}\n\n<LiveDemo />"
|
|
12253
|
+
},
|
|
12254
|
+
{
|
|
12255
|
+
"caption": "Read only: select and copy, but no typing",
|
|
12256
|
+
"source": "const ESC = '\\u001b'\n\nconst paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`\n\nconst COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]\n .map(code => paint(code, `${code} `.padEnd(4)))\n .join('')\n\nconst TRANSCRIPT = [\n paint(1, 'Sample session, written by this page'),\n '┌──────────────────────────────┐',\n `│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,\n '└──────────────────────────────┘',\n COLOURS,\n 'Docs at https://example.com/terminal',\n '',\n].join('\\r\\n')\n\nfunction SampleTerminal(props: Omit<TerminalProps, 'ref'>) {\n const term = useRef<TerminalHandle>(null)\n useEffect(() => term.current?.write(TRANSCRIPT), [])\n return <Terminal ref={term} className=\"h-64 max-w-3xl\" {...props} />\n}\n\n<SampleTerminal label=\"Deploy log\" status=\"live\" readOnly />"
|
|
12257
|
+
},
|
|
12258
|
+
{
|
|
12259
|
+
"caption": "Reconnecting, with the output kept",
|
|
12260
|
+
"source": "const ESC = '\\u001b'\n\nconst paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`\n\nconst COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]\n .map(code => paint(code, `${code} `.padEnd(4)))\n .join('')\n\nconst TRANSCRIPT = [\n paint(1, 'Sample session, written by this page'),\n '┌──────────────────────────────┐',\n `│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,\n '└──────────────────────────────┘',\n COLOURS,\n 'Docs at https://example.com/terminal',\n '',\n].join('\\r\\n')\n\nfunction SampleTerminal(props: Omit<TerminalProps, 'ref'>) {\n const term = useRef<TerminalHandle>(null)\n useEffect(() => term.current?.write(TRANSCRIPT), [])\n return <Terminal ref={term} className=\"h-64 max-w-3xl\" {...props} />\n}\n\n<SampleTerminal label=\"Test agent\" status=\"reconnecting\" statusMessage=\"Attempt 2 of 5\" />"
|
|
12261
|
+
},
|
|
12262
|
+
{
|
|
12263
|
+
"caption": "Exited with code 0",
|
|
12264
|
+
"source": "const ESC = '\\u001b'\n\nconst paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`\n\nconst COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]\n .map(code => paint(code, `${code} `.padEnd(4)))\n .join('')\n\nconst TRANSCRIPT = [\n paint(1, 'Sample session, written by this page'),\n '┌──────────────────────────────┐',\n `│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,\n '└──────────────────────────────┘',\n COLOURS,\n 'Docs at https://example.com/terminal',\n '',\n].join('\\r\\n')\n\nfunction SampleTerminal(props: Omit<TerminalProps, 'ref'>) {\n const term = useRef<TerminalHandle>(null)\n useEffect(() => term.current?.write(TRANSCRIPT), [])\n return <Terminal ref={term} className=\"h-64 max-w-3xl\" {...props} />\n}\n\n<SampleTerminal label=\"Migration\" status=\"exited\" exitCode={0} readOnly />"
|
|
12265
|
+
},
|
|
12266
|
+
{
|
|
12267
|
+
"caption": "Exited with an error code",
|
|
12268
|
+
"source": "const ESC = '\\u001b'\n\nconst paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`\n\nconst COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]\n .map(code => paint(code, `${code} `.padEnd(4)))\n .join('')\n\nconst TRANSCRIPT = [\n paint(1, 'Sample session, written by this page'),\n '┌──────────────────────────────┐',\n `│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,\n '└──────────────────────────────┘',\n COLOURS,\n 'Docs at https://example.com/terminal',\n '',\n].join('\\r\\n')\n\nfunction SampleTerminal(props: Omit<TerminalProps, 'ref'>) {\n const term = useRef<TerminalHandle>(null)\n useEffect(() => term.current?.write(TRANSCRIPT), [])\n return <Terminal ref={term} className=\"h-64 max-w-3xl\" {...props} />\n}\n\n<SampleTerminal label=\"Type check\" status=\"exited\" exitCode={2} readOnly />"
|
|
12269
|
+
},
|
|
12270
|
+
{
|
|
12271
|
+
"caption": "Connection error",
|
|
12272
|
+
"source": "const ESC = '\\u001b'\n\nconst paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`\n\nconst COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]\n .map(code => paint(code, `${code} `.padEnd(4)))\n .join('')\n\nconst TRANSCRIPT = [\n paint(1, 'Sample session, written by this page'),\n '┌──────────────────────────────┐',\n `│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,\n '└──────────────────────────────┘',\n COLOURS,\n 'Docs at https://example.com/terminal',\n '',\n].join('\\r\\n')\n\nfunction SampleTerminal(props: Omit<TerminalProps, 'ref'>) {\n const term = useRef<TerminalHandle>(null)\n useEffect(() => term.current?.write(TRANSCRIPT), [])\n return <Terminal ref={term} className=\"h-64 max-w-3xl\" {...props} />\n}\n\n<SampleTerminal label=\"Review agent\" status=\"error\" statusMessage=\"The connection was refused.\" readOnly />"
|
|
12273
|
+
},
|
|
12274
|
+
{
|
|
12275
|
+
"caption": "Switching theme updates the colours and keeps the buffer",
|
|
12276
|
+
"source": "const ESC = '\\u001b'\n\nconst paint = (code: number, text: string) => `${ESC}[${code}m${text}${ESC}[0m`\n\nconst COLOURS = [31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97]\n .map(code => paint(code, `${code} `.padEnd(4)))\n .join('')\n\nconst TRANSCRIPT = [\n paint(1, 'Sample session, written by this page'),\n '┌──────────────────────────────┐',\n `│ ${paint(32, '✔')} tests passed 宽字符 🚀 │`,\n '└──────────────────────────────┘',\n COLOURS,\n 'Docs at https://example.com/terminal',\n '',\n].join('\\r\\n')\n\nfunction SampleTerminal(props: Omit<TerminalProps, 'ref'>) {\n const term = useRef<TerminalHandle>(null)\n useEffect(() => term.current?.write(TRANSCRIPT), [])\n return <Terminal ref={term} className=\"h-64 max-w-3xl\" {...props} />\n}\n\n<div className=\"flex max-w-3xl flex-col gap-3\">\n <SampleTerminal label=\"Theme check\" status=\"live\" readOnly />\n <div className=\"flex flex-wrap gap-3\">\n <Button variant=\"secondary\" onClick={() => setTheme('light')}>Light theme</Button>\n <Button variant=\"secondary\" onClick={() => setTheme('dark')}>Dark theme</Button>\n </div>\n</div>"
|
|
12277
|
+
}
|
|
12278
|
+
],
|
|
12279
|
+
"closed": true
|
|
12044
12280
|
}
|
|
12045
12281
|
]
|
|
12046
12282
|
}
|
|
@@ -5,57 +5,57 @@
|
|
|
5
5
|
{
|
|
6
6
|
"name": "--color-signal-50",
|
|
7
7
|
"layer": "primitive",
|
|
8
|
-
"value": "#
|
|
8
|
+
"value": "#fff4f0"
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"name": "--color-signal-100",
|
|
12
12
|
"layer": "primitive",
|
|
13
|
-
"value": "#
|
|
13
|
+
"value": "#ffccb8"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"name": "--color-signal-200",
|
|
17
17
|
"layer": "primitive",
|
|
18
|
-
"value": "#
|
|
18
|
+
"value": "#ffa581"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"name": "--color-signal-300",
|
|
22
22
|
"layer": "primitive",
|
|
23
|
-
"value": "#
|
|
23
|
+
"value": "#ff7d49"
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"name": "--color-signal-400",
|
|
27
27
|
"layer": "primitive",
|
|
28
|
-
"value": "#
|
|
28
|
+
"value": "#ff5512"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"name": "--color-signal-500",
|
|
32
32
|
"layer": "primitive",
|
|
33
|
-
"value": "#
|
|
33
|
+
"value": "#d93e00"
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"name": "--color-signal-600",
|
|
37
37
|
"layer": "primitive",
|
|
38
|
-
"value": "#
|
|
38
|
+
"value": "#b13200"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"name": "--color-signal-700",
|
|
42
42
|
"layer": "primitive",
|
|
43
|
-
"value": "#
|
|
43
|
+
"value": "#882700"
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
"name": "--color-signal-800",
|
|
47
47
|
"layer": "primitive",
|
|
48
|
-
"value": "#
|
|
48
|
+
"value": "#601b00"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"name": "--color-signal-900",
|
|
52
52
|
"layer": "primitive",
|
|
53
|
-
"value": "#
|
|
53
|
+
"value": "#381000"
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
"name": "--color-signal-950",
|
|
57
57
|
"layer": "primitive",
|
|
58
|
-
"value": "#
|
|
58
|
+
"value": "#0f0400"
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"name": "--color-hostile-50",
|
|
@@ -566,7 +566,7 @@
|
|
|
566
566
|
{
|
|
567
567
|
"name": "--color-accent-signal-fill",
|
|
568
568
|
"layer": "semantic",
|
|
569
|
-
"value": "#
|
|
569
|
+
"value": "#d93e00",
|
|
570
570
|
"use": "A solid block in the brand colour, exactly as chosen: the primary Button, IconButton, a solid signal Badge. Put accent.signal.ink on it, never text.primary. Never use it as the only sign of a state; use accent.signal.indicator.",
|
|
571
571
|
"measured": {
|
|
572
572
|
"dark": "The brand colour, exactly as chosen: Button, IconButton and solid Badge backgrounds, and brand decoration. Text on it is accent.signal.ink. Never the only sign of a state; that is accent.signal.indicator. The theme builder never moves it.",
|
|
@@ -577,61 +577,61 @@
|
|
|
577
577
|
"name": "--color-accent-signal-text",
|
|
578
578
|
"layer": "semantic",
|
|
579
579
|
"value": {
|
|
580
|
-
"dark": "#
|
|
581
|
-
"light": "#
|
|
580
|
+
"dark": "#ff5512",
|
|
581
|
+
"light": "#b13200"
|
|
582
582
|
},
|
|
583
583
|
"use": "Orange text or icon on a surface: a current nav item, an emphasised figure, a link accent.",
|
|
584
584
|
"measured": {
|
|
585
|
-
"dark": "
|
|
586
|
-
"light": "
|
|
585
|
+
"dark": "6.26:1 on surface.base. A step lighter than the brand fill, which reaches 4.42:1 on the dark page, under the 4.5:1 text needs.",
|
|
586
|
+
"light": "5.73:1 on surface.base. A step darker than dark theme's 400; the brand fill itself reaches only 4.11:1 on the light page, under the 4.5:1 text needs."
|
|
587
587
|
}
|
|
588
588
|
},
|
|
589
589
|
{
|
|
590
590
|
"name": "--color-accent-signal-ink",
|
|
591
591
|
"layer": "semantic",
|
|
592
|
-
"value": "#
|
|
592
|
+
"value": "#ffffff",
|
|
593
593
|
"use": "Text and icons on accent.signal.fill.",
|
|
594
594
|
"measured": {
|
|
595
|
-
"dark": "
|
|
596
|
-
"light": "
|
|
595
|
+
"dark": "White, 4.53:1 on accent.signal.fill",
|
|
596
|
+
"light": "White, 4.53:1 on accent.signal.fill"
|
|
597
597
|
}
|
|
598
598
|
},
|
|
599
599
|
{
|
|
600
600
|
"name": "--color-accent-signal-indicator",
|
|
601
601
|
"layer": "semantic",
|
|
602
|
-
"value": "#
|
|
602
|
+
"value": "#d93e00",
|
|
603
603
|
"use": "The accent where colour alone shows a state or value: a checked control, Switch on, Progress, Slider, Rating stars, the current item in Sidebar, Navbar, Tabs, SegmentedControl, Stepper and highlighted menu rows. Put accent.signal.indicator-ink on it.",
|
|
604
604
|
"measured": {
|
|
605
|
-
"dark": "The accent where colour alone marks a state or a value: checked controls, Switch on, Progress, Slider, Rating stars, the current item of Sidebar, Navbar, Tabs, SegmentedControl, Stepper and highlighted menu rows, and the hero numerals.
|
|
606
|
-
"light": "The accent where colour alone marks a state or a value: checked controls, Switch on, Progress, Slider, Rating stars, the current item of Sidebar, Navbar, Tabs, SegmentedControl, Stepper and highlighted menu rows, and the hero numerals.
|
|
605
|
+
"dark": "The accent where colour alone marks a state or a value: checked controls, Switch on, Progress, Slider, Rating stars, the current item of Sidebar, Navbar, Tabs, SegmentedControl, Stepper and highlighted menu rows, and the hero numerals. 4.42:1 on surface.base. Same stop as accent.signal.fill here; the theme builder moves it to a nearby stop when a chosen accent is too pale or too dark to reach 3:1, and leaves accent.signal.fill alone.",
|
|
606
|
+
"light": "The accent where colour alone marks a state or a value: checked controls, Switch on, Progress, Slider, Rating stars, the current item of Sidebar, Navbar, Tabs, SegmentedControl, Stepper and highlighted menu rows, and the hero numerals. 4.11:1 on surface.base. Same stop as accent.signal.fill here; the theme builder moves it to a nearby stop when a chosen accent is too pale or too dark to reach 3:1, and leaves accent.signal.fill alone."
|
|
607
607
|
}
|
|
608
608
|
},
|
|
609
609
|
{
|
|
610
610
|
"name": "--color-accent-signal-indicator-ink",
|
|
611
611
|
"layer": "semantic",
|
|
612
|
-
"value": "#
|
|
612
|
+
"value": "#ffffff",
|
|
613
613
|
"use": "Text and icons on accent.signal.indicator.",
|
|
614
614
|
"measured": {
|
|
615
|
-
"dark": "Text and icons on accent.signal.indicator
|
|
616
|
-
"light": "Text and icons on accent.signal.indicator
|
|
615
|
+
"dark": "Text and icons on accent.signal.indicator: white, 4.53:1. The theme builder picks white or a dark ink, whichever reaches 4.5:1.",
|
|
616
|
+
"light": "Text and icons on accent.signal.indicator: white, 4.53:1. The theme builder picks white or a dark ink, whichever reaches 4.5:1."
|
|
617
617
|
}
|
|
618
618
|
},
|
|
619
619
|
{
|
|
620
620
|
"name": "--color-accent-signal-wash",
|
|
621
621
|
"layer": "semantic",
|
|
622
|
-
"value": "rgba(
|
|
622
|
+
"value": "rgba(217, 62, 0, 0.12)",
|
|
623
623
|
"use": "The faint orange hover wash under a nav item or an inactive tab.",
|
|
624
624
|
"measured": {
|
|
625
|
-
"dark": "Hover wash under a nav item or an inactive tab. Text on it is text.primary and gated.
|
|
626
|
-
"light": "Hover wash under a nav item or an inactive tab. Text on it is text.primary and gated.
|
|
625
|
+
"dark": "Hover wash under a nav item or an inactive tab. Text on it is text.primary and gated. Neither a checked control's fill nor the focus ring is gated on it, so neither is ever drawn on it.",
|
|
626
|
+
"light": "Hover wash under a nav item or an inactive tab. Text on it is text.primary and gated. Neither a checked control's indicator nor the focus ring is gated on it, so neither is ever drawn on it."
|
|
627
627
|
}
|
|
628
628
|
},
|
|
629
629
|
{
|
|
630
630
|
"name": "--color-accent-signal-glow",
|
|
631
631
|
"layer": "semantic",
|
|
632
632
|
"value": {
|
|
633
|
-
"dark": "rgba(
|
|
634
|
-
"light": "rgba(
|
|
633
|
+
"dark": "rgba(217, 62, 0, 0.38)",
|
|
634
|
+
"light": "rgba(217, 62, 0, 0.3)"
|
|
635
635
|
},
|
|
636
636
|
"use": "The faint shadow of an interactive Card on hover and focus. Used by the Card component only.",
|
|
637
637
|
"measured": {
|
|
@@ -655,7 +655,7 @@
|
|
|
655
655
|
"use": "Error and destructive text or icon on a surface: field errors, an invalid border, a failed status.",
|
|
656
656
|
"measured": {
|
|
657
657
|
"dark": "6.23:1 on surface.base, 5.42:1 on surface.raised, 4.80:1 on raised nested in raised, 5.92:1 on surface.sunken. hostile.500 measured 4.48:1 on surface.raised, under 4.5:1.",
|
|
658
|
-
"light": "
|
|
658
|
+
"light": "5.73:1 on surface.base. A step darker than dark theme's 400; the brand fill itself reaches only 4.11:1 on the light page, under the 4.5:1 text needs."
|
|
659
659
|
}
|
|
660
660
|
},
|
|
661
661
|
{
|
|
@@ -765,7 +765,7 @@
|
|
|
765
765
|
"use": "The dimmed leading zeros of a hero numeral. Decorative; never text that has to be read.",
|
|
766
766
|
"measured": {
|
|
767
767
|
"dark": "The dimmed leading-zero pad on a hero numeral (Overview, Progress readout, docs section index). Decorative and aria-hidden: it is a column width, never a value, so no SC 1.4.3 obligation attaches to it. 5.35:1 on surface.base, which is 31% of text.primary's 17.17:1 there -- that share, not the absolute ratio, is what makes it read as padding rather than as a digit. Holds the same value text.muted holds in this theme and is deliberately NOT an alias of it: text.muted is real secondary text with an AA floor, and the two move for different reasons.",
|
|
768
|
-
"light": "The dimmed leading-zero pad. 3.82:1 on surface.base, 22% of text.primary's 17.55:1 there, against the dark theme's 31%. Light theme needs a LIGHTER step than text.muted for the same role: muted aliases neutral.500 and measures 11.44:1 here, 65% of ink, which does not read as dimmed and out-shouts the signal.fill numeral it is padding (
|
|
768
|
+
"light": "The dimmed leading-zero pad. 3.82:1 on surface.base, 22% of text.primary's 17.55:1 there, against the dark theme's 31%. Light theme needs a LIGHTER step than text.muted for the same role: muted aliases neutral.500 and measures 11.44:1 here, 65% of ink, which does not read as dimmed and out-shouts the signal.fill numeral it is padding (4.11:1 on this surface). neutral.400 would land at 6.58:1 and 38% of ink, closer to dark's share but more than twice the contrast of the digits beside it, so neutral.300 is the step that keeps the pad quieter than what it pads."
|
|
769
769
|
}
|
|
770
770
|
},
|
|
771
771
|
{
|
|
@@ -811,25 +811,23 @@
|
|
|
811
811
|
"name": "--color-focus-ring",
|
|
812
812
|
"layer": "semantic",
|
|
813
813
|
"value": {
|
|
814
|
-
"dark": "#
|
|
815
|
-
"light": "#
|
|
814
|
+
"dark": "#ff5512",
|
|
815
|
+
"light": "#d93e00"
|
|
816
816
|
},
|
|
817
817
|
"use": "The keyboard focus outline. Use it only in focus-visible:outline-[var(--color-focus-ring)], with a 2px outline and a 2px offset.",
|
|
818
818
|
"measured": {
|
|
819
|
-
"
|
|
819
|
+
"dark": "6.26:1 on surface.base. A step lighter than accent.signal.fill, which reaches 3.41:1 on nested panels: the ring is the one mark every keyboard user depends on, so it gets a margin.",
|
|
820
|
+
"light": "4.11:1 on surface.base. The brand colour itself: it clears 3:1 on every light surface, so the ring no longer needs a step of its own."
|
|
820
821
|
}
|
|
821
822
|
},
|
|
822
823
|
{
|
|
823
824
|
"name": "--color-chart-series-1",
|
|
824
825
|
"layer": "semantic",
|
|
825
|
-
"value":
|
|
826
|
-
"dark": "#f2550f",
|
|
827
|
-
"light": "#c6440b"
|
|
828
|
-
},
|
|
826
|
+
"value": "#d93e00",
|
|
829
827
|
"use": "The first data series in a chart. A graphic colour, never text. ChartPanel applies it.",
|
|
830
828
|
"measured": {
|
|
831
|
-
"dark": "First chart series: a line, a bar, a legend swatch. A graphic, 3:1:
|
|
832
|
-
"light": "First chart series: a line, a bar, a legend swatch. A graphic, 3:1: 4.
|
|
829
|
+
"dark": "First chart series: a line, a bar, a legend swatch. A graphic, 3:1: 4.42:1 on surface.base, 3.85:1 on surface.raised, 3.41:1 nested. Never text.",
|
|
830
|
+
"light": "First chart series: a line, a bar, a legend swatch. A graphic, 3:1: 4.11:1 on surface.base, 4.37:1 on surface.raised, 4.45:1 nested. Never text."
|
|
833
831
|
}
|
|
834
832
|
},
|
|
835
833
|
{
|
|
@@ -845,6 +843,174 @@
|
|
|
845
843
|
"light": "Second chart series. 5.09:1 on surface.base, 5.41:1 on surface.raised, 5.52:1 nested. Never text. There is no third: the other hues are status colours."
|
|
846
844
|
}
|
|
847
845
|
},
|
|
846
|
+
{
|
|
847
|
+
"name": "--color-terminal-black",
|
|
848
|
+
"layer": "semantic",
|
|
849
|
+
"value": {
|
|
850
|
+
"dark": "#7b8592",
|
|
851
|
+
"light": "#3a4049"
|
|
852
|
+
},
|
|
853
|
+
"use": "ANSI black in the Terminal component's output. Terminal applies it; never use it outside a terminal.",
|
|
854
|
+
"measured": {
|
|
855
|
+
"dark": "Not literal black: ANSI black must stay readable on this page, so it is a grey here.",
|
|
856
|
+
"light": "Not literal black: ANSI black must stay readable on this page, so it is a grey here."
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
"name": "--color-terminal-red",
|
|
861
|
+
"layer": "semantic",
|
|
862
|
+
"value": {
|
|
863
|
+
"dark": "#ff6b5e",
|
|
864
|
+
"light": "#b3261e"
|
|
865
|
+
},
|
|
866
|
+
"use": "ANSI red in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
"name": "--color-terminal-green",
|
|
870
|
+
"layer": "semantic",
|
|
871
|
+
"value": {
|
|
872
|
+
"dark": "#3ddc84",
|
|
873
|
+
"light": "#14713d"
|
|
874
|
+
},
|
|
875
|
+
"use": "ANSI green in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
"name": "--color-terminal-yellow",
|
|
879
|
+
"layer": "semantic",
|
|
880
|
+
"value": {
|
|
881
|
+
"dark": "#e8b931",
|
|
882
|
+
"light": "#7a5a00"
|
|
883
|
+
},
|
|
884
|
+
"use": "ANSI yellow in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
"name": "--color-terminal-blue",
|
|
888
|
+
"layer": "semantic",
|
|
889
|
+
"value": {
|
|
890
|
+
"dark": "#5aa7ff",
|
|
891
|
+
"light": "#0057c2"
|
|
892
|
+
},
|
|
893
|
+
"use": "ANSI blue in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
"name": "--color-terminal-magenta",
|
|
897
|
+
"layer": "semantic",
|
|
898
|
+
"value": {
|
|
899
|
+
"dark": "#d98cf0",
|
|
900
|
+
"light": "#8a2bb0"
|
|
901
|
+
},
|
|
902
|
+
"use": "ANSI magenta in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
"name": "--color-terminal-cyan",
|
|
906
|
+
"layer": "semantic",
|
|
907
|
+
"value": {
|
|
908
|
+
"dark": "#3fd0d8",
|
|
909
|
+
"light": "#00696f"
|
|
910
|
+
},
|
|
911
|
+
"use": "ANSI cyan in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
"name": "--color-terminal-white",
|
|
915
|
+
"layer": "semantic",
|
|
916
|
+
"value": {
|
|
917
|
+
"dark": "#c4ccd6",
|
|
918
|
+
"light": "#5c636d"
|
|
919
|
+
},
|
|
920
|
+
"use": "ANSI white in the Terminal component's output. Terminal applies it; never use it outside a terminal.",
|
|
921
|
+
"measured": {
|
|
922
|
+
"dark": "Not literal white: ANSI white must stay readable on this page, so it is a grey here.",
|
|
923
|
+
"light": "Not literal white: ANSI white must stay readable on this page, so it is a grey here."
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
"name": "--color-terminal-bright-black",
|
|
928
|
+
"layer": "semantic",
|
|
929
|
+
"value": {
|
|
930
|
+
"dark": "#98a2af",
|
|
931
|
+
"light": "#646b75"
|
|
932
|
+
},
|
|
933
|
+
"use": "ANSI bright black in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
"name": "--color-terminal-bright-red",
|
|
937
|
+
"layer": "semantic",
|
|
938
|
+
"value": {
|
|
939
|
+
"dark": "#ff9288",
|
|
940
|
+
"light": "#8f1c16"
|
|
941
|
+
},
|
|
942
|
+
"use": "ANSI bright red in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"name": "--color-terminal-bright-green",
|
|
946
|
+
"layer": "semantic",
|
|
947
|
+
"value": {
|
|
948
|
+
"dark": "#7debaa",
|
|
949
|
+
"light": "#0e5a30"
|
|
950
|
+
},
|
|
951
|
+
"use": "ANSI bright green in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
"name": "--color-terminal-bright-yellow",
|
|
955
|
+
"layer": "semantic",
|
|
956
|
+
"value": {
|
|
957
|
+
"dark": "#f5d36a",
|
|
958
|
+
"light": "#5f4600"
|
|
959
|
+
},
|
|
960
|
+
"use": "ANSI bright yellow in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
961
|
+
},
|
|
962
|
+
{
|
|
963
|
+
"name": "--color-terminal-bright-blue",
|
|
964
|
+
"layer": "semantic",
|
|
965
|
+
"value": {
|
|
966
|
+
"dark": "#8cc2ff",
|
|
967
|
+
"light": "#00459c"
|
|
968
|
+
},
|
|
969
|
+
"use": "ANSI bright blue in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
"name": "--color-terminal-bright-magenta",
|
|
973
|
+
"layer": "semantic",
|
|
974
|
+
"value": {
|
|
975
|
+
"dark": "#e8b3f7",
|
|
976
|
+
"light": "#6e1f8e"
|
|
977
|
+
},
|
|
978
|
+
"use": "ANSI bright magenta in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
"name": "--color-terminal-bright-cyan",
|
|
982
|
+
"layer": "semantic",
|
|
983
|
+
"value": {
|
|
984
|
+
"dark": "#7fe4ea",
|
|
985
|
+
"light": "#005257"
|
|
986
|
+
},
|
|
987
|
+
"use": "ANSI bright cyan in the Terminal component's output. Terminal applies it; never use it outside a terminal."
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
"name": "--color-terminal-bright-white",
|
|
991
|
+
"layer": "semantic",
|
|
992
|
+
"value": {
|
|
993
|
+
"dark": "#f4f7fa",
|
|
994
|
+
"light": "#16191e"
|
|
995
|
+
},
|
|
996
|
+
"use": "ANSI bright white in the Terminal component's output. Terminal applies it; never use it outside a terminal.",
|
|
997
|
+
"measured": {
|
|
998
|
+
"light": "Inverted on purpose in the light theme: the strongest ink, not a pale colour."
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
"name": "--color-terminal-selection",
|
|
1003
|
+
"layer": "semantic",
|
|
1004
|
+
"value": {
|
|
1005
|
+
"dark": "rgba(90, 167, 255, 0.58)",
|
|
1006
|
+
"light": "rgba(0, 99, 213, 0.72)"
|
|
1007
|
+
},
|
|
1008
|
+
"use": "The selected text wash in the Terminal component. Terminal applies it.",
|
|
1009
|
+
"measured": {
|
|
1010
|
+
"dark": "A wash over surface.base: at least 3:1 against the page and text.primary at 4.5:1 on it. Gated.",
|
|
1011
|
+
"light": "A wash over surface.base: at least 3:1 against the page and text.primary at 4.5:1 on it. Gated."
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
848
1014
|
{
|
|
849
1015
|
"name": "--component-control-height-sm",
|
|
850
1016
|
"layer": "component",
|
|
@@ -62,7 +62,16 @@ export function publicExports(root, tier) {
|
|
|
62
62
|
entry[isType ? 'types' : 'values'].push(element.name.text)
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
return bySlug
|
|
65
|
+
return { ...bySlug, ...OUTSIDE_BARREL[tier] }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Components deliberately not re-exported by their tier's barrel, because a barrel import would
|
|
69
|
+
* put a heavy library in every screen that imports the barrel. They are still public: their docs
|
|
70
|
+
* page gives the direct import. */
|
|
71
|
+
const OUTSIDE_BARREL = {
|
|
72
|
+
organisms: {
|
|
73
|
+
terminal: { values: ['Terminal'], types: ['TerminalHandle', 'TerminalProps', 'TerminalStatus'], from: ['./terminal/Terminal'] },
|
|
74
|
+
},
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
/** What a folder's files export, read from the type checker:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: division-ds
|
|
3
|
-
description: Use when building or changing UI with Division DS (React 19, Tailwind 4, Radix;
|
|
3
|
+
description: Use when building or changing UI with Division DS (React 19, Tailwind 4, Radix; 75 components). Covers which component and token to use, the composition rules prop types cannot express (providers, FormField, button gaps, card borders), planning a screen's states, and how to check the result. Load it before writing any screen or component in a project that has Division DS components.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Division DS
|