@softize/opus 12.10.0 → 13.0.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 (154) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/bin/lib/check.mjs +1098 -310
  3. package/bin/lib/copy.mjs +12 -5
  4. package/docs/adr/0003-dictionary-presentation-is-declared.md +3 -0
  5. package/docs/adr/0004-page-content-state-is-composed.md +65 -0
  6. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +180 -0
  7. package/docs/adr/0006-semantic-context-precedes-visual-variant.md +182 -0
  8. package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
  9. package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
  10. package/docs/radius-scale.md +1 -1
  11. package/package.json +1 -1
  12. package/registry/instructions/opus.md +5 -0
  13. package/registry/skills/build-opus-ui/SKILL.md +27 -16
  14. package/registry/skills/build-opus-ui/references/evaluations.md +16 -5
  15. package/registry/skills/build-opus-ui/references/ui-patterns.md +38 -15
  16. package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
  17. package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
  18. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
  19. package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
  20. package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
  21. package/registry/skills/model-opus-dictionary/SKILL.md +4 -2
  22. package/registry/skills/model-opus-dictionary/references/evaluations.md +4 -3
  23. package/registry/templates/app/src/App.tsx +1 -1
  24. package/src/core/dictionary.ts +52 -14
  25. package/src/core/index.ts +10 -0
  26. package/src/core/ui-context.ts +29 -0
  27. package/src/schema/drivers/zod.ts +17 -8
  28. package/src/ui/components/patterns/action-form-card.tsx +18 -12
  29. package/src/ui/components/patterns/confirm.tsx +163 -40
  30. package/src/ui/components/patterns/content-header.tsx +335 -61
  31. package/src/ui/components/patterns/data-state.tsx +23 -10
  32. package/src/ui/components/patterns/list.tsx +1097 -783
  33. package/src/ui/components/patterns/page-state.tsx +115 -0
  34. package/src/ui/components/patterns/page.tsx +231 -41
  35. package/src/ui/components/patterns/sidebar.tsx +357 -83
  36. package/src/ui/components/patterns/trigger.tsx +37 -30
  37. package/src/ui/components/patterns/view.tsx +7 -11
  38. package/src/ui/components/primitives/alert.tsx +298 -110
  39. package/src/ui/components/primitives/ask.tsx +2 -1
  40. package/src/ui/components/primitives/badge.tsx +91 -30
  41. package/src/ui/components/primitives/button.tsx +99 -60
  42. package/src/ui/components/primitives/calendar.tsx +39 -39
  43. package/src/ui/components/primitives/card.tsx +96 -23
  44. package/src/ui/components/primitives/detail.tsx +2 -2
  45. package/src/ui/components/primitives/dialog.tsx +196 -39
  46. package/src/ui/components/primitives/dictionary-value.tsx +9 -14
  47. package/src/ui/components/primitives/dot.tsx +74 -21
  48. package/src/ui/components/primitives/drawer.tsx +40 -24
  49. package/src/ui/components/primitives/empty.tsx +3 -3
  50. package/src/ui/components/primitives/item.tsx +135 -79
  51. package/src/ui/components/primitives/menu.tsx +11 -3
  52. package/src/ui/components/primitives/metric-card.tsx +133 -0
  53. package/src/ui/components/primitives/sonner.tsx +187 -8
  54. package/src/ui/components/primitives/table.tsx +2 -2
  55. package/src/ui/docs/DocBrowser.tsx +104 -25
  56. package/src/ui/docs/changelog.tsx +1 -1
  57. package/src/ui/docs/content/accordion.md +22 -16
  58. package/src/ui/docs/content/action-form-card.md +8 -8
  59. package/src/ui/docs/content/action-form-dialog.md +9 -9
  60. package/src/ui/docs/content/action-form.md +28 -34
  61. package/src/ui/docs/content/action-list-dialog.md +11 -6
  62. package/src/ui/docs/content/action-list.md +64 -39
  63. package/src/ui/docs/content/action-trigger.md +21 -14
  64. package/src/ui/docs/content/action-view.md +8 -8
  65. package/src/ui/docs/content/actions.md +9 -9
  66. package/src/ui/docs/content/ai.md +3 -3
  67. package/src/ui/docs/content/alert.md +54 -28
  68. package/src/ui/docs/content/aspect-ratio.md +4 -4
  69. package/src/ui/docs/content/audit.md +2 -2
  70. package/src/ui/docs/content/auth.md +3 -3
  71. package/src/ui/docs/content/avatar.md +34 -14
  72. package/src/ui/docs/content/badge.md +21 -22
  73. package/src/ui/docs/content/breadcrumb.md +13 -8
  74. package/src/ui/docs/content/button.md +93 -15
  75. package/src/ui/docs/content/calendar.md +5 -5
  76. package/src/ui/docs/content/card.md +6 -6
  77. package/src/ui/docs/content/carousel.md +16 -11
  78. package/src/ui/docs/content/chat.md +3 -3
  79. package/src/ui/docs/content/checkbox.md +7 -7
  80. package/src/ui/docs/content/cli.md +5 -5
  81. package/src/ui/docs/content/collapsible.md +8 -8
  82. package/src/ui/docs/content/command.md +16 -8
  83. package/src/ui/docs/content/composer.md +2 -2
  84. package/src/ui/docs/content/content.md +44 -0
  85. package/src/ui/docs/content/copyable.md +4 -3
  86. package/src/ui/docs/content/customization.md +7 -7
  87. package/src/ui/docs/content/cycle.md +3 -3
  88. package/src/ui/docs/content/data-state.md +11 -12
  89. package/src/ui/docs/content/data.md +26 -33
  90. package/src/ui/docs/content/detail.md +8 -5
  91. package/src/ui/docs/content/dialog.md +339 -31
  92. package/src/ui/docs/content/dictionary-value.md +19 -18
  93. package/src/ui/docs/content/dock.md +3 -3
  94. package/src/ui/docs/content/dot.md +7 -7
  95. package/src/ui/docs/content/drawer.md +32 -16
  96. package/src/ui/docs/content/empty-value.md +2 -2
  97. package/src/ui/docs/content/empty.md +19 -12
  98. package/src/ui/docs/content/events.md +4 -4
  99. package/src/ui/docs/content/field.md +34 -12
  100. package/src/ui/docs/content/getting-started.md +1 -1
  101. package/src/ui/docs/content/icon-picker.md +8 -4
  102. package/src/ui/docs/content/input-otp.md +20 -12
  103. package/src/ui/docs/content/input.md +121 -9
  104. package/src/ui/docs/content/item.md +64 -24
  105. package/src/ui/docs/content/kbd.md +19 -11
  106. package/src/ui/docs/content/label.md +5 -3
  107. package/src/ui/docs/content/log.md +4 -4
  108. package/src/ui/docs/content/markdown.md +7 -6
  109. package/src/ui/docs/content/mcp.md +13 -15
  110. package/src/ui/docs/content/menu.md +36 -17
  111. package/src/ui/docs/content/metric-card.md +41 -0
  112. package/src/ui/docs/content/observability.md +2 -2
  113. package/src/ui/docs/content/page.md +93 -10
  114. package/src/ui/docs/content/pagination.md +22 -17
  115. package/src/ui/docs/content/popover.md +16 -8
  116. package/src/ui/docs/content/progress.md +7 -5
  117. package/src/ui/docs/content/queue.md +5 -5
  118. package/src/ui/docs/content/radio-group.md +20 -12
  119. package/src/ui/docs/content/router.md +11 -6
  120. package/src/ui/docs/content/scheduler.md +4 -5
  121. package/src/ui/docs/content/scroll-area.md +12 -7
  122. package/src/ui/docs/content/select.md +42 -29
  123. package/src/ui/docs/content/semantic-context.md +63 -0
  124. package/src/ui/docs/content/separator.md +5 -5
  125. package/src/ui/docs/content/sidebar.md +325 -56
  126. package/src/ui/docs/content/skeleton.md +5 -4
  127. package/src/ui/docs/content/slider.md +8 -7
  128. package/src/ui/docs/content/spinner.md +8 -8
  129. package/src/ui/docs/content/split.md +8 -5
  130. package/src/ui/docs/content/storage.md +6 -8
  131. package/src/ui/docs/content/switch.md +8 -7
  132. package/src/ui/docs/content/table.md +16 -6
  133. package/src/ui/docs/content/tabs.md +28 -14
  134. package/src/ui/docs/content/testing.md +9 -11
  135. package/src/ui/docs/content/textarea.md +5 -4
  136. package/src/ui/docs/content/toast.md +47 -13
  137. package/src/ui/docs/content/toggle.md +75 -7
  138. package/src/ui/docs/content/tokens.md +31 -3
  139. package/src/ui/docs/content/tooltip.md +19 -11
  140. package/src/ui/docs/content/truncate.md +7 -8
  141. package/src/ui/docs/content/ui.md +10 -9
  142. package/src/ui/docs/content/upgrading.md +7 -8
  143. package/src/ui/docs/doc-client.tsx +2 -2
  144. package/src/ui/docs/registry.tsx +580 -229
  145. package/src/ui/lib/semantic-context.ts +30 -0
  146. package/src/ui/meta.ts +278 -286
  147. package/src/ui/react.tsx +377 -111
  148. package/src/ui/theme.css +116 -0
  149. package/src/ui/components/primitives/alert-dialog.tsx +0 -190
  150. package/src/ui/docs/content/alert-dialog.md +0 -73
  151. package/src/ui/docs/content/button-group.md +0 -71
  152. package/src/ui/docs/content/confirm.md +0 -120
  153. package/src/ui/docs/content/input-group.md +0 -78
  154. package/src/ui/docs/content/toggle-group.md +0 -81
