@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
@@ -1,6 +1,7 @@
1
1
  ## Lista filtrável inline
2
2
 
3
- Digite pra filtrar navegação por teclado, grupos e CommandEmpty de graça (cmdk). É a base do Select buscável; pra escolha em form, use o Select.
3
+ Digite para filtrar uma coleção com navegação por teclado, grupos e estado vazio. Command é a base
4
+ do Select pesquisável; para uma escolha em formulário, use Select.
4
5
 
5
6
  ```tsx preview
6
7
  <Command className="max-w-sm rounded-lg border border-border">
@@ -22,7 +23,7 @@ Digite pra filtrar — navegação por teclado, grupos e CommandEmpty de graça
22
23
 
23
24
  ## Palette modal (CommandDialog)
24
25
 
25
- O Command embrulhado num Dialog, com header sr-only pra a11y. O atalho ⌘K (keydown no app) só troca o open — o conteúdo é o mesmo do inline.
26
+ O Command embrulhado em um Dialog, com header sr-only para a11y. O atalho ⌘K (keydown no app) só troca o open — o conteúdo é o mesmo do inline.
26
27
 
27
28
  ```tsx preview
28
29
  const [open, setOpen] = useState(false)
@@ -46,11 +47,18 @@ render(
46
47
  )
47
48
  ```
48
49
 
49
- ## Props
50
+ ## Propriedades de CommandDialog
50
51
 
51
- | Prop | Tipo | Default | Descrição |
52
+ | Propriedade | Tipo | Padrão | Descrição |
52
53
  |---|---|---|---|
53
- | `CommandDialog.open / onOpenChange` | `boolean / (open: boolean) => void` | | Controle do modal ligue ao atalho de teclado do app. |
54
- | `CommandDialog.title / description` | `string` | `'Comandos' / 'Busque um comando pra executar.'` | Texto sr-only do header (a11y do Dialog) — não aparece na tela. |
55
- | `CommandDialog.showCloseButton` | `boolean` | `true` | Mostra o X do Dialog desligue se o Esc/clique fora bastarem. |
56
- | `CommandItem.onSelect` | `(value: string) => void` | | Dispara ao escolher (Enter ou clique) feche o palette aqui. |
54
+ | `open` | `boolean` | | Estado do modal no modo controlado. |
55
+ | `onOpenChange` | `(open: boolean) => void` | | Atualiza o estado do modal; pode ser conectado ao atalho do aplicativo. |
56
+ | `title` | `string` | `'Comandos'` | Nome acessível do diálogo, disponível para leitura assistiva. |
57
+ | `description` | `string` | `'Busque um comando para executar.'` | Descrição acessível do diálogo. |
58
+ | `showCloseButton` | `boolean` | `true` | Exibe o botão de fechamento. |
59
+
60
+ ## Propriedades de CommandItem
61
+
62
+ | Propriedade | Tipo | Padrão | Descrição |
63
+ |---|---|---|---|
64
+ | `onSelect` | `(value: string) => void` | | Chamado ao selecionar o item por clique ou teclado. |
@@ -1,6 +1,6 @@
1
- ## Básico
1
+ ## Envio de texto
2
2
 
3
- A caixa de escrever da casa: textarea numa pílula elevada (`rounded-xl` + `border` + `shadow-sm`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/components/chat) extraído — use SOZINHO quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse dono consuma **↑/↓**; sem eles, as setas mantêm o comportamento nativo da textarea.
3
+ A caixa de escrever da casa: textarea em uma pílula elevada (`rounded-xl` + `border` + `shadow-sm`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/components/chat) extraído — use sozinho quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você. Os callbacks opcionais `onHistoryPrevious` e `onHistoryNext` permitem que esse dono consuma **↑/↓**; sem eles, as setas mantêm o comportamento nativo da textarea.
4
4
 
5
5
  ```tsx preview col
6
6
  const [text, setText] = React.useState('')
