@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.
- package/.agents/skills/opentui/SKILL.md +198 -0
- package/.agents/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.agents/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.agents/skills/opentui/references/components/code-diff.md +496 -0
- package/.agents/skills/opentui/references/components/containers.md +412 -0
- package/.agents/skills/opentui/references/components/inputs.md +531 -0
- package/.agents/skills/opentui/references/components/text-display.md +384 -0
- package/.agents/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.agents/skills/opentui/references/core/api.md +506 -0
- package/.agents/skills/opentui/references/core/configuration.md +166 -0
- package/.agents/skills/opentui/references/core/gotchas.md +393 -0
- package/.agents/skills/opentui/references/core/patterns.md +448 -0
- package/.agents/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.agents/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.agents/skills/opentui/references/layout/patterns.md +444 -0
- package/.agents/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.agents/skills/opentui/references/react/api.md +435 -0
- package/.agents/skills/opentui/references/react/configuration.md +301 -0
- package/.agents/skills/opentui/references/react/gotchas.md +443 -0
- package/.agents/skills/opentui/references/react/patterns.md +501 -0
- package/.agents/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.agents/skills/opentui/references/solid/api.md +543 -0
- package/.agents/skills/opentui/references/solid/configuration.md +315 -0
- package/.agents/skills/opentui/references/solid/gotchas.md +415 -0
- package/.agents/skills/opentui/references/solid/patterns.md +558 -0
- package/.agents/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/.claude/skills/opentui/SKILL.md +198 -0
- package/.claude/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.claude/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.claude/skills/opentui/references/components/code-diff.md +496 -0
- package/.claude/skills/opentui/references/components/containers.md +412 -0
- package/.claude/skills/opentui/references/components/inputs.md +531 -0
- package/.claude/skills/opentui/references/components/text-display.md +384 -0
- package/.claude/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.claude/skills/opentui/references/core/api.md +506 -0
- package/.claude/skills/opentui/references/core/configuration.md +166 -0
- package/.claude/skills/opentui/references/core/gotchas.md +393 -0
- package/.claude/skills/opentui/references/core/patterns.md +448 -0
- package/.claude/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.claude/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.claude/skills/opentui/references/layout/patterns.md +444 -0
- package/.claude/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.claude/skills/opentui/references/react/api.md +435 -0
- package/.claude/skills/opentui/references/react/configuration.md +301 -0
- package/.claude/skills/opentui/references/react/gotchas.md +443 -0
- package/.claude/skills/opentui/references/react/patterns.md +501 -0
- package/.claude/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.claude/skills/opentui/references/solid/api.md +543 -0
- package/.claude/skills/opentui/references/solid/configuration.md +315 -0
- package/.claude/skills/opentui/references/solid/gotchas.md +415 -0
- package/.claude/skills/opentui/references/solid/patterns.md +558 -0
- package/.claude/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/index.ts +169 -38
- package/package.json +1 -1
- package/update.ts +87 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# React Gotchas
|
|
2
|
+
|
|
3
|
+
## Critical
|
|
4
|
+
|
|
5
|
+
### Never use `process.exit()` directly
|
|
6
|
+
|
|
7
|
+
**This is the most common mistake.** Using `process.exit()` leaves the terminal in a broken state (cursor hidden, raw mode, alternate screen).
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// WRONG - Terminal left in broken state
|
|
11
|
+
process.exit(0)
|
|
12
|
+
|
|
13
|
+
// CORRECT - Use renderer.destroy()
|
|
14
|
+
import { useRenderer } from "@opentui/react"
|
|
15
|
+
|
|
16
|
+
function App() {
|
|
17
|
+
const renderer = useRenderer()
|
|
18
|
+
|
|
19
|
+
const handleExit = () => {
|
|
20
|
+
renderer.destroy() // Cleans up and exits properly
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`renderer.destroy()` restores the terminal (exits alternate screen, restores cursor, etc.) before exiting.
|
|
26
|
+
|
|
27
|
+
### Signal Handling
|
|
28
|
+
|
|
29
|
+
OpenTUI automatically handles cleanup for these signals:
|
|
30
|
+
- `SIGINT` (Ctrl+C), `SIGTERM`, `SIGQUIT` - Standard termination
|
|
31
|
+
- `SIGHUP` - Terminal closed/hangup
|
|
32
|
+
- `SIGBREAK` - Ctrl+Break (Windows)
|
|
33
|
+
- `SIGPIPE` - Broken pipe (output closed)
|
|
34
|
+
- `SIGBUS`, `SIGFPE` - Hardware errors
|
|
35
|
+
|
|
36
|
+
This ensures terminal state is restored even on unexpected termination. If you need custom signal handling, use `exitOnCtrlC: false` and handle signals yourself while still calling `renderer.destroy()`.
|
|
37
|
+
|
|
38
|
+
## JSX Configuration
|
|
39
|
+
|
|
40
|
+
### Missing jsxImportSource
|
|
41
|
+
|
|
42
|
+
**Symptom**: JSX elements have wrong types, components don't render
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
// Error: Property 'text' does not exist on type 'JSX.IntrinsicElements'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Fix**: Configure tsconfig.json:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"compilerOptions": {
|
|
53
|
+
"jsx": "react-jsx",
|
|
54
|
+
"jsxImportSource": "@opentui/react"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### HTML Elements vs TUI Elements
|
|
60
|
+
|
|
61
|
+
OpenTUI's JSX elements are **not** HTML elements:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// WRONG - These are HTML concepts
|
|
65
|
+
<div>Not supported</div>
|
|
66
|
+
<button>Not supported</button>
|
|
67
|
+
<span>Only works inside <text></span>
|
|
68
|
+
|
|
69
|
+
// CORRECT - OpenTUI elements
|
|
70
|
+
<box>Container</box>
|
|
71
|
+
<text>Display text</text>
|
|
72
|
+
<text><span>Inline styled</span></text>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Component Issues
|
|
76
|
+
|
|
77
|
+
### Text Modifiers Outside Text
|
|
78
|
+
|
|
79
|
+
Text modifiers only work inside `<text>`:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// WRONG
|
|
83
|
+
<box>
|
|
84
|
+
<strong>This won't work</strong>
|
|
85
|
+
</box>
|
|
86
|
+
|
|
87
|
+
// CORRECT
|
|
88
|
+
<box>
|
|
89
|
+
<text>
|
|
90
|
+
<strong>This works</strong>
|
|
91
|
+
</text>
|
|
92
|
+
</box>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Focus Not Working
|
|
96
|
+
|
|
97
|
+
Components must be explicitly focused:
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
// WRONG - Won't receive keyboard input
|
|
101
|
+
<input placeholder="Type here..." />
|
|
102
|
+
|
|
103
|
+
// CORRECT
|
|
104
|
+
<input placeholder="Type here..." focused />
|
|
105
|
+
|
|
106
|
+
// Or manage focus state
|
|
107
|
+
const [isFocused, setIsFocused] = useState(true)
|
|
108
|
+
<input placeholder="Type here..." focused={isFocused} />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Select Not Responding
|
|
112
|
+
|
|
113
|
+
Select requires focus and proper options format:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
// WRONG - Missing required properties
|
|
117
|
+
<select options={["a", "b", "c"]} />
|
|
118
|
+
|
|
119
|
+
// CORRECT
|
|
120
|
+
<select
|
|
121
|
+
options={[
|
|
122
|
+
{ name: "Option A", description: "First option", value: "a" },
|
|
123
|
+
{ name: "Option B", description: "Second option", value: "b" },
|
|
124
|
+
]}
|
|
125
|
+
onSelect={(index, option) => {
|
|
126
|
+
// Called when Enter is pressed
|
|
127
|
+
console.log("Selected:", option.name)
|
|
128
|
+
}}
|
|
129
|
+
focused
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Select Events Confusion
|
|
134
|
+
|
|
135
|
+
Remember: `onSelect` fires on Enter (selection confirmed), `onChange` fires on navigation:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// WRONG - expecting onChange to fire on Enter
|
|
139
|
+
<select
|
|
140
|
+
options={options}
|
|
141
|
+
onChange={(i, opt) => submitForm(opt)} // This fires on arrow keys!
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
// CORRECT
|
|
145
|
+
<select
|
|
146
|
+
options={options}
|
|
147
|
+
onSelect={(i, opt) => submitForm(opt)} // Enter pressed - submit
|
|
148
|
+
onChange={(i, opt) => showPreview(opt)} // Arrow keys - preview
|
|
149
|
+
/>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Hook Issues
|
|
153
|
+
|
|
154
|
+
### useKeyboard Not Firing
|
|
155
|
+
|
|
156
|
+
Multiple `useKeyboard` hooks can conflict:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// Both handlers fire - may cause issues
|
|
160
|
+
function App() {
|
|
161
|
+
useKeyboard((key) => { /* parent handler */ })
|
|
162
|
+
return <ChildWithKeyboard />
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function ChildWithKeyboard() {
|
|
166
|
+
useKeyboard((key) => { /* child handler */ })
|
|
167
|
+
return <text>Child</text>
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Solution**: Use a single keyboard handler or implement event stopping:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
function App() {
|
|
175
|
+
const [handled, setHandled] = useState(false)
|
|
176
|
+
|
|
177
|
+
useKeyboard((key) => {
|
|
178
|
+
if (handled) {
|
|
179
|
+
setHandled(false)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
// Handle at app level
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
return <Child onKeyHandled={() => setHandled(true)} />
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### useEffect Cleanup
|
|
190
|
+
|
|
191
|
+
Always clean up intervals and listeners:
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
// WRONG - Memory leak
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
setInterval(() => updateData(), 1000)
|
|
197
|
+
}, [])
|
|
198
|
+
|
|
199
|
+
// CORRECT
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
const interval = setInterval(() => updateData(), 1000)
|
|
202
|
+
return () => clearInterval(interval) // Cleanup!
|
|
203
|
+
}, [])
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Styling Issues
|
|
207
|
+
|
|
208
|
+
### Colors Not Applying
|
|
209
|
+
|
|
210
|
+
Check color format:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
// CORRECT formats
|
|
214
|
+
<text fg="#FF0000">Red</text>
|
|
215
|
+
<text fg="red">Red</text>
|
|
216
|
+
<box backgroundColor="#1a1a2e">Box</box>
|
|
217
|
+
|
|
218
|
+
// WRONG
|
|
219
|
+
<text fg="FF0000">Missing #</text>
|
|
220
|
+
<text color="#FF0000">Wrong prop name (use fg)</text>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Layout Not Working
|
|
224
|
+
|
|
225
|
+
Ensure parent has dimensions:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
// WRONG - Parent has no height
|
|
229
|
+
<box flexDirection="column">
|
|
230
|
+
<box flexGrow={1}>Won't grow</box>
|
|
231
|
+
</box>
|
|
232
|
+
|
|
233
|
+
// CORRECT
|
|
234
|
+
<box flexDirection="column" height="100%">
|
|
235
|
+
<box flexGrow={1}>Will grow</box>
|
|
236
|
+
</box>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Percentage Widths Not Working
|
|
240
|
+
|
|
241
|
+
Parent must have explicit dimensions:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
// WRONG
|
|
245
|
+
<box>
|
|
246
|
+
<box width="50%">Won't work</box>
|
|
247
|
+
</box>
|
|
248
|
+
|
|
249
|
+
// CORRECT
|
|
250
|
+
<box width="100%">
|
|
251
|
+
<box width="50%">Works</box>
|
|
252
|
+
</box>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Performance Issues
|
|
256
|
+
|
|
257
|
+
### Too Many Re-renders
|
|
258
|
+
|
|
259
|
+
Avoid inline objects/functions in props:
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
// WRONG - New object every render
|
|
263
|
+
<box style={{ padding: 2 }}>Content</box>
|
|
264
|
+
|
|
265
|
+
// BETTER - Use direct props
|
|
266
|
+
<box padding={2}>Content</box>
|
|
267
|
+
|
|
268
|
+
// OR memoize style objects
|
|
269
|
+
const style = useMemo(() => ({ padding: 2 }), [])
|
|
270
|
+
<box style={style}>Content</box>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Heavy Components
|
|
274
|
+
|
|
275
|
+
Use React.memo for expensive components:
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
const ExpensiveList = React.memo(function ExpensiveList({
|
|
279
|
+
items
|
|
280
|
+
}: {
|
|
281
|
+
items: Item[]
|
|
282
|
+
}) {
|
|
283
|
+
return (
|
|
284
|
+
<box flexDirection="column">
|
|
285
|
+
{items.map(item => (
|
|
286
|
+
<text key={item.id}>{item.name}</text>
|
|
287
|
+
))}
|
|
288
|
+
</box>
|
|
289
|
+
)
|
|
290
|
+
})
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### State Updates During Render
|
|
294
|
+
|
|
295
|
+
Don't update state during render:
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
// WRONG
|
|
299
|
+
function Component({ value }: { value: number }) {
|
|
300
|
+
const [count, setCount] = useState(0)
|
|
301
|
+
|
|
302
|
+
// This causes infinite loop!
|
|
303
|
+
if (value > 10) {
|
|
304
|
+
setCount(value)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return <text>{count}</text>
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// CORRECT
|
|
311
|
+
function Component({ value }: { value: number }) {
|
|
312
|
+
const [count, setCount] = useState(0)
|
|
313
|
+
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
if (value > 10) {
|
|
316
|
+
setCount(value)
|
|
317
|
+
}
|
|
318
|
+
}, [value])
|
|
319
|
+
|
|
320
|
+
return <text>{count}</text>
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Debugging
|
|
325
|
+
|
|
326
|
+
### Console Not Visible
|
|
327
|
+
|
|
328
|
+
OpenTUI captures console output. Show the overlay:
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
import { useRenderer } from "@opentui/react"
|
|
332
|
+
import { useEffect } from "react"
|
|
333
|
+
|
|
334
|
+
function App() {
|
|
335
|
+
const renderer = useRenderer()
|
|
336
|
+
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
renderer.console.show()
|
|
339
|
+
console.log("Now you can see this!")
|
|
340
|
+
}, [renderer])
|
|
341
|
+
|
|
342
|
+
return <box>{/* ... */}</box>
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Component Not Rendering
|
|
347
|
+
|
|
348
|
+
Check if component is in the tree:
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
// WRONG - Conditional returns nothing
|
|
352
|
+
function MaybeComponent({ show }: { show: boolean }) {
|
|
353
|
+
if (!show) return // Returns undefined!
|
|
354
|
+
return <text>Visible</text>
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// CORRECT
|
|
358
|
+
function MaybeComponent({ show }: { show: boolean }) {
|
|
359
|
+
if (!show) return null // Explicit null
|
|
360
|
+
return <text>Visible</text>
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Events Not Firing
|
|
365
|
+
|
|
366
|
+
Check event handler names:
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
// WRONG
|
|
370
|
+
<box onClick={() => {}}>Click</box> // No onClick in TUI
|
|
371
|
+
|
|
372
|
+
// CORRECT
|
|
373
|
+
<box onMouseDown={() => {}}>Click</box>
|
|
374
|
+
<box onMouseUp={() => {}}>Click</box>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Runtime Issues
|
|
378
|
+
|
|
379
|
+
### Use Bun, Not Node
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
# WRONG
|
|
383
|
+
node src/index.tsx
|
|
384
|
+
npm run start
|
|
385
|
+
|
|
386
|
+
# CORRECT
|
|
387
|
+
bun run src/index.tsx
|
|
388
|
+
bun run start
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Async Top-level
|
|
392
|
+
|
|
393
|
+
Bun supports top-level await, but be careful:
|
|
394
|
+
|
|
395
|
+
```tsx
|
|
396
|
+
// index.tsx - This works in Bun
|
|
397
|
+
const renderer = await createCliRenderer()
|
|
398
|
+
createRoot(renderer).render(<App />)
|
|
399
|
+
|
|
400
|
+
// If you need to handle errors
|
|
401
|
+
try {
|
|
402
|
+
const renderer = await createCliRenderer()
|
|
403
|
+
createRoot(renderer).render(<App />)
|
|
404
|
+
} catch (error) {
|
|
405
|
+
console.error("Failed to initialize:", error)
|
|
406
|
+
process.exit(1)
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Common Error Messages
|
|
411
|
+
|
|
412
|
+
### "Cannot read properties of undefined (reading 'root')"
|
|
413
|
+
|
|
414
|
+
Renderer not initialized:
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
// WRONG
|
|
418
|
+
const renderer = createCliRenderer() // Missing await!
|
|
419
|
+
createRoot(renderer).render(<App />)
|
|
420
|
+
|
|
421
|
+
// CORRECT
|
|
422
|
+
const renderer = await createCliRenderer()
|
|
423
|
+
createRoot(renderer).render(<App />)
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### "Invalid hook call"
|
|
427
|
+
|
|
428
|
+
Hooks called outside component:
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
// WRONG
|
|
432
|
+
const dimensions = useTerminalDimensions() // Outside component!
|
|
433
|
+
|
|
434
|
+
function App() {
|
|
435
|
+
return <text>{dimensions.width}</text>
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// CORRECT
|
|
439
|
+
function App() {
|
|
440
|
+
const dimensions = useTerminalDimensions()
|
|
441
|
+
return <text>{dimensions.width}</text>
|
|
442
|
+
}
|
|
443
|
+
```
|