@softize/opus 18.5.0 → 20.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 (151) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +298 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +16 -12
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/heading-level.ts +16 -0
  20. package/src/ui/components/patterns/list.tsx +103 -73
  21. package/src/ui/components/patterns/page.tsx +20 -4
  22. package/src/ui/components/patterns/pane.tsx +294 -0
  23. package/src/ui/components/patterns/presentation.tsx +81 -54
  24. package/src/ui/components/patterns/sidebar.tsx +13 -72
  25. package/src/ui/components/patterns/split.tsx +51 -38
  26. package/src/ui/components/patterns/surface-anatomy.ts +246 -0
  27. package/src/ui/components/patterns/surface-header.tsx +51 -6
  28. package/src/ui/components/patterns/surface-media.tsx +69 -0
  29. package/src/ui/components/patterns/trigger.tsx +12 -2
  30. package/src/ui/components/primitives/alert.tsx +9 -1
  31. package/src/ui/components/primitives/avatar.tsx +7 -3
  32. package/src/ui/components/primitives/card.tsx +91 -29
  33. package/src/ui/components/primitives/carousel.tsx +34 -5
  34. package/src/ui/components/primitives/checkbox.tsx +11 -3
  35. package/src/ui/components/primitives/command.tsx +14 -5
  36. package/src/ui/components/primitives/copyable.tsx +120 -10
  37. package/src/ui/components/primitives/detail.tsx +10 -13
  38. package/src/ui/components/primitives/dialog.tsx +179 -45
  39. package/src/ui/components/primitives/drawer.tsx +131 -24
  40. package/src/ui/components/primitives/empty.tsx +1 -1
  41. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  42. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  43. package/src/ui/components/primitives/input-group.tsx +9 -2
  44. package/src/ui/components/primitives/input-otp.tsx +1 -1
  45. package/src/ui/components/primitives/kbd.tsx +1 -1
  46. package/src/ui/components/primitives/label.tsx +3 -0
  47. package/src/ui/components/primitives/menu.tsx +5 -0
  48. package/src/ui/components/primitives/metric-card.tsx +98 -39
  49. package/src/ui/components/primitives/popover.tsx +1 -1
  50. package/src/ui/components/primitives/progress.tsx +9 -1
  51. package/src/ui/components/primitives/slider.tsx +62 -11
  52. package/src/ui/components/primitives/truncate.tsx +10 -1
  53. package/src/ui/docs/content/accordion.md +74 -27
  54. package/src/ui/docs/content/action-form-card.md +24 -15
  55. package/src/ui/docs/content/action-form-dialog.md +17 -7
  56. package/src/ui/docs/content/action-form.md +92 -77
  57. package/src/ui/docs/content/action-list-dialog.md +75 -38
  58. package/src/ui/docs/content/action-list.md +134 -52
  59. package/src/ui/docs/content/action-trigger.md +27 -10
  60. package/src/ui/docs/content/action-view.md +14 -14
  61. package/src/ui/docs/content/actions.md +161 -66
  62. package/src/ui/docs/content/ai.md +43 -44
  63. package/src/ui/docs/content/alert.md +83 -29
  64. package/src/ui/docs/content/ask.md +51 -26
  65. package/src/ui/docs/content/aspect-ratio.md +22 -24
  66. package/src/ui/docs/content/audit.md +23 -19
  67. package/src/ui/docs/content/auth.md +38 -19
  68. package/src/ui/docs/content/avatar.md +54 -5
  69. package/src/ui/docs/content/badge.md +29 -9
  70. package/src/ui/docs/content/breadcrumb.md +21 -8
  71. package/src/ui/docs/content/button.md +73 -43
  72. package/src/ui/docs/content/calendar.md +134 -26
  73. package/src/ui/docs/content/card.md +252 -35
  74. package/src/ui/docs/content/carousel.md +37 -6
  75. package/src/ui/docs/content/chat.md +118 -37
  76. package/src/ui/docs/content/checkbox.md +73 -7
  77. package/src/ui/docs/content/cli.md +94 -55
  78. package/src/ui/docs/content/collapsible.md +43 -19
  79. package/src/ui/docs/content/command.md +30 -8
  80. package/src/ui/docs/content/composer.md +23 -8
  81. package/src/ui/docs/content/content.md +37 -7
  82. package/src/ui/docs/content/copyable.md +39 -8
  83. package/src/ui/docs/content/customization.md +55 -46
  84. package/src/ui/docs/content/cycle.md +33 -18
  85. package/src/ui/docs/content/data-products.md +115 -0
  86. package/src/ui/docs/content/data-state.md +20 -6
  87. package/src/ui/docs/content/data.md +20 -19
  88. package/src/ui/docs/content/detail.md +12 -5
  89. package/src/ui/docs/content/dialog.md +219 -28
  90. package/src/ui/docs/content/dictionary-value.md +4 -4
  91. package/src/ui/docs/content/dock.md +54 -19
  92. package/src/ui/docs/content/dot.md +35 -5
  93. package/src/ui/docs/content/drawer.md +193 -22
  94. package/src/ui/docs/content/empty-value.md +20 -6
  95. package/src/ui/docs/content/empty.md +24 -3
  96. package/src/ui/docs/content/events.md +76 -16
  97. package/src/ui/docs/content/executions.md +9 -9
  98. package/src/ui/docs/content/field.md +21 -6
  99. package/src/ui/docs/content/getting-started.md +44 -34
  100. package/src/ui/docs/content/icon-picker.md +19 -13
  101. package/src/ui/docs/content/icon-tile.md +28 -5
  102. package/src/ui/docs/content/input-otp.md +58 -7
  103. package/src/ui/docs/content/input.md +22 -3
  104. package/src/ui/docs/content/item.md +53 -6
  105. package/src/ui/docs/content/kbd.md +5 -1
  106. package/src/ui/docs/content/label.md +13 -4
  107. package/src/ui/docs/content/log.md +5 -5
  108. package/src/ui/docs/content/markdown.md +5 -4
  109. package/src/ui/docs/content/mcp.md +49 -23
  110. package/src/ui/docs/content/menu.md +95 -10
  111. package/src/ui/docs/content/metric-card.md +68 -21
  112. package/src/ui/docs/content/observability.md +10 -9
  113. package/src/ui/docs/content/page.md +125 -11
  114. package/src/ui/docs/content/pagination.md +13 -5
  115. package/src/ui/docs/content/pane.md +300 -0
  116. package/src/ui/docs/content/popover.md +42 -5
  117. package/src/ui/docs/content/presentation.md +91 -47
  118. package/src/ui/docs/content/progress.md +27 -14
  119. package/src/ui/docs/content/queue.md +39 -21
  120. package/src/ui/docs/content/radio-group.md +26 -4
  121. package/src/ui/docs/content/router.md +29 -10
  122. package/src/ui/docs/content/runtime.md +90 -54
  123. package/src/ui/docs/content/scheduler.md +53 -21
  124. package/src/ui/docs/content/scroll-area.md +42 -35
  125. package/src/ui/docs/content/select.md +78 -33
  126. package/src/ui/docs/content/semantic-context.md +21 -2
  127. package/src/ui/docs/content/separator.md +15 -9
  128. package/src/ui/docs/content/sidebar.md +25 -45
  129. package/src/ui/docs/content/skeleton.md +13 -4
  130. package/src/ui/docs/content/slider.md +37 -9
  131. package/src/ui/docs/content/spinner.md +4 -3
  132. package/src/ui/docs/content/split.md +34 -20
  133. package/src/ui/docs/content/storage.md +8 -9
  134. package/src/ui/docs/content/switch.md +10 -7
  135. package/src/ui/docs/content/table.md +46 -8
  136. package/src/ui/docs/content/tabs.md +14 -4
  137. package/src/ui/docs/content/testing.md +76 -31
  138. package/src/ui/docs/content/textarea.md +5 -4
  139. package/src/ui/docs/content/toast.md +48 -7
  140. package/src/ui/docs/content/toggle.md +3 -3
  141. package/src/ui/docs/content/tokens.md +15 -26
  142. package/src/ui/docs/content/tooltip.md +18 -2
  143. package/src/ui/docs/content/truncate.md +36 -6
  144. package/src/ui/docs/content/ui.md +74 -22
  145. package/src/ui/docs/content/upgrading.md +45 -36
  146. package/src/ui/docs/plugin.ts +36 -2
  147. package/src/ui/docs/registry.tsx +14 -2
  148. package/src/ui/drivers/react.tsx +16 -8
  149. package/src/ui/meta.ts +38 -32
  150. package/src/ui/react.tsx +14 -0
  151. package/src/vite/design.ts +16 -4
