@telemetryos/cli 1.7.4 → 1.8.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 (25) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/commands/init.js +11 -0
  3. package/dist/services/generate-application.d.ts +1 -0
  4. package/dist/services/generate-application.js +127 -4
  5. package/dist/utils/validate-project-name.d.ts +19 -0
  6. package/dist/utils/validate-project-name.js +44 -0
  7. package/package.json +2 -2
  8. package/templates/vite-react-typescript/CLAUDE.md +68 -1244
  9. package/templates/vite-react-typescript/_claude/settings.local.json +17 -0
  10. package/templates/vite-react-typescript/_claude/skills/tos-architecture/SKILL.md +313 -0
  11. package/templates/vite-react-typescript/_claude/skills/tos-debugging/SKILL.md +299 -0
  12. package/templates/vite-react-typescript/_claude/skills/tos-media-api/SKILL.md +335 -0
  13. package/templates/vite-react-typescript/_claude/skills/tos-proxy-fetch/SKILL.md +319 -0
  14. package/templates/vite-react-typescript/_claude/skills/tos-render-design/SKILL.md +332 -0
  15. package/templates/vite-react-typescript/_claude/skills/tos-requirements/SKILL.md +252 -0
  16. package/templates/vite-react-typescript/_claude/skills/tos-settings-ui/SKILL.md +636 -0
  17. package/templates/vite-react-typescript/_claude/skills/tos-store-sync/SKILL.md +359 -0
  18. package/templates/vite-react-typescript/_claude/skills/tos-weather-api/SKILL.md +384 -0
  19. package/templates/vite-react-typescript/src/hooks/store.ts +3 -3
  20. package/templates/vite-react-typescript/src/index.css +0 -1
  21. package/templates/vite-react-typescript/src/views/Render.css +2 -1
  22. package/templates/vite-react-typescript/src/views/Render.tsx +2 -3
  23. package/templates/vite-react-typescript/src/views/Settings.tsx +2 -3
  24. package/templates/vite-react-typescript/AGENTS.md +0 -7
  25. /package/templates/vite-react-typescript/{gitignore → _gitignore} +0 -0
