@dealdeploy/skl 0.1.7 → 0.2.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.
Files changed (55) hide show
  1. package/.agents/skills/opentui/SKILL.md +198 -0
  2. package/.agents/skills/opentui/references/animation/REFERENCE.md +431 -0
  3. package/.agents/skills/opentui/references/components/REFERENCE.md +143 -0
  4. package/.agents/skills/opentui/references/components/code-diff.md +496 -0
  5. package/.agents/skills/opentui/references/components/containers.md +412 -0
  6. package/.agents/skills/opentui/references/components/inputs.md +531 -0
  7. package/.agents/skills/opentui/references/components/text-display.md +384 -0
  8. package/.agents/skills/opentui/references/core/REFERENCE.md +145 -0
  9. package/.agents/skills/opentui/references/core/api.md +506 -0
  10. package/.agents/skills/opentui/references/core/configuration.md +166 -0
  11. package/.agents/skills/opentui/references/core/gotchas.md +393 -0
  12. package/.agents/skills/opentui/references/core/patterns.md +448 -0
  13. package/.agents/skills/opentui/references/keyboard/REFERENCE.md +511 -0
  14. package/.agents/skills/opentui/references/layout/REFERENCE.md +337 -0
  15. package/.agents/skills/opentui/references/layout/patterns.md +444 -0
  16. package/.agents/skills/opentui/references/react/REFERENCE.md +174 -0
  17. package/.agents/skills/opentui/references/react/api.md +435 -0
  18. package/.agents/skills/opentui/references/react/configuration.md +301 -0
  19. package/.agents/skills/opentui/references/react/gotchas.md +443 -0
  20. package/.agents/skills/opentui/references/react/patterns.md +501 -0
  21. package/.agents/skills/opentui/references/solid/REFERENCE.md +201 -0
  22. package/.agents/skills/opentui/references/solid/api.md +543 -0
  23. package/.agents/skills/opentui/references/solid/configuration.md +315 -0
  24. package/.agents/skills/opentui/references/solid/gotchas.md +415 -0
  25. package/.agents/skills/opentui/references/solid/patterns.md +558 -0
  26. package/.agents/skills/opentui/references/testing/REFERENCE.md +614 -0
  27. package/.claude/skills/opentui/SKILL.md +198 -0
  28. package/.claude/skills/opentui/references/animation/REFERENCE.md +431 -0
  29. package/.claude/skills/opentui/references/components/REFERENCE.md +143 -0
  30. package/.claude/skills/opentui/references/components/code-diff.md +496 -0
  31. package/.claude/skills/opentui/references/components/containers.md +412 -0
  32. package/.claude/skills/opentui/references/components/inputs.md +531 -0
  33. package/.claude/skills/opentui/references/components/text-display.md +384 -0
  34. package/.claude/skills/opentui/references/core/REFERENCE.md +145 -0
  35. package/.claude/skills/opentui/references/core/api.md +506 -0
  36. package/.claude/skills/opentui/references/core/configuration.md +166 -0
  37. package/.claude/skills/opentui/references/core/gotchas.md +393 -0
  38. package/.claude/skills/opentui/references/core/patterns.md +448 -0
  39. package/.claude/skills/opentui/references/keyboard/REFERENCE.md +511 -0
  40. package/.claude/skills/opentui/references/layout/REFERENCE.md +337 -0
  41. package/.claude/skills/opentui/references/layout/patterns.md +444 -0
  42. package/.claude/skills/opentui/references/react/REFERENCE.md +174 -0
  43. package/.claude/skills/opentui/references/react/api.md +435 -0
  44. package/.claude/skills/opentui/references/react/configuration.md +301 -0
  45. package/.claude/skills/opentui/references/react/gotchas.md +443 -0
  46. package/.claude/skills/opentui/references/react/patterns.md +501 -0
  47. package/.claude/skills/opentui/references/solid/REFERENCE.md +201 -0
  48. package/.claude/skills/opentui/references/solid/api.md +543 -0
  49. package/.claude/skills/opentui/references/solid/configuration.md +315 -0
  50. package/.claude/skills/opentui/references/solid/gotchas.md +415 -0
  51. package/.claude/skills/opentui/references/solid/patterns.md +558 -0
  52. package/.claude/skills/opentui/references/testing/REFERENCE.md +614 -0
  53. package/index.ts +169 -38
  54. package/package.json +1 -1
  55. package/update.ts +87 -0