@@ -1,36 +1,51 @@
1
+ ---
2
+ title: Evolução contínua
3
+ ---
4
+
1
5
  # Evolução contínua
2
6
 
3
- O Opus cresce por **reincidência, nunca por especulação**. Quando o mesmo comportamento genérico reaparece em mais de um projeto, ele vira candidato a peça do Opus — e o projeto troca a implementação local pelo import. Estilo e conveniência não entram; comportamento que se repete, sim.
7
+ Use este fluxo quando o seu projeto precisar de algo que o Opus ainda não oferece ou encontrar um
8
+ defeito nele. O Opus incorpora um comportamento quando ele se repete em mais de um projeto; então o
9
+ projeto troca a implementação local pelo import. Preferência de estilo e conveniência isolada não
10
+ são motivo para incorporar uma peça.
4
11
 
5
- ## Onde você aponta
12
+ ## Registrar um apontamento
6
13
 
7
- Cada apontamento vira uma Issue no repositório do Opus. Antes de abrir, procure uma Issue existente para o mesmo comportamento e comente nela. Dois tipos:
14
+ Cada apontamento vira uma Issue em
15
+ [github.com/softize-dev/opus](https://github.com/softize-dev/opus/issues). Antes de abrir uma nova,
16
+ procure uma Issue sobre o mesmo comportamento e comente nela. Há dois tipos:
8
17
 
9
- - **`enhancement`** — código genérico que o Opus deveria ganhar (serviria a qualquer projeto). Antes de apontar, confira o que o Opus já tem (o inventário de componentes, os exports, esta doc): se já existe, use.
10
- - **`bug`** — um defeito ou limitação no próprio Opus, achado usando-o.
18
+ - **`enhancement`:** código genérico que serviria a qualquer projeto. Antes de apontar, confira se o
19
+ Opus já oferece o recurso no inventário de componentes, nos exports ou nesta documentação; se
20
+ oferecer, use-o.
21
+ - **`bug`:** defeito ou limitação do próprio Opus, encontrado durante o uso.
11
22
 
12
- Descreva cada apontamento com estes campos — um exemplo de cada tipo:
23
+ Descreva cada apontamento com estes campos, como nos exemplos:
13
24
 
14
25
  ```text
15
26
  {"type": "enhancement", "behavior": "o comportamento em uma frase.", "file": "src/onde-vive.ts", "kind": "ui|sdk|driver|infra"}
16
- {"type": "bug", "behavior": "o que quebra ou falta.", "file": "onde se manifesta.", "note": "repro + o workaround aplicado."}
27
+ {"type": "bug", "behavior": "o que quebra ou falta.", "file": "onde se manifesta.", "note": "como reproduzir e o contorno aplicado."}
17
28
  ```
18
29
 
19
- ## Não trave esperando
30
+ O pacote não coleta apontamentos do seu projeto automaticamente.
20
31
 
21
- O ponto do ciclo é **não bloquear a entrega**. Bateu em um gap ou em um bug do Opus:
32
+ ## Seguir sem esperar a correção
22
33
 
23
- 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).
24
- 2. **Entregue** a feature com o workaround.
25
- 3. **Aponte** na Issue e siga em frente.
34
+ Uma lacuna ou um defeito do Opus não precisa bloquear a entrega:
26
35
 