@@ -0,0 +1,636 @@
1
+ ---
2
+ name: tos-settings-ui
3
+ description: REQUIRED for TelemetryOS Settings UI. MUST invoke BEFORE writing ANY Settings components - raw HTML won't work, SDK components are mandatory. Contains SettingsContainer, SettingsField, SettingsInputFrame, and all input patterns.
4
+ ---
5
+
6
+ # TelemetryOS Settings UI Components
7
+
8
+ All Settings components are imported from `@telemetryos/sdk/react`. Always use these styled components - raw HTML won't match Studio's design system.
9
+
10
+ ## Quick Reference
11
+
12
+ ```typescript
13
+ import {
14
+ // Layout
15
+ SettingsContainer,
16
+ SettingsHeading,
17
+ SettingsBox,
18
+ SettingsDivider,
19
+ // Fields
20
+ SettingsField,
21
+ SettingsLabel,
22
+ SettingsHint,
23
+ SettingsError,
24
+ // Inputs
25
+ SettingsInputFrame,
26
+ SettingsTextAreaFrame,
27
+ SettingsSelectFrame,
28
+ SettingsSliderFrame,
29
+ SettingsSliderRuler,
30
+ SettingsColorFrame,
31
+ // Toggles
32
+ SettingsSwitchFrame,
33
+ SettingsSwitchLabel,
34
+ SettingsCheckboxFrame,
35
+ SettingsCheckboxLabel,
36
+ SettingsRadioFrame,
37
+ SettingsRadioLabel,
38
+ // Actions
39
+ SettingsButtonFrame,
40
+ } from '@telemetryos/sdk/react'
41
+ ```
42
+
43
+ ## Debounce Guidelines
44
+
45
+ Store hooks accept an optional debounce delay (default 0ms - immediate). Choose based on input type:
46
+
47
+ | Input Type | Debounce | Reason |
48
+ |------------|----------|--------|
49
+ | Text input | 250ms | Wait for typing to pause |
50
+ | Textarea | 250ms | Wait for typing to pause |
51
+ | Select/Dropdown | 0ms (default) | Immediate feedback expected |
52
+ | Switch/Toggle | 0ms (default) | Immediate feedback expected |
53
+ | Checkbox | 0ms (default) | Immediate feedback expected |
54
+ | Radio | 0ms (default) | Immediate feedback expected |
55
+ | Slider | 5ms | Responsive feel, reduced message traffic |
56
+ | Color picker | 5ms | Responsive feel while dragging |
57
+
58
+ **Usage:**
59
+ ```typescript
60
+ // Text input - debounce to wait for typing to pause
61
+ const [isLoading, city, setCity] = useCityStoreState(250)
62
+
63
+ // Dropdown - immediate (default, no argument needed)
64
+ const [isLoading, league, setLeague] = useLeagueStoreState()
65
+
66
+ // Slider - responsive (5ms)
67
+ const [isLoading, volume, setVolume] = useVolumeStoreState(5)
68
+ ```
69
+
70
+ ## Component Patterns
71
+
72
+ ### Container (Required)
73
+
74
+ Every Settings view must wrap content in `SettingsContainer`:
75
+
76
+ ```typescript
77
+ import { SettingsContainer } from '@telemetryos/sdk/react'
78
+
79
+ export function Settings() {
80
+ return (
81
+ <SettingsContainer>
82
+ {/* All settings content here */}
83
+ </SettingsContainer>
84
+ )
85
+ }
86
+ ```
87
+
88
+ ### Section Heading
89
+
90
+ Use `SettingsHeading` to divide settings into logical sections:
91
+
92
+ ```typescript
93
+ import { SettingsHeading, SettingsDivider } from '@telemetryos/sdk/react'
94
+
95
+ <SettingsHeading>Display Options</SettingsHeading>
96
+ {/* Fields for this section */}
97
+
98
+ <SettingsDivider />
99
+
100
+ <SettingsHeading>Advanced Settings</SettingsHeading>
101
+ {/* Fields for next section */}
102
+ ```
103
+
104
+ ### Text Input
105
+
106
+ ```typescript
107
+ import {
108
+ SettingsContainer,
109
+ SettingsField,
110
+ SettingsLabel,
111
+ SettingsInputFrame,
112
+ } from '@telemetryos/sdk/react'
113
+ import { useTeamStoreState } from '../hooks/store'
114
+
115
+ export function Settings() {
116
+ const [isLoading, team, setTeam] = useTeamStoreState(250) // 250ms debounce for text input
117
+
118
+ return (
119
+ <SettingsContainer>
120
+ <SettingsField>
121
+ <SettingsLabel>Team Name</SettingsLabel>
122
+ <SettingsInputFrame>
123
+ <input
124
+ type="text"
125
+ placeholder="Enter team name..."
126
+ disabled={isLoading}
127
+ value={team}
128
+ onChange={(e) => setTeam(e.target.value)}
129
+ />
130
+ </SettingsInputFrame>
131
+ </SettingsField>
132
+ </SettingsContainer>
133
+ )
134
+ }
135
+ ```
136
+
137
+ ### Textarea (Multiline)
138
+
139
+ ```typescript
140
+ import { SettingsTextAreaFrame } from '@telemetryos/sdk/react'
141
+
142
+ <SettingsField>
143
+ <SettingsLabel>Description</SettingsLabel>
144
+ <SettingsTextAreaFrame>
145
+ <textarea
146
+ placeholder="Enter description..."
147
+ disabled={isLoading}
148
+ value={description}
149
+ onChange={(e) => setDescription(e.target.value)}
150
+ rows={4}
151
+ />
152
+ </SettingsTextAreaFrame>
153
+ </SettingsField>
154
+ ```
155
+
156
+ ### Hint Text
157
+
158
+ Add helper text below any field with `SettingsHint`:
159
+
160
+ ```typescript
161
+ import { SettingsHint } from '@telemetryos/sdk/react'
162
+
163
+ <SettingsField>
164
+ <SettingsLabel>API Key</SettingsLabel>
165
+ <SettingsInputFrame>
166
+ <input type="text" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
167
+ </SettingsInputFrame>
168
+ <SettingsHint>Found in your dashboard under Settings → API</SettingsHint>
169
+ </SettingsField>
170
+ ```
171
+
172
+ ### Error Message
173
+
174
+ Display validation errors with `SettingsError`:
175
+
176
+ ```typescript
177
+ import { SettingsError } from '@telemetryos/sdk/react'
178
+
179
+ <SettingsField>
180
+ <SettingsLabel>Email</SettingsLabel>
181
+ <SettingsInputFrame>
182
+ <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
183
+ </SettingsInputFrame>
184
+ {error && <SettingsError>{error}</SettingsError>}
185
+ </SettingsField>
186
+ ```
187
+
188
+ ### Dropdown Select
189
+
190
+ ```typescript
191
+ import { SettingsSelectFrame } from '@telemetryos/sdk/react'
192
+ import { useLeagueStoreState } from '../hooks/store'
193
+
194
+ const [isLoading, league, setLeague] = useLeagueStoreState(0) // 0ms for immediate feedback
195
+
196
+ <SettingsField>
197
+ <SettingsLabel>League</SettingsLabel>
198
+ <SettingsSelectFrame>
199
+ <select
200
+ disabled={isLoading}
201
+ value={league}
202
+ onChange={(e) => setLeague(e.target.value)}
203
+ >
204
+ <option value="nfl">NFL</option>
205
+ <option value="nba">NBA</option>
206
+ <option value="mlb">MLB</option>
207
+ <option value="nhl">NHL</option>
208
+ </select>
209
+ </SettingsSelectFrame>
210
+ </SettingsField>
211
+ ```
212
+
213
+ ### Slider
214
+
215
+ ```typescript
216
+ import { SettingsSliderFrame } from '@telemetryos/sdk/react'
217
+ import { useVolumeStoreState } from '../hooks/store'
218
+
219
+ const [isLoading, volume, setVolume] = useVolumeStoreState(5) // 5ms for responsive feel
220
+
221
+ <SettingsField>
222
+ <SettingsLabel>Volume</SettingsLabel>
223
+ <SettingsSliderFrame>
224
+ <input
225
+ type="range"
226
+ min="0"
227
+ max="100"
228
+ disabled={isLoading}
229
+ value={volume}
230
+ onChange={(e) => setVolume(Number(e.target.value))}
231
+ />
232
+ <span>{volume}%</span>
233
+ </SettingsSliderFrame>
234
+ </SettingsField>
235
+ ```
236
+
237
+ The frame uses flexbox - add a `<span>` after the input to show the current value.
238
+
239
+ ### Slider with Ruler
240
+
241
+ Add tick labels below a slider with `SettingsSliderRuler`:
242
+
243
+ ```typescript
244
+ import { SettingsSliderFrame, SettingsSliderRuler } from '@telemetryos/sdk/react'
245
+ import { useQualityStoreState } from '../hooks/store'
246
+
247
+ const [isLoading, quality, setQuality] = useQualityStoreState(5) // 5ms for responsive feel
248
+
249
+ <SettingsField>
250
+ <SettingsLabel>Quality</SettingsLabel>
251
+ <SettingsSliderFrame>
252
+ <input
253
+ type="range"
254
+ min="1"
255
+ max="3"
256
+ disabled={isLoading}
257
+ value={quality}
258
+ onChange={(e) => setQuality(Number(e.target.value))}
259
+ />
260
+ <span>{quality}</span>
261
+ </SettingsSliderFrame>
262
+ <SettingsSliderRuler>
263
+ <span>Low</span>
264
+ <span>Medium</span>
265
+ <span>High</span>
266
+ </SettingsSliderRuler>
267
+ </SettingsField>
268
+ ```
269
+
270
+ ### Color Picker
271
+
272
+ ```typescript
273
+ import { SettingsColorFrame } from '@telemetryos/sdk/react'
274
+ import { useColorStoreState } from '../hooks/store'
275
+
276
+ const [isLoading, color, setColor] = useColorStoreState(5) // 5ms for responsive feel while dragging
277
+
278
+ <SettingsField>
279
+ <SettingsLabel>Brand Color</SettingsLabel>
280
+ <SettingsColorFrame>
281
+ <input
282
+ type="color"
283
+ disabled={isLoading}
284
+ value={color}
285
+ onChange={(e) => setColor(e.target.value)}
286
+ />
287
+ <span>{color}</span>
288
+ </SettingsColorFrame>
289
+ </SettingsField>
290
+ ```
291
+
292
+ ### Toggle Switch
293
+
294
+ ```typescript
295
+ import { SettingsSwitchFrame, SettingsSwitchLabel } from '@telemetryos/sdk/react'
296
+ import { useShowScoresStoreState } from '../hooks/store'
297
+
298
+ const [isLoading, showScores, setShowScores] = useShowScoresStoreState(0) // 0ms for immediate feedback
299
+
300
+ <SettingsField>
301
+ <SettingsSwitchFrame>
302
+ <input
303
+ type="checkbox"
304
+ role="switch"
305
+ disabled={isLoading}
306
+ checked={showScores}
307
+ onChange={(e) => setShowScores(e.target.checked)}
308
+ />
309
+ <SettingsSwitchLabel>Show Live Scores</SettingsSwitchLabel>
310
+ </SettingsSwitchFrame>
311
+ </SettingsField>
312
+ ```
313
+
314
+ Note: Use `role="switch"` on the checkbox for proper switch styling.
315
+
316
+ ### Checkbox
317
+
318
+ ```typescript
319
+ import { SettingsCheckboxFrame, SettingsCheckboxLabel } from '@telemetryos/sdk/react'
320
+ import { useAutoRefreshStoreState } from '../hooks/store'
321
+
322
+ const [isLoading, autoRefresh, setAutoRefresh] = useAutoRefreshStoreState(0) // 0ms for immediate feedback
323
+
324
+ <SettingsField>
325
+ <SettingsCheckboxFrame>
326
+ <input
327
+ type="checkbox"
328
+ disabled={isLoading}
329
+ checked={autoRefresh}
330
+ onChange={(e) => setAutoRefresh(e.target.checked)}
331
+ />
332
+ <SettingsCheckboxLabel>Enable Auto-Refresh</SettingsCheckboxLabel>
333
+ </SettingsCheckboxFrame>
334
+ </SettingsField>
335
+ ```
336
+
337
+ ### Radio Group
338
+
339
+ ```typescript
340
+ import { SettingsRadioFrame, SettingsRadioLabel } from '@telemetryos/sdk/react'
341
+ import { useDisplayModeStoreState } from '../hooks/store'
342
+
343
+ const [isLoading, displayMode, setDisplayMode] = useDisplayModeStoreState(0) // 0ms for immediate feedback
344
+
345
+ <SettingsField>
346
+ <SettingsLabel>Display Mode</SettingsLabel>
347
+ <SettingsRadioFrame>
348
+ <input
349
+ type="radio"
350
+ name="displayMode"
351
+ value="compact"
352
+ disabled={isLoading}
353
+ checked={displayMode === 'compact'}
354
+ onChange={(e) => setDisplayMode(e.target.value)}
355
+ />
356
+ <SettingsRadioLabel>Compact</SettingsRadioLabel>
357
+ </SettingsRadioFrame>
358
+ <SettingsRadioFrame>
359
+ <input
360
+ type="radio"
361
+ name="displayMode"
362
+ value="expanded"
363
+ disabled={isLoading}
364
+ checked={displayMode === 'expanded'}
365
+ onChange={(e) => setDisplayMode(e.target.value)}
366
+ />
367
+ <SettingsRadioLabel>Expanded</SettingsRadioLabel>
368
+ </SettingsRadioFrame>
369
+ </SettingsField>
370
+ ```
371
+
372
+ ### Button
373
+
374
+ ```typescript
375
+ import { SettingsButtonFrame } from '@telemetryos/sdk/react'
376
+
377
+ <SettingsButtonFrame>
378
+ <button onClick={handleReset}>Reset to Defaults</button>
379
+ </SettingsButtonFrame>
380
+ ```
381
+
382
+ ## Layout Components
383
+
384
+ ### SettingsBox (Grouping)
385
+
386
+ Bordered container, typically used for individual items in a repeatable list:
387
+
388
+ ```typescript
389
+ import { SettingsBox, SettingsHeading, SettingsButtonFrame } from '@telemetryos/sdk/react'
390
+
391
+ <SettingsHeading>Teams</SettingsHeading>
392
+
393
+ {teams.map((team, index) => (
394
+ <SettingsBox key={index}>
395
+ <SettingsHeading>Team {index + 1}</SettingsHeading>
396
+ {/* Team fields */}
397
+ <SettingsButtonFrame>
398
+ <button onClick={() => removeTeam(index)}>Remove</button>
399
+ </SettingsButtonFrame>
400
+ </SettingsBox>
401
+ ))}
402
+
403
+ <SettingsButtonFrame>
404
+ <button onClick={addTeam}>+ Add Team</button>
405
+ </SettingsButtonFrame>
406
+ ```
407
+
408
+ ### SettingsDivider (Separator)
409
+
410
+ Add a horizontal rule between sections:
411
+
412
+ ```typescript
413
+ import { SettingsDivider } from '@telemetryos/sdk/react'
414
+
415
+ <SettingsField>...</SettingsField>
416
+ <SettingsDivider />
417
+ <SettingsField>...</SettingsField>
418
+ ```
419
+
420
+ ## Dynamic Lists
421
+
422
+ For settings with repeatable items (teams, locations, etc.), use store hooks with array types:
423
+
424
+ ```typescript
425
+ // hooks/store.ts defines: useTeamsStoreState
426
+ import { useTeamsStoreState } from '../hooks/store'
427
+
428
+ const [isLoading, teams, setTeams] = useTeamsStoreState(250) // contains text inputs
429
+
430
+ const addTeam = () => {
431
+ setTeams([...teams, { name: '', league: 'nfl' }])
432
+ }
433
+
434
+ const removeTeam = (index: number) => {
435
+ setTeams(teams.filter((_, i) => i !== index))
436
+ }
437
+
438
+ const updateTeam = (index: number, updates: Partial<typeof teams[0]>) => {
439
+ const updated = [...teams]
440
+ updated[index] = { ...updated[index], ...updates }
441
+ setTeams(updated)
442
+ }
443
+ ```
444
+
445
+ ## Complete Example
446
+
447
+ A complete Settings view with dynamic lists, sections, and multiple input types:
448
+
449
+ ```typescript
450
+ // Store hooks - see tos-store-sync skill for how to define these in hooks/store.ts
451
+ import {
452
+ useTeamsStoreState,
453
+ useRefreshIntervalStoreState,
454
+ useShowScoresStoreState,
455
+ useBackgroundColorStoreState,
456
+ } from '../hooks/store'
457
+ import {
458
+ SettingsContainer,
459
+ SettingsBox,
460
+ SettingsHeading,
461
+ SettingsDivider,
462
+ SettingsField,
463
+ SettingsLabel,
464
+ SettingsHint,
465
+ SettingsInputFrame,
466
+ SettingsSelectFrame,
467
+ SettingsSliderFrame,
468
+ SettingsColorFrame,
469
+ SettingsSwitchFrame,
470
+ SettingsSwitchLabel,
471
+ SettingsButtonFrame,
472
+ } from '@telemetryos/sdk/react'
473
+
474
+ export function Settings() {
475
+ // Store hooks return [isLoading, value, setValue]
476
+ // Debounce: 250ms for text inputs, 0ms (default) for toggles, 5ms for sliders/colors
477
+ const [isLoadingTeams, teams, setTeams] = useTeamsStoreState(250) // contains text inputs
478
+ const [isLoadingInterval, interval, setInterval] = useRefreshIntervalStoreState(5)
479
+ const [isLoadingScores, showScores, setShowScores] = useShowScoresStoreState()
480
+ const [isLoadingBg, backgroundColor, setBackgroundColor] = useBackgroundColorStoreState(5)
481
+
482
+ const isLoading = isLoadingTeams || isLoadingInterval || isLoadingScores || isLoadingBg
483
+
484
+ const addTeam = () => {
485
+ setTeams([...teams, { name: '', league: 'nfl' }])
486
+ }
487
+
488
+ const removeTeam = (index: number) => {
489
+ setTeams(teams.filter((_, i) => i !== index))
490
+ }
491
+
492
+ const updateTeam = (index: number, updates: Partial<typeof teams[0]>) => {
493
+ const updated = [...teams]
494
+ updated[index] = { ...updated[index], ...updates }
495
+ setTeams(updated)
496
+ }
497
+
498
+ return (
499
+ <SettingsContainer>
500
+ <SettingsHeading>Teams</SettingsHeading>
501
+
502
+ {teams.map((team, index) => (
503
+ <SettingsBox key={index}>
504
+ <SettingsHeading>Team {index + 1}</SettingsHeading>
505
+
506
+ <SettingsField>
507
+ <SettingsLabel>Team Name</SettingsLabel>
508
+ <SettingsInputFrame>
509
+ <input
510
+ type="text"
511
+ placeholder="Enter team name..."
512
+ disabled={isLoading}
513
+ value={team.name}
514
+ onChange={(e) => updateTeam(index, { name: e.target.value })}
515
+ />
516
+ </SettingsInputFrame>
517
+ <SettingsHint>This name appears in the header</SettingsHint>
518
+ </SettingsField>
519
+
520
+ <SettingsField>
521
+ <SettingsLabel>League</SettingsLabel>
522
+ <SettingsSelectFrame>
523
+ <select
524
+ disabled={isLoading}
525
+ value={team.league}
526
+ onChange={(e) => updateTeam(index, { league: e.target.value })}
527
+ >
528
+ <option value="nfl">NFL</option>
529
+ <option value="nba">NBA</option>
530
+ <option value="mlb">MLB</option>
531
+ <option value="nhl">NHL</option>
532
+ </select>
533
+ </SettingsSelectFrame>
534
+ </SettingsField>
535
+
536
+ <SettingsButtonFrame>
537
+ <button type="button" disabled={isLoading} onClick={() => removeTeam(index)}>
538
+ Remove Team
539
+ </button>
540
+ </SettingsButtonFrame>
541
+ </SettingsBox>
542
+ ))}
543
+
544
+ <SettingsButtonFrame>
545
+ <button type="button" disabled={isLoading} onClick={addTeam}>+ Add Team</button>
546
+ </SettingsButtonFrame>
547
+
548
+ <SettingsDivider />
549
+
550
+ <SettingsHeading>Display</SettingsHeading>
551
+
552
+ <SettingsField>
553
+ <SettingsLabel>Refresh Interval (seconds)</SettingsLabel>
554
+ <SettingsSliderFrame>
555
+ <input
556
+ type="range"
557
+ min="10"
558
+ max="120"
559
+ disabled={isLoading}
560
+ value={interval}
561
+ onChange={(e) => setInterval(Number(e.target.value))}
562
+ />
563
+ <span>{interval}s</span>
564
+ </SettingsSliderFrame>
565
+ </SettingsField>
566
+
567
+ <SettingsField>
568
+ <SettingsSwitchFrame>
569
+ <input
570
+ type="checkbox"
571
+ role="switch"
572
+ disabled={isLoading}
573
+ checked={showScores}
574
+ onChange={(e) => setShowScores(e.target.checked)}
575
+ />
576
+ <SettingsSwitchLabel>Show Live Scores</SettingsSwitchLabel>
577
+ </SettingsSwitchFrame>
578
+ </SettingsField>
579
+
580
+ <SettingsField>
581
+ <SettingsLabel>Background Color</SettingsLabel>
582
+ <SettingsColorFrame>
583
+ <input
584
+ type="color"
585
+ disabled={isLoading}
586
+ value={backgroundColor}
587
+ onChange={(e) => setBackgroundColor(e.target.value)}
588
+ />
589
+ <span>{backgroundColor}</span>
590
+ <SettingsButtonFrame>
591
+ <button type="button" disabled={isLoading} onClick={() => setBackgroundColor('transparent')}>
592
+ Clear
593
+ </button>
594
+ </SettingsButtonFrame>
595
+ </SettingsColorFrame>
596
+ </SettingsField>
597
+ </SettingsContainer>
598
+ )
599
+ }
600
+ ```
601
+
602
+ ## Component Reference
603
+
604
+ | Component | Purpose |
605
+ |-----------|---------|
606
+ | `SettingsContainer` | Root wrapper, handles color scheme |
607
+ | `SettingsHeading` | Section heading |
608
+ | `SettingsBox` | Container for list items |
609
+ | `SettingsDivider` | Horizontal separator |
610
+ | `SettingsField` | Wrapper for each field (renders as label) |
611
+ | `SettingsLabel` | Field label |
612
+ | `SettingsHint` | Help text below a field |
613
+ | `SettingsError` | Error message below a field |
614
+ | `SettingsInputFrame` | Text input wrapper |
615
+ | `SettingsTextAreaFrame` | Multiline text wrapper |
616
+ | `SettingsSelectFrame` | Dropdown wrapper |
617
+ | `SettingsSliderFrame` | Range slider wrapper |
618
+ | `SettingsSliderRuler` | Tick labels below a slider |
619
+ | `SettingsColorFrame` | Color picker wrapper |
620
+ | `SettingsSwitchFrame` | Toggle switch wrapper |
621
+ | `SettingsSwitchLabel` | Toggle switch label |
622
+ | `SettingsCheckboxFrame` | Checkbox wrapper |
623
+ | `SettingsCheckboxLabel` | Checkbox label |
624
+ | `SettingsRadioFrame` | Radio button wrapper |
625
+ | `SettingsRadioLabel` | Radio button label |
626
+ | `SettingsButtonFrame` | Action button wrapper |
627
+
628
+ ## Common Mistakes
629
+
630
+ 1. **Missing SettingsContainer** - Always wrap in SettingsContainer
631
+ 2. **Forgetting disabled={isLoading}** - Disable inputs while loading from store
632
+ 3. **Using raw HTML** - Always use Frame components for proper styling
633
+ 4. **Missing role="switch"** - Required on toggle switches for proper styling
634
+ 5. **Wrong onChange for sliders** - Use `Number(e.target.value)` for numeric values
635
+ 6. **Missing SettingsHeading** - Use headings to organize sections
636
+ 7. **Not using SettingsHint** - Add helpful context for complex fields