@softize/opus 12.11.0 → 13.1.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 (213) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/PROMOTED.md +46 -0
  3. package/README.md +28 -19
  4. package/bin/cli.mjs +87 -216
  5. package/bin/lib/check.mjs +2 -7
  6. package/bin/lib/cli-shared.mjs +131 -0
  7. package/bin/lib/copy.mjs +1 -5
  8. package/bin/lib/db.mjs +16 -74
  9. package/bin/lib/gen-openapi.mjs +3 -3
  10. package/bin/lib/gen-runner.mjs +1 -1
  11. package/bin/lib/gen.mjs +14 -69
  12. package/bin/lib/mcp.mjs +3 -1
  13. package/bin/lib/seed.mjs +5 -62
  14. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
  15. package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
  16. package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
  17. package/docs/ownership-vs-shadcn-lock.md +2 -3
  18. package/docs/protocol.md +7 -7
  19. package/docs/radius-scale.md +1 -1
  20. package/docs/releasing.md +8 -2
  21. package/package.json +7 -3
  22. package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
  23. package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
  24. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
  25. package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
  26. package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
  27. package/registry/templates/app/package.json +1 -1
  28. package/registry/templates/app/src/main.tsx +4 -4
  29. package/src/audit/drivers/console.ts +1 -0
  30. package/src/auth/drivers/better-auth.ts +1 -0
  31. package/src/auth/drivers/jwt.ts +1 -0
  32. package/src/cache/drivers/memory.ts +1 -0
  33. package/src/client/drivers/fetch.ts +2 -1
  34. package/src/core/actions.ts +6 -1
  35. package/src/core/audit.ts +9 -3
  36. package/src/core/contracts.ts +7 -0
  37. package/src/core/domain.ts +1 -1
  38. package/src/core/errors.ts +18 -15
  39. package/src/core/index.ts +4 -2
  40. package/src/core/package-version.ts +26 -0
  41. package/src/core/reactions.ts +1 -1
  42. package/src/core/runtime.ts +33 -23
  43. package/src/core/schedules.ts +1 -1
  44. package/src/core/types.ts +2 -2
  45. package/src/dsl/eval.ts +2 -2
  46. package/src/dsl/kysely.ts +2 -2
  47. package/src/dsl/loads.ts +1 -1
  48. package/src/dsl/parser.ts +5 -5
  49. package/src/events/drivers/mitt.ts +1 -0
  50. package/src/mcp/index.ts +2 -1
  51. package/src/observability/drivers/opentelemetry.ts +1 -0
  52. package/src/queue/drivers/bullmq.ts +3 -3
  53. package/src/scheduler/drivers/node-cron.ts +3 -2
  54. package/src/scheduler/every.ts +7 -7
  55. package/src/schema/openapi.ts +3 -3
  56. package/src/seed/index.ts +29 -0
  57. package/src/server/drivers/fastify.ts +5 -2
  58. package/src/server/drivers/node.ts +9 -6
  59. package/src/server/index.ts +3 -1
  60. package/src/storage/drivers/fs.ts +1 -0
  61. package/src/testing/index.ts +3 -3
  62. package/src/ui/components/patterns/confirm.tsx +142 -42
  63. package/src/ui/components/patterns/content-header.tsx +7 -1
  64. package/src/ui/components/patterns/data-state.tsx +1 -1
  65. package/src/ui/components/patterns/dock.tsx +20 -3
  66. package/src/ui/components/patterns/form.tsx +12 -8
  67. package/src/ui/components/patterns/list.tsx +36 -41
  68. package/src/ui/components/patterns/page-state.tsx +2 -2
  69. package/src/ui/components/patterns/page.tsx +19 -1
  70. package/src/ui/components/patterns/shell-nav.tsx +10 -3
  71. package/src/ui/components/patterns/sidebar.tsx +43 -32
  72. package/src/ui/components/patterns/trigger.tsx +39 -38
  73. package/src/ui/components/patterns/view.tsx +26 -17
  74. package/src/ui/components/primitives/alert.tsx +14 -8
  75. package/src/ui/components/primitives/ask.tsx +3 -3
  76. package/src/ui/components/primitives/badge.tsx +11 -6
  77. package/src/ui/components/primitives/breadcrumb.tsx +2 -2
  78. package/src/ui/components/primitives/button.tsx +16 -3
  79. package/src/ui/components/primitives/calendar.tsx +28 -2
  80. package/src/ui/components/primitives/carousel.tsx +3 -3
  81. package/src/ui/components/primitives/chat.tsx +1 -1
  82. package/src/ui/components/primitives/checkbox.tsx +1 -1
  83. package/src/ui/components/primitives/command.tsx +2 -2
  84. package/src/ui/components/primitives/control.ts +12 -0
  85. package/src/ui/components/primitives/copyable.tsx +1 -1
  86. package/src/ui/components/primitives/dialog.tsx +202 -40
  87. package/src/ui/components/primitives/dot.tsx +5 -0
  88. package/src/ui/components/primitives/drawer.tsx +18 -8
  89. package/src/ui/components/primitives/empty.tsx +3 -3
  90. package/src/ui/components/primitives/field.tsx +3 -3
  91. package/src/ui/components/primitives/icon-picker.tsx +3 -1
  92. package/src/ui/components/primitives/input-group.tsx +1 -1
  93. package/src/ui/components/primitives/input-otp.tsx +1 -1
  94. package/src/ui/components/primitives/input.tsx +2 -2
  95. package/src/ui/components/primitives/item.tsx +3 -3
  96. package/src/ui/components/primitives/progress.tsx +32 -3
  97. package/src/ui/components/primitives/radio-group.tsx +1 -1
  98. package/src/ui/components/primitives/resizable.tsx +3 -1
  99. package/src/ui/components/primitives/select.tsx +5 -5
  100. package/src/ui/components/primitives/slider.tsx +5 -1
  101. package/src/ui/components/primitives/sonner.tsx +190 -8
  102. package/src/ui/components/primitives/switch.tsx +1 -0
  103. package/src/ui/components/primitives/tabs.tsx +1 -0
  104. package/src/ui/components/primitives/textarea.tsx +1 -1
  105. package/src/ui/components/primitives/toggle.tsx +1 -1
  106. package/src/ui/components/primitives/tooltip.tsx +1 -0
  107. package/src/ui/docs/DocBrowser.tsx +102 -23
  108. package/src/ui/docs/changelog.tsx +1 -1
  109. package/src/ui/docs/content/accordion.md +22 -16
  110. package/src/ui/docs/content/action-form-card.md +8 -8
  111. package/src/ui/docs/content/action-form-dialog.md +9 -9
  112. package/src/ui/docs/content/action-form.md +37 -36
  113. package/src/ui/docs/content/action-list-dialog.md +11 -6
  114. package/src/ui/docs/content/action-list.md +73 -39
  115. package/src/ui/docs/content/action-trigger.md +29 -15
  116. package/src/ui/docs/content/action-view.md +17 -10
  117. package/src/ui/docs/content/actions.md +9 -9
  118. package/src/ui/docs/content/ai.md +3 -3
  119. package/src/ui/docs/content/alert.md +14 -12
  120. package/src/ui/docs/content/ask.md +11 -0
  121. package/src/ui/docs/content/aspect-ratio.md +4 -4
  122. package/src/ui/docs/content/audit.md +2 -2
  123. package/src/ui/docs/content/auth.md +3 -3
  124. package/src/ui/docs/content/avatar.md +34 -14
  125. package/src/ui/docs/content/badge.md +3 -3
  126. package/src/ui/docs/content/breadcrumb.md +13 -8
  127. package/src/ui/docs/content/button.md +81 -6
  128. package/src/ui/docs/content/calendar.md +18 -5
  129. package/src/ui/docs/content/card.md +27 -1
  130. package/src/ui/docs/content/carousel.md +16 -11
  131. package/src/ui/docs/content/chat.md +23 -3
  132. package/src/ui/docs/content/checkbox.md +7 -7
  133. package/src/ui/docs/content/cli.md +74 -22
  134. package/src/ui/docs/content/collapsible.md +8 -8
  135. package/src/ui/docs/content/command.md +16 -8
  136. package/src/ui/docs/content/composer.md +17 -2
  137. package/src/ui/docs/content/content.md +17 -2
  138. package/src/ui/docs/content/copyable.md +12 -3
  139. package/src/ui/docs/content/customization.md +5 -5
  140. package/src/ui/docs/content/cycle.md +3 -3
  141. package/src/ui/docs/content/data-state.md +11 -12
  142. package/src/ui/docs/content/data.md +26 -33
  143. package/src/ui/docs/content/detail.md +22 -4
  144. package/src/ui/docs/content/dialog.md +339 -31
  145. package/src/ui/docs/content/dictionary-value.md +17 -10
  146. package/src/ui/docs/content/dock.md +11 -3
  147. package/src/ui/docs/content/dot.md +8 -0
  148. package/src/ui/docs/content/drawer.md +27 -14
  149. package/src/ui/docs/content/empty-value.md +2 -2
  150. package/src/ui/docs/content/empty.md +19 -12
  151. package/src/ui/docs/content/events.md +4 -4
  152. package/src/ui/docs/content/field.md +34 -12
  153. package/src/ui/docs/content/getting-started.md +3 -3
  154. package/src/ui/docs/content/icon-picker.md +19 -4
  155. package/src/ui/docs/content/input-otp.md +20 -12
  156. package/src/ui/docs/content/input.md +121 -9
  157. package/src/ui/docs/content/item.md +27 -13
  158. package/src/ui/docs/content/kbd.md +19 -11
  159. package/src/ui/docs/content/label.md +12 -3
  160. package/src/ui/docs/content/log.md +4 -4
  161. package/src/ui/docs/content/markdown.md +7 -6
  162. package/src/ui/docs/content/mcp.md +13 -15
  163. package/src/ui/docs/content/menu.md +40 -16
  164. package/src/ui/docs/content/metric-card.md +13 -0
  165. package/src/ui/docs/content/observability.md +2 -2
  166. package/src/ui/docs/content/page.md +59 -6
  167. package/src/ui/docs/content/pagination.md +22 -17
  168. package/src/ui/docs/content/popover.md +22 -8
  169. package/src/ui/docs/content/progress.md +15 -16
  170. package/src/ui/docs/content/queue.md +5 -5
  171. package/src/ui/docs/content/radio-group.md +20 -12
  172. package/src/ui/docs/content/router.md +11 -6
  173. package/src/ui/docs/content/scheduler.md +4 -5
  174. package/src/ui/docs/content/scroll-area.md +12 -7
  175. package/src/ui/docs/content/select.md +47 -34
  176. package/src/ui/docs/content/semantic-context.md +2 -2
  177. package/src/ui/docs/content/separator.md +5 -5
  178. package/src/ui/docs/content/sidebar.md +329 -54
  179. package/src/ui/docs/content/skeleton.md +9 -2
  180. package/src/ui/docs/content/slider.md +8 -7
  181. package/src/ui/docs/content/spinner.md +8 -8
  182. package/src/ui/docs/content/split.md +29 -5
  183. package/src/ui/docs/content/storage.md +6 -8
  184. package/src/ui/docs/content/switch.md +8 -7
  185. package/src/ui/docs/content/table.md +13 -3
  186. package/src/ui/docs/content/tabs.md +28 -14
  187. package/src/ui/docs/content/testing.md +9 -11
  188. package/src/ui/docs/content/textarea.md +12 -4
  189. package/src/ui/docs/content/toast.md +47 -13
  190. package/src/ui/docs/content/toggle.md +75 -7
  191. package/src/ui/docs/content/tokens.md +7 -7
  192. package/src/ui/docs/content/tooltip.md +19 -11
  193. package/src/ui/docs/content/truncate.md +15 -8
  194. package/src/ui/docs/content/ui.md +24 -9
  195. package/src/ui/docs/content/upgrading.md +7 -8
  196. package/src/ui/docs/doc-client.tsx +5 -5
  197. package/src/ui/docs/doc.tsx +26 -14
  198. package/src/ui/docs/registry.tsx +25 -42
  199. package/src/ui/docs/standalone.tsx +2 -2
  200. package/src/ui/drivers/react.tsx +17 -12
  201. package/src/ui/lib/action-errors.ts +45 -0
  202. package/src/ui/lib/zod-pt-br.ts +31 -4
  203. package/src/ui/meta.ts +65 -95
  204. package/src/ui/react.tsx +17 -16
  205. package/src/ui/theme.css +60 -8
  206. package/src/vite/design.ts +6 -18
  207. package/src/ui/components/primitives/alert-dialog.tsx +0 -192
  208. package/src/ui/docs/content/alert-dialog.md +0 -73
  209. package/src/ui/docs/content/button-group.md +0 -71
  210. package/src/ui/docs/content/confirm.md +0 -120
  211. package/src/ui/docs/content/input-group.md +0 -79
  212. package/src/ui/docs/content/page-state.md +0 -45
  213. package/src/ui/docs/content/toggle-group.md +0 -81
