@softize/opus 12.11.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 (119) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/bin/lib/check.mjs +2 -7
  3. package/bin/lib/copy.mjs +1 -5
  4. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
  5. package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
  6. package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
  7. package/docs/radius-scale.md +1 -1
  8. package/package.json +1 -1
  9. package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
  10. package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
  11. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
  12. package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
  13. package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
  14. package/src/ui/components/patterns/confirm.tsx +140 -40
  15. package/src/ui/components/patterns/list.tsx +35 -40
  16. package/src/ui/components/patterns/page-state.tsx +2 -2
  17. package/src/ui/components/patterns/sidebar.tsx +26 -26
  18. package/src/ui/components/patterns/trigger.tsx +25 -22
  19. package/src/ui/components/primitives/alert.tsx +3 -3
  20. package/src/ui/components/primitives/dialog.tsx +196 -39
  21. package/src/ui/components/primitives/drawer.tsx +8 -5
  22. package/src/ui/components/primitives/empty.tsx +3 -3
  23. package/src/ui/components/primitives/item.tsx +3 -3
  24. package/src/ui/components/primitives/sonner.tsx +187 -8
  25. package/src/ui/docs/DocBrowser.tsx +102 -23
  26. package/src/ui/docs/content/accordion.md +22 -16
  27. package/src/ui/docs/content/action-form-card.md +8 -8
  28. package/src/ui/docs/content/action-form-dialog.md +9 -9
  29. package/src/ui/docs/content/action-form.md +28 -34
  30. package/src/ui/docs/content/action-list-dialog.md +11 -6
  31. package/src/ui/docs/content/action-list.md +64 -39
  32. package/src/ui/docs/content/action-trigger.md +21 -14
  33. package/src/ui/docs/content/action-view.md +8 -8
  34. package/src/ui/docs/content/actions.md +9 -9
  35. package/src/ui/docs/content/ai.md +3 -3
  36. package/src/ui/docs/content/alert.md +14 -12
  37. package/src/ui/docs/content/aspect-ratio.md +4 -4
  38. package/src/ui/docs/content/audit.md +2 -2
  39. package/src/ui/docs/content/auth.md +3 -3
  40. package/src/ui/docs/content/avatar.md +34 -14
  41. package/src/ui/docs/content/badge.md +3 -3
  42. package/src/ui/docs/content/breadcrumb.md +13 -8
  43. package/src/ui/docs/content/button.md +81 -6
  44. package/src/ui/docs/content/calendar.md +5 -5
  45. package/src/ui/docs/content/card.md +1 -1
  46. package/src/ui/docs/content/carousel.md +16 -11
  47. package/src/ui/docs/content/chat.md +3 -3
  48. package/src/ui/docs/content/checkbox.md +7 -7
  49. package/src/ui/docs/content/cli.md +5 -5
  50. package/src/ui/docs/content/collapsible.md +8 -8
  51. package/src/ui/docs/content/command.md +16 -8
  52. package/src/ui/docs/content/composer.md +2 -2
  53. package/src/ui/docs/content/content.md +2 -2
  54. package/src/ui/docs/content/copyable.md +4 -3
  55. package/src/ui/docs/content/customization.md +5 -5
  56. package/src/ui/docs/content/cycle.md +3 -3
  57. package/src/ui/docs/content/data-state.md +11 -12
  58. package/src/ui/docs/content/data.md +26 -33
  59. package/src/ui/docs/content/detail.md +3 -3
  60. package/src/ui/docs/content/dialog.md +339 -31
  61. package/src/ui/docs/content/dictionary-value.md +8 -8
  62. package/src/ui/docs/content/dock.md +3 -3
  63. package/src/ui/docs/content/drawer.md +27 -14
  64. package/src/ui/docs/content/empty-value.md +2 -2
  65. package/src/ui/docs/content/empty.md +19 -12
  66. package/src/ui/docs/content/events.md +4 -4
  67. package/src/ui/docs/content/field.md +34 -12
  68. package/src/ui/docs/content/getting-started.md +1 -1
  69. package/src/ui/docs/content/icon-picker.md +8 -4
  70. package/src/ui/docs/content/input-otp.md +20 -12
  71. package/src/ui/docs/content/input.md +121 -9
  72. package/src/ui/docs/content/item.md +27 -13
  73. package/src/ui/docs/content/kbd.md +19 -11
  74. package/src/ui/docs/content/label.md +5 -3
  75. package/src/ui/docs/content/log.md +4 -4
  76. package/src/ui/docs/content/markdown.md +7 -6
  77. package/src/ui/docs/content/mcp.md +13 -15
  78. package/src/ui/docs/content/menu.md +34 -16
  79. package/src/ui/docs/content/observability.md +2 -2
  80. package/src/ui/docs/content/page.md +51 -6
  81. package/src/ui/docs/content/pagination.md +22 -17
  82. package/src/ui/docs/content/popover.md +16 -8
  83. package/src/ui/docs/content/progress.md +7 -5
  84. package/src/ui/docs/content/queue.md +5 -5
  85. package/src/ui/docs/content/radio-group.md +20 -12
  86. package/src/ui/docs/content/router.md +11 -6
  87. package/src/ui/docs/content/scheduler.md +4 -5
  88. package/src/ui/docs/content/scroll-area.md +12 -7
  89. package/src/ui/docs/content/select.md +42 -29
  90. package/src/ui/docs/content/separator.md +5 -5
  91. package/src/ui/docs/content/sidebar.md +323 -54
  92. package/src/ui/docs/content/skeleton.md +3 -2
  93. package/src/ui/docs/content/slider.md +8 -7
  94. package/src/ui/docs/content/spinner.md +8 -8
  95. package/src/ui/docs/content/split.md +8 -5
  96. package/src/ui/docs/content/storage.md +6 -8
  97. package/src/ui/docs/content/switch.md +8 -7
  98. package/src/ui/docs/content/table.md +13 -3
  99. package/src/ui/docs/content/tabs.md +28 -14
  100. package/src/ui/docs/content/testing.md +9 -11
  101. package/src/ui/docs/content/textarea.md +5 -4
  102. package/src/ui/docs/content/toast.md +47 -13
  103. package/src/ui/docs/content/toggle.md +75 -7
  104. package/src/ui/docs/content/tokens.md +3 -3
  105. package/src/ui/docs/content/tooltip.md +19 -11
  106. package/src/ui/docs/content/truncate.md +7 -8
  107. package/src/ui/docs/content/ui.md +10 -9
  108. package/src/ui/docs/content/upgrading.md +7 -8
  109. package/src/ui/docs/registry.tsx +20 -37
  110. package/src/ui/meta.ts +64 -94
  111. package/src/ui/react.tsx +15 -16
  112. package/src/ui/theme.css +50 -0
  113. package/src/ui/components/primitives/alert-dialog.tsx +0 -192
  114. package/src/ui/docs/content/alert-dialog.md +0 -73
  115. package/src/ui/docs/content/button-group.md +0 -71
  116. package/src/ui/docs/content/confirm.md +0 -120
  117. package/src/ui/docs/content/input-group.md +0 -79
  118. package/src/ui/docs/content/page-state.md +0 -45
  119. package/src/ui/docs/content/toggle-group.md +0 -81
