product-runner 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +165 -0
  2. package/common/agents/README.md +68 -0
  3. package/common/agents/agente-conceituacao.md +141 -0
  4. package/common/agents/agente-documentacao-funcional.md +107 -0
  5. package/common/agents/agente-gerador-spec.md +106 -0
  6. package/common/agents/agente-kickoff.md +121 -0
  7. package/common/agents/agente-prod-runner.md +107 -0
  8. package/common/agents/agente-review-code.md +97 -0
  9. package/common/agents/agente-review-llm.md +94 -0
  10. package/common/agents/agente-review-product.md +98 -0
  11. package/common/agents/agente-user-review.md +99 -0
  12. package/common/agents/protocolo-de-gates.md +51 -0
  13. package/common/claude-md.template.md +210 -0
  14. package/common/design-principles.md +229 -0
  15. package/common/lessons-learned.md +440 -0
  16. package/common/pipeline.md +143 -0
  17. package/common/spec-guide.md +327 -0
  18. package/common/specs/_open-issues.md +46 -0
  19. package/common/specs/_overview.md +75 -0
  20. package/dist/cli.js +187 -0
  21. package/dist/migrations.js +147 -0
  22. package/dist/scaffold.js +276 -0
  23. package/dist/update.js +400 -0
  24. package/migrations/0.3.0.md +54 -0
  25. package/migrations/0.4.0.md +76 -0
  26. package/migrations/0.5.0.md +55 -0
  27. package/migrations/README.md +68 -0
  28. package/package.json +41 -0
  29. package/profile-cli/README.md +54 -0
  30. package/profile-cli/claude-md.extension.md +102 -0
  31. package/profile-cli/code-patterns.md +363 -0
  32. package/profile-ssr/DESIGN-SYSTEM.md +795 -0
  33. package/profile-ssr/README.md +51 -0
  34. package/profile-ssr/api-patterns.md +70 -0
  35. package/profile-ssr/claude-md.extension.md +113 -0
  36. package/profile-ssr/code-patterns.md +175 -0
  37. package/profile-ssr/ui-patterns.md +97 -0