@@ -1,7 +1,8 @@
1
- ## Padrão (single)
1
+ ## Um painel por vez
2
2
 
3
- `type=single` abre um painel por vez. `collapsible` deixa fechar o que está aberto — sem ele,
4
- sempre fica um item expandido. `defaultValue` deixa o estado com o componente.
3
+ Use `type="single"` quando somente um painel deve permanecer aberto. Com `collapsible`, a pessoa
4
+ também pode fechar o painel atual; sem essa propriedade, um item permanece expandido.
5
+ `defaultValue` define o item inicialmente aberto no modo não controlado.
5
6
 
6
7
  ```tsx preview
7
8
  <Accordion type="single" collapsible defaultValue="overview" className="w-full">
@@ -28,8 +29,8 @@ sempre fica um item expandido. `defaultValue` deixa o estado com o componente.
28
29
 
29
30
  ## Múltiplos abertos
30
31
 
31
- `type=multiple` permite vários painéis expandidos ao mesmo tempo `defaultValue` vira um
32
- array com os itens abertos de saída.
32
+ Use `type="multiple"` quando os painéis puderem permanecer abertos ao mesmo tempo. Nesse modo,
33
+ `defaultValue` recebe um array com os itens inicialmente expandidos.
33
34
 
34
35
  ```tsx preview