@@ -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}>
@@ -1,6 +1,22 @@
1
- ## Composição
1
+ ## Escolher a forma do diálogo
2
2
 
3
- Content é superfície pura; o espaço mora nos slots (body p-5; header/footer px-5 py-4): DialogHeader (fixo, com divisor), DialogBody (rola) e DialogFooter (faixa da casa — borda + fundo muted). DialogClose fecha sem estado manual.
3
+ Use a família `Dialog` quando a pessoa precisar concluir uma tarefa ou responder sem perder o
4
+ contexto da página. A forma escolhida depende do tipo de interrupção:
5
+
6
+ | Necessidade | Escolha |
7
+ |---|---|
8
+ | Exibir uma tarefa ou conteúdo modal que pode ser fechado. | Componha `Dialog`. |
9
+ | Exigir uma resposta e impedir o fechamento pelo clique externo. | Componha `Dialog mode="alert"`. |
10
+ | Pedir reconhecimento, confirmação binária ou uma string sem montar a superfície. | Use `dialog.alert`, `dialog.confirm` ou `dialog.prompt`. |
11
+ | Escolher entre vários resultados simples. | Use `dialog.choose`. |
12
+
13
+ Para conteúdo ancorado e não modal, use `Popover`. Para uma lista de ações, use `Menu`.
14
+
15
+ ## Compor uma tarefa modal
16
+
17
+ `DialogContent` delimita a superfície. `DialogHeader`, `DialogBody` e `DialogFooter` organizam
18
+ título, conteúdo rolável e ações com o espaçamento da família. A superfície usa o fundo base,
19
+ borda semântica, raio `xl` e elevação para permanecer distinta da página.
4
20
 
