division-ds 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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: 74 components, tokens in three layers, light and
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.3.1 mcp` (`list_components`, `get_component`,
54
+ `claude mcp add division-ds -- npx -y division-ds@0.4.0 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.4.0",
3
+ "version": "0.4.1",
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
  }
@@ -845,6 +845,174 @@
845
845
  "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
846
  }
847
847
  },
848
+ {
849
+ "name": "--color-terminal-black",
850
+ "layer": "semantic",
851
+ "value": {
852
+ "dark": "#7b8592",
853
+ "light": "#3a4049"
854
+ },
855
+ "use": "ANSI black in the Terminal component's output. Terminal applies it; never use it outside a terminal.",
856
+ "measured": {
857
+ "dark": "Not literal black: ANSI black must stay readable on this page, so it is a grey here.",
858
+ "light": "Not literal black: ANSI black must stay readable on this page, so it is a grey here."
859
+ }
860
+ },
861
+ {
862
+ "name": "--color-terminal-red",
863
+ "layer": "semantic",
864
+ "value": {
865
+ "dark": "#ff6b5e",
866
+ "light": "#b3261e"
867
+ },
868
+ "use": "ANSI red in the Terminal component's output. Terminal applies it; never use it outside a terminal."
869
+ },
870
+ {
871
+ "name": "--color-terminal-green",
872
+ "layer": "semantic",
873
+ "value": {
874
+ "dark": "#3ddc84",
875
+ "light": "#14713d"
876
+ },
877
+ "use": "ANSI green in the Terminal component's output. Terminal applies it; never use it outside a terminal."
878
+ },
879
+ {
880
+ "name": "--color-terminal-yellow",
881
+ "layer": "semantic",
882
+ "value": {
883
+ "dark": "#e8b931",
884
+ "light": "#7a5a00"
885
+ },
886
+ "use": "ANSI yellow in the Terminal component's output. Terminal applies it; never use it outside a terminal."
887
+ },
888
+ {
889
+ "name": "--color-terminal-blue",
890
+ "layer": "semantic",
891
+ "value": {
892
+ "dark": "#5aa7ff",
893
+ "light": "#0057c2"
894
+ },
895
+ "use": "ANSI blue in the Terminal component's output. Terminal applies it; never use it outside a terminal."
896
+ },
897
+ {
898
+ "name": "--color-terminal-magenta",
899
+ "layer": "semantic",
900
+ "value": {
901
+ "dark": "#d98cf0",
902
+ "light": "#8a2bb0"
903
+ },
904
+ "use": "ANSI magenta in the Terminal component's output. Terminal applies it; never use it outside a terminal."
905
+ },
906
+ {
907
+ "name": "--color-terminal-cyan",
908
+ "layer": "semantic",
909
+ "value": {
910
+ "dark": "#3fd0d8",
911
+ "light": "#00696f"
912
+ },
913
+ "use": "ANSI cyan in the Terminal component's output. Terminal applies it; never use it outside a terminal."
914
+ },
915
+ {
916
+ "name": "--color-terminal-white",
917
+ "layer": "semantic",
918
+ "value": {
919
+ "dark": "#c4ccd6",
920
+ "light": "#5c636d"
921
+ },
922
+ "use": "ANSI white in the Terminal component's output. Terminal applies it; never use it outside a terminal.",
923
+ "measured": {
924
+ "dark": "Not literal white: ANSI white must stay readable on this page, so it is a grey here.",
925
+ "light": "Not literal white: ANSI white must stay readable on this page, so it is a grey here."
926
+ }
927
+ },
928
+ {
929
+ "name": "--color-terminal-bright-black",
930
+ "layer": "semantic",
931
+ "value": {
932
+ "dark": "#98a2af",
933
+ "light": "#646b75"
934
+ },
935
+ "use": "ANSI bright black in the Terminal component's output. Terminal applies it; never use it outside a terminal."
936
+ },
937
+ {
938
+ "name": "--color-terminal-bright-red",
939
+ "layer": "semantic",
940
+ "value": {
941
+ "dark": "#ff9288",
942
+ "light": "#8f1c16"
943
+ },
944
+ "use": "ANSI bright red in the Terminal component's output. Terminal applies it; never use it outside a terminal."
945
+ },
946
+ {
947
+ "name": "--color-terminal-bright-green",
948
+ "layer": "semantic",
949
+ "value": {
950
+ "dark": "#7debaa",
951
+ "light": "#0e5a30"
952
+ },
953
+ "use": "ANSI bright green in the Terminal component's output. Terminal applies it; never use it outside a terminal."
954
+ },
955
+ {
956
+ "name": "--color-terminal-bright-yellow",
957
+ "layer": "semantic",
958
+ "value": {
959
+ "dark": "#f5d36a",
960
+ "light": "#5f4600"
961
+ },
962
+ "use": "ANSI bright yellow in the Terminal component's output. Terminal applies it; never use it outside a terminal."
963
+ },
964
+ {
965
+ "name": "--color-terminal-bright-blue",
966
+ "layer": "semantic",
967
+ "value": {
968
+ "dark": "#8cc2ff",
969
+ "light": "#00459c"
970
+ },
971
+ "use": "ANSI bright blue in the Terminal component's output. Terminal applies it; never use it outside a terminal."
972
+ },
973
+ {
974
+ "name": "--color-terminal-bright-magenta",
975
+ "layer": "semantic",
976
+ "value": {
977
+ "dark": "#e8b3f7",
978
+ "light": "#6e1f8e"
979
+ },
980
+ "use": "ANSI bright magenta in the Terminal component's output. Terminal applies it; never use it outside a terminal."
981
+ },
982
+ {
983
+ "name": "--color-terminal-bright-cyan",
984
+ "layer": "semantic",
985
+ "value": {
986
+ "dark": "#7fe4ea",
987
+ "light": "#005257"
988
+ },
989
+ "use": "ANSI bright cyan in the Terminal component's output. Terminal applies it; never use it outside a terminal."
990
+ },
991
+ {
992
+ "name": "--color-terminal-bright-white",
993
+ "layer": "semantic",
994
+ "value": {
995
+ "dark": "#f4f7fa",
996
+ "light": "#16191e"
997
+ },
998
+ "use": "ANSI bright white in the Terminal component's output. Terminal applies it; never use it outside a terminal.",
999
+ "measured": {
1000
+ "light": "Inverted on purpose in the light theme: the strongest ink, not a pale colour."
1001
+ }
1002
+ },
1003
+ {
1004
+ "name": "--color-terminal-selection",
1005
+ "layer": "semantic",
1006
+ "value": {
1007
+ "dark": "rgba(90, 167, 255, 0.58)",
1008
+ "light": "rgba(0, 99, 213, 0.72)"
1009
+ },
1010
+ "use": "The selected text wash in the Terminal component. Terminal applies it.",
1011
+ "measured": {
1012
+ "dark": "A wash over surface.base: at least 3:1 against the page and text.primary at 4.5:1 on it. Gated.",
1013
+ "light": "A wash over surface.base: at least 3:1 against the page and text.primary at 4.5:1 on it. Gated."
1014
+ }
1015
+ },
848
1016
  {
849
1017
  "name": "--component-control-height-sm",
850
1018
  "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; 74 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.
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