@@ -0,0 +1,44 @@
1
+ ## Região de conteúdo
2
+
3
+ Use `Content` para dar título e estrutura a uma região dentro de `PageBody`, `CardBody`,
4
+ `DialogBody` ou outra superfície. Ele renderiza uma `section` ligada ao próprio título e mantém o
5
+ espaçamento entre header e body. `ContentHeader` pertence sempre a essa estrutura; não o use solto.
6
+
7
+ No caso comum, prefira o shorthand:
8
+
9
+ ```tsx preview col
10
+ render(
11
+ <Content
12
+ title="Dispositivos conectados"
13
+ description="Sessões com acesso à sua conta."
14
+ actions={<Button variant="outline">Encerrar outras sessões</Button>}
15
+ >
16
+ <div className="rounded-lg border border-border p-4">MacBook Para o · ativo agora</div>
17
+ </Content>,
18
+ )
19
+ ```
20
+
21
+ ## Composição explícita
22
+
23
+ Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
24
+ `ContentHeader` e um `ContentBody` como filhos diretos. O header exige um `ContentTitle` e aceita
25
+ uma descrição, um metadado e uma região de ações.
26
+
27
+ ```tsx preview col
28
+ render(
29
+ <Content level={2}>
30
+ <ContentHeader>
31
+ <ContentTitle>Dispositivos conectados</ContentTitle>
32
+ <ContentMeta>3</ContentMeta>
33
+ <ContentDescription>Sessões com acesso à sua conta.</ContentDescription>
34
+ <ContentActions><Button variant="outline">Atualizar</Button></ContentActions>
35
+ </ContentHeader>
36
+ <ContentBody>
37
+ <div className="rounded-lg border border-border p-4">MacBook Para o · ativo agora</div>
38
+ </ContentBody>
39
+ </Content>,
40
+ )
41
+ ```
42
+
43
+ As duas formas geram os mesmos elementos, estilos e `data-slot`. `opus check` reprova slots fora
44
+ do pai correto, filhos estruturais indiretos e a mistura de shorthand com composição explícita.
@@ -1,6 +1,6 @@
1
- ## Básico
1
+ ## Copiar por ícone
2
2
 
3
- Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5s. Bom pra toolbar ou célula estreita, ao lado de um ID/token/slug.
3
+ Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5s. Bom para toolbar ou célula estreita, ao lado de um ID/token/slug.
4
4
 
5
5
  ```tsx preview
6
6
  <Copyable value="opus_sk_1a2b3c4d5e6f" className="text-muted-foreground hover:text-foreground" />
@@ -21,7 +21,8 @@ Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar n
21
21
 
22
22
  ## Duração do feedback
23
23
 
24
- `feedbackMs` ajusta quanto o check dura (default 1500). Some no-op silencioso se o clipboard não existir (contexto inseguro/SSR).
24
+ `feedbackMs` ajusta por quanto tempo a confirmação aparece; o padrão é 1500 milissegundos. Se a
25
+ área de transferência não estiver disponível, a ação não produz efeito.
25
26
 
26
27
  ```tsx preview
27
28
  <Copyable value="copiado devagar" feedbackMs={3000} className="text-muted-foreground hover:text-foreground">
@@ -6,7 +6,7 @@ title: Customização
6
6
 
7
7
  Cinco alavancas, do global ao pontual — e um limite de propósito. O caminho previsto é compor
8
8
  e configurar, nunca forkar componente: o que não cabe nas alavancas evolui no Opus (com
9
- divergência declarada), pra valer pra casa toda.
9
+ divergência declarada), para valer para casa toda.
10
10
 
11
11
  ## 1 · Identidade por tokens
12
12
 
@@ -36,7 +36,7 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
36
36
  ## 2 · className em tudo
37
37
 
38
38
  > Todo componente termina em `cn(base, className)` com tailwind-merge: o utilitário do consumidor
39
- > vence o conflito. Pra layout local (largura, margem, grid) — não pra repintar o visual da casa.
39
+ > vence o conflito. Para layout local (largura, margem, grid) — não para repintar o visual da casa.
40
40
 