5
21
  ```tsx preview
6
22
  <Dialog>
@@ -13,48 +29,340 @@ Content é superfície pura; o espaço mora nos slots (body p-5; header/footer p
13
29
  <DialogDescription>O workspace agrupa os repositórios e agentes do cliente.</DialogDescription>
14
30
  </DialogHeader>
15
31
  <DialogBody className="space-y-2">
16
- <Label htmlFor="ws-nome">Nome</Label>
17
- <Input id="ws-nome" placeholder="Ex.: Empresa X" />
32
+ <Label htmlFor="workspace-name">Nome</Label>
33
+ <Input id="workspace-name" placeholder="Ex.: Empresa X" />
18
34
  </DialogBody>
19
35
  <DialogFooter>
20
36
  <DialogClose asChild>
21
- <Button variant="ghost" size="sm">Cancelar</Button>
37
+ <Button variant="ghost">Cancelar</Button>
22
38
  </DialogClose>
23
- <Button size="sm">Criar</Button>
39
+ <Button>Criar workspace</Button>
24
40
  </DialogFooter>
25
41
  </DialogContent>
26
42
  </Dialog>
27
43
  ```
28
44
 
29
- ## Sem o X (ação obrigatória)
45
+ O corpo cresce até o limite da janela e passa a rolar; cabeçalho e rodapé permanecem visíveis.
46
+ `DialogClose` encerra o modal sem exigir estado controlado. Use `open` e `onOpenChange` quando outra
47
+ parte da interface também precisar controlar a abertura.
48
+
49
+ ## Pedir uma resposta pela API imperativa
30
50
 
31
- showCloseButton={false} esconde o X: o usuário decide pelos botões pra confirmação que não pode ser dispensada no escuro.
51
+ Use `dialog.alert`, `dialog.confirm`, `dialog.prompt` e `dialog.choose` quando a resposta couber em
52
+ um contrato pronto. As chamadas são assíncronas e resolvem somente depois da resposta.
32
53
 
33
54
  ```tsx preview
34
- <Dialog>
35
- <DialogTrigger asChild>
36
- <Button context="danger">Excluir sessão</Button>
37
- </DialogTrigger>
38
- <DialogContent showCloseButton={false}>
39
- <DialogHeader>
40
- <DialogTitle>Excluir a sessão?</DialogTitle>
41
- <DialogDescription>O worktree e o preview desta sessão serão removidos.</DialogDescription>
42
- </DialogHeader>
43
- <DialogFooter>
44
- <DialogClose asChild>
45
- <Button variant="ghost" size="sm">Cancelar</Button>
46
- </DialogClose>
47
- <DialogClose asChild>
48
- <Button context="danger" size="sm">Excluir</Button>
49
- </DialogClose>
50
- </DialogFooter>
51
- </DialogContent>
52
- </Dialog>
55
+ function Demo() {
56
+ const [result, setResult] = useState('Nenhuma resposta.')
57
+
58
+ return (
59
+ <div className="flex flex-wrap items-center gap-3">
60
+ <DialogHost />
61
+ <Button variant="outline" size="sm" onClick={async () => {
62
+ await dialog.alert({
63
+ title: 'Sessão expirada',
64
+ description: 'Entre novamente para continuar.',
65
+ })
66
+ setResult('Aviso reconhecido.')
67
+ }}>
68
+ Abrir aviso
69
+ </Button>
70
+ <Button variant="outline" size="sm" onClick={async () => {
71
+ const accepted = await dialog.confirm({
72
+ title: 'Excluir sessão?',
73
+ description: 'O worktree e o preview desta sessão serão removidos.',
74
+ action: 'Excluir',
75
+ context: 'danger',
76
+ })
77
+ setResult(accepted ? 'Sessão excluída.' : 'Sessão mantida.')
78
+ }}>
79
+ Confirmar exclusão
80
+ </Button>
81
+ <Button variant="outline" size="sm" onClick={async () => {
82
+ const name = await dialog.prompt({
83
+ title: 'Renomear snapshot',
84
+ action: 'Renomear',
85
+ placeholder: 'Ex.: versão final',
86
+ })
87
+ setResult(name === null ? 'Nome mantido.' : `Novo nome: ${name}`)
88
+ }}>
89
+ Renomear snapshot
90
+ </Button>
91
+ <Button variant="outline" size="sm" onClick={async () => {
92
+ const choice = await dialog.choose({
93
+ title: 'Fechar editor?',
94
+ description: 'O contrato possui alterações que ainda não foram salvas.',
95
+ actions: [
96
+ { result: 'continue', label: 'Continuar editando', initialFocus: true },
97
+ { result: 'discard', label: 'Descartar', variant: 'outline' },
98
+ { result: 'save', label: 'Salvar e fechar' },
99
+ ],
100
+ })
101
+ setResult(choice === null ? 'Editor mantido.' : `Resultado: ${choice}`)
102
+ }}>
103
+ Fechar editor
104
+ </Button>
105
+ <span className="text-sm text-muted-foreground">{result}</span>
106
+ </div>
107
+ )
108
+ }
109
+
110
+ render(<Demo />)
111
+ ```
112
+
113
+ `dialog.alert` devolve `Promise<void>`, `dialog.confirm` devolve `Promise<boolean>`,
114
+ `dialog.prompt` devolve `Promise<string | null>` e `dialog.choose` infere a união dos resultados
115
+ declarados, acrescida de `null`. Fechar ou cancelar resolve a resposta negativa de cada operação,
116
+ em vez de deixar a promise pendente. No aviso com uma ação, o botão ocupa toda a largura do rodapé;
117
+ confirmações e prompts distribuem confirmar e cancelar em duas colunas.
118
+
119
+ Em `dialog.choose`, declare pelo menos uma ação e marque exatamente uma ação habilitada com
120
+ `initialFocus`. Essa ação representa a saída segura. Os valores de `result` precisam ser únicos.
121
+ Por padrão, a última ação recebe contexto primário e variante sólida; as anteriores usam contexto
122
+ neutro e variante ghost. Defina `context` ou `variant` na própria ação para substituir o padrão.
123
+
124
+ ### Montar o host
125
+
126
+ Monte um `DialogHost` no shell do aplicativo, ao lado do `Toaster`:
127
+
128
+ ```tsx
129
+ <>
130
+ {routes}
131
+ <Toaster />
132
+ <DialogHost />
133
+ </>
134
+ ```
135
+
136
+ Sem o host, a chamada lança um erro imediatamente. Solicitações concorrentes entram em uma fila e
137
+ aparecem uma por vez, preservando a resposta de cada `await`.
138
+
139
+ ### Adicionar conteúdo próprio
140
+
141
+ Use `body` quando a descrição não for suficiente, como em uma lista de recursos afetados:
142
+
143
+ ```tsx
144
+ const accepted = await dialog.confirm({
145
+ title: 'Encerrar três sessões?',
146
+ description: 'Os agentes vinculados serão interrompidos.',
147
+ body: <AffectedSessions />,
148
+ action: 'Encerrar sessões',
149
+ })
53
150
  ```