35
36
  <Accordion type="multiple" defaultValue={['skills', 'prompt']} className="w-full">
@@ -48,10 +49,10 @@ array com os itens abertos de saída.
48
49
  </Accordion>
49
50
  ```
50
51
 
51
- ## Controlado (com estado)
52
+ ## Estado controlado
52
53
 
53
- No modo `single`, `value`/`onValueChange` tiram o estado do componente dá pra abrir um item
54
- de fora. Exemplo **com estado** (o `render()` deixa o hook rodar):
54
+ Use `value` e `onValueChange` quando outro elemento ou estado do aplicativo também precisar
55
+ controlar o painel aberto.
55
56
 
56
57
  ```tsx preview
57
58
  const [open, setOpen] = React.useState('developer')
@@ -74,13 +75,18 @@ render(
74
75
  )
75
76
  ```
76
77
 
77
- ## Props
78
+ ## Propriedades de Accordion
78
79
 
79
- | Prop | Tipo | Default | Descrição |
80
+ | Propriedade | Tipo | Padrão | Descrição |
80
81
  |---|---|---|---|
81
- | `type` (Accordion) | `'single' \| 'multiple'` | | single abre um painel por vez; multiple permite vários. |
82
- | `collapsible` (Accordion) | `boolean` | `false` | no single permite fechar o item aberto. |
83
- | `defaultValue` (Accordion) | `string \| string[]` | | Item(ns) aberto(s) no modo não controlado. |
84
- | `value` (Accordion) | `string \| string[]` | | Item(ns) aberto(s) no modo controlado pareie com onValueChange. |
85
- | `onValueChange` (Accordion) | `(value) => void` | | Chamado quando o usuário abre ou fecha um item. |
86
- | `value` (AccordionItem) | `string` | | Identificador do item — é o que defaultValue/value referenciam. |
82
+ | `type` | `'single' \| 'multiple'` | | `single` abre um painel por vez; `multiple` permite manter vários abertos. |
83
+ | `collapsible` | `boolean` | `false` | No modo `single`, permite fechar o item aberto. |
84
+ | `defaultValue` | `string \| string[]` | | Item ou itens inicialmente abertos no modo não controlado. |
85
+ | `value` | `string \| string[]` | | Item ou itens abertos no modo controlado. Use com `onValueChange`. |
86
+ | `onValueChange` | `(value) => void` | | Chamado quando a pessoa abre ou fecha um item. |
87
+
88
+ ## Propriedades de AccordionItem
89
+
90
+ | Propriedade | Tipo | Padrão | Descrição |
91
+ |---|---|---|---|
92
+ | `value` | `string` | | Identificador usado por `defaultValue` e `value` no `Accordion`. |
@@ -1,7 +1,7 @@
1
- ## Form como card
1
+ ## Formulário em uma seção
2
2
 
3
- O ActionForm dentro de um Card header, conteúdo e rodapé, no respiro do Card (sem divisor nem
4
- faixa de modal). Pra estruturar uma seção da página como painel. O título é opcional.
3
+ Use `ActionFormCard` para apresentar um `ActionForm` como seção delimitada da página. O componente
4
+ aplica a estrutura e o espaçamento de `Card`, sem a faixa de ações de um modal. O título é opcional.
5
5
 
6
6
  ```tsx preview col md
7
7
  <DocBrowserActionProvider>
@@ -14,11 +14,11 @@ faixa de modal). Pra estruturar uma seção da página como painel. O título é
14
14
  </DocBrowserActionProvider>
15
15
  ```
16
16
 
17
- ## Props
17
+ ## Propriedades de ActionFormCard
18
18
 
19
- | Prop | Tipo | Default | Descrição |
19
+ | Propriedade | Tipo | Padrão | Descrição |
20
20
  |---|---|---|---|
21
- | `title` | `string` | | Título do header (com divisor embaixo). Sem ele, o card começa direto no corpo. |
21
+ | `title` | `string` | | Título do cabeçalho. Sem ele, o card começa diretamente pelo corpo. |
22
22
  | `description` | `string` | | Subtítulo opcional, abaixo do título. |
23
- | `action / defaultValues / fieldOptions / submitLabel / onSuccess / onCancel` | `— (iguais ao ActionForm)` | | O resto é o ActionForm o card injeta o corpo (CardBody) e o rodapé (CardFooter) por baixo. |
24
- | `cardClassName` | `string` | | Classes da SUPERFÍCIE do card (ex.: largura). `className` vai pro `<form>`. |
23
+ | `action / defaultValues / fieldOptions / submitLabel / onSuccess / onCancel` | Propriedades de `ActionForm` | | Mantêm o mesmo comportamento do formulário interno. |
24
+ | `cardClassName` | `string` | | Classes aplicadas à superfície do card. `className` continua sendo aplicado ao `<form>`. |
@@ -1,8 +1,8 @@
1
- ## Form em modal
1
+ ## Formulário em um modal
2
2
 
3
- O open é de quem orquestra; o fechamento no sucesso é do pattern. Header e X fixos, os campos
4
- scrollam, o rodapé é a faixa da casa. O preview roda num client de mentira no app, o contrato
5
- vem do spec.
3
+ Use `ActionFormDialog` quando o formulário precisar interromper o fluxo atual sem levar a pessoa
4
+ para outra página. O consumidor controla `open`; depois de uma execução bem-sucedida, o componente
5
+ fecha o modal. O cabeçalho e o rodapé permanecem visíveis enquanto os campos podem rolar.
6
6
 
7
7
  ```tsx preview
8
8
  const [open, setOpen] = useState(false)
@@ -22,11 +22,11 @@ render(
22
22
  )