@@ -0,0 +1,795 @@
1
+ # Design System — Reference (LLM)
2
+
3
+ > Reference document for AI tools (Claude Code, etc.) and developers working on the painel.
4
+ > When in doubt about UI decisions, consult this file before improvising.
5
+
6
+ ---
7
+
8
+ ## Invariants (non-negotiable)
9
+
10
+ 1. **Tokens are the source of truth.** Import from `@/lib/tokens`. Never hardcode color, spacing, radius, motion, or typography values in components.
11
+ 2. **Components in `components/ui/` import tokens, never hardcode.** Tailwind utilities backed by tokens are the only sanctioned styling path.
12
+ 3. **DS rules apply everywhere.** Any UI built without consulting this file is suspect. If a rule blocks something legitimate, surface the conflict instead of working around it silently.
13
+ 4. **Semantic theme tokens are mandatory for structural styling.** Background, text, border, state and focus-ring of base components must use the semantic utilities (`bg-surface`, `text-text-primary`, `border-border-default`, `bg-state-*`, `ring-focus-ring`) — never raw `neutral-*` / `primary-*`. Primitives (`primary/neutral/success/warning/danger`) are reserved for decorative / non-structural cases. See [Theming (Light/Dark)](#theming-lightdark).
14
+ 5. **Light/dark support is mandatory for DS components.** Components must be theme-agnostic: they render correctly in both themes via CSS variables + `darkMode: 'class'`, without changing component code. The active theme is selected by toggling the `.dark` class on the root (`<html>`).
15
+
16
+ ---
17
+
18
+ ## Tokens
19
+
20
+ Tokens live in `@/lib/tokens`. Tailwind config consumes them via `theme.extend`.
21
+
22
+ There are two layers:
23
+
24
+ - **Primitives** — raw scales. Decorative / non-structural use only.
25
+ - **Semantic** — theme-aware roles (background, text, border, state, focus). Mandatory for structural styling. See [Theming (Light/Dark)](#theming-lightdark).
26
+
27
+ Available namespaces:
28
+
29
+ - `tokens.color.{primary, neutral, success, warning, danger}.{50..900}` — primitives
30
+ - `tokens.semantic.{light,dark}.{bg,text,border,state,focus}` — semantic roles
31
+ - `tokens.space.{1, 2, 3, 4, 6, 8}`
32
+ - `tokens.radius.{sm, md, lg, xl}`
33
+ - `tokens.motion.{fast, base, slow}`
34
+ - `tokens.font.{family.{sans, mono}, size.{xs, sm, base, lg, xl}}`
35
+
36
+ Use Tailwind utilities for everything: `bg-surface`, `text-text-primary`, `p-4`, `rounded-lg`, `duration-base`. For inline styles needing a token value, import from `@/lib/tokens` directly.
37
+
38
+ ---
39
+
40
+ ## Theming (Light/Dark)
41
+
42
+ The DS supports light and dark themes through a semantic token layer. Components never branch on theme — they reference semantic utilities, and the active theme is chosen by toggling the `.dark` class on `<html>`.
43
+
44
+ ### Token model: primitives × semantic
45
+
46
+ | Layer | Where | Example | When to use |
47
+ | ------------- | --------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------ |
48
+ | **Primitive** | `tokens.color.*` → `bg-primary-600` | `primary-600`, `neutral-200` | Decorative accents, charts, illustrations, one-off non-structural color. |
49
+ | **Semantic** | `tokens.semantic.*` → CSS vars → `bg-surface` | `surface`, `text-primary` | **All structural styling** of base components: bg, text, border, state, focus. |
50
+
51
+ Flow: `tokens.semantic.{light,dark}` (conceptual map) → RGB-triplet CSS variables in `src/styles/globals.css` (`:root` = light, `.dark` = dark) → Tailwind utilities in `tailwind.config.ts` (`rgb(var(--token) / <alpha-value>)`). `tokens.ts` stays the single source of truth for the role→primitive mapping; keep both files in sync.
52
+
53
+ ### Rule of use
54
+
55
+ - **Structural → semantic, always.** Surfaces, body/secondary text, separators, the focus ring, and primary/success/warning/danger _state fills_ on base components use semantic utilities.
56
+ - **Primitive → decorative only.** Reach for `neutral-*` / `primary-*` only when a value is genuinely not structural (and never for the structural roles a semantic token already covers).
57
+ - **State tones (success/warning/danger primitives)** remain acceptable for tonal chips/alerts (non-structural), but neutral and primary structural color must go through semantic tokens.
58
+ - **On-color foreground** (text on a saturated state fill, e.g. a primary button) stays `text-white` — it is correct in both themes.
59
+
60
+ ### Official semantic utilities
61
+
62
+ | Role | Utilities | CSS variable(s) |
63
+ | ---------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
64
+ | Background | `bg-canvas`, `bg-surface`, `bg-elevated` | `--bg-canvas`, `--bg-surface`, `--bg-elevated` |
65
+ | Text | `text-text-primary`, `text-text-secondary`, `text-text-inverse` | `--text-primary`, `--text-secondary`, `--text-inverse` |
66
+ | Border | `border-border-default`, `border-border-subtle` | `--border-default`, `--border-subtle` |
67
+ | State | `bg-state-primary`, `bg-state-success`, `bg-state-warning`, `bg-state-danger` | `--state-primary`, `--state-success`, `--state-warning`, `--state-danger` |
68
+ | Focus ring | `ring-focus-ring` (use with `focus-visible:ring-2`) | `--focus-ring` |
69
+
70
+ These accept alpha (e.g. `bg-state-primary/10`, `hover:bg-text-primary/5`) because the config maps them as `rgb(var(--token) / <alpha-value>)`. Two recurring theme-agnostic patterns: subtle hovers via `hover:bg-text-primary/5|10`, and inverse surfaces (tooltip/toast info) via `bg-text-primary text-text-inverse`.
71
+
72
+ ### Do / Don't
73
+
74
+ **Do**
75
+
76
+ - Use `bg-surface` / `bg-canvas` / `bg-elevated` for component surfaces.
77
+ - Use `text-text-primary` / `text-text-secondary` for content text.
78
+ - Use `border-border-default` / `-subtle` for separators and outlines.
79
+ - Use `ring-focus-ring` for focus-visible rings, `border-state-danger` + `ring-state-danger` for error fields.
80
+ - Express subtle hovers as `hover:bg-text-primary/5` (or `/10`) so they adapt to the theme.
81
+
82
+ **Don't**
83
+
84
+ - Don't use `bg-white`, `bg-neutral-50`, `text-neutral-900`, `border-neutral-200`, `focus:ring-primary-500` for structural styling.
85
+ - Don't hardcode hex/rgb/hsl in `components/ui/*`.
86
+ - Don't branch component logic on the theme — semantic tokens already do it.
87
+ - Don't add a new CSS variable without also adding its `:root` + `.dark` value and a Tailwind utility mapping.
88
+
89
+ ### Quick substitution table
90
+
91
+ | Before | After |
92
+ | ------------------------ | ----------------------- |
93
+ | `bg-white` | `bg-surface` |
94
+ | `bg-neutral-50` | `bg-canvas` |
95
+ | `text-neutral-900` | `text-text-primary` |
96
+ | `text-neutral-600/500` | `text-text-secondary` |
97
+ | `border-neutral-200` | `border-border-default` |
98
+ | `focus:ring-primary-500` | `focus:ring-focus-ring` |
99
+
100
+ ### Example
101
+
102
+ ```tsx
103
+ // Theme-agnostic surface — renders correctly in light and dark.
104
+ <div className="bg-surface text-text-primary border border-border-default rounded-lg p-4">
105
+ <h3 className="font-semibold">Resumo</h3>
106
+ <p className="text-text-secondary">Detalhe secundário.</p>
107
+ <button className="mt-3 h-9 px-4 rounded-md bg-state-primary text-white hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring">
108
+ Salvar
109
+ </button>
110
+ </div>
111
+ ```
112
+
113
+ Enabling dark mode: toggle the class on the root — `document.documentElement.classList.toggle('dark')`. The DS Explorer header (`/ds-explorer`) has a working toggle that exercises every component in both themes.
114
+
115
+ ---
116
+
117
+ ## Patterns
118
+
119
+ Transversal rules that govern how components compose. Each component declares which patterns it applies.
120
+
121
+ ### Alinhamento de ações (`action-alignment`)
122
+
123
+ Em qualquer container que agrupe ações (footer de form, card, modal, drawer, toolbar), as ações ficam alinhadas à direita, com a primária na ponta direita.
124
+
125
+ **Rule:** Ações secundárias (Cancelar/Limpar/Voltar) à esquerda do grupo. Primária (Salvar/Enviar/Confirmar/Excluir destrutivo) sempre na ponta direita. Espaçamento entre botões = `space.2`.
126
+
127
+ **Applies to:** Form, Card (footer com ações), Modal/Dialog, Drawer/Sheet, Page toolbar
128
+ **Does NOT apply to:** Botões-gatilho avulsos no meio de conteúdo, ações inline em linhas de tabela, CTAs em landing/marketing, toolbar contextual em editor
129
+
130
+ ---
131
+
132
+ ### Timing de validação (`validation-timing`)
133
+
134
+ Quando disparar validação para que o feedback seja útil sem ser ruidoso.
135
+
136
+ **Rule:** Campos individuais validam onBlur. Form inteiro valida onSubmit. Nunca onChange a cada tecla, exceto para feedback contínuo previsível (medidor de senha, contador de caracteres). Após erro de submit, revalidar onChange para o erro sumir conforme o usuário corrige.
137
+
138
+ **Applies to:** Input, Select, Form, Form fields compostos
139
+ **Does NOT apply to:** Search-as-you-type, editores colaborativos em tempo real, medidor de força de senha / contador de caracteres
140
+
141
+ ---
142
+
143
+ ### Estados vazios (`empty-states`)
144
+
145
+ Toda lista, tabela ou área de conteúdo dinâmico precisa de um estado vazio que explique e ofereça caminho.
146
+
147
+ **Rule:** Estado vazio tem: (1) ícone/ilustração simples, (2) título curto que diz por que está vazio, (3) descrição opcional com próximo passo, (4) ação primária quando aplicável. Diferenciar 3 tipos: nunca-teve-dados (onboarding), filtro-vazio (limpar filtro), erro-de-carga (tentar novamente).
148
+
149
+ **Applies to:** Table, Listas, Dashboard widgets, Resultados de busca/filtro, Caixa de entrada
150
+ **Does NOT apply to:** Áreas vazias durante loading (use Skeleton), campos de formulário vazios, containers que falham por erro de rede (use Alert)
151
+
152
+ ---
153
+
154
+ ### Estados de carregamento (`loading-states`)
155
+
156
+ Qual indicador usar em função da latência esperada.
157
+
158
+ **Rule:** <300ms: nada. 300ms–1s: spinner inline ou loading no botão acionado. 1–10s: skeleton replicando layout final. >10s: skeleton + mensagem de progresso ou estimativa. Sempre prever fallback de erro com retry.
159
+
160
+ **Applies to:** Table (skeleton de linhas), Card com dados async, Dashboard widgets, Botão durante submit, Página em navegação
161
+ **Does NOT apply to:** Operações locais síncronas, ações instantâneas confirmadas (use Toast pós-conclusão)
162
+
163
+ ---
164
+
165
+ ### Hierarquia de feedback (`feedback-hierarchy`)
166
+
167
+ Qual canal usar — Toast, Alert ou Modal — em função da urgência e do bloqueio que o feedback deve impor.
168
+
169
+ **Rule:** Toast = evento transitório do sistema, não-bloqueante (salvo, copiado, enviado), auto-dismiss 4–7s. Alert = info persistente em-página, contextual à view, não bloqueia. Modal = decisão obrigatória antes de continuar, bloqueia até resposta.
170
+
171
+ **Applies to:** Toast, Alert, Modal, Banners de sistema
172
+ **Does NOT apply to:** Mensagens de erro inline em formulário (parte do Input), Tooltips (informação on-demand)
173
+
174
+ ---
175
+
176
+ ## Components
177
+
178
+ 21 components total, alphabetical.
179
+
180
+ ### Accordion
181
+
182
+ `@/components/ui/accordion` — Seções colapsáveis para revelar conteúdo sob demanda, reduzindo a primeira impressão de uma tela densa.
183
+
184
+ **Patterns:** —
185
+
186
+ | prop | type | default |
187
+ | ------------- | ----------------------------------------------------- | ------- |
188
+ | items | `{ id: string, title: string, content: ReactNode }[]` | — |
189
+ | allowMultiple | `boolean` | false |
190
+ | defaultOpen | `string[]` | [] |
191
+
192
+ **Do:** Use para FAQs, configurações em grupos, detalhes opcionais • Títulos descritivos (não "Mais") • allowMultiple quando seções são independentes • Estado inicial fechado por padrão
193
+ **Don't:** Accordion para fluxo sequencial obrigatório (use Stepper) • Esconder informação crítica em accordion fechado • Aninhar accordions além de 1 nível • Animar abertura por mais de 200ms
194
+
195
+ **Example:**
196
+
197
+ ```tsx
198
+ <Accordion items={faqItems} allowMultiple />
199
+ ```
200
+
201
+ ---
202
+
203
+ ### Alert
204
+
205
+ `@/components/ui/alert` — Mensagem persistente em-página sobre estado relevante.
206
+
207
+ **Patterns:** `feedback-hierarchy`
208
+
209
+ | prop | type | default |
210
+ | ------- | ---------------------------------------- | ------- |
211
+ | tone | `'info'\|'success'\|'warning'\|'danger'` | info |
212
+ | title | `string` | — |
213
+ | onClose | `() => void` | — |
214
+
215
+ **Do:** Use para info contextual permanente • Inclua ação se houver ("Tentar novamente") • Tom semântico correto
216
+ **Don't:** Múltiplos alerts amontoados • Tom vermelho em info benigna
217
+
218
+ **Example:**
219
+
220
+ ```tsx
221
+ <Alert tone="warning" title="Sessão expira em 5 min">
222
+ Salve seu trabalho.
223
+ </Alert>
224
+ ```
225
+
226
+ ---
227
+
228
+ ### Avatar
229
+
230
+ `@/components/ui/avatar` — Representar identidade de usuário ou entidade.
231
+
232
+ **Patterns:** —
233
+
234
+ | prop | type | default |
235
+ | ---- | ------------------ | ------- |
236
+ | name | `string` | — |
237
+ | src | `string` | — |
238
+ | size | `'sm'\|'md'\|'lg'` | md |
239
+
240
+ **Do:** Sempre aria-label com nome • Fallback de iniciais • Tamanhos consistentes
241
+ **Don't:** Avatar sem alt/label • Imagem genérica que confunde com ícone • Múltiplos tamanhos misturados na mesma lista
242
+
243
+ **Example:**
244
+
245
+ ```tsx
246
+ <Avatar name="Henrique Souza" />
247
+ ```
248
+
249
+ ---
250
+
251
+ ### Badge
252
+
253
+ `@/components/ui/badge` — Etiqueta curta para status, contagem ou categoria.
254
+
255
+ **Patterns:** —
256
+
257
+ | prop | type | default |
258
+ | ---- | --------------------------------------------------- | ------- |
259
+ | tone | `'neutral'\|'info'\|'success'\|'warning'\|'danger'` | neutral |
260
+
261
+ **Do:** 1-2 palavras • Use junto a um elemento que ele descreve • Tom semântico
262
+ **Don't:** Badge isolado sem âncora visual • Badge em vez de Button • Frase completa
263
+
264
+ **Example:**
265
+
266
+ ```tsx
267
+ <Badge tone="success">Aprovado</Badge>
268
+ ```
269
+
270
+ ---
271
+
272
+ ### Breadcrumb
273
+
274
+ `@/components/ui/breadcrumb` — Mostrar localização hierárquica e oferecer caminho de volta.
275
+
276
+ **Patterns:** —
277
+
278
+ | prop | type | default |
279
+ | ----- | ------------------------------------ | ------- |
280
+ | items | `{ label: string, href?: string }[]` | — |
281
+
282
+ **Do:** Último item não-link, com aria-current="page" • Use em hierarquias profundas (≥2) • Separador consistente
283
+ **Don't:** Breadcrumb em telas planas • Substituir nav primária • Quebrar em múltiplas linhas em desktop
284
+
285
+ **Example:**
286
+
287
+ ```tsx
288
+ <Breadcrumb
289
+ items={[
290
+ { label: 'Início', href: '/' },
291
+ { label: 'Documentos', href: '/docs' },
292
+ { label: 'Inventário 2025' },
293
+ ]}
294
+ />
295
+ ```
296
+
297
+ ---
298
+
299
+ ### Button
300
+
301
+ `@/components/ui/button` — Disparar ação imediata do usuário.
302
+
303
+ **Patterns:** `action-alignment`
304
+
305
+ | prop | type | default |
306
+ | --------- | ------------------------------------------- | ------- |
307
+ | variant | `'primary'\|'secondary'\|'danger'\|'ghost'` | primary |
308
+ | size | `'sm'\|'md'\|'lg'` | md |
309
+ | loading | `boolean` | false |
310
+ | disabled | `boolean` | false |
311
+ | leftIcon | `IconComponent` | — |
312
+ | rightIcon | `IconComponent` | — |
313
+
314
+ **Do:** Use 1 botão primário por área de decisão • Texto-verbo no infinitivo ("Salvar", "Enviar") • Use loading em ações ≥ 200ms
315
+ **Don't:** Múltiplos primários competindo • Texto vago ("OK", "Clique aqui") • Tamanho < 40px em mobile
316
+
317
+ **Example:**
318
+
319
+ ```tsx
320
+ <Button loading>Salvando…</Button>
321
+ ```
322
+
323
+ ---
324
+
325
+ ### Card
326
+
327
+ `@/components/ui/card` — Agrupar conteúdo relacionado em uma unidade visual.
328
+
329
+ **Patterns:** `action-alignment`
330
+
331
+ | prop | type | default |
332
+ | --------- | ----------- | ------- |
333
+ | title | `ReactNode` | — |
334
+ | footer | `ReactNode` | — |
335
+ | className | `string` | — |
336
+
337
+ **Do:** Use para agrupar conteúdo coeso • Mantenha hierarquia: title → content → actions • Footer só para ações ou metadados
338
+ **Don't:** Card aninhado em card aninhado em card • Conteúdo desconexo no mesmo card • Sombra agressiva
339
+
340
+ **Example:**
341
+
342
+ ```tsx
343
+ <Card
344
+ title="Resumo"
345
+ footer={
346
+ <div className="flex justify-end">
347
+ <Button size="sm">Detalhes</Button>
348
+ </div>
349
+ }
350
+ >
351
+ <p>Conteúdo principal.</p>
352
+ </Card>
353
+ ```
354
+
355
+ ---
356
+
357
+ ### Checkbox
358
+
359
+ `@/components/ui/checkbox` — Seleção independente (0..N) ou toggle binário em formulário.
360
+
361
+ **Patterns:** —
362
+
363
+ | prop | type | default |
364
+ | -------- | ------------- | ------- |
365
+ | label | `string` | — |
366
+ | checked | `boolean` | — |
367
+ | onChange | `(e) => void` | — |
368
+
369
+ **Do:** Label clicável (associada via htmlFor) • Use para opções independentes • Erro de obrigatório claro
370
+ **Don't:** Checkbox para escolha exclusiva (use radio) • Sem label visível • Estado indeterminado sem necessidade
371
+
372
+ **Example:**
373
+
374
+ ```tsx
375
+ <Checkbox
376
+ label="Receber novidades"
377
+ checked={v}
378
+ onChange={(e) => setV(e.target.checked)}
379
+ />
380
+ ```
381
+
382
+ ---
383
+
384
+ ### DatePicker
385
+
386
+ `@/components/ui/date-picker` — Selecionar uma data por digitação ou pelo calendário, com formato local.
387
+
388
+ **Patterns:** `validation-timing`
389
+
390
+ | prop | type | default |
391
+ | -------- | ------------------------------ | ------- |
392
+ | value | `Date \| null` | — |
393
+ | onChange | `(date: Date \| null) => void` | — |
394
+ | label | `string` | — |
395
+ | hint | `string` | — |
396
+ | error | `string` | — |
397
+
398
+ **Do:** Aceite digitação E seleção visual • Use formato local (dd/mm/aaaa em pt-BR) • Hoje destacado, selecionado destacado distintamente • Botões "Hoje" e "Limpar" como atalhos
399
+ **Don't:** Forçar máscara que bloqueia colar • Calendário sem destaque visual de hoje • DatePicker para datas pré-definidas (use Select com opções) • Calendário cobrindo o input após seleção
400
+
401
+ **Example:**
402
+
403
+ ```tsx
404
+ <DatePicker
405
+ label="Data de nascimento"
406
+ value={d}
407
+ onChange={setD}
408
+ hint="Use dd/mm/aaaa"
409
+ />
410
+ ```
411
+
412
+ ---
413
+
414
+ ### Form
415
+
416
+ `@/components/ui/form` — Composição de campos com validação, submissão e feedback. Usar com react-hook-form + zod resolver.
417
+
418
+ **Patterns:** `action-alignment`, `validation-timing`
419
+
420
+ _Sem props diretas — é composição._
421
+
422
+ **Do:** Reutilize schema do service (não duplique validação) • Foco vai para o primeiro campo com erro • Confirme sucesso explicitamente (Toast ou Alert) • Erros de API via Toast
423
+ **Don't:** Submeter sem feedback • Esconder campos obrigatórios em accordions fechados • Resetar dados após erro • Duplicar validação entre client e service
424
+
425
+ A API real é `Form` + `FormFooter` (não `FormField`/`FormItem`/`FormMessage`).
426
+ Os campos (`Input`, `Select`, `Checkbox`) trazem label/hint/error embutidos.
427
+
428
+ **Example:**
429
+
430
+ ```tsx
431
+ const [data, setData] = useState({ email: '', role: 'analyst' });
432
+ const [errors, setErrors] = useState<Record<string, string>>({});
433
+
434
+ <Form onSubmit={handleSubmit}>
435
+ <Input
436
+ label="E-mail"
437
+ type="email"
438
+ required
439
+ value={data.email}
440
+ onChange={(e) => setData({ ...data, email: e.target.value })}
441
+ onBlur={() => validateEmail(data.email, setErrors)}
442
+ error={errors.email}
443
+ />
444
+ <FormFooter>
445
+ <Button variant="ghost" type="button">
446
+ Limpar
447
+ </Button>
448
+ <Button type="submit">Enviar</Button>
449
+ </FormFooter>
450
+ </Form>;
451
+ ```
452
+
453
+ **RHF + zod compatibility:** Input/Select/Checkbox exportam `forwardRef`. Use `{...register('campo')}` direto.
454
+
455
+ ---
456
+
457
+ ### Input
458
+
459
+ `@/components/ui/input` — Coletar texto curto do usuário.
460
+
461
+ **Patterns:** `validation-timing`
462
+
463
+ | prop | type | default |
464
+ | -------- | --------------- | ------- |
465
+ | label | `string` | — |
466
+ | hint | `string` | — |
467
+ | error | `string` | — |
468
+ | required | `boolean` | false |
469
+ | type | `HTMLInputType` | text |
470
+
471
+ **Do:** Sempre use label visível • Indique \* em campos obrigatórios • Erros descrevem o problema E como corrigir
472
+ **Don't:** Placeholder substituindo label • Erro só por cor da borda • Validação só ao submeter, sem hint prévio
473
+
474
+ **Example:**
475
+
476
+ ```tsx
477
+ <Input label="E-mail" hint="Usaremos para login" type="email" />
478
+ ```
479
+
480
+ ---
481
+
482
+ ### Modal
483
+
484
+ `@/components/ui/dialog` — Tarefa focada que exige atenção total ou confirmação destrutiva.
485
+
486
+ **Patterns:** `action-alignment`, `feedback-hierarchy`
487
+
488
+ | prop | type | default |
489
+ | ------- | ------------ | ------- |
490
+ | open | `boolean` | — |
491
+ | onClose | `() => void` | — |
492
+ | title | `ReactNode` | — |
493
+ | footer | `ReactNode` | — |
494
+
495
+ **Do:** Foco vai para 1º elemento ao abrir • Esc fecha
496
+ **Don't:** Modal para fluxo longo • Modal sobre modal • Confirmações triviais ("Tem certeza que quer salvar?")
497
+
498
+ **Example:**
499
+
500
+ ```tsx
501
+ <Modal
502
+ open={open}
503
+ onClose={() => setOpen(false)}
504
+ title="Excluir documento"
505
+ footer={
506
+ <>
507
+ <Button variant="ghost">Cancelar</Button>
508
+ <Button variant="danger">Excluir</Button>
509
+ </>
510
+ }
511
+ >
512
+ Esta ação não pode ser desfeita.
513
+ </Modal>
514
+ ```
515
+
516
+ ---
517
+
518
+ ### Nav
519
+
520
+ `@/components/ui/nav` — Estrutura de navegação primária. Exporta dois componentes para composição flexível: `TopBar` (header) e `SideNav` (lista lateral).
521
+
522
+ **Patterns:** —
523
+
524
+ **TopBar:**
525
+
526
+ | prop | type | default |
527
+ | ------- | ----------- | ------- |
528
+ | brand | `ReactNode` | — |
529
+ | actions | `ReactNode` | — |
530
+
531
+ **SideNav:**
532
+
533
+ | prop | type | default |
534
+ | ---------- | ------------------------------- | ------- |
535
+ | items | `{ id, label, href?, icon? }[]` | — |
536
+ | activeId | `string` | — |
537
+ | onNavigate | `(id: string) => void` | — |
538
+
539
+ **Do:** Marque rota ativa com aria-current="page" • Limite top-level a ~5-7 itens • Mantenha ordem estável
540
+ **Don't:** Reordenar itens dinamicamente • Esconder navegação principal em hover • Ícones sem texto em desktop
541
+
542
+ **Example:**
543
+
544
+ ```tsx
545
+ <TopBar brand={<Logo />} actions={<Avatar name="HS" />} />
546
+ <SideNav items={navItems} activeId={route} />
547
+ ```
548
+
549
+ ---
550
+
551
+ ### Pagination
552
+
553
+ `@/components/ui/pagination` — Navegar entre páginas de uma lista longa, mantendo posição clara e atalhos para extremos.
554
+
555
+ **Patterns:** `loading-states`
556
+
557
+ | prop | type | default |
558
+ | ------------ | ------------------------ | ------- |
559
+ | page | `number` | — |
560
+ | totalPages | `number` | — |
561
+ | onChange | `(page: number) => void` | — |
562
+ | siblingCount | `number` | 1 |
563
+
564
+ **Do:** Mostre primeira e última página sempre • Use ellipsis para gaps • Indique página atual com aria-current="page"
565
+ **Don't:** Listar todas as páginas em listas longas (>10) • Pagination quando infinite scroll é melhor (feeds) • Tamanho de alvo < 44px em mobile • Esconder total de páginas
566
+
567
+ **Example:**
568
+
569
+ ```tsx
570
+ <Pagination page={page} totalPages={50} onChange={setPage} />
571
+ ```
572
+
573
+ ---
574
+
575
+ ### Popover
576
+
577
+ `@/components/ui/popover` — Painel flutuante interativo, ancorado a um gatilho, com conteúdo rico (texto, ações, formulários curtos).
578
+
579
+ **Patterns:** `action-alignment`
580
+
581
+ | prop | type | default |
582
+ | -------- | ----------------- | ------- |
583
+ | trigger | `ReactNode` | — |
584
+ | side | `'top'\|'bottom'` | bottom |
585
+ | children | `ReactNode` | — |
586
+
587
+ **Do:** Use para conteúdo rico mas curto (até 3 ações) • Esc deve fechar • Posicione próximo ao gatilho
588
+ **Don't:** Popover para hint simples (use Tooltip) • Popover para fluxo longo (use Modal/Drawer) • Popover sem ancoragem visual ao gatilho • Múltiplos popovers abertos simultaneamente
589
+
590
+ **Example:**
591
+
592
+ ```tsx
593
+ <Popover trigger={<Button>Conta</Button>}>
594
+ <button>Perfil</button>
595
+ <button>Configurações</button>
596
+ <button>Sair</button>
597
+ </Popover>
598
+ ```
599
+
600
+ ---
601
+
602
+ ### Select
603
+
604
+ `@/components/ui/select` — Escolha única entre poucas opções conhecidas.
605
+
606
+ **Patterns:** `validation-timing`
607
+
608
+ | prop | type | default |
609
+ | ------- | ------------------------------------ | ------- |
610
+ | label | `string` | — |
611
+ | options | `{ value: string, label: string }[]` | [] |
612
+ | hint | `string` | — |
613
+ | error | `string` | — |
614
+
615
+ **Do:** Default razoável já selecionado • Ordem lógica (alfabética/frequência) • Para >10 opções use combobox com busca
616
+ **Don't:** Select com 1 opção • Select para múltipla seleção (use checkboxes) • Mudança de opção disparando navegação sem aviso
617
+
618
+ **Example:**
619
+
620
+ ```tsx
621
+ <Select
622
+ label="Função"
623
+ options={[
624
+ { value: 'analyst', label: 'Analista' },
625
+ { value: 'manager', label: 'Gerente' },
626
+ ]}
627
+ />
628
+ ```
629
+
630
+ ---
631
+
632
+ ### Skeleton
633
+
634
+ `@/components/ui/skeleton` — Placeholder de carregamento que reproduz o layout final.
635
+
636
+ **Patterns:** `loading-states`
637
+
638
+ | prop | type | default |
639
+ | --------- | -------- | ------- |
640
+ | className | `string` | — |
641
+
642
+ **Do:** Reproduza dimensões aproximadas do conteúdo final • aria-hidden + role=presentation
643
+ **Don't:** Forma muito diferente do real (causa shift) • Spinner + skeleton ao mesmo tempo
644
+
645
+ **Example:**
646
+
647
+ ```tsx
648
+ <div className="space-y-2">
649
+ <Skeleton className="h-5 w-1/2" />
650
+ <Skeleton className="h-4 w-full" />
651
+ </div>
652
+ ```
653
+
654
+ ---
655
+
656
+ ### Table
657
+
658
+ `@/components/ui/table` — Exibir dados tabulares para comparação e ordenação.
659
+
660
+ **Patterns:** `empty-states`, `loading-states`
661
+
662
+ | prop | type | default |
663
+ | --------- | ------------------------------------------------------ | ------- |
664
+ | columns | `{ key: string, label: string, sortable?: boolean }[]` | — |
665
+ | rows | `Record<string, ReactNode>[]` | — |
666
+ | maxHeight | `number \| string` | — |
667
+
668
+ **Do:** Headers de coluna sempre visíveis • Alinhe números à direita • Para encaixar uma lista pequena/moderada num espaço limitado, use `maxHeight` (rolagem vertical com header fixo no topo) • Forneça paginação >25 linhas
669
+ **Don't:** Tabela para dados não tabulares • Ordenação só por mouse • Larguras instáveis a cada render • `maxHeight` como substituto de paginação (a regra dos 25 vale dentro e fora do scroll)
670
+
671
+ **Altura máxima + scroll vertical:** quando `maxHeight` é definida, o wrapper vira contexto de rolagem (`overflow-auto`) e o `thead` fica `sticky` no topo, mantendo os headers visíveis enquanto as linhas rolam. `number` é interpretado como px. É **ortogonal** à paginação: serve para encaixar uma lista que cresce (ex.: compras de um par) dentro de um card sem esticar a página — **não** substitui paginação. Datasets grandes seguem a regra de paginação (>25 linhas) mesmo dentro do scroll, para não renderizar/rolar centenas de linhas.
672
+
673
+ **Example:**
674
+
675
+ ```tsx
676
+ <Table
677
+ columns={[
678
+ { key: 'doc', label: 'Documento', sortable: true },
679
+ { key: 'status', label: 'Status' },
680
+ ]}
681
+ rows={data}
682
+ />
683
+
684
+ // Lista longa em altura limitada: corpo rola, header fica fixo.
685
+ <Table columns={columns} rows={manyRows} maxHeight={280} />
686
+ ```
687
+
688
+ ---
689
+
690
+ ### Tabs
691
+
692
+ `@/components/ui/tabs` — Alternar entre seções de mesmo nível em uma área de conteúdo.
693
+
694
+ **Patterns:** —
695
+
696
+ | prop | type | default |
697
+ | ----- | ----------------------------------------- | ------- |
698
+ | items | `{ label: string, content: ReactNode }[]` | — |
699
+
700
+ **Do:** Use 2-6 abas • Labels curtos (1-2 palavras) • Mantenha conteúdos de mesma natureza
701
+ **Don't:** Tabs para fluxo sequencial (use Stepper) • Tabs aninhadas • Conteúdo crítico só em aba secundária
702
+
703
+ **Example:**
704
+
705
+ ```tsx
706
+ <Tabs
707
+ items={[
708
+ { label: 'Visão geral', content: <p>Resumo</p> },
709
+ { label: 'Detalhes', content: <p>Detalhes</p> },
710
+ ]}
711
+ />
712
+ ```
713
+
714
+ ---
715
+
716
+ ### Toast
717
+
718
+ `@/components/ui/toast` — Feedback transitório de evento do sistema.
719
+
720
+ **Patterns:** `feedback-hierarchy`
721
+
722
+ | prop | type | default |
723
+ | -------- | ---------------------------------------- | ------- |
724
+ | tone | `'info'\|'success'\|'warning'\|'danger'` | info |
725
+ | title | `string` | — |
726
+ | children | `ReactNode` | — |
727
+
728
+ **Do:** Mensagens curtas (1 linha) • Auto-dismiss em 4-7s para info/success • Erros persistentes até dismiss manual
729
+ **Don't:** Vários toasts empilhados sem agrupamento • Texto comprido
730
+
731
+ **Example:**
732
+
733
+ ```tsx
734
+ const { toast } = useToast();
735
+
736
+ toast({
737
+ tone: 'success',
738
+ title: 'Salvo',
739
+ description: 'Alterações persistidas.',
740
+ });
741
+ toast({ tone: 'danger', title: 'Falha', duration: Infinity });
742
+ ```
743
+
744
+ **Setup:** wrap app tree em `<ToastProvider>` no `_app.tsx`.
745
+
746
+ ---
747
+
748
+ ### Tooltip
749
+
750
+ `@/components/ui/tooltip` — Hint curto e on-demand sobre um elemento, exibido em hover/focus.
751
+
752
+ **Patterns:** —
753
+
754
+ | prop | type | default |
755
+ | ------- | ---------------------------------- | ------- |
756
+ | content | `string \| ReactNode` | — |
757
+ | side | `'top'\|'bottom'\|'left'\|'right'` | top |
758
+ | delay | `number (ms)` | 300 |
759
+
760
+ **Do:** Use para esclarecer ícones sem rótulo • Texto curto (1 linha, sem ações) • Delay de ~300ms antes de aparecer • Aparece em hover E focus
761
+ **Don't:** Tooltip com info crítica (ela some) • Tooltip com botões/links interativos (use Popover) • Tooltip em texto já legível • Tooltip que cobre o gatilho
762
+
763
+ **Example:**
764
+
765
+ ```tsx
766
+ <Tooltip content="Filtrar resultados">
767
+ <Button variant="ghost" size="icon">
768
+ <Filter />
769
+ </Button>
770
+ </Tooltip>
771
+ ```
772
+
773
+ ---
774
+
775
+ ## Workflow for AI tools (Claude Code, etc.)
776
+
777
+ When implementing or editing UI:
778
+
779
+ 1. Identify the components involved. If unsure, check this file's component sections.
780
+ 2. Identify which patterns apply (from the **Patterns** field of each component).
781
+ 3. Respect the Do/Don't rules per component and the Rule of each applied pattern.
782
+ 4. Use tokens from `@/lib/tokens` for all visual decisions. If hardcoding seems necessary, the spec is incomplete — surface that.
783
+ 5. If you cannot fulfill a request without violating a Do/Don't or an invariant, surface the conflict instead of silently working around it.
784
+
785
+ ---
786
+
787
+ ## PR checklist (UI / DS)
788
+
789
+ Before opening a PR that touches UI, confirm:
790
+
791
+ - [ ] No hardcoded colors (hex/rgb/hsl) in `components/ui/*`.
792
+ - [ ] Structural styling uses semantic utilities (`bg-surface`/`bg-canvas`, `text-text-*`, `border-border-*`, `bg-state-*`, `ring-focus-ring`) — no raw `neutral-*` / `primary-*`.
793
+ - [ ] Renders correctly in both light and dark (verify with the `/ds-explorer` theme toggle).
794
+ - [ ] Focus-visible state is clearly visible in both themes.
795
+ - [ ] Contrast is acceptable (text vs background, in both themes).