54
151
 
55
- ## Props
152
+ Use `ActionTrigger` quando a confirmação já pertencer ao contrato de uma action. Use
153
+ `ActionFormDialog` para formulários com validação ou vários campos. `dialog.prompt` atende somente
154
+ uma string livre. Componha `Dialog` quando uma ação precisar manter a superfície
155
+ aberta para exibir carregamento, validação ou falha antes do fechamento.
156
+
157
+ ## Compor uma resposta personalizada
158
+
159
+ `Dialog mode="alert"` deriva `role="alertdialog"`, mantém o foco dentro da superfície e não fecha
160
+ com o clique externo. Prefira a API imperativa quando a resposta couber em `alert`, `confirm`,
161
+ `prompt` ou `choose`. Componha o componente quando a resposta depender de estado próprio ou de um
162
+ layout específico.
163
+
164
+ ```tsx preview
165
+ function ResponseDialogDemo() {
166
+ const [result, setResult] = useState('Nenhuma resposta.')
167
+
168
+ return (
169
+ <div className="space-y-3">
170
+ <Dialog mode="alert" onResult={setResult}>
171
+ <DialogTrigger asChild>
172
+ <Button variant="outline">Fechar editor</Button>
173
+ </DialogTrigger>
174
+ <DialogContent>
175
+ <DialogHeader>
176
+ <DialogTitle>Fechar editor?</DialogTitle>
177
+ <DialogDescription>
178
+ O contrato possui alterações que ainda não foram salvas.
179
+ </DialogDescription>
180
+ </DialogHeader>
181
+ <DialogFooter className="grid-cols-3">
182
+ <DialogClose result="continue" initialFocus asChild>
183
+ <Button variant="ghost">Continuar editando</Button>
184
+ </DialogClose>
185
+ <DialogClose result="discard" asChild>
186
+ <Button variant="outline">Descartar</Button>
187
+ </DialogClose>
188
+ <DialogClose result="save" asChild>
189
+ <Button>Salvar e fechar</Button>
190
+ </DialogClose>
191
+ </DialogFooter>
192
+ </DialogContent>
193
+ </Dialog>
194
+ <p className="text-sm text-muted-foreground">{result}</p>
195
+ </div>
196
+ )
197
+ }
198
+
199
+ render(<ResponseDialogDemo />)
200
+ ```
201
+
202
+ `DialogClose` fecha a superfície sem escolher a aparência. Use `asChild` e componha um `Button`;
203
+ contexto, variante e tamanho pertencem ao botão. `result` identifica a resposta entregue a
204
+ `Dialog onResult`. Marque com `initialFocus` a saída segura — normalmente `Cancelar`; em um aviso
205
+ com uma única ação, marque essa ação. Para uma confirmação destrutiva, use `context="danger"` no
206
+ botão. O texto informa o efeito real, como `Excluir`, `Revogar acesso` ou `Encerrar sessões`.
207
+
208
+ ## Acessibilidade
209
+
210
+ - `DialogTitle` e `DialogDescription` nomeiam e descrevem a superfície para tecnologias assistivas.
211
+ - O modo padrão permite fechamento externo; `mode="alert"` exige uma resposta explícita.
212
+ - O comportamento selecionado por `mode` determina o papel de acessibilidade; `role` não configura
213
+ o comportamento.
214
+ - O foco permanece dentro do diálogo aberto e retorna ao gatilho após o fechamento.
215
+ - `DialogHost` apresenta uma solicitação por vez para que cada resposta tenha um alvo inequívoco.
216
+
217
+ ## Propriedades de Dialog
218
+
219
+ | Propriedade | Tipo | Padrão | Descrição |
220
+ |---|---|---|---|
221
+ | `open` | `boolean` | | Controla a abertura. |
222
+ | `defaultOpen` | `boolean` | `false` | Define a abertura inicial no modo não controlado. |
223
+ | `onOpenChange` | `(open: boolean) => void` | | Recebe mudanças de abertura. |
224
+ | `mode` | `'default' \| 'alert'` | `'default'` | Exige resposta explícita em `alert`; o componente deriva o papel de acessibilidade. |
225
+ | `onResult` | `(result: string) => void` | | Recebe o resultado do `DialogClose` acionado. |
226
+ | `modal` | `boolean` | `true` | No modo padrão, mantém foco e interação restritos à superfície aberta. |
227
+
228
+ ## Propriedades de DialogTrigger
229
+
230
+ | Propriedade | Tipo | Padrão | Descrição |
231
+ |---|---|---|---|
232
+ | `asChild` | `boolean` | `false` | Usa o filho como gatilho sem criar outro elemento. |
233
+ | `children` | `ReactNode` | | Controle que abre o diálogo. |
234
+
235
+ ## Propriedades de DialogContent
236
+
237
+ | Propriedade | Tipo | Padrão | Descrição |
238
+ |---|---|---|---|
239
+ | `showCloseButton` | `boolean` | `true` | No modo padrão, exibe o botão de fechamento no canto; o modo de alerta exige uma resposta identificável. |
240
+ | `className` | `string` | | Ajusta a superfície. |
241
+ | `children` | `ReactNode` | | Cabeçalho, corpo, rodapé ou conteúdo próprio. |
242
+
243
+ ## Propriedades de DialogHeader
244
+
245
+ | Propriedade | Tipo | Padrão | Descrição |
246
+ |---|---|---|---|
247
+ | `className` | `string` | | Ajusta a faixa superior. |
248
+ | `children` | `ReactNode` | | Título, descrição e mídia opcional. |
249
+
250
+ ## Propriedades de DialogBody
251
+
252
+ | Propriedade | Tipo | Padrão | Descrição |
253
+ |---|---|---|---|
254
+ | `className` | `string` | | Ajusta o corpo rolável do modo padrão ou o conteúdo próprio do alerta. |
255
+ | `children` | `ReactNode` | | Conteúdo principal da tarefa. |
256
+
257
+ ## Propriedades de DialogFooter
258
+
259
+ | Propriedade | Tipo | Padrão | Descrição |
260
+ |---|---|---|---|
261
+ | `className` | `string` | | Ajusta a faixa do modo padrão ou a grade de respostas do alerta. |
262
+ | `children` | `ReactNode` | | Ações secundárias e principal. |
263
+
264
+ ## Propriedades de DialogTitle
265
+
266
+ | Propriedade | Tipo | Padrão | Descrição |
267
+ |---|---|---|---|
268
+ | `className` | `string` | | Ajusta o título. |
269
+ | `children` | `ReactNode` | | Nome da tarefa ou do conteúdo modal. |
270
+
271
+ ## Propriedades de DialogDescription
272
+
273
+ | Propriedade | Tipo | Padrão | Descrição |
274
+ |---|---|---|---|
275
+ | `className` | `string` | | Ajusta a descrição. |
276
+ | `children` | `ReactNode` | | Contexto, efeito ou consequência relevante. |
277
+
278
+ ## Propriedades de DialogMedia
279
+
280
+ | Propriedade | Tipo | Padrão | Descrição |
281
+ |---|---|---|---|
282
+ | `className` | `string` | | Ajusta a moldura quadrada da mídia. |
283
+ | `children` | `ReactNode` | | Ícone que reforça o contexto da resposta. |
284
+
285
+ ## Propriedades de DialogClose
56
286
 