23
23
  ```
24
24
 
25
- ## Props
25
+ ## Propriedades de ActionFormDialog
26
26
 
27
- | Prop | Tipo | Default | Descrição |
27
+ | Propriedade | Tipo | Padrão | Descrição |
28
28
  |---|---|---|---|
29
- | `open / onOpenChange` | `boolean / (open: boolean) => void` | | Controle do modal — o pattern chama onOpenChange(false) no sucesso e no Cancelar. |
30
- | `title / description` | `string` | | O DialogHeader fixo description é opcional. |
29
+ | `open / onOpenChange` | `boolean / (open: boolean) => void` | | Estado controlado do modal. `onOpenChange(false)` é chamado no sucesso e ao cancelar. |
30
+ | `title / description` | `string` | | Conteúdo do cabeçalho; `description` é opcional. |
31
31
  | `submitLabel` | `string` | `'Salvar'` | Resultado da ação principal. Em criação, informe `Criar {recurso}`; em edição, `Salvar alterações`. |
32
- | `…ActionFormProps` | `action, defaultValues, onSuccess, fieldOptions…` | | Todo o resto desce pro ActionForm interno — mesma API da página dele. |
32
+ | `…ActionFormProps` | `action, defaultValues, onSuccess, fieldOptions…` | | Demais propriedades repassadas ao `ActionForm` interno. |
@@ -1,8 +1,8 @@
1
- ## Form derivado do contrato
1
+ ## Formulário derivado do contrato
2
2
 
3
- Zero JSX de campo: enum vira Select, string longa vira Textarea, obrigatório ganha o asterisco. O
4
- preview roda num client de mentira digite "Softize" no nome pra ver o erro de servidor inline; o
5
- submit habilita com mudança real (dirty).
3
+ Use `ActionForm` para gerar campos, validação e mensagens a partir de uma `FormAction`. Sem
4
+ `children`, o componente escolhe o controle adequado para cada campo e preserva a ordem declarada
5
+ no contrato. No exemplo, altere um valor para habilitar a ação principal.
6
6
 
7
7
  ```tsx preview col md
8
8
  <DocBrowserActionProvider>
@@ -10,13 +10,12 @@ submit só habilita com mudança real (dirty).
10
10
  </DocBrowserActionProvider>
11
11
  ```
12
12
 
13
- ## Composição (diagramação livre)
13
+ ## Composição dos campos
14
14
 
15
- Com children, o AUTO desliga e você diagrama: `<ActionFormField name />` coloca cada campo — com
16
- label, hint, widget, erro e asterisco derivados do contrato — onde quiser. Condicional é JSX;
17
- opções de runtime entram por prop no campo. O comportamento (submit, validação, toast,
18
- invalidação) continua encapsulado e o espaçamento é 100% seu: o campo não impõe margem
19
- (quem diagrama usa grid/gap/space-y como quiser).
15
+ Passe `children` quando a disposição automática não atender ao formulário. Cada
16
+ `ActionFormField` mantém rótulo, ajuda, controle, obrigatoriedade e erro derivados do contrato;
17
+ o consumidor define apenas a grade, as condições e o espaçamento. Execução, validação, toast e
18
+ invalidação continuam sob responsabilidade de `ActionForm`.
20
19
 
21
20
  ```tsx preview col md
22
21
  <DocBrowserActionProvider>
@@ -35,8 +34,8 @@ invalidação) continua encapsulado — e o espaçamento é 100% seu: o campo n
35
34
 
36
35
  ## Valores iniciais e rótulos
37
36
 
38
- defaultValues pré-carrega (modo edição); submitLabel/cancelLabel trocam o rodapé. O onCancel é de
39
- quem orquestra (fechar drawer, voltar…).
37
+ `defaultValues` preenche o formulário para edição. `submitLabel` e `cancelLabel` nomeiam as ações;
38
+ `onCancel` devolve ao consumidor a decisão de fechar um painel ou navegar para outra página.
40
39
 
41
40
  ```tsx preview col md
42
41
  <DocBrowserActionProvider>
@@ -51,47 +50,50 @@ quem orquestra (fechar drawer, voltar…).
51
50
 
52
51
  ## Pré-requisitos
53
52
 
54
- O driver precisa dos dois providers no root exatamente o wiring do admin (o consumidor
55
- canônico).
53
+ Monte os providers de consulta e execução uma vez na raiz do aplicativo:
56
54
 
57
55
  ```tsx
58
56
  /* main.tsx do app. */
59
57
  <QueryClientProvider client={queryClient}>
60
- <TbdlibProvider client={apiClient}>
58
+ <OpusProvider client={apiClient}>
61
59
  <App />
62
- </TbdlibProvider>
60
+ </OpusProvider>
63
61
  </QueryClientProvider>
64
62
  ```
65
63
 
66
- ## Props
64
+ ## useFormAction
67
65
 
68
- | Prop | Tipo | Default | Descrição |
66
+ `ActionForm` é uma composição sobre `useFormAction(action, { defaultValues, onSuccess, onError })`,
67
+ que devolve o `form` do react-hook-form já com o resolver Zod em pt-BR, `submit`, `isLoading`,
68
+ `error`, `isSuccess` e `reset`. Use o hook direto quando o formulário precisar de um layout ou de um
69
+ ciclo de vida que o pattern não cobre; validação e execução continuam iguais.
70
+
71
+ ## Propriedades de ActionForm
72
+
73
+ | Propriedade | Tipo | Padrão | Descrição |
69
74
  |---|---|---|---|
70
75
  | `action` | `FormContract<TInput, TData>` | | O contrato da FormAction — dele saem campos (input Zod + fields), mensagens e invalidação de cache. |
71
76
  | `defaultValues` | `Partial<TInput>` | | Valores iniciais — o modo edição de um update/patch. |
72
77
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (o toast e a invalidação de cache já aconteceram). |
73
78
  | `submitLabel / cancelLabel / onCancel` | `string / string / () => void` | `'Salvar' / 'Cancelar'` | Rodapé do form — o Cancelar só aparece com onCancel. |
74
79
  | `fieldOptions` | `Record<string, SelectOption[]>` | | Opções de runtime por campo (ex.: ids de skills) — sobrepõe as inferidas do z.enum. |
75
- | `className / body / footer` | `string / (node) => node / (node) => node` | | className = classes do `<form>`. body/footer = SLOTS: recebem os campos / os botões e escolhem o invólucro — é como o ActionFormDialog injeta DialogBody/DialogFooter (scroll + faixa). |
76
- | `children` | `ReactNode` | | Modo COMPOSIÇÃO: diagrame com `<ActionFormField name />`. Sem children, o AUTO monta todos os campos na ordem do contrato. |
80
+ | `className / body / footer` | `string / (node) => node / (node) => node` | | className = classes do `<form>`. body/footer = slots: recebem os campos / os botões e escolhem o invólucro — é como o ActionFormDialog injeta DialogBody/DialogFooter (scroll + faixa). |
81
+ | `children` | `ReactNode` | | Modo composição: diagrame com `<ActionFormField name />`. Sem children, o automático monta todos os campos na ordem do contrato. |
77
82
 
78
- ## ActionFormField
83
+ ## Propriedades de ActionFormField
79
84
 
80
- | Prop | Tipo | Descrição |
85
+ | Propriedade | Tipo | Descrição |
81
86
  |---|---|---|
82
87
  | `name` | `string` | O campo do contrato (chave em `fields`/schema). Fora do schema, não renderiza. |
83
88
  | `options` | `SelectOption[]` | Opções por id de runtime — sobrepõe o fieldOptions do form e o z.enum. |
84
- | `className` | `string` | Classes do invólucro (ex.: `col-span-2` numa grid). |
89
+ | `className` | `string` | Classes do invólucro (ex.: `col-span-2` em uma grid). |
85
90
 
86
- ## De onde vêm as opções de um select
91
+ ## Origem das opções de seleção
87
92
 
88
- Precedência, da mais específica pra mais automática: **prop `options` do campo** >
89
- **`fieldOptions` do form** > **`options` do FieldSpec no contrato** (`{ kind: 'dictionary',
90
- ref }` resolve pelos dicts do `<TbdlibProvider dicts={{ ref: meuDict }}>` — o `DictType` do
91
- `t.dict` encaixa direto; `{ kind: 'static', items }` renderiza como declarado) > **meta do
92
- `t.dict` no schema** (zero-config: campo `meuDict.zod()` — ou multiselect com elemento dict —
93
- resolve value→label pela meta que viaja no contrato, sem registry) > **chaves cruas do
94
- z.enum**. Campo texto com opções declaradas (runtime ou spec) vira single-select por-id.
93
+ As opções seguem esta precedência: `options` no campo, `fieldOptions` no formulário, `options` no
94
+ `FieldSpec`, metadata de `t.dict` no schema e, por último, as chaves de `z.enum`. Uma fonte mais
95
+ específica substitui as seguintes. Dicionários registrados no `OpusProvider` resolvem rótulos por
96
+ referência; itens estáticos permanecem como declarados no contrato.
95
97
 
96
98
  ## Widgets declarativos
97
99
 
@@ -103,11 +105,10 @@ escolha rica em card, com conteúdo de apoio; para uma lista textual comum, pref
103
105
  Outros identificadores continuam válidos como metadado
104
106
  para renderers próprios; o `ActionForm` aplica sua inferência normal quando não reconhece o widget.
105
107
 
106
- ## Controle custom: useActionFormContext
108
+ ## Controle próprio com useActionFormContext
107
109
 
108
- Campo com UI própria (grade de permissões, canvas…) que nenhum widget cobre? No modo
109
- composição, o hook acesso ao form do contrato o campo custom participa do submit
110
- sem abandonar o `<ActionForm>`:
110
+ Quando nenhum widget atender ao campo, use `useActionFormContext` no modo de composição. O controle
111
+ próprio participa da mesma validação e execução sem abandonar o `ActionForm`:
111
112
 
112
113
  ```tsx