41
41
  ```tsx
42
42
  <Button className="w-full">Continuar</Button>
@@ -47,7 +47,7 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
47
47
  ## 3 · Recomposição estrutural
48
48
 
49
49
  > Os componentes são explodidos em slots. `asChild` (Radix Slot) renderiza como outro elemento
50
- > mantendo estilo e comportamento; os `*Variants` aplicam a cara da casa num elemento arbitrário.
50
+ > mantendo estilo e comportamento; os `*Variants` aplicam a cara da casa em um elemento arbitrário.
51
51
 
52
52
  ```tsx preview
53
53
  <Button asChild variant="outline">
@@ -59,13 +59,13 @@ de 16, sem transformar esse valor em uma regra da biblioteca.
59
59
  // Slots: só o que a tela pede.
60
60
  <Card>
61
61
  <CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
62
- <CardContent>…</CardContent>
62
+ <CardBody>…</CardBody>
63
63
  </Card>
64
64
 
65
65
  // asChild: o filho VIRA o botão (sem forkar estilo).
66
66
  <Button asChild><a href="/docs">Abrir documentação</a></Button>
67
67
 
68
- // buttonVariants: a cara da casa num elemento qualquer.
68
+ // buttonVariants: a cara da casa em um elemento qualquer.
69
69
  <a className={buttonVariants({ variant: 'outline' })}>Link estilizado</a>
70
70
  ```
71
71
 
@@ -110,9 +110,9 @@ const [open, setOpen] = useState(false)
110
110
  > do botão, a seta do tooltip — é identidade da casa, igual em todo projeto.
111
111
 
112
112
  ```tsx preview
113
- <Badge variant="success">Cabe nas alavancas</Badge>
113
+ <Badge context="success">Cabe nas alavancas</Badge>
114
114
  ```
115
115
 
116
116
  Se uma necessidade real não cabe nas alavancas (tokens · className · slots/asChild · props ·
117
117
  contrato), o movimento não é dialeto local: é evoluir o componente **no Opus**, com a divergência
118
- declarada (skill `build-opus-ui`) — assim a mudança vale pra casa toda, e esta doc passa a mostrá-la.
118
+ declarada (skill `build-opus-ui`) — assim a mudança vale para casa toda, e esta doc passa a mostrá-la.
@@ -16,9 +16,9 @@ No seu projeto, uma linha por apontamento em `.opus/issues.jsonl` na raiz do rep
16
16
 
17
17
  ## Não trave esperando
18
18
 
19
- O ponto do ciclo é **não bloquear a entrega**. Bateu num gap ou num bug do Opus:
19
+ O ponto do ciclo é **não bloquear a entrega**. Bateu em um gap ou em um bug do Opus:
20
20
 
21
- 1. **Contorne local** — componha um wrapper no seu projeto. O Opus entrega _source_, então dá pra embrulhar qualquer superfície dele. Nunca edite `node_modules` (some no próximo install).
21
+ 1. **Contorne local** — componha um wrapper no seu projeto. O Opus entrega _source_, então dá para embrulhar qualquer superfície dele. Nunca edite `node_modules` (some no próximo install).
22
22
  2. **Entregue** a feature com o workaround.
23
23
  3. **Aponte** no `.opus/issues.jsonl` e siga em frente.
24
24
 
@@ -28,7 +28,7 @@ O "depois" — o conserto no Opus — corre em paralelo. Ele não segura o seu t
28
28
 
29
29
  O apontamento é colhido e abre uma **Issue no repositório do Opus**, onde a triagem acontece (deduplicada — re-apontar o mesmo é idempotente):
30
30
 
31
- - **Enhancement** aceito (com reincidência) → implementado no Opus → sai num _bump_ → seu projeto atualiza o pin (`opus.json`) e troca o workaround pelo import.
31
+ - **Enhancement** aceito (com reincidência) → implementado no Opus → sai em um _bump_ → seu projeto atualiza o pin (`opus.json`) e troca o workaround pelo import.
32
32
  - **Bug** → vira _fix_ + entrada no `CHANGELOG` → no _bump_, o workaround sai.
33
33
 
34
34
  A régua e a decisão ficam com quem mantém o Opus — hoje, a **Softize**. O registro curado das promoções vive no `PROMOTED.md` do pacote.
@@ -1,9 +1,9 @@
1
1
  ## Estados
2
2
 
3
- Um lugar pro erro/carregando/vazio/conteúdo de uma carga. Carregando = Spinner centralizado;
4
- vazio = texto em uma moldura sólida no modo bloco; erro = aviso calmo (a mensagem técnica não vai
5
- pra tela). É o "antes" do conteúdo
6
- pro "depois" (ação em andamento), use o busy do Button.
3
+ Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta. O
4
+ carregamento usa `Spinner`; o vazio preserva uma moldura sólida; e o erro apresenta uma mensagem
5
+ segura, sem expor detalhes técnicos. Para uma ação em andamento depois do clique, use `busy` em
6
+ `Button`.
7
7
 
8
8
  ```tsx preview col