57
- | Prop | Tipo | Default | Descrição |
287
+ | Propriedade | Tipo | Padrão | Descrição |
58
288
  |---|---|---|---|
59
- | `Dialog.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) pra abrir por código (ex.: depois de uma action). |
60
- | `DialogContent.showCloseButton` | `boolean` | `true` | Mostra o X de fechar no canto. Desligue pra modal que exige ação explícita. |
289
+ | `initialFocus` | `boolean` | `false` | No modo de alerta, marca o controle como destino inicial seguro do foco. |
290
+ | `result` | `string` | | Entrega esta resposta a `Dialog onResult` antes de fechar. |
291
+ | `asChild` | `boolean` | `false` | Aplica o fechamento ao filho; use com `Button`. |
292
+ | `children` | `ReactNode` | | Controle que fecha o diálogo. |
293
+
294
+ ## Propriedades de DialogOverlay
295
+
296
+ | Propriedade | Tipo | Padrão | Descrição |
297
+ |---|---|---|---|
298
+ | `className` | `string` | | Ajusta a camada que cobre a página. |
299
+
300
+ ## Propriedades de DialogPortal
301
+
302
+ `DialogContent` já monta portal e overlay. Use `DialogPortal` diretamente somente em uma composição
303
+ avançada; ele recebe `children` e `container` conforme o portal do Radix.
304
+
305
+ ## Propriedades de DialogHost
306
+
307
+ `DialogHost` não recebe propriedades. Monte uma instância no shell para atender toda a API
308
+ imperativa. `ConfirmHost` permanece como alias de migração.
309
+
310
+ ## Opções de dialog.alert
311
+
312
+ | Opção | Tipo | Padrão | Descrição |
313
+ |---|---|---|---|
314
+ | `title` | `ReactNode` | | Nomeia o aviso que exige reconhecimento. |
315
+ | `description` | `ReactNode` | | Explica efeito, alcance ou próximo passo. |
316
+ | `body` | `ReactNode` | | Acrescenta conteúdo próprio abaixo da descrição. |
317
+ | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
318
+ | `action` | `string` | `'OK'` | Define o rótulo de reconhecimento. |
319
+
320
+ ## Opções de dialog.confirm
321
+
322
+ | Opção | Tipo | Padrão | Descrição |
323
+ |---|---|---|---|
324
+ | `title` | `ReactNode` | | Nomeia a decisão. |
325
+ | `description` | `ReactNode` | | Explica a consequência necessária para decidir. |
326
+ | `body` | `ReactNode` | | Acrescenta conteúdo próprio abaixo da descrição. |
327
+ | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
328
+ | `action` | `string` | `'Confirmar'` | Define o rótulo do resultado afirmativo. |
329
+ | `cancel` | `string` | `'Cancelar'` | Define o rótulo da resposta negativa. |
330
+ | `context` | `'primary' \| 'danger'` | `'primary'` | Define o contexto da ação afirmativa. |
331
+
332
+ ## Opções de dialog.prompt
333
+
334
+ | Opção | Tipo | Padrão | Descrição |
335
+ |---|---|---|---|
336
+ | `title` | `ReactNode` | | Nomeia a informação solicitada. |
337
+ | `description` | `ReactNode` | | Explica como a informação será usada. |
338
+ | `body` | `ReactNode` | | Acrescenta conteúdo próprio abaixo da descrição. |
339
+ | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
340
+ | `action` | `string` | `'Confirmar'` | Define o rótulo do resultado afirmativo. |
341
+ | `cancel` | `string` | `'Cancelar'` | Define o rótulo da resposta negativa. |
342
+ | `context` | `'primary' \| 'danger'` | `'primary'` | Define o contexto da ação afirmativa. |
343
+ | `placeholder` | `string` | | Orienta o formato esperado quando um exemplo ajuda. |
344
+ | `defaultValue` | `string` | | Define e seleciona o valor inicial do campo. |
345
+
346
+ ## Opções de dialog.choose
347
+
348
+ | Opção | Tipo | Padrão | Descrição |
349
+ |---|---|---|---|
350
+ | `title` | `ReactNode` | | Nomeia a escolha. |
351
+ | `description` | `ReactNode` | | Explica o contexto necessário para escolher. |
352
+ | `body` | `ReactNode` | | Acrescenta conteúdo próprio antes das ações. |
353
+ | `media` | `ReactNode` | | Exibe um ícone na moldura de `DialogMedia`. |
354
+ | `actions` | `readonly DialogChoice[]` | | Declara os resultados disponíveis. |
355
+
356
+ ## Propriedades de DialogChoice
357
+
358
+ | Propriedade | Tipo | Padrão | Descrição |
359
+ |---|---|---|---|
360
+ | `result` | `string` | | Valor único devolvido quando a ação for escolhida. |
361
+ | `label` | `ReactNode` | | Rótulo que descreve o resultado da ação. |
362
+ | `initialFocus` | `boolean` | `false` | Marca a única saída segura que recebe o foco inicial. |
363
+ | `context` | `ButtonContext` | Última ação: `primary`; demais: `neutral` | Define o significado semântico. |
364
+ | `variant` | `ButtonVariant` | Última ação: `solid`; demais: `ghost` | Define o tratamento visual. |
365
+ | `disabled` | `boolean` | `false` | Impede a escolha desta ação. |
366
+
367
+ `confirm()` e `ConfirmHost` continuam disponíveis apenas como aliases de migração para
368
+ `dialog.confirm()` e `DialogHost`.
@@ -1,7 +1,6 @@
1
- Um valor de dicionário (`t.dict`) apresentado do jeito que o **dicionário** declarou. A tela não
2
- decide se o valor vira badge, cor ou ícone: ela passa o dicionário e o código, e o renderer
3
- aplica os defaults da apresentação declarada. O rótulo está sempre presente como texto — cor e
4
- ícone nunca comunicam sozinhos.
1
+ Use `DictionaryValue` para apresentar um valor de `t.dict` de acordo com o papel declarado pelo
2
+ próprio dicionário. A tela fornece o dicionário e o código; o componente escolhe texto, badge e ícone
3
+ sem perder o rótulo visível.
5
4
 
6
5
  ```tsx preview