@@ -4,8 +4,8 @@ title: Filas
4
4
 
5
5
  # Filas
6
6
 
7
- Action pesada não segura o request: marcada como background, ela é enfileirada e processada
8
- fora da linha, e o cliente acompanha por um handle. O contrato é o `QueueAdapter`.
7
+ Use uma action em background quando a operação não puder manter a requisição aberta. O
8
+ `QueueAdapter` enfileira o trabalho e devolve um `JobHandle` para acompanhamento pelo cliente.
9
9
 
10
10
  ## O contrato
11
11
 
@@ -44,7 +44,7 @@ const queue = bullmqQueue({ queue: new Queue('opus', { connection: { host: 'loca
44
44
  ```
45
45
 
46
46
  Redis-backed (durável, entre-processos, retries). Peers opcionais (`bullmq`, `ioredis`) só
47
- pra quem usa o driver.
47
+ para quem usa o driver.
48
48
 
49
49
  ## No runtime
50
50
 
@@ -82,7 +82,7 @@ Multiplicador exponencial diferente de 2 ou `maxMs` exige `backoffStrategy` cust
82
82
  é rejeitado pelo driver em vez de ser silenciosamente ignorado. `timeout` permanece no
83
83
  envelope para o worker aplicar, pois não é uma opção de execução do `Queue.add`.
84
84
 
85
- ## Limites (por enquanto)
85
+ ## Limites atuais
86
86
 
87
- Driver hoje: `bullmq` (Redis). In-memory pra dev e outros backends (SQS, pg-boss…) entram
87
+ Driver hoje: `bullmq` (Redis). In-memory para dev e outros backends (SQS, pg-boss…) entram
88
88
  por reincidência.
@@ -1,6 +1,8 @@
1
- ## Básico
1
+ ## Escolha única
2
2
 
3
- Cada RadioGroupItem tem um value; o item escolhido é o value do RadioGroup. defaultValue deixa o estado com o componente. Pareie cada item com um Label (htmlFor↔id).
3
+ Use `RadioGroup` quando todas as opções mutuamente exclusivas precisarem permanecer visíveis. Cada
4
+ `RadioGroupItem` declara um `value` e deve estar associado a um `Label`. `defaultValue` define a
5
+ opção inicial no modo não controlado.
4
6
 
5
7
  ```tsx preview col-start
6
8
  <RadioGroup defaultValue="balanced">
@@ -21,7 +23,7 @@ Cada RadioGroupItem tem um value; o item escolhido é o value do RadioGroup. def
21
23
 
22
24
  ## Controlado
23
25
 
24
- value + onValueChange no RadioGroup levam o estado pra fora o padrão pra escolher quem recebe o handoff de uma task.
26
+ Use `value` e `onValueChange` quando outro estado da aplicação também precisar acompanhar a escolha.
25
27
 
26
28
  ```tsx preview col-start
27
29
  const [agent, setAgent] = useState('reviewer')
@@ -46,7 +48,8 @@ render(
46
48
 
47
49
  ## Item desabilitado
48
50
 
49
- disabled num RadioGroupItem esmaece e tira a opção da escolha; o Label em par esmaece junto (peer-disabled). Pra bloquear o grupo inteiro, ponha disabled no RadioGroup.
51
+ `disabled` em `RadioGroupItem` bloqueia somente aquela opção e atualiza o `Label` associado. Para
52
+ bloquear todas as opções, aplique `disabled` ao `RadioGroup`.
50
53
 
51
54
  ```tsx preview col-start
52
55
  <RadioGroup defaultValue="empresa-x-web">
@@ -65,13 +68,18 @@ disabled num RadioGroupItem esmaece e tira a opção da escolha; o Label em par
65
68
  </RadioGroup>
66
69
  ```
67
70
 
68
- ## Props
71
+ ## Propriedades de RadioGroup
69
72
 
70
- | Prop | Tipo | Default | Descrição |
73
+ | Propriedade | Tipo | Padrão | Descrição |
71
74
  |---|---|---|---|
72
- | `value (RadioGroup)` | `string` | | A opção escolhida, no modo controlado pareie com onValueChange. |
73
- | `onValueChange (RadioGroup)` | `(value: string) => void` | | Chamado quando o usuário escolhe outra opção. |
74
- | `defaultValue (RadioGroup)` | `string` | | A opção inicial no modo não controlado. |
75
- | `disabled (RadioGroup)` | `boolean` | `false` | Bloqueia e esmaece o grupo inteiro. |
76
- | `value (RadioGroupItem)` | `string` | | O valor que este item representa — vira o value do grupo quando escolhido. |
77
- | `disabled (RadioGroupItem)` | `boolean` | `false` | Esmaece e tira só este item da escolha — o Label em par esmaece junto. |
75
+ | `value` | `string` | | Opção escolhida no modo controlado. Use com `onValueChange`. |
76
+ | `onValueChange` | `(value: string) => void` | | Chamado quando a pessoa escolhe outra opção. |
77
+ | `defaultValue` | `string` | | Opção inicial no modo não controlado. |
78
+ | `disabled` | `boolean` | `false` | Desabilita todo o grupo. |
79
+
80
+ ## Propriedades de RadioGroupItem
81
+
82
+ | Propriedade | Tipo | Padrão | Descrição |
83
+ |---|---|---|---|
84
+ | `value` | `string` | | Valor que o item atribui ao grupo quando selecionado. |
85
+ | `disabled` | `boolean` | `false` | Desabilita somente este item; o `Label` associado acompanha o estado. |
@@ -1,6 +1,8 @@
1
- ## O pathname É o estado
1
+ ## A URL como estado
2
2
 
3
- Roteamento history-based, sem dependência: `pushState` + `popstate` + `useSyncExternalStore`. Páginas orientadas a URL deep-link, reload e o botão voltar funcionam de graça, porque não há um segundo lugar guardando "onde estou".
3
+ Use o router do Opus em aplicações pequenas que precisam reagir à URL sem uma tabela de rotas.
4
+ `pushState`, `popstate` e `useSyncExternalStore` preservam links diretos, recarregamento e o botão
5
+ Voltar sem manter uma segunda cópia do destino atual.
4
6
 
5
7
  ```tsx
6
8
  import { navigate, useSegments } from '@softize/opus/ui/react'
@@ -12,9 +14,10 @@ function App() {
12
14
  }
13
15
  ```
14
16
 
15
- ## O escopo é pequeno de propósito
17
+ ## Limite do router
16
18
 
17
- Não tabela de rotas, `<Route>`, params tipados nem carregamento de dados. Isto é a **leitura reativa da URL + um `navigate`** quem decide o que renderizar é o app, com `if`/`switch` sobre os segmentos.
19
+ O router oferece leitura reativa da URL e `navigate`; o aplicativo decide o que renderizar. Ele não
20
+ inclui tabela de rotas, parâmetros tipados nem carregamento de dados.
18
21
 
19
22
  A régua: se a sua tela precisa de casamento de padrão (`/users/:id/posts/:postId`), params tipados ou data loaders, o caso pede uma biblioteca de rotas, não isto. Um app de back-office com uma dúzia de destinos quase nunca precisa.
20
23
 
@@ -27,13 +30,15 @@ A régua: se a sua tela precisa de casamento de padrão (`/users/:id/posts/:post
27
30
  | `useSearchParams()` | A querystring reativa — o lar natural do estado interno de uma seção (qual relatório está aberto, o recorte de uma lista). |
28
31
  | `navigate(path, opts?)` | `pushState` + notifica. `{ replace: true }` troca a entrada corrente. |
29
32
 
30
- ## Dois detalhes que custaram caro nas cópias à mão
33
+ ## Comportamentos preservados
31
34
 
32
35
  **Destino igual é no-op.** `navigate` resolve o destino com `new URL` e compara o `href` inteiro — não faz nada se você já está exatamente lá. Sem isso, clicar duas vezes no mesmo item do menu empilha entradas idênticas e o botão "voltar" não sai do lugar.
33
36
 
34
37
  Comparar strings cruas parece bastar e não basta, em duas frentes. O **hash**: estando em `/a#secao`, `navigate('/a')` pareceria destino repetido e a âncora nunca sairia da URL. E o **encoding**: `window.location` devolve `/relatórios` como `/relat%C3%B3rios`, então a comparação crua nunca casa e cada clique empilha — bem no caso pt-BR, e no ``navigate(`/reports?report=${arquivo}`)`` com nome de arquivo acentuado.
35
38
 
36
- **A notificação é um evento, não uma lista.** `pushState` não dispara `popstate`, então `navigate` dispara — e é `window.dispatchEvent`, não um `Set` de assinantes em escopo de módulo. A diferença aparece nas bordas: código que ainda escuta `popstate` na unha continua acompanhando, e duas cópias do pacote no `node_modules` continuam se enxergando. Uma lista privada mora numa instância do bundle; o `window` é um só.
39
+ **A notificação usa um evento do navegador.** Como `pushState` não dispara `popstate`, `navigate`
40
+ emite o evento explicitamente. Isso mantém listeners existentes e cópias diferentes do pacote
41
+ sincronizados pela mesma janela.
37
42
 
38
43
  **`useSyncExternalStore`, não `useState`.** Ler `window.location` dentro de `useState`/`useEffect` sofre *tearing* no modo concurrent: dois componentes podem renderizar o mesmo commit com URLs diferentes. As cópias que este módulo substituiu faziam isso.
39
44
 
@@ -4,9 +4,8 @@ title: Agendador
4
4
 
5
5
  # Agendador
6
6
 
7
- Rodar uma action no tempo — cron ou intervalo. Você declara o schedule (qual action, quando)
8
- e o runtime dispara na hora, com provenance `schedule` (rastreável no audit como qualquer
9
- execução). O contrato é o `SchedulerAdapter`.
7
+ Use um schedule para executar uma action por cron ou intervalo. O `SchedulerAdapter` registra o
8
+ agendamento e o runtime executa a action com provenance `schedule`, preservando sua rastreabilidade.
10
9
 
11
10
  ## O contrato
12
11
 
@@ -59,8 +58,8 @@ const runtime = createRuntime({
59
58
  O runtime registra os schedules do domínio no `start()` e chama a action quando o tempo bate
60
59
  — nada de disparar no handler.
61
60
 
62
- ## Limites (por enquanto)
61
+ ## Limites atuais
63
62
 
64
- Driver hoje: `node-cron` (in-process — some se o processo cair; num cluster, cada nó
63
+ Driver hoje: `node-cron` (in-process — some se o processo cair; em um cluster, cada nó
65
64
  dispararia). Backends duráveis/distribuídos (BullMQ repeatable, Temporal…) entram por
66
65
  reincidência.
@@ -4,7 +4,7 @@ Dê a altura ao ScrollArea (h-48) e ponha o conteúdo dentro — a barra vertica
4
4
 
5
5
  ```tsx preview col
6
6
  const sessions = [
7
- { id: 'empresa-x-1842', title: 'Migrar billing pro novo schema', agent: 'developer' },
7
+ { id: 'empresa-x-1842', title: 'Migrar billing para o novo schema', agent: 'developer' },
8
8
  { id: 'empresa-x-1839', title: 'Revisar handoff do checkout', agent: 'reviewer' },
9
9
  { id: 'empresa-x-1835', title: 'Redesenhar o painel de rotas', agent: 'designer' },
10
10
  { id: 'empresa-x-1830', title: 'Corrigir flaky no teste de webhook', agent: 'developer' },
@@ -30,7 +30,7 @@ render(
30
30
 
31
31
  ## Trilho horizontal
32
32
 
33
- Pra rolar na horizontal, acrescente <ScrollBar orientation="horizontal" /> como filho e deixe o conteúdo numa linha que não quebra (flex + w-max).
33
+ Para rolar na horizontal, acrescente <ScrollBar orientation="horizontal" /> como filho e deixe o conteúdo em uma linha que não quebra (flex + w-max).
34
34
 
35
35
  ```tsx preview col
36
36
  const skills = ['implement-opus-change', 'create-opus-action', 'test-opus-action', 'build-opus-ui', 'upgrade-opus']
@@ -80,10 +80,15 @@ render(
80
80
  )
81
81
  ```
82
82
 
83
- ## Props
83
+ ## Propriedades de ScrollArea
84
84
 
85
- | Prop | Tipo | Default | Descrição |
85
+ | Propriedade | Tipo | Padrão | Descrição |
86
86
  |---|---|---|---|
87
- | `className (ScrollArea)` | `string` | | Onde a altura (h-48) ou a largura mora — é o que define a janela rolável. Sem dimensão, não há o que rolar. |
88
- | `children (ScrollArea)` | `React.ReactNode` | | O conteúdo da janela. Inclua um <ScrollBar orientation="horizontal" /> entre os filhos pra habilitar a rolagem lateral. |
89
- | `orientation (ScrollBar)` | `'vertical' \| 'horizontal'` | `'vertical'` | A direção da barra. A vertical já vem embutida; adicione a horizontal só quando precisar. |
87
+ | `className` | `string` | | Classes que definem as dimensões da janela rolável. Sem uma dimensão limitada, não há conteúdo a recortar. |
88
+ | `children` | `React.ReactNode` | | Conteúdo da janela, incluindo uma `ScrollBar` horizontal quando necessária. |
89
+
90
+ ## Propriedades de ScrollBar
91
+
92
+ | Propriedade | Tipo | Padrão | Descrição |
93
+ |---|---|---|---|
94
+ | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Direção da barra. A barra vertical já faz parte de `ScrollArea`. |
@@ -1,6 +1,7 @@
1
- ## Básico
1
+ ## Escolha em lista
2
2
 
3
- Um componente para toda escolha em lista. As opções são DADO (`options`), não JSX: `{ value, label }` — o `label` é o que o campo mostra, o que a busca casa e o que o leitor de tela lê. O `id` pareia com o `htmlFor` do Label.
3
+ Use `Select` para escolhas em lista. Declare as opções como dados em `options`; `label` é usado no
4
+ campo, na busca e na leitura assistiva. Associe `id` ao `htmlFor` de `Label` em formulários.
4
5
 
5
6
  ```tsx preview col md
6
7
  const [role, setRole] = useState('')
@@ -23,9 +24,10 @@ render(
23
24
  )
24
25
  ```
25
26
 
26
- ## Com busca (searchable)
27
+ ## Lista pesquisável
27
28
 
28
- `searchable` transforma o próprio campo na busca: digitou, a lista filtra embaixo (fuzzy do cmdk, casando label, hint e value). É o que resolve a **achabilidade** — a régua é essa, não performance: 200 itens sem busca é embaçado, achar é rolar até cansar.
29
+ Use `searchable` quando a quantidade ou os rótulos dificultarem encontrar uma opção. O próprio
30
+ campo passa a filtrar `label`, `hint` e `value` com a busca do cmdk.
29
31
 
30
32
  ```tsx preview col md
31
33
  const [issue, setIssue] = useState('')
@@ -49,9 +51,11 @@ render(
49
51
  )
50
52
  ```
51
53
 
52
- ## Múltiplo (chips)
54
+ ## Seleção múltipla
53
55
 
54
- `multiple` muda o contrato (`value` e `onChange` viram `string[]`): os escolhidos viram chips removíveis e a lista fica aberta pra seleção em sequência. Com a busca vazia, o topo do dropdown oferece Selecionar tudo / Limpar seleção (só no modo client — com filtro ativo ou `onSearch`, "tudo" seria ambíguo).
56
+ Com `multiple`, `value` e `onChange` usam `string[]`. As opções escolhidas aparecem como chips
57
+ removíveis e a lista permanece aberta para escolhas sucessivas. “Selecionar tudo” fica disponível
58
+ somente quando todas as opções estão carregadas localmente e a busca está vazia.
55
59
 
56
60
  ```tsx preview col md
57
61
  const [skills, setSkills] = useState<string[]>(['clean-code'])
@@ -77,9 +81,10 @@ render(
77
81
  )
78
82
  ```
79
83
 
80
- ## Nativo (native)
84
+ ## Controle nativo
81
85
 
82
- `native` renderiza o `<select>` do sistema (só com a seta restilizada — a nativa cola na borda e ignora o tema). Ganha o picker do próprio SO: roda de scroll no iOS, teclado do Android, zero JS. Use em **lista curta e sabida** (2–8 itens que a pessoa já conhece: Sim/Não, prioridade, ambiente). Item rico, busca ou multi não existem aqui — pra isso, o modo default.
86
+ Use `native` para uma lista curta e conhecida, como prioridade ou ambiente. O sistema operacional
87
+ controla a interação do `<select>`. Busca, seleção múltipla e conteúdo rico exigem o modo padrão.
83
88
 
84
89
  ```tsx preview col md
85
90
  const [env, setEnv] = useState('preview')
@@ -104,7 +109,8 @@ render(
104
109
 
105
110
  ## Grupos
106
111
 
107
- `group` na opção agrupa a lista — bloco nomeado nos dois modos (`<optgroup>` no nativo, heading no custom). Pra listas com origens distintas (membros × agentes).
112
+ `group` reúne opções sob um título nos dois modos. Use grupos quando a lista combinar conjuntos com
113
+ origens ou papéis distintos.
108
114
 
109
115
  ```tsx preview col md
110
116
  const [reviewer, setReviewer] = useState('')
@@ -124,9 +130,10 @@ render(
124
130
  )
125
131
  ```
126
132
 
127
- ## Busca server-side
133
+ ## Busca no servidor
128
134
 
129
- Com `onSearch` o filtro do cmdk desliga: o pai busca (debounced, ao abrir e ao digitar) e devolve `options`; `loading` mostra Buscando… enquanto o fetch corre. Implica campo buscável (não precisa repetir `searchable`).
135
+ Com `onSearch`, o consumidor consulta as opções ao abrir e ao digitar, com debounce. Atualize
136
+ `options` com a resposta e use `loading` durante a consulta. Esse modo já habilita o campo de busca.
130
137
 
131
138
  ```tsx preview col md
132
139
  const ASSIGNEES = [
@@ -163,7 +170,8 @@ render(
163
170
 
164
171
  ## Ícone dentro do campo
165
172
 
166
- `icon` põe o ícone DENTRO do controle, antes do texto (e dos chips, no multi) — identifica o campo numa barra de filtros. Ícone AO LADO do campo é o antipadrão: desalinha na quebra de linha e some no responsivo. Vale nos dois modos.
173
+ `icon` posiciona um ícone decorativo antes do texto ou dos chips. Use-o para reforçar a identidade do
174
+ campo sem criar um elemento separado ao lado do controle.
167
175
 
168
176
  ```tsx preview col-start
169
177
  const [task, setTask] = useState('')
@@ -182,9 +190,10 @@ render(
182
190
  )
183
191
  ```
184
192
 
185
- ## Densidade (size)
193
+ ## Tamanho
186
194
 
187
- O controle segue a altura do design system: default é h-9 (a mesma do Input e do Button) e `size="sm"` h-8 pra barra de filtros/toolbar densa.
195
+ O tamanho padrão acompanha `Input` e `Button`. Use `size="sm"` em barras de filtros e outras
196
+ composições densas.
188
197
 
189
198
  ```tsx preview col-start
190
199
  const [a, setA] = useState('gra-2')
@@ -203,9 +212,10 @@ render(
203
212
  )
204
213
  ```
205
214
 
206
- ## Limpar (clearable)
215
+ ## Limpar a seleção
207
216
 
208
- `clearable` põe o X no controle quando há seleção — limpa num clique (single volta a vazio, multi a `[]`), sem abrir a lista. Bom pra filtro, onde "sem valor" é um estado de uso frequente; os filtros do ActionList já vêm assim.
217
+ `clearable` acrescenta uma ação que limpa o valor sem abrir a lista. No modo simples, o valor volta
218
+ a vazio; no múltiplo, volta a `[]`. Os filtros de `ActionList` já habilitam esse comportamento.
209
219
 
210
220
  ```tsx preview col md
211
221
  function Demo() {
@@ -229,9 +239,10 @@ function Demo() {
229
239
  render(<Demo />)
230
240
  ```
231
241
 
232
- ## Item rico (content)
242
+ ## Conteúdo rico nas opções
233
243
 
234
- `label` é sempre string o que o campo mostra e a busca casa); `content` é o render RICO do item na lista — ícone, avatar, duas linhas. Assim o item pode ser elaborado sem estragar busca nem a11y.
244
+ Mantenha `label` como string para busca e leitura assistiva. Use `content` para acrescentar ícone,
245
+ avatar ou texto complementar à opção apresentada na lista.
235
246
 
236
247
  ```tsx preview col md
237
248
  const [agent, setAgent] = useState('developer')
@@ -271,9 +282,10 @@ render(
271
282
  )
272
283
  ```
273
284
 
274
- ## Barra do composer (variant ghost)
285
+ ## Select na barra do Composer
275
286
 
276
- `variant="ghost"` tira moldura e padding: o seletor discreto que vive na barra de ações do Composer/Chat (app, task, agente), à esquerda do enviar. `triggerLabel` na opção encurta o gatilho quando o rótulo da lista é longo (o título inteiro fica só na lista).
287
+ Use `variant="ghost"` para integrar o seletor à barra de ações de `Composer`. `triggerLabel` pode
288
+ encurtar somente o texto do gatilho; a lista continua exibindo o `label` completo.
277
289
 
278
290
  ```tsx preview col-start
279
291
  const [task, setTask] = useState('GB-42')
@@ -292,9 +304,10 @@ render(
292
304
  )
293
305
  ```
294
306
 
295
- ## Ação no fim do campo (trailing)
307
+ ## Ação no fim do campo
296
308
 
297
- `trailing` põe uma ação custom DENTRO do controle, no fim (antes do chevron) — um botão que age sobre o valor escolhido, sem virar um irmão solto ao lado do campo. O clique no trailing **não** abre a lista (o slot para a propagação).
309
+ `trailing` posiciona uma ação relacionada ao valor antes do chevron. Interagir com essa região não
310
+ abre a lista.
298
311
 
299
312
  ```tsx preview col-start
300
313
  const [task, setTask] = useState('gra-2')
@@ -318,9 +331,9 @@ render(
318
331
  )
319
332
  ```
320
333
 
321
- ## Props
334
+ ## Propriedades de Select
322
335
 
323
- | Prop | Tipo | Default | Descrição |
336
+ | Propriedade | Tipo | Padrão | Descrição |
324
337
  |---|---|---|---|
325
338
  | `options` | `SelectOption[]` | | As opções: `{ value, label, hint?, content?, triggerLabel?, group?, disabled? }`. |
326
339
  | `value` | `string \| string[]` | | O selecionado: string no single, string[] no multiple. |
@@ -328,16 +341,16 @@ render(
328
341
  | `native` | `boolean` | `false` | Renderiza o `<select>` do sistema. Exclui busca, multi e ghost (o browser é quem desenha a lista). |
329
342
  | `searchable` | `boolean` | `false` | O campo vira busca: filtra a lista enquanto digita. |
330
343
  | `multiple` | `boolean` | `false` | Chips removíveis, lista que permanece aberta e Selecionar tudo. |
331
- | `variant` | `'default' \| 'ghost'` | `'default'` | `ghost` = sem moldura, pra barra do composer. |
344
+ | `variant` | `'default' \| 'ghost'` | `'default'` | `ghost` = sem moldura, para barra do composer. |
332
345
  | `placeholder` | `string` | `'Selecione…'` | Texto do campo vazio. |
333
- | `searchPlaceholder` | `string` | | Placeholder enquanto busca; cai pro `placeholder` se ausente. |
346
+ | `searchPlaceholder` | `string` | | Placeholder enquanto busca; cai para o `placeholder` se ausente. |
334
347
  | `emptyText` | `string` | `'Nada encontrado.'` | Mensagem quando a busca não acha nada. |
335
348
  | `onSearch` | `(query: string) => void` | | Busca server-side (debounced, ao abrir e ao digitar): desliga o filtro do cmdk — o pai atualiza `options`. |
336
349
  | `loading` | `boolean` | | Mostra Buscando… enquanto o fetch corre (use com `onSearch`). |
337
- | `clearable` | `boolean` | `false` | X no controle quando há seleção — limpa num clique. |
350
+ | `clearable` | `boolean` | `false` | X no controle quando há seleção — limpa em um clique. |
338
351
  | `icon` | `React.ReactNode` | | Ícone leading DENTRO do controle (decorativo) — herda `size-4` e o tom muted. |
339
352
  | `trailing` | `React.ReactNode` | | Ação custom no FIM do controle (antes do chevron) — o clique não abre a lista. |
340
- | `size` | `'default' \| 'sm'` | `'default'` | Altura: default (h-9, a do Input e do Button) ou sm (h-8) pra toolbar densa. |
353
+ | `size` | `'default' \| 'sm'` | `'default'` | Altura: default (h-9, a do Input e do Button) ou sm (h-8) para toolbar densa. |
341
354
  | `disabled` | `boolean` | `false` | Esmaece e trava o controle. |
342
- | `id` | `string` | | Vai pro campo — pra parear com o `htmlFor` do Label. |
355
+ | `id` | `string` | | Vai para o campo — para parear com o `htmlFor` do Label. |
343
356
  | `className` | `string` | | Classes da raiz do controle, incluindo campo, ícones e ações, em todos os modos. |
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: Contexto & Variante
3
+ ---
4
+
5
+ # Contexto & Variante
6
+
7
+ Componentes semânticos separam significado de aparência. `context` responde por que o elemento
8
+ recebe destaque; `variant` escolhe como esse significado aparece. Essa ordem evita que nomes como
9
+ `success`, `warning` ou `danger` sejam tratados como decoração local.
10
+
11
+ ## Contextos
12
+
13
+ | Contexto | Uso |
14
+ | --- | --- |
15
+ | `neutral` | Estado normal, inativo ou sem julgamento positivo ou negativo. |
16
+ | `primary` | Ação ou elemento de maior destaque no contexto atual. |
17
+ | `info` | Informação ou processo em andamento sem alerta. |
18
+ | `success` | Resultado positivo ou estado saudável. |
19
+ | `warning` | Condição que pede atenção, mas ainda não é uma falha. |
20
+ | `danger` | Falha, impedimento ou consequência perigosa. |
21
+
22
+ `light` e `dark` são temas, não contextos. `secondary` descreve hierarquia de ação em APIs antigas;
23
+ um estado sem destaque usa `neutral`. `destructive` continua sendo uma propriedade comportamental
24
+ de actions e confirmações; sua projeção visual usa `danger`.
25
+
26
+ ## Variantes
27
+
28
+ `solid`, `subtle`, `outline`, `ghost` e `link` descrevem somente tratamento visual. Cada componente
29
+ aceita o subconjunto coerente com seu papel.
30
+
31
+ ```tsx preview col
32
+ <div className="flex flex-wrap gap-2">
33
+ <Badge context="success" variant="solid">Concluído</Badge>
34
+ <Badge context="success" variant="subtle">Concluído</Badge>
35
+ <Badge context="success" variant="outline">Concluído</Badge>
36
+ </div>
37
+ <div className="flex flex-wrap gap-2">
38
+ <Button context="primary" variant="solid">Salvar</Button>
39
+ <Button context="neutral" variant="outline">Voltar</Button>
40
+ <Button context="danger" variant="ghost">Excluir</Button>
41
+ </div>
42
+ ```
43
+
44
+ ## Dicionários
45
+
46
+ Status e estágios declaram `context` na entrada. A tela usa `DictionaryValue` ou `ActionList` e não
47
+ escolhe a aparência novamente.
48
+
49
+ ```ts
50
+ const status = t.dict(
51
+ {
52
+ running: { label: 'Em andamento', context: 'info' },
53
+ completed: { label: 'Concluído', context: 'success' },
54
+ failed: { label: 'Falhou', context: 'danger' },
55
+ },
56
+ { presentation: 'status' },
57
+ )
58
+ ```
59
+
60
+ ## Compatibilidade
61
+
62
+ `tone` em dicionários e variantes semânticas antigas continuam aceitos durante a migração. Código
63
+ novo usa `context`; não misture o contrato antigo e o novo na mesma ocorrência.
@@ -1,6 +1,6 @@
1
- ## Horizontal
1
+ ## Separar blocos empilhados
2
2
 
3
- Divide blocos empilhados ocupa a largura toda do contêiner.
3
+ Use a orientação horizontal entre blocos empilhados. A linha ocupa toda a largura disponível.
4
4
 
5
5
  ```tsx preview col
6
6
  <div>
@@ -13,7 +13,7 @@ Divide blocos empilhados — ocupa a largura toda do contêiner.
13
13
 
14
14
  ## Vertical
15
15
 
16
- Entre itens de uma linha. O vertical herda a altura do contêiner — dê altura à linha (ex.: h-4 no flex).
16
+ Use a orientação vertical entre itens lado a lado. A linha herda a altura do contêiner.
17
17
 
18
18
  ```tsx preview
19
19
  <div className="flex h-4 items-center gap-3 text-sm">
@@ -25,9 +25,9 @@ Entre itens de uma linha. O vertical herda a altura do contêiner — dê altura
25
25
  </div>
26
26
  ```
27
27
 
28
- ## Props
28
+ ## Propriedades de Separator
29
29
 
30
- | Prop | Tipo | Default | Descrição |
30
+ | Propriedade | Tipo | Padrão | Descrição |
31
31
  |---|---|---|---|
32
32
  | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção da linha. Vertical precisa de altura vinda do contêiner. |
33
33
  | `decorative` | `boolean` | `true` | Decorativa some da árvore de acessibilidade. Use false quando a divisão é semântica. |