9
9
  <div className="w-full space-y-3">
@@ -19,12 +19,11 @@ pro "depois" (ação em andamento), use o busy do Button.
19
19
  </div>
20
20
  ```
21
21
 
22
- ## Em tabela (colSpan)
22
+ ## Dentro de uma tabela
23
23
 
24
- Em lista ou tabela já emoldurada, passe colSpan: o estado vira UMA linha de largura cheia
25
- (`<tr><td colSpan>`) que cabe direto no `<tbody>`; o conteúdo são as `<tr>` dos itens. A tabela
26
- continua dona da borda, sem uma segunda moldura no vazio. Para uma região disponível para criação
27
- ou vínculo, com título, descrição ou ação, use `Empty`, cuja moldura é tracejada.
24
+ Em uma tabela já emoldurada, passe `colSpan` para ocupar uma linha inteira dentro de `<tbody>`. A
25
+ tabela continua responsável pela borda, evitando uma segunda moldura no estado vazio. Para uma
26
+ região disponível para criação ou vínculo, use `Empty`.
28
27
 
29
28
  ```tsx preview col
30
29
  <table className="w-full overflow-hidden rounded-lg border border-border text-sm">
@@ -38,13 +37,13 @@ ou vínculo, com título, descrição ou ação, use `Empty`, cuja moldura é tr
38
37
  </table>
39
38
  ```
40
39
 
41
- ## Props
40
+ ## Propriedades de DataState
42
41
 
43
- | Prop | Tipo | Default | Descrição |
42
+ | Propriedade | Tipo | Padrão | Descrição |
44
43
  |---|---|---|---|
45
44
  | `loading` | `boolean` | | Carregando (antes do conteúdo) — mostra o Spinner centralizado. |
46
45
  | `empty` | `boolean` | | Sem itens — mostra o emptyText. |
47
46
  | `emptyText` | `string` | | Texto do vazio (pt-BR, ex.: "Nenhum papel."). |
48
- | `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica NÃO vai pra tela (use errorText). |
47
+ | `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica não vai para tela (use errorText). |
49
48
  | `errorText` | `string` | `'Não foi possível carregar.'` | Aviso de erro, orientado ao usuário. |
50
49
  | `colSpan` | `number` | | Em tabela: renderiza o estado como `<tr><td colSpan>` (cabe direto no tbody). |
@@ -4,15 +4,15 @@ title: Camada de dados
4
4
 
5
5
  # Camada de dados
6
6
 
7
- A entidade é a spec do armazenamento; o manifest a projeta. As queries falam Kysely tipado.
8
- Uma regra firme atravessa tudo: o código fala camelCase, o banco fala snake e o
9
- `CamelCasePlugin` faz a ponte.
7
+ As entidades descrevem o armazenamento que o manifest projeta, enquanto o Kysely mantém as
8
+ consultas tipadas. No código, nomes usam camelCase; no banco, snake_case. `CamelCasePlugin` faz essa
9
+ conversão nos dois sentidos.
10
10
 
11
11
  ## Entidade e manifest
12
12
 
13
- > `defineEntity` declara o storage (campos + tipos lógicos `t.*`). A `description` de cada
14
- > entidade/action é a spec de negócio, projetada no manifest pelo `opus gen`; `opus db check`
15
- > acusa drift entidade banco.
13
+ `defineEntity` declara campos e tipos lógicos `t.*`. A `description` de cada entidade e action
14
+ registra o significado de negócio projetado no manifest por `opus gen`. Use `opus db check` para
15
+ detectar divergências entre a entidade e o banco.
16
16
 
17
17
  ```ts