7
6
  <DictionaryValue
@@ -73,9 +72,10 @@ export const customerStageDict = t.dict(
73
72
  />
74
73
  ```
75
74
 
76
- ## Override explícito
75
+ ## Substituição explícita
77
76
 
78
- Os defaults vêm do dicionário; a tela sobrepõe quando tem um motivo, e sobrepõe às claras.
77
+ Os padrões vêm do dicionário. Use as propriedades de apresentação somente quando esta ocorrência
78
+ precisar de um tratamento diferente e a decisão estiver explícita na tela.
79
79
 
80
80
  ```tsx preview
81
81
  <DictionaryValue
@@ -106,9 +106,9 @@ dicionário registrado no provider: `{ key: 'source', label: 'Fonte', dictionary
106
106
  Dimensões independentes (tipo e estágio, por exemplo) ficam em colunas distintas; não empilhar
107
107
  uma sob a outra como texto secundário.
108
108
 
109
- ## Props
109
+ ## Propriedades de DictionaryValue
110
110
 
111
- | Prop | Tipo | Default | Descrição |
111
+ | Propriedade | Tipo | Padrão | Descrição |
112
112
  |---|---|---|---|
113
113
  | `dict` | `DictType \| LogicalTypeMeta \| DictionaryDescriptor` | — | O dicionário (`t.dict`), a meta lida do schema ou um descritor normalizado. |
114
114
  | `value` | `string \| null \| undefined` | — | O código. Vazio renderiza `fallback`. |
@@ -32,11 +32,11 @@ esquerda.
32
32
 
33
33
  A divisória entre grupos pertence ao componente: o consumidor declara `DockGroup` e a linha
34
34
  aparece entre grupos consecutivos, nunca antes do primeiro. Agrupe por intenção — modo, criação,
35
- IA, publicação — em vez de espalhar ícones numa fileira única.
35
+ IA, publicação — em vez de espalhar ícones em uma fileira única.
36
36
 
37
37
  ## Estado da superfície
38
38
 
39
- Estado não é ferramenta, e por isso não mora na barra: `SurfaceStatus` flutua num canto da mesma
39
+ Estado não é ferramenta, e por isso não mora na barra: `SurfaceStatus` flutua em um canto da mesma
40
40
  superfície — `top-right` por padrão — e recebe salvamento, versão publicada, execução percorrida.
41
41
  A região é `role="status"` com `aria-live="polite"`, então a mudança é anunciada sem roubar o foco.
42
42
  Separar os dois preserva a barra como toolbar navegável e dá ao estado um lugar estável, que não
@@ -63,7 +63,7 @@ A barra é uma `toolbar`: as setas andam entre as ações e Home/End vão às po
63
63
  por tooltip, então `<TooltipProvider>` precisa existir na raiz do app — o esqueleto do `opus create`
64
64
  já monta.
65
65
 
66
- | Prop | Tipo | Default | O que faz |
66
+ | Propriedade | Tipo | Padrão | Descrição |
67
67
  | --- | --- | --- | --- |
68
68
  | `position` | `'bottom' \| 'bottom-left' \| 'bottom-right'` | `'bottom'` | Aresta do contêiner onde a barra se ancora. |
69
69
  | `label` | `string` | | Nome acessível da barra. |
@@ -1,8 +1,8 @@
1
- ## Painel de detalhe (à direita)
1
+ ## Painel lateral de detalhe
2
2
 
3
- O painel que desliza de uma borda da tela modal, com overlay, trap de foco e ESC (o motor é o Dialog do Radix). A régua contra o irmão: conteúdo curto e CENTRADO → `Dialog`; painel na borda que preserva o contexto atrás → `Drawer`.
4
-
5
- side='right' desliza da borda direita. ESC, clique no overlay e o X fecham sem estado manual. É o lugar de detalhe/edição lateral sem sair do contexto.
3
+ Use `Drawer` para abrir detalhes ou edição junto a uma borda, preservando a referência visual da
4
+ tela ao fundo. Para conteúdo curto e centralizado, prefira `Dialog`. Com `side="right"`, o painel
5
+ entra pela direita e pode ser fechado por Esc, pelo overlay ou pelo botão de fechamento.
6
6
 
7
7
  ```tsx preview
8
8
  <Drawer>
@@ -21,9 +21,12 @@ side='right' desliza da borda direita. ESC, clique no overlay e o X fecham — s
21
21
  </Drawer>
22
22
  ```
23
23
 
24
- ## Edição com rodapé (à esquerda)
24
+ ## Edição com ações persistentes
25
25
 
26
- side aceita top|right|bottom|left. DrawerFooter ancora as ações no rodapé; DrawerClose fecha sem estado manual.
26
+ `side` aceita `top`, `right`, `bottom` e `left`. `DrawerFooter` mantém as ações no rodapé;
27
+ `DrawerClose` fecha o painel sem exigir controle manual de estado. Como no `Dialog`, o cabeçalho
28
+ recebe um divisor inferior e o rodapé usa divisor superior sobre fundo sutil. O corpo preserva o
29
+ mesmo alinhamento horizontal entre título, conteúdo e ações.
27
30
 
28
31
  ```tsx preview
29
32
  <Drawer>
@@ -40,19 +43,29 @@ side aceita top|right|bottom|left. DrawerFooter ancora as ações no rodapé; Dr
40
43
  </DrawerBody>
41
44
  <DrawerFooter>
42
45
  <DrawerClose asChild>
43
- <Button variant="ghost" size="sm">Cancelar</Button>
46
+ <Button variant="ghost">Cancelar</Button>
44
47
  </DrawerClose>
45
- <Button size="sm">Salvar alterações</Button>
48
+ <Button>Salvar alterações</Button>
46
49
  </DrawerFooter>
47
50
  </DrawerContent>
48
51
  </Drawer>
49
52
  ```
50
53
 
51
- ## Props
54
+ ## Propriedades de Drawer
55
+
56
+ | Propriedade | Tipo | Padrão | Descrição |
57
+ |---|---|---|---|
58
+ | `open` | `boolean` | | Estado no modo controlado. |
59
+ | `onOpenChange` | `(open: boolean) => void` | | Atualiza o estado para permitir abertura ou fechamento por código. |
60
+
61
+ ## Propriedades de DrawerContent
52
62
 
53
- | Prop | Tipo | Default | Descrição |
63
+ | Propriedade | Tipo | Padrão | Descrição |
54
64
  |---|---|---|---|
55
- | `DrawerContent.side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Borda de onde o painel desliza. |
56
- | `DrawerContent.showCloseButton` | `boolean` | `true` | Mostra o X de fechar no canto. Desligue pra painel que exige ação explícita. |
57
- | `DrawerBody` | `HTMLAttributes<HTMLDivElement>` | Região flexível e rolável | Corpo principal entre header e footer. |
58
- | `Drawer.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) — pra abrir/fechar por código (ex.: detalhe de um item selecionado). |
65
+ | `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Borda de onde o painel aparece. |
66
+ | `showCloseButton` | `boolean` | `true` | Exibe o botão de fechamento. Desative quando o painel exigir uma ação explícita. |
67
+
68
+ ## Propriedades de DrawerBody
69
+
70
+ `DrawerBody` aceita os atributos de `HTMLDivElement` e ocupa a região flexível e rolável entre
71
+ `DrawerHeader` e `DrawerFooter`.
@@ -40,9 +40,9 @@ columns: [
40
40
  ]
41
41
  ```
42
42
 
43
- ## Props
43
+ ## Propriedades de EmptyValue
44
44
 
45
- | Prop | Tipo | Default | Descrição |
45
+ | Propriedade | Tipo | Padrão | Descrição |
46
46
  |---|---|---|---|
47
47
  | `label` | `string` | `'Não informado'` | O que a ausência significa neste domínio. |
48
48
  | `compact` | `boolean` | `false` | Célula compacta: travessão visível, rótulo só para leitura assistiva. |