@@ -0,0 +1,435 @@
1
+ # React API Reference
2
+
3
+ ## Rendering
4
+
5
+ ### createRoot(renderer)
6
+
7
+ Creates a React root for rendering.
8
+
9
+ ```tsx
10
+ import { createCliRenderer } from "@opentui/core"
11
+ import { createRoot } from "@opentui/react"
12
+
13
+ const renderer = await createCliRenderer({
14
+ exitOnCtrlC: false, // Handle Ctrl+C yourself
15
+ })
16
+
17
+ const root = createRoot(renderer)
18
+ root.render(<App />)
19
+ ```
20
+
21
+ ## Hooks
22
+
23
+ ### useRenderer()
24
+
25
+ Access the OpenTUI renderer instance.
26
+
27
+ ```tsx
28
+ import { useRenderer } from "@opentui/react"
29
+ import { useEffect } from "react"
30
+
31
+ function App() {
32
+ const renderer = useRenderer()
33
+
34
+ useEffect(() => {
35
+ // Access renderer properties
36
+ console.log(`Terminal: ${renderer.width}x${renderer.height}`)
37
+
38
+ // Show debug console
39
+ renderer.console.show()
40
+
41
+ // Access theme mode (dark/light based on terminal settings)
42
+ console.log(`Theme: ${renderer.themeMode}`) // "dark" | "light" | null
43
+ }, [renderer])
44
+
45
+ return <text>Hello</text>
46
+ }
47
+
48
+ // Listen for theme mode changes
49
+ function ThemedApp() {
50
+ const renderer = useRenderer()
51
+ const [theme, setTheme] = useState(renderer.themeMode ?? "dark")
52
+
53
+ useEffect(() => {
54
+ const handler = (mode: "dark" | "light") => setTheme(mode)
55
+ renderer.on("theme_mode", handler)
56
+ return () => renderer.off("theme_mode", handler)
57
+ }, [renderer])
58
+
59
+ return (
60
+ <box backgroundColor={theme === "dark" ? "#1a1a2e" : "#ffffff"}>
61
+ <text fg={theme === "dark" ? "#fff" : "#000"}>
62
+ Current theme: {theme}
63
+ </text>
64
+ </box>
65
+ )
66
+ }
67
+ ```
68
+
69
+ ### useKeyboard(handler, options?)
70
+
71
+ Handle keyboard events.
72
+
73
+ ```tsx
74
+ import { useKeyboard, useRenderer } from "@opentui/react"
75
+
76
+ function App() {
77
+ const renderer = useRenderer()
78
+
79
+ useKeyboard((key) => {
80
+ if (key.name === "escape") {
81
+ renderer.destroy() // Never use process.exit() directly!
82
+ }
83
+ if (key.ctrl && key.name === "s") {
84
+ saveDocument()
85
+ }
86
+ })
87
+
88
+ return <text>Press ESC to exit</text>
89
+ }
90
+
91
+ // With release events
92
+ function GameControls() {
93
+ const [pressed, setPressed] = useState(new Set<string>())
94
+
95
+ useKeyboard(
96
+ (event) => {
97
+ setPressed(keys => {
98
+ const newKeys = new Set(keys)
99
+ if (event.eventType === "release") {
100
+ newKeys.delete(event.name)
101
+ } else {
102
+ newKeys.add(event.name)
103
+ }
104
+ return newKeys
105
+ })
106
+ },
107
+ { release: true } // Include release events
108
+ )
109
+
110
+ return <text>Pressed: {Array.from(pressed).join(", ")}</text>
111
+ }
112
+ ```
113
+
114
+ **Options:**
115
+ - `release?: boolean` - Include key release events (default: false)
116
+
117
+ **KeyEvent properties:**
118
+ - `name: string` - Key name ("a", "escape", "f1", etc.)
119
+ - `sequence: string` - Raw escape sequence
120
+ - `ctrl: boolean` - Ctrl modifier
121
+ - `shift: boolean` - Shift modifier
122
+ - `meta: boolean` - Alt modifier
123
+ - `option: boolean` - Option modifier (macOS)
124
+ - `eventType: "press" | "release" | "repeat"`
125
+ - `repeated: boolean` - Key is being held
126
+
127
+ ### useOnResize(callback)
128
+
129
+ Handle terminal resize events.
130
+
131
+ ```tsx
132
+ import { useOnResize } from "@opentui/react"
133
+
134
+ function App() {
135
+ useOnResize((width, height) => {
136
+ console.log(`Resized to ${width}x${height}`)
137
+ })
138
+
139
+ return <text>Resize the terminal</text>
140
+ }
141
+ ```
142
+
143
+ ### useTerminalDimensions()
144
+
145
+ Get reactive terminal dimensions.
146
+
147
+ ```tsx
148
+ import { useTerminalDimensions } from "@opentui/react"
149
+
150
+ function ResponsiveLayout() {
151
+ const { width, height } = useTerminalDimensions()
152
+
153
+ return (
154
+ <box flexDirection={width > 80 ? "row" : "column"}>
155
+ <box flexGrow={1}>
156
+ <text>Width: {width}</text>
157
+ </box>
158
+ <box flexGrow={1}>
159
+ <text>Height: {height}</text>
160
+ </box>
161
+ </box>
162
+ )
163
+ }
164
+ ```
165
+
166
+ ### useTimeline(options?)
167
+
168
+ Create animations with the timeline system.
169
+
170
+ ```tsx
171
+ import { useTimeline } from "@opentui/react"
172
+ import { useEffect, useState } from "react"
173
+
174
+ function AnimatedBox() {
175
+ const [width, setWidth] = useState(0)
176
+
177
+ const timeline = useTimeline({
178
+ duration: 2000,
179
+ loop: false,
180
+ })
181
+
182
+ useEffect(() => {
183
+ timeline.add(
184
+ { width: 0 },
185
+ {
186
+ width: 50,
187
+ duration: 2000,
188
+ ease: "easeOutQuad",
189
+ onUpdate: (anim) => {
190
+ setWidth(Math.round(anim.targets[0].width))
191
+ },
192
+ }
193
+ )
194
+ }, [timeline])
195
+
196
+ return <box style={{ width, height: 3, backgroundColor: "#6a5acd" }} />
197
+ }
198
+ ```
199
+
200
+ **Options:**
201
+ - `duration?: number` - Default duration (ms)
202
+ - `loop?: boolean` - Loop the timeline
203
+ - `autoplay?: boolean` - Auto-start (default: true)
204
+ - `onComplete?: () => void` - Completion callback
205
+ - `onPause?: () => void` - Pause callback
206
+
207
+ **Timeline methods:**
208
+ - `add(target, properties, startTime?)` - Add animation
209
+ - `play()` - Start playback
210
+ - `pause()` - Pause playback
211
+ - `restart()` - Restart from beginning
212
+
213
+ ## Components
214
+
215
+ ### Text Component
216
+
217
+ ```tsx
218
+ <text
219
+ content="Hello" // Or use children
220
+ fg="#FFFFFF" // Foreground color
221
+ bg="#000000" // Background color
222
+ selectable={true} // Allow text selection
223
+ >
224
+ {/* Use nested modifier tags for styling */}
225
+ <span fg="red">Red</span>
226
+ <strong>Bold</strong>
227
+ <em>Italic</em>
228
+ <u>Underline</u>
229
+ <br />
230
+ <a href="https://...">Link</a>
231
+ </text>
232
+ ```
233
+
234
+ > **Note**: Do NOT use `bold`, `italic`, `underline` as props on `<text>`. Use nested modifier tags like `<strong>`, `<em>`, `<u>` instead.
235
+
236
+ ### Box Component
237
+
238
+ ```tsx
239
+ <box
240
+ // Borders
241
+ border // Enable border
242
+ borderStyle="single" // single | double | rounded | bold
243
+ borderColor="#FFFFFF"
244
+ title="Title"
245
+ titleAlignment="center" // left | center | right
246
+
247
+ // Colors
248
+ backgroundColor="#1a1a2e"
249
+
250
+ // Layout (see layout/REFERENCE.md)
251
+ flexDirection="row"
252
+ justifyContent="center"
253
+ alignItems="center"
254
+ gap={2}
255
+
256
+ // Spacing
257
+ padding={2}
258
+ paddingTop={1}
259
+ paddingX={2} // Horizontal (left + right)
260
+ paddingY={1} // Vertical (top + bottom)
261
+ margin={1}
262
+ marginX={2} // Horizontal (left + right)
263
+ marginY={1} // Vertical (top + bottom)
264
+
265
+ // Dimensions
266
+ width={40}
267
+ height={10}
268
+ flexGrow={1}
269
+
270
+ // Focus
271
+ focusable // Allow box to receive focus
272
+ focused={isFocused} // Controlled focus state
273
+
274
+ // Events
275
+ onMouseDown={(e) => {}}
276
+ onMouseUp={(e) => {}}
277
+ onMouseMove={(e) => {}}
278
+ >
279
+ {children}
280
+ </box>
281
+ ```
282
+
283
+ ### Scrollbox Component
284
+
285
+ ```tsx
286
+ <scrollbox
287
+ focused // Enable keyboard scrolling
288
+ style={{
289
+ rootOptions: { backgroundColor: "#24283b" },
290
+ wrapperOptions: { backgroundColor: "#1f2335" },
291
+ viewportOptions: { backgroundColor: "#1a1b26" },
292
+ contentOptions: { backgroundColor: "#16161e" },
293
+ scrollbarOptions: {
294
+ showArrows: true,
295
+ trackOptions: {
296
+ foregroundColor: "#7aa2f7",
297
+ backgroundColor: "#414868",
298
+ },
299
+ },
300
+ }}
301
+ >
302
+ {/* Scrollable content */}
303
+ {items.map((item, i) => (
304
+ <box key={i}>
305
+ <text>{item}</text>
306
+ </box>
307
+ ))}
308
+ </scrollbox>
309
+ ```
310
+
311
+ ### Input Component
312
+
313
+ ```tsx
314
+ <input
315
+ value={value}
316
+ onChange={(newValue) => setValue(newValue)}
317
+ placeholder="Enter text..."
318
+ focused // Start focused
319
+ width={30}
320
+ backgroundColor="#1a1a1a"
321
+ textColor="#FFFFFF"
322
+ cursorColor="#00FF00"
323
+ focusedBackgroundColor="#2a2a2a"
324
+ />
325
+ ```
326
+
327
+ ### Textarea Component
328
+
329
+ ```tsx
330
+ <textarea
331
+ value={text}
332
+ onChange={(newValue) => setText(newValue)}
333
+ placeholder="Enter multiple lines..."
334
+ focused
335
+ width={40}
336
+ height={10}
337
+ showLineNumbers
338
+ wrapText
339
+ />
340
+ ```
341
+
342
+ ### Select Component
343
+
344
+ ```tsx
345
+ <select
346
+ options={[
347
+ { name: "Option 1", description: "First option", value: "1" },
348
+ { name: "Option 2", description: "Second option", value: "2" },
349
+ ]}
350
+ onChange={(index, option) => setSelected(option)}
351
+ selectedIndex={0}
352
+ focused
353
+ showScrollIndicator
354
+ height={8}
355
+ />
356
+ ```
357
+
358
+ ### Tab Select Component
359
+
360
+ ```tsx
361
+ <tab-select
362
+ options={[
363
+ { name: "Home", description: "Dashboard" },
364
+ { name: "Settings", description: "Configuration" },
365
+ ]}
366
+ onChange={(index, option) => setTab(option)}
367
+ tabWidth={20}
368
+ focused
369
+ />
370
+ ```
371
+
372
+ ### ASCII Font Component
373
+
374
+ ```tsx
375
+ <ascii-font
376
+ text="TITLE"
377
+ font="tiny" // tiny | block | slick | shade
378
+ color="#FFFFFF"
379
+ />
380
+ ```
381
+
382
+ ### Code Component
383
+
384
+ ```tsx
385
+ <code
386
+ code={sourceCode}
387
+ language="typescript"
388
+ showLineNumbers
389
+ highlightLines={[1, 5, 10]}
390
+ />
391
+ ```
392
+
393
+ ### Line Number Component
394
+
395
+ ```tsx
396
+ <line-number
397
+ code={sourceCode}
398
+ language="typescript"
399
+ startLine={1}
400
+ highlightedLines={[5]}
401
+ diagnostics={[
402
+ { line: 3, severity: "error", message: "Syntax error" }
403
+ ]}
404
+ />
405
+ ```
406
+
407
+ ### Diff Component
408
+
409
+ ```tsx
410
+ <diff
411
+ oldCode={originalCode}
412
+ newCode={modifiedCode}
413
+ language="typescript"
414
+ mode="unified" // unified | split
415
+ showLineNumbers
416
+ />
417
+ ```
418
+
419
+ ## Type Exports
420
+
421
+ ```tsx
422
+ import type {
423
+ // Component props
424
+ TextProps,
425
+ BoxProps,
426
+ InputProps,
427
+ SelectProps,
428
+
429
+ // Hook types
430
+ KeyEvent,
431
+
432
+ // From core
433
+ CliRenderer,
434
+ } from "@opentui/react"
435
+ ```
@@ -0,0 +1,301 @@
1
+ # React Configuration
2
+
3
+ ## Project Setup
4
+
5
+ ### Quick Start
6
+
7
+ ```bash
8
+ bunx create-tui@latest -t react my-app
9
+ cd my-app && bun install
10
+ ```
11
+
12
+ The CLI creates the `my-app` directory for you - it must **not already exist**.
13
+
14
+ Options: `--no-git` (skip git init), `--no-install` (skip bun install)
15
+
16
+ ### Manual Setup
17
+
18
+ ```bash
19
+ mkdir my-tui && cd my-tui
20
+ bun init
21
+ bun install @opentui/react @opentui/core react
22
+ ```
23
+
24
+ ## TypeScript Configuration
25
+
26
+ ### tsconfig.json
27
+
28
+ ```json
29
+ {
30
+ "compilerOptions": {
31
+ "lib": ["ESNext", "DOM"],
32
+ "target": "ESNext",
33
+ "module": "ESNext",
34
+ "moduleResolution": "bundler",
35
+
36
+ "jsx": "react-jsx",
37
+ "jsxImportSource": "@opentui/react",
38
+
39
+ "strict": true,
40
+ "skipLibCheck": true,
41
+ "noEmit": true,
42
+ "types": ["bun-types"]
43
+ },
44
+ "include": ["src/**/*"]
45
+ }
46
+ ```
47
+
48
+ **Critical settings:**
49
+ - `jsx: "react-jsx"` - Use the new JSX transform
50
+ - `jsxImportSource: "@opentui/react"` - Import JSX runtime from OpenTUI
51
+
52
+ ### Why DOM lib?
53
+
54
+ The `DOM` lib is needed for React types. OpenTUI's JSX types extend React's.
55
+
56
+ ## Package Configuration
57
+
58
+ ### package.json
59
+
60
+ ```json
61
+ {
62
+ "name": "my-tui-app",
63
+ "type": "module",
64
+ "scripts": {
65
+ "start": "bun run src/index.tsx",
66
+ "dev": "bun --watch run src/index.tsx",
67
+ "test": "bun test",
68
+ "build": "bun build src/index.tsx --outdir=dist --target=bun"
69
+ },
70
+ "dependencies": {
71
+ "@opentui/core": "latest",
72
+ "@opentui/react": "latest",
73
+ "react": ">=19.0.0"
74
+ },
75
+ "devDependencies": {
76
+ "@types/bun": "latest",
77
+ "@types/react": ">=19.0.0",
78
+ "typescript": "latest"
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Project Structure
84
+
85
+ Recommended structure:
86
+
87
+ ```
88
+ my-tui-app/
89
+ ├── src/
90
+ │ ├── components/
91
+ │ │ ├── Header.tsx
92
+ │ │ ├── Sidebar.tsx
93
+ │ │ └── MainContent.tsx
94
+ │ ├── hooks/
95
+ │ │ └── useAppState.ts
96
+ │ ├── App.tsx
97
+ │ └── index.tsx
98
+ ├── package.json
99
+ └── tsconfig.json
100
+ ```
101
+
102
+ ### Entry Point (src/index.tsx)
103
+
104
+ ```tsx
105
+ import { createCliRenderer } from "@opentui/core"
106
+ import { createRoot } from "@opentui/react"
107
+ import { App } from "./App"
108
+
109
+ const renderer = await createCliRenderer({
110
+ exitOnCtrlC: true,
111
+ })
112
+
113
+ createRoot(renderer).render(<App />)
114
+ ```
115
+
116
+ ### App Component (src/App.tsx)
117
+
118
+ ```tsx
119
+ import { Header } from "./components/Header"
120
+ import { Sidebar } from "./components/Sidebar"
121
+ import { MainContent } from "./components/MainContent"
122
+
123
+ export function App() {
124
+ return (
125
+ <box flexDirection="column" width="100%" height="100%">
126
+ <Header />
127
+ <box flexDirection="row" flexGrow={1}>
128
+ <Sidebar />
129
+ <MainContent />
130
+ </box>
131
+ </box>
132
+ )
133
+ }
134
+ ```
135
+
136
+ ## Renderer Configuration
137
+
138
+ ### createCliRenderer Options
139
+
140
+ ```tsx
141
+ import { createCliRenderer, ConsolePosition } from "@opentui/core"
142
+
143
+ const renderer = await createCliRenderer({
144
+ // Rendering
145
+ targetFPS: 60,
146
+
147
+ // Behavior
148
+ exitOnCtrlC: true, // Set false to handle Ctrl+C yourself
149
+ autoFocus: true, // Auto-focus elements on click (default: true)
150
+ useMouse: true, // Enable mouse support (default: true)
151
+
152
+ // Debug console
153
+ consoleOptions: {
154
+ position: ConsolePosition.BOTTOM,
155
+ sizePercent: 30,
156
+ startInDebugMode: false,
157
+ },
158
+
159
+ // Cleanup
160
+ onDestroy: () => {
161
+ // Cleanup code
162
+ },
163
+ })
164
+ ```
165
+
166
+ ## Building for Distribution
167
+
168
+ ### Bundling with Bun
169
+
170
+ ```typescript
171
+ // build.ts
172
+ await Bun.build({
173
+ entrypoints: ["./src/index.tsx"],
174
+ outdir: "./dist",
175
+ target: "bun",
176
+ minify: true,
177
+ })
178
+ ```
179
+
180
+ Run: `bun run build.ts`
181
+
182
+ ### Creating Executables
183
+
184
+ ```typescript
185
+ // build.ts
186
+ await Bun.build({
187
+ entrypoints: ["./src/index.tsx"],
188
+ outdir: "./dist",
189
+ target: "bun",
190
+ compile: {
191
+ target: "bun-darwin-arm64", // or bun-linux-x64, etc.
192
+ outfile: "my-app",
193
+ },
194
+ })
195
+ ```
196
+
197
+ ## Environment Variables
198
+
199
+ Create `.env` for development:
200
+
201
+ ```env
202
+ # Debug settings
203
+ OTUI_SHOW_STATS=false
204
+ SHOW_CONSOLE=false
205
+
206
+ # App settings
207
+ API_URL=https://api.example.com
208
+ ```
209
+
210
+ Bun auto-loads `.env` files. Access via `process.env`:
211
+
212
+ ```tsx
213
+ const apiUrl = process.env.API_URL
214
+ ```
215
+
216
+ ## React DevTools
217
+
218
+ OpenTUI React supports React DevTools for debugging.
219
+
220
+ ### Setup
221
+
222
+ 1. Install DevTools as a dev dependency (must use version 7):
223
+ ```bash
224
+ bun add react-devtools-core@7 -d
225
+ ```
226
+
227
+ 2. Run DevTools standalone app:
228
+ ```bash
229
+ npx react-devtools@7
230
+ ```
231
+
232
+ 3. Start your app with `DEV=true` environment variable:
233
+ ```bash
234
+ DEV=true bun run src/index.tsx
235
+ ```
236
+
237
+ **Important**: Auto-connect to DevTools ONLY happens when `DEV=true` is set. Without this environment variable, the DevTools connection code is not loaded.
238
+
239
+ ### How It Works
240
+
241
+ OpenTUI checks for `process.env["DEV"] === "true"` at startup. When true, it dynamically imports `react-devtools-core` and connects to the standalone DevTools app.
242
+
243
+ ## Testing Configuration
244
+
245
+ ### Test Setup
246
+
247
+ ```typescript
248
+ // src/test-utils.tsx
249
+ import { createTestRenderer } from "@opentui/core/testing"
250
+ import { createRoot } from "@opentui/react"
251
+
252
+ export async function renderForTest(
253
+ element: React.ReactElement,
254
+ options = { width: 80, height: 24 }
255
+ ) {
256
+ const testSetup = await createTestRenderer(options)
257
+ createRoot(testSetup.renderer).render(element)
258
+ return testSetup
259
+ }
260
+ ```
261
+
262
+ ### Test Example
263
+
264
+ ```typescript
265
+ // src/components/Counter.test.tsx
266
+ import { test, expect } from "bun:test"
267
+ import { renderForTest } from "../test-utils"
268
+ import { Counter } from "./Counter"
269
+
270
+ test("Counter renders initial value", async () => {
271
+ const { snapshot } = await renderForTest(<Counter initialValue={5} />)
272
+ expect(snapshot()).toContain("Count: 5")
273
+ })
274
+ ```
275
+
276
+ ## Common Issues
277
+
278
+ ### JSX Types Not Working
279
+
280
+ Ensure `jsxImportSource` is set:
281
+
282
+ ```json
283
+ {
284
+ "compilerOptions": {
285
+ "jsx": "react-jsx",
286
+ "jsxImportSource": "@opentui/react"
287
+ }
288
+ }
289
+ ```
290
+
291
+ ### React Version Mismatch
292
+
293
+ Ensure React 19+:
294
+
295
+ ```bash
296
+ bun install react@19 @types/react@19
297
+ ```
298
+
299
+ ### Module Resolution Errors
300
+
301
+ Use `moduleResolution: "bundler"` for Bun compatibility.