27
- O "depois" — o conserto no Opus — corre em paralelo. Ele não segura o seu trabalho; o único custo de demorar é o workaround viver um pouco mais.
36
+ 1. **Contorne no projeto:** componha um wrapper local. O Opus é distribuído como código-fonte, então
37
+ qualquer superfície dele pode ser envolvida. Não edite `node_modules`, porque a alteração se
38
+ perde na próxima instalação.
39
+ 2. **Entregue** a funcionalidade com o contorno.
40
+ 3. **Registre** o apontamento na Issue.
28
41
 
29
- ## Como fecha
42
+ A correção no Opus acontece em paralelo. Enquanto ela não sai, o contorno continua no projeto.
30
43
 
31
- A triagem acontece em uma **Issue em [github.com/softize-dev/opus](https://github.com/softize-dev/opus/issues)**; procure uma existente antes de abrir. O pacote não coleta apontamentos do seu projeto por conta própria. O desfecho depende do tipo:
44
+ ## Como o apontamento termina
32
45
 
33
- - **Enhancement** aceito (com reincidência) → implementado no Opus → sai em um _bump_ → seu projeto [atualiza o Opus](upgrading) e troca o workaround pelo import.
34
- - **Bug** → vira _fix_ + entrada no `CHANGELOG` → no _bump_, o workaround sai.
46
+ - Um **enhancement** aceito é implementado no Opus e publicado em uma nova versão. Ao
47
+ [atualizar o Opus](/docs/upgrading), o projeto troca o contorno pelo import.
48
+ - Um **bug** vira uma correção com entrada no `CHANGELOG`; na atualização, o contorno sai.
35
49
 
36
- 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.
50
+ A decisão sobre o que entra no Opus fica com a Softize, que mantém o pacote. O registro das peças
51
+ incorporadas está no `PROMOTED.md` do pacote.
@@ -0,0 +1,115 @@
1
+ ---
2
+ title: Produtos de Dados
3
+ ---
4
+
5
+ # Produtos de Dados
6
+
7
+ Use um Produto de Dados quando um conjunto de dados precisar de identidade estável para catálogos,
8
+ agentes e revisão, independentemente da action que o entrega. A declaração diz o que o conjunto
9
+ representa, de onde vem, quem responde por ele e quais actions o disponibilizam. Ela não é uma
10
+ consulta, uma tabela nem uma view, e não executa nada: cada action continua sendo a fronteira de
11
+ execução, recorte e autorização.
12
+
13
+ ## Declarar e registrar
14
+
15
+ `defineDataProduct` declara o produto e valida seus campos no momento em que roda. O domínio o
16
+ registra em `dataProducts`, ao lado das actions e entidades citadas:
17
+
18
+ ```ts
19
+ import { defineDataProduct, defineDomain } from '@softize/opus/core'
20
+ import { LeadEntity } from './entity.ts' // defineEntity({ name: 'lead', … })
21
+ import { leadList } from './actions/list.ts' // action 'lead.list'
22
+ import { leadFunnel } from './actions/funnel.ts' // action 'lead.funnel'
23
+
24
+ export const salesLeads = defineDataProduct({
25
+ id: 'sales.leads',
26
+ version: 1,
27
+ label: 'Leads comerciais',
28
+ description: 'Leads e seus resultados comerciais.',
29
+ owner: 'Vendas',
30
+ grain: 'Um lead.',
31
+ classification: 'internal',
32
+ nature: 'real',
33
+ sources: [{ id: 'crm', label: 'CRM comercial' }],
34
+ entities: ['lead'],
35
+ access: {
36
+ permissionContexts: ['sales'],
37
+ organizationalScopes: ['unit', 'team'],
38
+ },
39
+ interfaces: ['lead.list', 'lead.funnel'],
40
+ })
41
+
42
+ export const leadDomain = defineDomain({
43
+ name: 'lead',
44
+ entities: { LeadEntity },
45
+ actions: { leadList, leadFunnel },
46
+ dataProducts: { salesLeads },
47
+ })
48
+ ```
49
+
50
+ `interfaces` e `entities` usam os nomes declarados em `bindAction`/`defineContract` e em
51
+ `defineEntity`, não nomes de arquivo ou de tabela. `sources` identifica os sistemas ou conjuntos de
52
+ origem; uma fonte não precisa ser uma entidade do Opus.
53
+
54
+ ## Verificações
55
+
56
+ A declaração é conferida em três momentos:
57
+
58
+ - `defineDataProduct` lança `TypeError` quando o `id` não tem namespace (como `sales.leads`), a
59
+ `version` não é um inteiro positivo, falta texto obrigatório, `sources` ou `interfaces` estão
60
+ vazias ou há itens repetidos.
61
+ - `defineDomain` recusa um produto duplicado no domínio e referências a actions ou entidades que o
62
+ domínio não declara (`domain.data_product_interface_not_found` e
63
+ `domain.data_product_entity_not_found`).
64
+ - `opus check` lê o código sem executá-lo e confere exportação, `id`, `version`, duplicidade e as
65
+ referências a actions e entidades do projeto, com as regras de prefixo `data-product`. Para isso,
66
+ o produto precisa ser `export const` e usar valores literais.
67
+
68
+ ## Acesso e segurança
69
+
70
+ `access` documenta o alcance esperado de forma verificável. Ele ajuda o catálogo e a revisão a
71
+ perceber, por exemplo, que uma interface deveria respeitar unidade e equipe. `access` não é RLS nem
72
+ autorização automática: cada action listada em `interfaces` precisa aplicar o próprio `authorize` e
73
+ os recortes no handler ou no repositório, inclusive quando chamada por IA ou por
74
+ [MCP](/docs/mcp).
75
+
76
+ `permissionContexts` nomeia chaves do vocabulário de permissão do projeto. Ele não descreve área,
77
+ departamento nem contexto de tela; um projeto que não usa essa dimensão declara uma lista vazia.
78
+ O nome antigo `contexts` ainda é aceito e projetado para compatibilidade, mas declarações novas usam
79
+ `permissionContexts`.
80
+
81
+ ## Onde o produto aparece
82
+
83
+ - `opus gen` publica os produtos no `.opus/manifest.json`, e `opus introspect` os inclui no modelo
84
+ do projeto. Veja [CLI opus](/docs/cli).
85
+ - Em `runtime.aiTools()`, cada action exposta à IA traz em `metadata.dataProducts` os IDs dos
86
+ produtos relacionados e, em `metadata.dataProductLabels`, o `label` de cada ID. Veja
87
+ [IA generativa](/docs/ai).
88
+ - No servidor MCP, as mesmas informações aparecem em `_meta['com.softize.opus/data-products']` e
89
+ `_meta['com.softize.opus/data-product-labels']`.
90
+
91
+ Esses metadados servem a quem hospeda o agente: mostre o `label` às pessoas e use o ID na execução e
92
+ na auditoria. Eles não entram no prompt e não concedem acesso. Nenhuma dessas projeções é a fonte
93
+ da verdade: para mudar um produto, corrija a declaração e gere de novo.
94
+
95
+ ## Evoluir um produto
96
+
97
+ Incremente `version` quando a forma observável do produto mudar. Para substituí-lo, mantenha a
98
+ declaração anterior com `status: 'deprecated'` e `replacedBy` apontando para o novo ID durante a
99
+ migração. Um produto ativo não declara `replacedBy`.
100
+
101
+ ## Referência de DataProductConfig
102
+
103
+ | Campo | Contrato |
104
+ | --- | --- |
105
+ | `id` | Identidade estável com namespace, como `sales.leads`. |
106
+ | `version` | Inteiro positivo; muda com a forma observável do produto. |
107
+ | `label`, `description` | Nome e descrição para pessoas. |
108
+ | `owner` | Responsável técnico ou de negócio. |
109
+ | `grain` | O que uma linha ou observação representa. |
110
+ | `classification`, `nature` | Classificação (como `internal` ou `personal`) e natureza (como `real`, `demo` ou `synthetic`) definidas pelo projeto. |
111
+ | `sources` | Pelo menos uma fonte `{ id, label, description? }`, sem `id` repetido. |
112
+ | `entities` | Nomes das entidades Opus que estruturam o produto; pode ser vazia. |
113
+ | `access` | `permissionContexts` e `organizationalScopes`, ambos listas de texto. |
114
+ | `interfaces` | Nomes das actions que disponibilizam o produto; pelo menos uma. |
115
+ | `status`, `replacedBy` | `active` (padrão) ou `deprecated`; um produto `deprecated` exige `replacedBy`. |
@@ -1,11 +1,25 @@
1
1
  ## Estados
2
2
 
3
- Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta. As
4
- superfícies são as mesmas de `PageState`, na escala de uma seção: o carregamento é um contêiner
5
- `role="status"` com o `Spinner`; o vazio compõe `Empty` com moldura sólida e `emptyMessage` como
6
- título; o erro é um `Alert` de contexto `danger` com uma mensagem segura, sem expor detalhes
7
- técnicos, e uma ação `ghost` só com ícone quando há `onRetry`. `retryLabel` fornece o nome acessível
8
- e o tooltip dessa ação. Para uma ação em andamento depois do clique, use `busy` em `Button`.
3
+ Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta, na escala
4
+ de uma seção. Para o estado da página inteira, use `PageState`, descrito em [`Page`](/ui/page); para
5
+ uma coleção carregada por uma action `kind: 'list'`, [`ActionList`](/ui/action-list) já trata esses
6
+ estados.
7
+
8
+ Cada estado tem uma apresentação fixa:
9
+
10
+ - **Carregando:** um contêiner `role="status"` com o [`Spinner`](/ui/spinner) centralizado. Quando o
11
+ formato do conteúdo já é conhecido, em vez de passar `loading`, renderize o
12
+ [`Skeleton`](/ui/skeleton) durante a espera: ele reproduz esse formato e evita o salto de layout.
13
+ - **Vazio:** um [`Empty`](/ui/empty) com moldura sólida e `emptyMessage` como frase discreta.
14
+ - **Erro:** um [`Alert`](/ui/alert) de contexto `danger` com a mensagem segura de `errorMessage`,
15
+ sem expor o texto técnico de `error`. Com `onRetry`, o aviso ganha uma ação `ghost` só com ícone,
16
+ e `retryLabel` fornece o nome acessível e o tooltip dessa ação.
17
+
18
+ Quando mais de uma condição vale ao mesmo tempo, vence a primeira desta ordem: erro, carregando,
19
+ vazio. O conteúdo aparece somente quando nenhuma delas vale. Assim, uma nova tentativa com
20
+ `loading` e `error` ativos continua mostrando o erro até que `error` volte a `null`.
21
+
22
+ Para uma ação em andamento depois do clique, use `busy` em [`Button`](/ui/button).
9
23
 
10
24
  ```tsx preview col
11
25
  <div className="w-full space-y-3">
@@ -32,9 +32,9 @@ export const SkillEntity = defineEntity({
32
32
 
33
33
  ## Código em camelCase, banco em snake_case
34
34
 
35
- O schema tipado do Kysely e todas as consultas usam camelCase
36
- (`workspaceId`, `ghRepo`). O banco é snake (a DDL no schema idempotente — `db/schema.sql`).
37
- `CamelCasePlugin` converte nomes nas consultas e nos resultados. Um sink que não usa o Kysely com o
35
+ O schema tipado do Kysely e todas as consultas usam camelCase (`workspaceId`, `ghRepo`). O banco
36
+ usa snake_case, definido na DDL do schema idempotente, como `db/schema.sql`. `CamelCasePlugin`
37
+ converte os nomes nas consultas e nos resultados. Um sink que não usa o Kysely com o
38
38
  plugin, como `audit_log`, mantém os nomes do banco.
39
39
 
40
40
  ```ts
@@ -52,19 +52,20 @@ await db.selectFrom('agents').select(['isDefault', 'roleId']).where('workspaceId
52
52
 
53
53
  Cada etapa possui um papel diferente:
54
54
 
55
- - `opus db migrate` — aplica o **schema idempotente** (`config.schema`, um script SQL
56
- evolutivo: `IF NOT EXISTS` + guards cobrem nascer do zero e upgrade no mesmo artefato)
57
- e roda o drift-check entidade ↔ banco na sequência (exit ≠ 0 se divergir).
58
- - `opus seed` — aplica datasets de desenvolvimento e teste declarados com `defineSeed` e registrados
59
- no `opus.config.ts`; só conecta com `NODE_ENV=development` ou `test`. Ver [CLI opus](cli).
55
+ - `opus db migrate` aplica o schema idempotente (`config.schema`), um único script SQL em que
56
+ `IF NOT EXISTS` e condições de guarda cobrem tanto o banco novo quanto a atualização. Em seguida,
57
+ compara entidades e banco e encerra com exit ≠ 0 quando há divergência.
58
+ - `opus seed` aplica datasets de desenvolvimento e teste declarados com `defineSeed` e registrados no
59
+ `opus.config.ts`. Ele só conecta com `NODE_ENV=development` ou `test`. Veja
60
+ [CLI opus](/docs/cli).
60
61
 
61
62
  ## Campos JSON
62
63
 
63
- `kyselyRepo` serializa campos `t.json()` na escrita (`insert` e `update`), guiado pela
64
- declaração. Sem isso, o pg até stringifica objeto plano — mas **array vira literal de
65
- array do PG** (errado para jsonb) sem quebrar typecheck. String passa direto (quem já
66
- mandava pré-serializado segue valendo); na leitura o pg devolve objeto. Query à mão
67
- (fora do repo) continua responsável pelo próprio stringify.
64
+ `kyselyRepo` serializa os campos `t.json()` na escrita (`insert` e `update`), guiado pela
65
+ declaração da entidade. Sem essa etapa, o driver `pg` converte um objeto simples, mas transforma um
66
+ array em literal de array do Postgres, o que é inválido para `jsonb` e não aparece no typecheck.
67
+ Uma string passa sem alteração, o que preserva valores já serializados; na leitura, o `pg` devolve
68
+ o objeto. Uma consulta escrita fora do repositório continua responsável pela própria serialização.
68
69
 
69
70
  ## SQL gerado por modelo
70
71
 
@@ -79,13 +80,13 @@ const bi = readOnlyContextPool({ pool: new Pool({ connectionString, max: 4 }) })
79
80
  const { rows } = await bi.query(sqlDoLlm, { role: roleFor(ctx) })
80
81
  ```
81
82
 
82
- 1. **Pool com limite** (`max` e/ou `maxConcurrent`) — impede que as consultas consumam todas as
83
+ 1. **Pool com limite** (`max`, `maxConcurrent` ou ambos): impede que as consultas consumam todas as
83
84
  conexões do aplicativo.
84
- 2. **`BEGIN TRANSACTION READ ONLY`** — o servidor rejeita tentativas de escrita;
85
- 3. **`SET LOCAL ROLE` por transação** — o alcance é definido pelo contexto (crie os roles com grants
86
- default-fechado nas suas migrações: sem isso, `sales_read` leria `hr_employees`);
87
- 4. **Protocolo estendido** — o SQL sempre recebe os valores separadamente; múltiplas instruções
88
- (`SELECT 1; DROP …`) é recusada pelo próprio protocolo.
85
+ 2. **`BEGIN TRANSACTION READ ONLY`:** o servidor rejeita tentativas de escrita.
86
+ 3. **`SET LOCAL ROLE` por transação:** o contexto define o alcance da consulta. Crie os roles nas
87
+ suas migrações com grants fechados por padrão; sem isso, `sales_read` leria `hr_employees`.
88
+ 4. **Protocolo estendido:** o SQL sempre recebe os valores separadamente, e múltiplas instruções
89
+ (`SELECT 1; DROP …`) são recusadas pelo próprio protocolo.
89
90
 
90
91
  Ao terminar, `DISCARD ALL` limpa a conexão. Se a limpeza falhar, a conexão é descartada em vez de
91
92
  voltar ao pool. `statement_timeout`, aplicado por transação, limita consultas demoradas; o padrão é
@@ -1,6 +1,6 @@
1
1
  Use `DetailGroup` para organizar dados somente leitura como pares de rótulo e valor.
2
2
  `DetailField` representa cada par e aceita conteúdo React em `label` e `value`. Para entrada e
3
- validação, use `Field`.
3
+ validação, use [`Field`](/ui/field).
4
4
 
5
5
  ```tsx preview col
6
6
  <DetailGroup columns={2}>
@@ -27,7 +27,8 @@ validação, use `Field`.
27
27
 
28
28
  `null`, `undefined`, string vazia ou só com espaços mostram um travessão; a leitura assistiva
29
29
  recebe “Não informado”. Uma string em `empty` troca esse significado no domínio, enquanto um nó
30
- React substitui a apresentação. `0` e `false` seguem como valores. Ver `EmptyValue`.
30
+ React substitui a apresentação. `0` e `false` seguem como valores. A regra é a mesma de
31
+ [`EmptyValue`](/ui/empty-value).
31
32
 
32
33
  ```tsx preview col
33
34
  <DetailGroup columns={2}>
@@ -64,15 +65,21 @@ começam depois da mesma coluna de rótulo, com largura padrão de `7rem` em gru
64
65
  | Propriedade | Tipo | Padrão | Descrição |
65
66
  |---|---|---|---|
66
67
  | `variant` | `'plain' \| 'framed'` | `'plain'` | `framed` aplica a moldura canônica ao conjunto, com fundo transparente; na orientação horizontal, a coluna de rótulos recebe fundo sutil. |
67
- | `dividers` | `boolean` | `false` | Hairlines somente entre os campos, sem exigir moldura externa. |
68
+ | `dividers` | `boolean` | `false` | Linhas finas somente entre os campos, sem exigir moldura externa. |
68
69
  | `columns` | `1 \| 2 \| 3 \| 4 \| 'auto'` | `1` | Número fixo de colunas ou distribuição automática por largura mínima. |
69
70
  | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Chave sobre o valor ou ao lado dele em cada campo. |
70
71
 
72
+ O grupo renderiza uma lista de definições (`<dl>`); os demais atributos, como `className`, vão para
73
+ ela.
74
+
71
75
  ## Propriedades de DetailField
72
76
 
73
77
  | Propriedade | Tipo | Padrão | Descrição |
74
78
  |---|---|---|---|
75
- | `label` | `ReactNode` | | A chave do par. |
76
- | `value` | `ReactNode` | | O valor; `null`, `undefined` e string vazia renderizam a ausência, `0` e `false` seguem como valores. |
79
+ | `label` | `ReactNode` | obrigatório | A chave do par. |
80
+ | `value` | `ReactNode` | obrigatório | O valor; `null`, `undefined` e string vazia renderizam a ausência, `0` e `false` seguem como valores. |
77
81
  | `icon` | `ReactNode` | | Ícone decorativo antes do par chave/valor. |
78
82
  | `empty` | `ReactNode` | `“Não informado”` | Uma string dá significado acessível ao travessão; um nó próprio substitui a apresentação. |
83
+
84
+ Os demais atributos, como `className`, vão para o contêiner do par. `DetailField` não aceita
85
+ `children`: o conteúdo entra por `value`.