18
18
  import { defineEntity } from '@softize/opus/schema'
@@ -30,16 +30,12 @@ export const SkillEntity = defineEntity({
30
30
  })
31
31
  ```
32
32
 
33
- ## Código camelCase, banco snake
33
+ ## Código em camelCase, banco em snake_case
34
34
 
35
- > Regra dura do protocolo: uma camada em snake e outra em camel é defeito — não tem meio-termo.
36
-
37
- O schema tipado do Kysely e **toda** query (select/insert/update/where) usam camelCase
35
+ O schema tipado do Kysely e todas as consultas usam camelCase
38
36
  (`workspaceId`, `ghRepo`). O banco é snake (a DDL no schema idempotente — `db/schema.sql`).
39
- O `CamelCasePlugin` no runtime faz a ponte camel↔snake nas queries E nos resultados o
40
- mesmo que o prepare faz.
41
- Única exceção: um sink escrito por fora do Kysely-com-plugin (ex.: o `audit_log` do Opus, pelo
42
- pool) fica snake.
37
+ `CamelCasePlugin` converte nomes nas consultas e nos resultados. Um sink que não usa o Kysely com o
38
+ plugin, como `audit_log`, mantém os nomes do banco.
43
39
 
44
40
  ```ts
45
41
  import { CamelCasePlugin, Kysely, PostgresDialect } from 'kysely'