113
114
  import { useActionFormContext } from '@softize/opus/ui/react'
@@ -132,4 +133,4 @@ function PermissionGrid() {
132
133
  ```
133
134
 
134
135
  O contexto também expõe `shape` (Zod por campo), `fields` (FieldSpec) e `fieldOptions`.
135
- Fora de um `<ActionForm>`, o hook explode com mensagem clara igual ao `ActionFormField`.
136
+ Fora de um `<ActionForm>`, o hook lança um erro que informa o provider ausente.
@@ -1,6 +1,9 @@
1
- ## ListAction em modal
1
+ ## Listagem em um modal
2
2
 
3
- O modo modal da listagem, action-driven: a lista é query-backed (busca no mount, re-busca quando o input muda e refaz sozinha quando um form/trigger invalida a action), os estados derivam do fetch, e a nota da toolbar já vem com o "N no total". Children diagrama os itens — composição, como nos irmãos.
3
+ Use `ActionListDialog` para consultar e apresentar uma `ListAction` sem sair do contexto atual. A
4
+ lista carrega ao abrir, refaz a consulta quando `input` muda e acompanha invalidações declaradas por
5
+ outras actions. O consumidor compõe os itens por `children`; o modal mantém os estados e a barra de
6
+ ações.
4
7
 
5
8
  ```tsx preview col
6
9
  const [open, setOpen] = useState(false)
@@ -38,9 +41,11 @@ render(
38
41
  )
39
42
  ```
40
43
 
41
- ## O que o contrato não sabe
44
+ ## Estado adicional do consumidor
42
45
 
43
- Dois escape hatches, pros casos em que o modal agrega mais de uma fonte: `loading` soma a carga de uma query irmã (ex.: os papéis que os cards precisam pra rotular) e `empty` sobrepõe o vazio derivado (ex.: com o form inline de criar aberto, a lista vazia mostra o form, não o emptyText).
46
+ Quando o modal depender de outra consulta, `loading` combina esse carregamento ao estado da lista.
47
+ Use `empty` para substituir a regra de vazio, por exemplo enquanto um formulário de criação ocupa o
48
+ corpo do modal.
44
49
 
45
50
  ```tsx
46
51
  <ActionListDialog
@@ -52,9 +57,9 @@ Dois escape hatches, pros casos em que o modal agrega mais de uma fonte: `loadin
52
57
  />
53
58
  ```
54
59
 
55
- ## Props
60
+ ## Propriedades de ActionListDialog
56
61
 
57
- | Prop | Tipo | Default | Descrição |
62
+ | Propriedade | Tipo | Padrão | Descrição |
58
63
  |---|---|---|---|
59
64
  | `action / input` | `ListAction / TInput` | | O contrato e os filtros — mudou o input, re-busca; `invalidates` de forms/triggers refaz sozinho. |
60
65
  | `open / onOpenChange` | `boolean / (open) => void` | | Controle do modal — de quem orquestra. |
@@ -1,6 +1,12 @@
1
- ## Declarativo pelo contrato (a diagramação da casa)
1
+ ## Listagem derivada do contrato
2
2
 
3
- O contrato descreve, a UI deriva — zero configuração no call site. `columns` vira a tabela emoldurada (o datagrid da casa; tipos text/number/date/badge, `fit`, `hidden`, headers ordenáveis que escrevem `sort: 'chave:dir'` no input); `filters` vira a toolbar (os não-avançados inline, os `advanced: true` no modal "Filtros" com contador); `text` liga a busca (param `q`); filtros do modal aplicados viram chips removíveis. A linha é RESPONSIVA: a busca tem largura auto (encolhe primeiro) e filtro inline que não cabe migra pro modal — medição real, re-avaliada no resize; só no caso extremo (nada mais a ceder) a linha quebra. O handler implementa o que o input diz (orderBy, where) — mesmo modelo do resto do Opus.
3
+ Use `ActionList` para apresentar uma coleção pesquisável descrita por uma `ListAction`. O contrato
4
+ define colunas, busca, filtros, período, ordenação e paginação; a interface materializa esses
5
+ recursos e envia o estado correspondente no input. Filtros avançados aparecem no modal “Filtros” e
6
+ os filtros aplicados permanecem visíveis como chips removíveis.
7
+
8
+ A barra se adapta ao espaço disponível. A busca cede largura primeiro e filtros que deixam de caber
9
+ migram para o modal; a linha só quebra quando nenhum controle restante puder ceder espaço.
4
10
 
5
11
  ```tsx preview col
6
12
  render(
@@ -28,18 +34,16 @@ superfícies que não são listagens, como relatórios. O estado é controlado p
28
34
  />
29
35
  ```
30
36
 
31
- Selects inline têm largura fixa e previsível (`w-40`; lookup e múltiplo `w-52`): nem a label nem
32
- o valor selecionado alargam o controle o rótulo trunca e a opção inteira continua na lista. No
33
- modal de filtros avançados, o select ocupa a largura toda. Busca e filtros são renderizados
34
- somente quando declarados. Períodos incluem os presets do
35
- contrato e o intervalo personalizado no mesmo calendário usado pela `ActionList`.
37
+ Seletores inline mantêm largura previsível (`w-40`; lookup e múltiplo usam `w-52`). Rótulos longos
38
+ são truncados no controle, mas permanecem completos na lista. No modal de filtros avançados, o
39
+ seletor ocupa toda a largura. Busca, filtros e período aparecem somente quando declarados.
36
40
 
37
41
  ## Colunas de dicionário
38
42
 
39
- Coluna cujo campo no schema de saída é um `t.dict().zod()` renderiza `DictionaryValue` com a
40
- apresentação que o dicionário declarou (`presentation`: classificação em badge `outline`, status
41
- e estágio em badge tonal, `plain` ou ausente como texto). Nada a configurar na tela. Quando o
42
- campo de saída é uma string comum, a coluna nomeia o dicionário registrado no provider:
43
+ Quando o campo de saída usa `t.dict().zod()`, a coluna apresenta o valor com `DictionaryValue` e
44
+ respeita o papel declarado pelo dicionário. Classificações usam badge `outline`; status e estágio
45
+ usam badge tonal; `plain` e papéis ausentes permanecem como texto. Para um campo `string`, a coluna
46
+ pode indicar um dicionário registrado no provider:
43
47
  `{ key: 'source', label: 'Fonte', dictionary: 'customerSource' }`. Dimensões independentes, como
44
48
  tipo e estágio, ficam em colunas distintas — a leitura de comparação depende disso. `type:
45
49
  'badge'` continua sendo o chip `outline` legado; com dicionário, ele mostra o rótulo em vez do
@@ -56,7 +60,8 @@ Renderer customizado reutiliza `EmptyValue`.
56
60
 
57
61
  ## Células custom (cells)
58
62
 
59
- As colunas do contrato descrevem DADOS; apresentação especial entra por cima com `cells` (chave = key da coluna). É onde vivem links, composições e a coluna de ações; valor de dicionário já resolve sozinho pela coluna, sem `cells`.
63
+ Use `cells` somente quando uma coluna precisar de apresentação própria, como link, composição ou
64
+ ação. A chave corresponde à `key` da coluna. Valores de dicionário não precisam desse override.
60
65
 
61
66
  ```tsx preview col
62
67
  render(
@@ -78,9 +83,12 @@ render(
78
83
  )
79
84
  ```
80
85
 
81
- ## Período (o campo dinâmico)
86
+ ## Período obrigatório
82
87
 
83
- Com `periods` no contrato, a toolbar ganha o controle de período: um popover com os presets e, no "Personalizado", o calendário de range ali mesmo. Período é recorte OBRIGATÓRIO do caso de uso: o default (o preset com `default: true`, ou o primeiro) já vem aplicado e não existe "sem período". O preset fica RELATIVO na URL (`?period=last7`; o default é omitido); no custom o range vai direto no param (`?period=2026-07-01..2026-07-07` — o "custom" é implícito). No fetch, o pattern materializa o range nos params `from`/`to` — o handler implementa o recorte.
88
+ Quando o contrato declara `periods`, a listagem sempre possui um recorte temporal. O preset marcado
89
+ com `default: true`, ou o primeiro da coleção, começa aplicado. Presets usam um valor relativo na
90
+ URL, como `?period=last7`; o padrão é omitido. Um intervalo personalizado usa diretamente as datas,
91
+ como `?period=2026-07-01..2026-07-07`, e chega ao handler pelos parâmetros `from` e `to`.
84
92
 
85
93
  ```tsx
86
94
  periods: [
@@ -89,15 +97,23 @@ periods: [
89
97
  { value: 'thisMonth', label: 'Este mês' },
90
98
  ]
91
99
  // Presets computáveis: today · yesterday · last7 · last30 · thisMonth · lastMonth.
100
+ // presetRange('last7') → { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' } — o mesmo cálculo do pattern.
92
101
  ```
93
102
 
94
- ## Paginação e loading
103
+ ## Paginação e carregamento
95
104
 
96
- O pattern manda `limit` (= `pageSize`, default 50) e `page` no input; o handler implementa o OFFSET e devolve `total` no Paginated. O rodapé (Página X de Y · páginas numeradas com reticências · "N itens no total") compõe a primitiva `Pagination` na escala densa — números com largura mínima que cresce com os dígitos, setas quadradas, nome acessível por página — e aparece sempre que há total; mudar filtro/busca/sort/período volta pra página 1 (a página vive na URL: `?page=2`). No primeiro carregamento, `DataState` centraliza o spinner; no refetch com a lista já na tela, o corpo esmaece (`aria-busy`) até os dados chegarem.
105
+ `ActionList` envia `limit`, definido por `pageSize`, e `page` no input. O handler aplica o recorte e
106
+ devolve `total` na resposta paginada. Quando existe total, o rodapé mostra a página atual, os links
107
+ disponíveis e a quantidade de itens. Alterar busca, filtro, ordenação ou período retorna à primeira
108
+ página. Durante a primeira consulta, `DataState` apresenta o carregamento; em atualizações
109
+ posteriores, a lista permanece visível com `aria-busy`.
97
110
 
98
- ## Multi-seleção com can (batch)
111
+ ## Ações em lote
99
112
 
100
- `batch` liga a coluna de checkbox na tabela. O `can(item)` é a fonte única de elegibilidade: linha inelegível não marca, o "selecionar todos" pega só os elegíveis, o botão mostra quantos dos selecionados valem e o `run` recebe apenas esses. Com `confirm`, um dialog pede confirmação; ao resolver, a seleção limpa e a lista refaz.
113
+ `batch` acrescenta seleção à tabela. `can(item)` determina quais linhas podem participar: itens
114
+ inelegíveis não são selecionáveis, “Selecionar todos” inclui somente os elegíveis e `run` recebe
115
+ apenas essa seleção. Com `confirm`, o componente pede confirmação antes de executar. Ao concluir, a
116
+ seleção é limpa e a consulta é refeita.
101
117
 
102
118
  ```tsx
103
119
  <ActionList action={runList} input={{}}
@@ -110,9 +126,12 @@ O pattern manda `limit` (= `pageSize`, default 50) e `page` no input; o handler
110
126
  />
111
127
  ```
112
128
 
113
- ## Views (a mesma listagem, outro renderer)
129
+ ## Outras visualizações
114
130
 
115
- Nem toda listagem é tabela: board, galeria, lista, calendário A view é APRESENTAÇÃO de quem chama (`render`); o pattern dá o segment na toolbar (ToggleGroup, ICON-ONLY: declare `icon` — o label vira title/aria; sem ícone cai pro texto), o estado (`view` na URL) e os dados — a mesma fonte, os mesmos filtros. A tabela ('Tabela') participa quando há columns.
131
+ Uma coleção também pode aparecer como quadro, galeria, lista ou calendário. Cada entrada de `views`
132
+ fornece um `render`; `ActionList` preserva a mesma fonte de dados, filtros e estado na URL. Declare
133
+ `icon` para controles somente com ícone; sem ele, o rótulo permanece visível. A visualização
134
+ “Tabela” aparece quando existem colunas.
116
135
 
117
136
  ```tsx preview col
118
137
  render(
@@ -146,13 +165,16 @@ render(
146
165
  )
147
166
  ```
148
167
 
149
- ## Filtros dependentes, dictionary e lookup
168
+ ## Filtros dependentes e opções remotas
150
169
 
151
- Três recursos do FilterSpec que a toolbar honra:
170
+ `FilterSpec` também cobre dependências, dicionários e consultas remotas:
152
171
 
153
- - **`depends: ['outroFiltro']`** o filtro dependente fica DESABILITADO até o pai ter valor, e mudar o pai limpa o dependente em cascata (o recorte perde o sentido quando o pai muda). Vale inline e no modal.
154
- - **`options: { kind: 'dictionary', ref: 'sessionStatus' }`** — as opções vêm de um DICT registrado no `<TbdlibProvider dicts={{ sessionStatus: statusDict }}>` (o `DictType` do `t.dict` encaixa direto). Labels do vocabulário no filtro E nos chips; runtime (`filterOptions`) sobrepõe.
155
- - **`options: { kind: 'lookup', source: 'x.lookup' }`** as opções vêm de uma ACTION (server-side): o campo vira Select typeahead que chama a `source` (debounced) com `{ q }` — e com os valores dos `depends` no input. Convenção: a action de lookup devolve itens `{ value, label }`. Com valor aplicado mas opções ainda não carregadas (URL, chip), o chip mostra o próprio value.
172
+ - **`depends: ['outroFiltro']`:** desabilita o filtro até que suas dependências tenham valor e o
173
+ limpa quando uma delas muda.
174
+ - **`options: { kind: 'dictionary', ref: 'sessionStatus' }`:** resolve opções e rótulos por um
175
+ dicionário registrado no `OpusProvider`. `filterOptions` substitui essa fonte quando informado.
176
+ - **`options: { kind: 'lookup', source: 'x.lookup' }`:** consulta uma action com `{ q }` e os valores
177
+ das dependências. A resposta segue o formato `{ value, label }`.
156
178
 
157
179
  ```tsx
158
180
  filters: {
@@ -166,9 +188,12 @@ filters: {
166
188
  }
167
189
  ```
168
190
 
169
- ## Exibição e URL sync
191
+ ## Exibição e estado na URL
170
192
 
171
- O botão de exibição (engrenagem) abre o popover de configuração da listagem — presente em QUALQUER view: colunas (liga/desliga, incluindo as `hidden` do contrato; só com a tabela ativa) e itens por página (o default do caller fica fora da URL) — e é a casa do que vier depois. E o estado inteiro da toolbar (q, sort, filtros, view, colunas, limit) serializa pra querystring com os helpers padrão:
193
+ O botão de exibição abre as preferências da listagem em qualquer visualização. Na tabela, permite
194
+ mostrar ou ocultar colunas, inclusive as declaradas com `hidden`; em todas as visualizações, permite
195
+ alterar a quantidade de itens por página. Busca, ordenação, filtros, visualização, colunas e limite
196
+ podem ser serializados na query string pelos helpers do componente:
172
197
 
173
198
  ```tsx
174
199
  <ActionList
@@ -181,9 +206,10 @@ O botão de exibição (engrenagem) abre o popover de configuração da listagem
181
206
 
182
207
  Convenção compacta (defaults omitidos): `?q=…&sort=chave:dir&status=…&view=board&cols=a,b,c&limit=25`.
183
208
 
184
- ## Composição (layout livre)
209
+ ## Composição dos itens
185
210
 
186
- Com children, a tabela sai de cena e o layout dos itens é seu (cards, grid, o que for) — a toolbar declarativa e os estados (skeleton/erro/vazio) seguem daqui. Mesmo princípio do ActionForm com children.
211
+ Passe `children` para substituir a tabela por uma composição própria. A barra de filtros e os
212
+ estados de carregamento, erro e vazio continuam sendo tratados por `ActionList`.
187
213
 
188
214
  ```tsx preview col
189
215
  render(
@@ -209,30 +235,38 @@ render(
209
235
  )
210
236
  ```
211
237
 
212
- ## No contrato (ListAction)
238
+ ## Declaração na ListAction
213
239
 
214
240
  | Chave | O que declara |
215
241
  |---|---|
216
242
  | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat?, dictionary?, empty? }` — a tabela. `hidden` fica fora (base do futuro column picker); `dictionary` nomeia o dicionário do provider; `empty` dá o significado da ausência. |
217
- | `filters` | `{ [nome]: { label, type, options?, multiple?, advanced?, depends?, … } }` — a toolbar. `advanced` vai pro modal; `depends` desabilita/cascateia; options `kind: 'lookup'` busca numa action. Valor aplicado entra no input com o MESMO nome. |
243
+ | `filters` | `{ [nome]: { label, type, options?, multiple?, advanced?, depends?, … } }` — a toolbar. `advanced` vai para o modal; `depends` desabilita/cascateia; options `kind: 'lookup'` busca em uma action. Valor aplicado entra no input com o mesmo nome. |
218
244
  | `text` | `{ fields }` — liga a busca; convenção: param `q` no input. Com período/filtros no contrato ela fica à direita; sendo a ÚNICA forma de recorte, abre a linha. |
219
245
  | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
220
246
  | `periods` | `{ value, label }[]` — o controle de período (presets + Personalizado com calendário); materializa em `from`/`to` no input. |
221
247
 
222
- ## Props
248
+ ## useListAction
249
+
250
+ Quando o layout não cabe no `ActionList`, `useListAction(action, input)` entrega o mesmo fetch
251
+ declarativo (`items`, `total`, `error`, `isLoading`, `isFetching`, `refetch`) sob o
252
+ `QueryClientProvider`, e `presetRange(preset)` materializa os presets de período (`today`, `last7`,
253
+ `thisMonth`…) em `{ from, to }` no formato `YYYY-MM-DD` — o mesmo cálculo que o pattern faz antes de
254
+ chamar a action.
255
+
256
+ ## Propriedades de ActionList
223
257
 
224
- | Prop | Tipo | Default | Descrição |
258
+ | Propriedade | Tipo | Padrão | Descrição |
225
259
  |---|---|---|---|
226
260
  | `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item, paginate cursor). |
227
261
  | `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
228
262
  | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
229
- | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime pros filtros select/lookup (chave = nome do filtro). |
263
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime para os filtros select/lookup (chave = nome do filtro). |
230
264
  | `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
231
- | `children` | `(items, refetch) => ReactNode` | | Modo COMPOSIÇÃO: layout livre; a toolbar segue. Tem precedência sobre columns. |
265
+ | `children` | `(items, refetch) => ReactNode` | | Modo composição: layout livre; a toolbar segue. Tem precedência sobre columns. |
232
266
  | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?, destructive? }` — liga a multi-seleção. |
233
- | `rowId` | `(item) => string` | `item.id` | Identidade da linha pra seleção. |
234
- | `pageSize` | `number` | `50` | Itens por página DEFAULT (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
235
- | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — pra quem embala sincronizar com a URL. |
267
+ | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
268
+ | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
269
+ | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
236
270
  | `emptyMessage` | `string` | `'Nenhum resultado.'` | O texto do estado vazio. |
237
- | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar pro detalhe) — só na tabela. |
271
+ | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
238
272
  | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita) — apresentação de quem chama, como `cells`; cliques ali não disparam o `onRowClick`. |
@@ -1,7 +1,8 @@
1
- ## Disparo direto
1
+ ## Executar uma action
2
2
 
3
- Sem confirm, dispara no clique: desabilita enquanto roda e toca o toast do contrato
4
- (messages.success/error). O label default vem do action.label.
3
+ Use `ActionTrigger` para executar uma `SimpleAction` a partir de um botão. Sem confirmação, o clique
4
+ inicia a operação, desabilita o controle durante a execução e apresenta as mensagens do contrato em
5
+ um toast. O rótulo padrão vem de `action.label`.
5
6
 
6
7
  ```tsx preview
7
8
  <DocBrowserActionProvider>
@@ -11,8 +12,9 @@ Sem confirm, dispara no clique: desabilita enquanto roda e toca o toast do contr
11
12
 
12
13
  ## Confirmação declarada no contrato
13
14
 
14
- O ConfirmSpec mora na action (title/message/destructive) o pattern monta o Dialog sozinho e o
15
- destructive tonaliza o botão. Prop confirm sobrepõe quando a tela precisar de outro texto.
15
+ Quando a action declara `confirm`, o componente monta o diálogo e aplica o contexto `danger` se a
16
+ operação for destrutiva. Use a propriedade `confirm` somente quando esta ocorrência precisar de uma
17
+ mensagem diferente da declarada no contrato.
16
18
 
17
19
  ```tsx preview
18
20
  <DocBrowserActionProvider>
@@ -33,9 +35,11 @@ confirm: {
33
35
  <ActionTrigger action={sessionDeleteContract} input={{ id: session.id }} />
34
36
  ```
35
37
 
36
- ## A ação que mora no item (icon)
38
+ ## Ação somente com ícone
37
39
 
38
- Com `icon`, o botão vira icon-only: o `label` (ou o `action.label`) migra pro tooltip e pro `aria-label`, o visual fica discreto (ghost, e vermelho no hover quando o contrato marca `destructive`) e o clique **não vaza** pro item em volta — é o que permite viver dentro de uma linha ou card clicável. `itemLabel` nomeia o alvo na pergunta.
40
+ Com `icon`, o botão exibe somente o ícone e usa `label`, ou `action.label`, no tooltip e no nome
41
+ acessível. O clique não aciona o item clicável ao redor. `itemLabel` identifica o registro na
42
+ mensagem de confirmação.
39
43
 
40
44
  ```tsx
41
45
  <ActionTrigger
@@ -47,26 +51,36 @@ Com `icon`, o botão vira icon-only: o `label` (ou o `action.label`) migra pro t
47
51
  />
48
52
  ```
49
53
 
50
- > Isto absorveu o antigo `DeleteButton` (7.0.0), que era exatamente este componente com uma lixeira dentro. Se você tinha `<DeleteButton action input itemLabel />`, troque por `<ActionTrigger … icon={<Trash2 />} />` — **e confira se o contrato declara `confirm`**: sem ele o disparo é direto, e o DeleteButton perguntava sempre.
54
+ > `ActionTrigger` substitui o antigo `DeleteButton`. Ao migrar, declare `confirm` no contrato para
55
+ > preservar a confirmação que o componente anterior sempre apresentava.
51
56
 
52
57
  ## Erro que a pessoa entende
53
58
 
54
- Quando a action falha, a frase do SERVIDOR vence o rótulo do contrato se o erro for de negócio — `conflict`, `validation`, `not_found`:
59
+ Quando a action falha, uma mensagem do servidor substitui o texto genérico somente nos erros de
60
+ negócio `conflict`, `validation` e `not_found`:
55
61
 
56
62
  > Este agente tem 3 conversas — desabilite em vez de excluir.
57
63
 
58
- Isso é acionável. Erro inesperado carrega texto técnico cru: sem a allowlist, uma violação de FK viraria `violates foreign key constraint "user_unit_id_fkey"` no toast de quem só clicou num botão. Por isso allowlist e não denylist — categoria desconhecida cai no rótulo genérico, que é o lado seguro de errar.
64
+ Erros inesperados mantêm a mensagem genérica do contrato, evitando expor detalhes de banco ou
65
+ infraestrutura. O servidor registra o detalhe técnico para diagnóstico.
59
66
 
60
- ## Props
67
+ ## useTriggerAction
61
68
 
62
- | Prop | Tipo | Default | Descrição |
69
+ `ActionTrigger` é uma composição sobre `useTriggerAction(action, { onSuccess, onError })`, que
70
+ devolve `trigger(input)`, `isLoading`, `error` e `reset`, e invalida o cache declarado em
71
+ `action.invalidates`. Use o hook direto quando a execução parte de um gesto que não é um botão —
72
+ um atalho, um arrastar, um item de menu.
73
+
74
+ ## Propriedades de ActionTrigger
75
+
76
+ | Propriedade | Tipo | Padrão | Descrição |
63
77
  |---|---|---|---|
64
78
  | `action` | `SimpleContract<TInput, TData>` | | A SimpleAction do Opus — label, messages e confirm vêm do contrato. |
65
79
  | `input` | `TInput` | | O que a action recebe — geralmente { id }. |
66
80
  | `label` | `string` | `action.label` | Sobrepõe o texto do botão. |
67
- | `variant / size` | `variants do Button` | `'default' (ou 'destructive' se o confirm declarar) / 'default'` | Visual do botão o destructive do ConfirmSpec escolhe sozinho. |
81
+ | `context / variant / size` | `do Button` | gatilho: `primary`/`solid`; `danger` quando a action é `destructive`; no modo ícone, `ghost` e `neutral` (ou `danger` se destrutiva) / `default` | Visual do gatilho; `context` explícito vence. O botão de confirmar é sempre `solid`: `danger` quando a action é `destructive`, senão a prop `context` do gatilho (ou `primary`). |
68
82
  | `confirm` | `{ title, description?, actionLabel?, cancelLabel? }` | | Confirmação via prop — sobrepõe o ConfirmSpec do contrato. |
69
83
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
70
- | `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza pro item. |
84
+ | `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza para o item. |
71
85
  | `itemLabel` | `string` | | Nome do alvo na pergunta (sai entre aspas, em destaque, antes da mensagem do contrato). |
72
- | `className` | `string` | | Classes do botão (ex.: apertar o tamanho numa linha densa). |
86
+ | `className` | `string` | | Classes do botão (ex.: apertar o tamanho em uma linha densa). |