@@ -52,31 +48,28 @@ const db = new Kysely<AdminDB>({
52
48
  await db.selectFrom('agents').select(['isDefault', 'roleId']).where('workspaceId', '=', id).execute()
53
49
  ```
54
50
 
55
- ## Migrações · prepare · seed
51
+ ## Migrações, preparação e seeds
56
52
 
57
- > Três coisas distintas não confundir o que roda em prod.
53
+ Cada etapa possui um papel diferente:
58
54
 
59
55
  - `opus db migrate` — aplica o **schema idempotente** (`config.schema`, um script SQL
60
56
  evolutivo: `IF NOT EXISTS` + guards cobrem nascer do zero e upgrade no mesmo artefato)
61
57
  e roda o drift-check entidade ↔ banco na sequência (exit ≠ 0 se divergir).
62
58
  - `prepare` — backfill estrutural, prod-safe e idempotente; roda no deploy (depois do migrate).
63
- - `seed` — fixtures de desenvolvimento. **Não** roda em prod.
64
-
65
- ## Campo `t.json()`: objeto entra, objeto sai
59
+ - `seed` — cria dados de desenvolvimento e teste. Não é executado em produção.
66
60
 
67
- > A dupla `ColumnType<unknown, string, never>` + `JSON.stringify` na mão está aposentada
68
- > no caminho do `kyselyRepo`.
61
+ ## Campos JSON
69
62
 
70
- O `kyselyRepo` serializa campo `t.json()` na ESCRITA (insert/update), guiado pela
63
+ `kyselyRepo` serializa campos `t.json()` na escrita (`insert` e `update`), guiado pela
71
64
  declaração. Sem isso, o pg até stringifica objeto plano — mas **array vira literal de
72
- array do PG** (errado pra jsonb) sem quebrar typecheck. String passa direto (quem já
65
+ array do PG** (errado para jsonb) sem quebrar typecheck. String passa direto (quem já
73
66
  mandava pré-serializado segue valendo); na leitura o pg devolve objeto. Query à mão
74
67
  (fora do repo) continua responsável pelo próprio stringify.
75
68
 
76
- ## SQL escrito por LLM: `readOnlyContextPool`
69
+ ## SQL gerado por modelo
77
70
 
78
- > Action `ai: true` que executa SQL livre precisa das **quatro defesas** todas no
79
- > BANCO, nenhuma em regex sobre o texto da query.
71
+ Uma action `ai: true` que executa SQL livre precisa de quatro proteções no banco. Validar o texto
72
+ com expressão regular não substitui nenhuma delas:
80
73
 
81
74
  ```ts
82
75
  import { Pool } from 'pg'
@@ -86,14 +79,14 @@ const bi = readOnlyContextPool({ pool: new Pool({ connectionString, max: 4 }) })
86
79
  const { rows } = await bi.query(sqlDoLlm, { role: roleFor(ctx) })
87
80
  ```
88
81
 
89
- 1. **Pool com teto** (`max` do pool e/ou `maxConcurrent`) — query de LLM não esgota as
90
- conexões do app;
82
+ 1. **Pool com limite** (`max` e/ou `maxConcurrent`) — impede que as consultas consumam todas as
83
+ conexões do aplicativo.
91
84
  2. **`BEGIN TRANSACTION READ ONLY`** — o servidor rejeita tentativas de escrita;
92
- 3. **`SET LOCAL ROLE` por transação** — o alcance é do CONTEXTO (crie os roles com grants
85
+ 3. **`SET LOCAL ROLE` por transação** — o alcance é definido pelo contexto (crie os roles com grants
93
86
  default-fechado nas suas migrações: sem isso, `sales_read` leria `hr_employees`);
94
- 4. **Protocolo estendido** — o SQL roda sempre com array de valores; multi-sentença
87
+ 4. **Protocolo estendido** — o SQL sempre recebe os valores separadamente; múltiplas instruções
95
88
  (`SELECT 1; DROP …`) é recusada pelo próprio protocolo.
96
89
 
97
- Saindo, `DISCARD ALL` devolve a conexão limpa; se a limpeza falhar, a conexão é
98
- destruída nunca volta suja pro pool. `statement_timeout` local por transação
99
- (default 15s) segura a query fugitiva.
90
+ Ao terminar, `DISCARD ALL` limpa a conexão. Se a limpeza falhar, a conexão é descartada em vez de
91
+ voltar ao pool. `statement_timeout`, aplicado por transação, limita consultas demoradas; o padrão é
92
+ 15 segundos.
@@ -1,6 +1,6 @@
1
- DetailGroup organiza dados somente leitura como uma lista semântica de pares chave/valor.
2
- DetailField representa cada par e aceita conteúdo React rico em `label` e `value`. Use Field
3
- para entrada e validação; use DetailField quando a pessoa apenas consulta um valor.
1
+ Use `DetailGroup` para organizar dados somente leitura como pares de rótulo e valor.
2
+ `DetailField` representa cada par e aceita conteúdo React em `label` e `value`. Para entrada e
3
+ validação, use `Field`.
4
4
 
5
5
  ```tsx preview col
6
6
  <DetailGroup columns={2}>
@@ -9,7 +9,7 @@ para entrada e validação; use DetailField quando a pessoa apenas consulta um v
9
9
  label="Estágio"
10
10
  value={
11
11
  <DictionaryValue
12
- dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente', tone: 'success' } }, presentation: 'stage' }}
12
+ dict={{ keys: ['prospect', 'customer'], entries: { prospect: { label: 'Prospect' }, customer: { label: 'Cliente', context: 'success' } }, presentation: 'stage' }}
13
13
  value="prospect"
14
14
  />
15
15
  }
@@ -39,7 +39,10 @@ significado no domínio. `0` e `false` seguem como valores. Ver `EmptyValue`.
39
39
 
40
40
  `variant="framed"` adiciona a superfície e a borda externa. `dividers` desenha apenas as
41
41
  divisórias internas; as duas opções são independentes e podem ser combinadas. `orientation`
42
- define se a chave fica sobre o valor ou ao lado dele.
42
+ define se a chave fica sobre o valor ou ao lado dele. Na orientação horizontal, todos os valores
43
+ começam depois da mesma coluna de rótulo, com largura padrão de `7rem`. Quando a superfície exigir
44
+ mais espaço para os rótulos, ajuste a variável no grupo, por exemplo com
45
+ `className="[--detail-label-width:9rem]"`.
43
46
 
44
47
  ```tsx preview col
45
48
  <DetailGroup columns={2} orientation="horizontal" variant="framed" dividers>