@softize/opus 8.6.6

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 (286) hide show
  1. package/CHANGELOG.md +1616 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -0
  4. package/bin/cli.mjs +528 -0
  5. package/bin/lib/check.mjs +307 -0
  6. package/bin/lib/components.mjs +151 -0
  7. package/bin/lib/create.mjs +208 -0
  8. package/bin/lib/db-check-runner.mjs +86 -0
  9. package/bin/lib/db-migrate-runner.mjs +89 -0
  10. package/bin/lib/db-scaffold-runner.mjs +84 -0
  11. package/bin/lib/db.mjs +261 -0
  12. package/bin/lib/docs-include.mjs +48 -0
  13. package/bin/lib/gen-dicts.mjs +134 -0
  14. package/bin/lib/gen-docs.mjs +288 -0
  15. package/bin/lib/gen-manifest.mjs +102 -0
  16. package/bin/lib/gen-openapi.mjs +195 -0
  17. package/bin/lib/gen-runner.mjs +472 -0
  18. package/bin/lib/gen-stubs.mjs +463 -0
  19. package/bin/lib/gen.mjs +311 -0
  20. package/bin/lib/init.mjs +514 -0
  21. package/bin/lib/introspect.mjs +107 -0
  22. package/bin/lib/mcp.mjs +85 -0
  23. package/bin/lib/postinstall.mjs +56 -0
  24. package/docs/chat-event-protocol.md +85 -0
  25. package/docs/code-style.md +16 -0
  26. package/docs/data-layer.md +246 -0
  27. package/docs/ownership-vs-shadcn-lock.md +102 -0
  28. package/docs/protocol.md +2053 -0
  29. package/docs/releasing.md +110 -0
  30. package/docs/shellnav.md +131 -0
  31. package/package.json +338 -0
  32. package/registry/hooks/hooks.json +26 -0
  33. package/registry/hooks/link-memory-on-start.mjs +46 -0
  34. package/registry/hooks/opus-check-on-stop.mjs +114 -0
  35. package/registry/skills/create-action/SKILL.md +49 -0
  36. package/registry/skills/create-action/scaffold.mjs +122 -0
  37. package/registry/templates/app/_gitignore +3 -0
  38. package/registry/templates/app/_npmrc +1 -0
  39. package/registry/templates/app/_opus/_gitignore +5 -0
  40. package/registry/templates/app/_prettierrc.json +6 -0
  41. package/registry/templates/app/index.html +13 -0
  42. package/registry/templates/app/opus.config.ts +16 -0
  43. package/registry/templates/app/package.json +43 -0
  44. package/registry/templates/app/pnpm-workspace.yaml +11 -0
  45. package/registry/templates/app/public/favicon.svg +4 -0
  46. package/registry/templates/app/src/App.tsx +37 -0
  47. package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
  48. package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
  49. package/registry/templates/app/src/domains/tasks/index.ts +13 -0
  50. package/registry/templates/app/src/index.css +18 -0
  51. package/registry/templates/app/src/main.tsx +25 -0
  52. package/registry/templates/app/tsconfig.json +20 -0
  53. package/registry/templates/app/vite.config.ts +46 -0
  54. package/registry/templates/monorepo/_gitignore +3 -0
  55. package/registry/templates/monorepo/_npmrc +1 -0
  56. package/registry/templates/monorepo/package.json +9 -0
  57. package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
  58. package/src/ai/ask.ts +64 -0
  59. package/src/ai/drivers/anthropic.ts +309 -0
  60. package/src/ai/index.ts +17 -0
  61. package/src/audit/drivers/console.ts +117 -0
  62. package/src/audit/drivers/pg.ts +172 -0
  63. package/src/audit/index.ts +51 -0
  64. package/src/auth/drivers/better-auth.ts +103 -0
  65. package/src/auth/drivers/jwt.ts +188 -0
  66. package/src/auth/index.ts +9 -0
  67. package/src/client/drivers/fetch.ts +202 -0
  68. package/src/client/index.ts +22 -0
  69. package/src/core/actions.ts +110 -0
  70. package/src/core/audit.ts +239 -0
  71. package/src/core/contracts.ts +137 -0
  72. package/src/core/domain.ts +310 -0
  73. package/src/core/errors.ts +181 -0
  74. package/src/core/index.ts +174 -0
  75. package/src/core/logical-type.ts +31 -0
  76. package/src/core/reactions.ts +81 -0
  77. package/src/core/runtime.ts +1167 -0
  78. package/src/core/schedules.ts +41 -0
  79. package/src/core/types.ts +1356 -0
  80. package/src/data/drivers/kysely.ts +389 -0
  81. package/src/data/index.ts +10 -0
  82. package/src/data/readonly-pool.ts +160 -0
  83. package/src/dsl/eval.ts +136 -0
  84. package/src/dsl/index.ts +29 -0
  85. package/src/dsl/kysely.ts +230 -0
  86. package/src/dsl/loads.ts +123 -0
  87. package/src/dsl/parser.ts +423 -0
  88. package/src/dsl/types.ts +113 -0
  89. package/src/events/drivers/mitt.ts +70 -0
  90. package/src/events/index.ts +9 -0
  91. package/src/log/drivers/pino.ts +57 -0
  92. package/src/log/index.ts +9 -0
  93. package/src/mcp/index.ts +62 -0
  94. package/src/queue/drivers/bullmq.ts +190 -0
  95. package/src/queue/index.ts +9 -0
  96. package/src/scheduler/drivers/node-cron.ts +93 -0
  97. package/src/scheduler/every.ts +45 -0
  98. package/src/scheduler/index.ts +9 -0
  99. package/src/schema/drivers/zod.ts +765 -0
  100. package/src/schema/entity.ts +439 -0
  101. package/src/schema/format/locale.ts +144 -0
  102. package/src/schema/index.ts +65 -0
  103. package/src/schema/openapi.ts +302 -0
  104. package/src/schema/scaffold.ts +160 -0
  105. package/src/server/drivers/fastify.ts +224 -0
  106. package/src/server/drivers/node.ts +386 -0
  107. package/src/server/index.ts +142 -0
  108. package/src/storage/drivers/fs.ts +90 -0
  109. package/src/storage/drivers/s3.ts +117 -0
  110. package/src/storage/index.ts +27 -0
  111. package/src/testing/fake.ts +298 -0
  112. package/src/testing/index.ts +324 -0
  113. package/src/ui/components/patterns/action-form-card.tsx +48 -0
  114. package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
  115. package/src/ui/components/patterns/app-shell.tsx +227 -0
  116. package/src/ui/components/patterns/confirm.tsx +226 -0
  117. package/src/ui/components/patterns/data-state.tsx +75 -0
  118. package/src/ui/components/patterns/form-dialog.tsx +64 -0
  119. package/src/ui/components/patterns/form.tsx +584 -0
  120. package/src/ui/components/patterns/list.tsx +1488 -0
  121. package/src/ui/components/patterns/page.tsx +46 -0
  122. package/src/ui/components/patterns/section-shell.tsx +246 -0
  123. package/src/ui/components/patterns/shell-nav.tsx +150 -0
  124. package/src/ui/components/patterns/sidebar.tsx +89 -0
  125. package/src/ui/components/patterns/split.tsx +93 -0
  126. package/src/ui/components/patterns/trigger.tsx +196 -0
  127. package/src/ui/components/patterns/view.tsx +84 -0
  128. package/src/ui/components/primitives/accordion.tsx +64 -0
  129. package/src/ui/components/primitives/alert-dialog.tsx +190 -0
  130. package/src/ui/components/primitives/alert.tsx +116 -0
  131. package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
  132. package/src/ui/components/primitives/avatar.tsx +107 -0
  133. package/src/ui/components/primitives/badge.tsx +37 -0
  134. package/src/ui/components/primitives/breadcrumb.tsx +109 -0
  135. package/src/ui/components/primitives/button-group.tsx +83 -0
  136. package/src/ui/components/primitives/button.tsx +102 -0
  137. package/src/ui/components/primitives/calendar.tsx +218 -0
  138. package/src/ui/components/primitives/card.tsx +56 -0
  139. package/src/ui/components/primitives/carousel.tsx +239 -0
  140. package/src/ui/components/primitives/chat.tsx +407 -0
  141. package/src/ui/components/primitives/checkbox.tsx +30 -0
  142. package/src/ui/components/primitives/collapsible.tsx +31 -0
  143. package/src/ui/components/primitives/command.tsx +182 -0
  144. package/src/ui/components/primitives/composer.tsx +121 -0
  145. package/src/ui/components/primitives/copyable.tsx +50 -0
  146. package/src/ui/components/primitives/dialog.tsx +147 -0
  147. package/src/ui/components/primitives/drawer.tsx +141 -0
  148. package/src/ui/components/primitives/empty.tsx +104 -0
  149. package/src/ui/components/primitives/field.tsx +246 -0
  150. package/src/ui/components/primitives/icon-picker.tsx +180 -0
  151. package/src/ui/components/primitives/input-group.tsx +168 -0
  152. package/src/ui/components/primitives/input-otp.tsx +75 -0
  153. package/src/ui/components/primitives/input.tsx +72 -0
  154. package/src/ui/components/primitives/item.tsx +193 -0
  155. package/src/ui/components/primitives/kbd.tsx +28 -0
  156. package/src/ui/components/primitives/label.tsx +22 -0
  157. package/src/ui/components/primitives/markdown.tsx +35 -0
  158. package/src/ui/components/primitives/menu.tsx +255 -0
  159. package/src/ui/components/primitives/pagination.tsx +127 -0
  160. package/src/ui/components/primitives/popover.tsx +87 -0
  161. package/src/ui/components/primitives/progress.tsx +29 -0
  162. package/src/ui/components/primitives/radio-group.tsx +43 -0
  163. package/src/ui/components/primitives/resizable.tsx +51 -0
  164. package/src/ui/components/primitives/scroll-area.tsx +56 -0
  165. package/src/ui/components/primitives/select.tsx +479 -0
  166. package/src/ui/components/primitives/separator.tsx +26 -0
  167. package/src/ui/components/primitives/skeleton.tsx +13 -0
  168. package/src/ui/components/primitives/slider.tsx +61 -0
  169. package/src/ui/components/primitives/sonner.tsx +46 -0
  170. package/src/ui/components/primitives/spinner.tsx +29 -0
  171. package/src/ui/components/primitives/switch.tsx +33 -0
  172. package/src/ui/components/primitives/table.tsx +114 -0
  173. package/src/ui/components/primitives/tabs.tsx +104 -0
  174. package/src/ui/components/primitives/textarea.tsx +18 -0
  175. package/src/ui/components/primitives/toggle-group.tsx +81 -0
  176. package/src/ui/components/primitives/toggle.tsx +45 -0
  177. package/src/ui/components/primitives/tooltip.tsx +55 -0
  178. package/src/ui/components/primitives/truncate.tsx +49 -0
  179. package/src/ui/docs/DocBrowser.tsx +90 -0
  180. package/src/ui/docs/changelog.tsx +80 -0
  181. package/src/ui/docs/content/accordion.md +86 -0
  182. package/src/ui/docs/content/action-form-card.md +24 -0
  183. package/src/ui/docs/content/action-form-dialog.md +30 -0
  184. package/src/ui/docs/content/action-form.md +125 -0
  185. package/src/ui/docs/content/action-list-dialog.md +68 -0
  186. package/src/ui/docs/content/action-list.md +194 -0
  187. package/src/ui/docs/content/action-trigger.md +72 -0
  188. package/src/ui/docs/content/action-view.md +47 -0
  189. package/src/ui/docs/content/actions.md +138 -0
  190. package/src/ui/docs/content/ai.md +112 -0
  191. package/src/ui/docs/content/alert-dialog.md +73 -0
  192. package/src/ui/docs/content/alert.md +69 -0
  193. package/src/ui/docs/content/app-shell.md +155 -0
  194. package/src/ui/docs/content/aspect-ratio.md +66 -0
  195. package/src/ui/docs/content/audit.md +84 -0
  196. package/src/ui/docs/content/auth.md +70 -0
  197. package/src/ui/docs/content/avatar.md +94 -0
  198. package/src/ui/docs/content/badge.md +48 -0
  199. package/src/ui/docs/content/breadcrumb.md +87 -0
  200. package/src/ui/docs/content/button-group.md +71 -0
  201. package/src/ui/docs/content/button.md +60 -0
  202. package/src/ui/docs/content/calendar.md +62 -0
  203. package/src/ui/docs/content/card.md +49 -0
  204. package/src/ui/docs/content/carousel.md +85 -0
  205. package/src/ui/docs/content/chat.md +69 -0
  206. package/src/ui/docs/content/checkbox.md +75 -0
  207. package/src/ui/docs/content/cli.md +58 -0
  208. package/src/ui/docs/content/collapsible.md +64 -0
  209. package/src/ui/docs/content/command.md +56 -0
  210. package/src/ui/docs/content/composer.md +50 -0
  211. package/src/ui/docs/content/confirm.md +120 -0
  212. package/src/ui/docs/content/copyable.md +30 -0
  213. package/src/ui/docs/content/customization.md +110 -0
  214. package/src/ui/docs/content/cycle.md +34 -0
  215. package/src/ui/docs/content/data-state.md +47 -0
  216. package/src/ui/docs/content/data.md +99 -0
  217. package/src/ui/docs/content/dialog.md +60 -0
  218. package/src/ui/docs/content/drawer.md +55 -0
  219. package/src/ui/docs/content/empty.md +66 -0
  220. package/src/ui/docs/content/events.md +61 -0
  221. package/src/ui/docs/content/field.md +58 -0
  222. package/src/ui/docs/content/getting-started.md +109 -0
  223. package/src/ui/docs/content/icon-picker.md +51 -0
  224. package/src/ui/docs/content/input-group.md +78 -0
  225. package/src/ui/docs/content/input-otp.md +72 -0
  226. package/src/ui/docs/content/input.md +78 -0
  227. package/src/ui/docs/content/item.md +84 -0
  228. package/src/ui/docs/content/kbd.md +62 -0
  229. package/src/ui/docs/content/label.md +32 -0
  230. package/src/ui/docs/content/log.md +55 -0
  231. package/src/ui/docs/content/markdown.md +41 -0
  232. package/src/ui/docs/content/mcp.md +44 -0
  233. package/src/ui/docs/content/menu.md +114 -0
  234. package/src/ui/docs/content/microcopy.md +83 -0
  235. package/src/ui/docs/content/page.md +34 -0
  236. package/src/ui/docs/content/pagination.md +99 -0
  237. package/src/ui/docs/content/popover.md +49 -0
  238. package/src/ui/docs/content/progress.md +69 -0
  239. package/src/ui/docs/content/queue.md +62 -0
  240. package/src/ui/docs/content/radio-group.md +77 -0
  241. package/src/ui/docs/content/resizable.md +86 -0
  242. package/src/ui/docs/content/router.md +56 -0
  243. package/src/ui/docs/content/runtime.md +77 -0
  244. package/src/ui/docs/content/scheduler.md +66 -0
  245. package/src/ui/docs/content/scroll-area.md +89 -0
  246. package/src/ui/docs/content/section-shell.md +121 -0
  247. package/src/ui/docs/content/select.md +342 -0
  248. package/src/ui/docs/content/separator.md +33 -0
  249. package/src/ui/docs/content/sidebar.md +38 -0
  250. package/src/ui/docs/content/skeleton.md +34 -0
  251. package/src/ui/docs/content/slider.md +64 -0
  252. package/src/ui/docs/content/spinner.md +37 -0
  253. package/src/ui/docs/content/split.md +33 -0
  254. package/src/ui/docs/content/storage.md +69 -0
  255. package/src/ui/docs/content/switch.md +69 -0
  256. package/src/ui/docs/content/table.md +102 -0
  257. package/src/ui/docs/content/tabs.md +94 -0
  258. package/src/ui/docs/content/testing.md +89 -0
  259. package/src/ui/docs/content/textarea.md +30 -0
  260. package/src/ui/docs/content/toast.md +67 -0
  261. package/src/ui/docs/content/toggle-group.md +81 -0
  262. package/src/ui/docs/content/toggle.md +72 -0
  263. package/src/ui/docs/content/tokens.md +171 -0
  264. package/src/ui/docs/content/tooltip.md +50 -0
  265. package/src/ui/docs/content/truncate.md +37 -0
  266. package/src/ui/docs/content/ui.md +40 -0
  267. package/src/ui/docs/content/upgrading.md +48 -0
  268. package/src/ui/docs/doc-client.tsx +214 -0
  269. package/src/ui/docs/doc.tsx +301 -0
  270. package/src/ui/docs/folder.tsx +149 -0
  271. package/src/ui/docs/index.ts +21 -0
  272. package/src/ui/docs/markdown.tsx +130 -0
  273. package/src/ui/docs/md-raw.d.ts +4 -0
  274. package/src/ui/docs/plugin.ts +104 -0
  275. package/src/ui/docs/registry.tsx +424 -0
  276. package/src/ui/docs/standalone.tsx +107 -0
  277. package/src/ui/drivers/react.tsx +627 -0
  278. package/src/ui/index.ts +92 -0
  279. package/src/ui/lib/cn.ts +10 -0
  280. package/src/ui/lib/zod-pt-br.ts +38 -0
  281. package/src/ui/meta.ts +412 -0
  282. package/src/ui/react.tsx +235 -0
  283. package/src/ui/router.ts +96 -0
  284. package/src/ui/theme.css +234 -0
  285. package/src/vite/design.ts +652 -0
  286. package/src/vite/index.ts +8 -0
@@ -0,0 +1,86 @@
1
+ ## Padrão (single)
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.
5
+
6
+ ```tsx preview
7
+ <Accordion type="single" collapsible defaultValue="overview" className="w-full">
8
+ <AccordionItem value="overview">
9
+ <AccordionTrigger>Visão geral</AccordionTrigger>
10
+ <AccordionContent>
11
+ <p className="text-muted-foreground">Resumo do workspace Empresa X.</p>
12
+ </AccordionContent>
13
+ </AccordionItem>
14
+ <AccordionItem value="agents">
15
+ <AccordionTrigger>Agentes</AccordionTrigger>
16
+ <AccordionContent>
17
+ <p className="text-muted-foreground">developer, reviewer e designer ativos.</p>
18
+ </AccordionContent>
19
+ </AccordionItem>
20
+ <AccordionItem value="repos">
21
+ <AccordionTrigger>Repositórios</AccordionTrigger>
22
+ <AccordionContent>
23
+ <p className="text-muted-foreground">empresa-x-api e empresa-x-web vinculados.</p>
24
+ </AccordionContent>
25
+ </AccordionItem>
26
+ </Accordion>
27
+ ```
28
+
29
+ ## Múltiplos abertos
30
+
31
+ `type=multiple` permite vários painéis expandidos ao mesmo tempo — `defaultValue` vira um
32
+ array com os itens abertos de saída.
33
+
34
+ ```tsx preview
35
+ <Accordion type="multiple" defaultValue={['skills', 'prompt']} className="w-full">
36
+ <AccordionItem value="prompt">
37
+ <AccordionTrigger>Prompt</AccordionTrigger>
38
+ <AccordionContent>
39
+ <p className="text-muted-foreground">O prompt do agente developer.</p>
40
+ </AccordionContent>
41
+ </AccordionItem>
42
+ <AccordionItem value="skills">
43
+ <AccordionTrigger>Skills</AccordionTrigger>
44
+ <AccordionContent>
45
+ <p className="text-muted-foreground">clean-code, code-style, ddd e test vinculadas.</p>
46
+ </AccordionContent>
47
+ </AccordionItem>
48
+ </Accordion>
49
+ ```
50
+
51
+ ## Controlado (com estado)
52
+
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):
55
+
56
+ ```tsx preview
57
+ const [open, setOpen] = React.useState('developer')
58
+
59
+ render(
60
+ <Accordion type="single" collapsible value={open} onValueChange={setOpen} className="w-full">
61
+ <AccordionItem value="developer">
62
+ <AccordionTrigger>Agente developer</AccordionTrigger>
63
+ <AccordionContent>
64
+ <p className="text-muted-foreground">Implementa a task no worktree da sessão.</p>
65
+ </AccordionContent>
66
+ </AccordionItem>
67
+ <AccordionItem value="reviewer">
68
+ <AccordionTrigger>Agente reviewer</AccordionTrigger>
69
+ <AccordionContent>
70
+ <p className="text-muted-foreground">Revisa o handoff e carimba o diff.</p>
71
+ </AccordionContent>
72
+ </AccordionItem>
73
+ </Accordion>,
74
+ )
75
+ ```
76
+
77
+ ## Props
78
+
79
+ | Prop | Tipo | Default | Descrição |
80
+ |---|---|---|---|
81
+ | `type` (Accordion) | `'single' \| 'multiple'` | | single abre um painel por vez; multiple permite vários. |
82
+ | `collapsible` (Accordion) | `boolean` | `false` | Só 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. |
@@ -0,0 +1,24 @@
1
+ ## Form como card
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.
5
+
6
+ ```tsx preview col md
7
+ <DocBrowserActionProvider>
8
+ <ActionFormCard
9
+ title="Editar workspace"
10
+ action={docWorkspaceCreate}
11
+ defaultValues={{ name: 'Empresa X', status: 'active' }}
12
+ submitLabel="Salvar"
13
+ />
14
+ </DocBrowserActionProvider>
15
+ ```
16
+
17
+ ## Props
18
+
19
+ | Prop | Tipo | Default | Descrição |
20
+ |---|---|---|---|
21
+ | `title` | `string` | | Título do header (com divisor embaixo). Sem ele, o card começa direto no corpo. |
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 conteúdo (CardContent) e o rodapé (CardFooter) por baixo. |
24
+ | `cardClassName` | `string` | | Classes da SUPERFÍCIE do card (ex.: largura). `className` vai pro `<form>`. |
@@ -0,0 +1,30 @@
1
+ ## Form em modal
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.
6
+
7
+ ```tsx preview
8
+ const [open, setOpen] = useState(false)
9
+
10
+ render(
11
+ <DocBrowserActionProvider>
12
+ <Button onClick={() => setOpen(true)}><Plus /> Novo workspace</Button>
13
+ <ActionFormDialog
14
+ open={open}
15
+ onOpenChange={setOpen}
16
+ title="Novo workspace"
17
+ description="O workspace agrupa os repositórios e agentes do cliente."
18
+ action={docWorkspaceCreate}
19
+ />
20
+ </DocBrowserActionProvider>,
21
+ )
22
+ ```
23
+
24
+ ## Props
25
+
26
+ | Prop | Tipo | Default | Descrição |
27
+ |---|---|---|---|
28
+ | `open / onOpenChange` | `boolean / (open: boolean) => void` | | Controle do modal — o pattern chama onOpenChange(false) no sucesso e no Cancelar. |
29
+ | `title / description` | `string` | | O DialogHeader fixo — description é opcional. |
30
+ | `…ActionFormProps` | `action, defaultValues, onSuccess, fieldOptions…` | | Todo o resto desce pro ActionForm interno — mesma API da página dele. |
@@ -0,0 +1,125 @@
1
+ ## Form derivado do contrato
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 só habilita com mudança real (dirty).
6
+
7
+ ```tsx preview col md
8
+ <DocBrowserActionProvider>
9
+ <ActionForm action={docWorkspaceCreate} />
10
+ </DocBrowserActionProvider>
11
+ ```
12
+
13
+ ## Composição (diagramação livre)
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).
20
+
21
+ ```tsx preview col md
22
+ <DocBrowserActionProvider>
23
+ <ActionForm action={docWorkspaceCreate}>
24
+ <div className="space-y-6">
25
+ <div className="grid grid-cols-2 gap-4">
26
+ <ActionFormField name="name" />
27
+ <ActionFormField name="status" />
28
+ </div>
29
+ <ActionFormField name="contact" />
30
+ <ActionFormField name="notes" />
31
+ </div>
32
+ </ActionForm>
33
+ </DocBrowserActionProvider>
34
+ ```
35
+
36
+ ## Valores iniciais e rótulos
37
+
38
+ defaultValues pré-carrega (modo edição); submitLabel/cancelLabel trocam o rodapé. O onCancel é de
39
+ quem orquestra (fechar drawer, voltar…).
40
+
41
+ ```tsx preview col md
42
+ <DocBrowserActionProvider>
43
+ <ActionForm
44
+ action={docWorkspaceCreate}
45
+ defaultValues={{ name: 'Empresa X', status: 'active' }}
46
+ submitLabel="Atualizar"
47
+ onCancel={() => undefined}
48
+ />
49
+ </DocBrowserActionProvider>
50
+ ```
51
+
52
+ ## Pré-requisitos
53
+
54
+ O driver precisa dos dois providers no root — exatamente o wiring do admin (o consumidor
55
+ canônico).
56
+
57
+ ```tsx
58
+ /* main.tsx do app. */
59
+ <QueryClientProvider client={queryClient}>
60
+ <TbdlibProvider client={apiClient}>
61
+ <App />
62
+ </TbdlibProvider>
63
+ </QueryClientProvider>
64
+ ```
65
+
66
+ ## Props
67
+
68
+ | Prop | Tipo | Default | Descrição |
69
+ |---|---|---|---|
70
+ | `action` | `FormContract<TInput, TData>` | | O contrato da FormAction — dele saem campos (input Zod + fields), mensagens e invalidação de cache. |
71
+ | `defaultValues` | `Partial<TInput>` | | Valores iniciais — o modo edição de um update/patch. |
72
+ | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (o toast e a invalidação de cache já aconteceram). |
73
+ | `submitLabel / cancelLabel / onCancel` | `string / string / () => void` | `'Salvar' / 'Cancelar'` | Rodapé do form — o Cancelar só aparece com onCancel. |
74
+ | `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. |
77
+
78
+ ## ActionFormField
79
+
80
+ | Prop | Tipo | Descrição |
81
+ |---|---|---|
82
+ | `name` | `string` | O campo do contrato (chave em `fields`/schema). Fora do schema, não renderiza. |
83
+ | `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). |
85
+
86
+ ## De onde vêm as opções de um select
87
+
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.
95
+
96
+ ## Controle custom: useActionFormContext
97
+
98
+ Campo com UI própria (grade de permissões, canvas…) que nenhum widget cobre? No modo
99
+ composição, o hook dá acesso ao form do contrato — o campo custom participa do submit
100
+ sem abandonar o `<ActionForm>`:
101
+
102
+ ```tsx
103
+ import { useActionFormContext } from '@softize/opus/ui/react'
104
+
105
+ function PermissionGrid() {
106
+ const { form } = useActionFormContext()
107
+ const perms = (form.watch('permissions') as string[] | undefined) ?? []
108
+ const toggle = (p: string) =>
109
+ form.setValue(
110
+ 'permissions',
111
+ perms.includes(p) ? perms.filter((x) => x !== p) : [...perms, p],
112
+ { shouldValidate: true, shouldDirty: true },
113
+ )
114
+ const error = form.formState.errors['permissions']
115
+ // …sua grade; zod-resolver, erro inline e submit continuam do contrato.
116
+ }
117
+
118
+ <ActionForm action={roleUpdate}>
119
+ <ActionFormField name="name" />
120
+ <PermissionGrid />
121
+ </ActionForm>
122
+ ```
123
+
124
+ O contexto também expõe `shape` (Zod por campo), `fields` (FieldSpec) e `fieldOptions`.
125
+ Fora de um `<ActionForm>`, o hook explode com mensagem clara — igual ao `ActionFormField`.
@@ -0,0 +1,68 @@
1
+ ## ListAction em modal
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.
4
+
5
+ ```tsx preview col
6
+ const [open, setOpen] = useState(false)
7
+
8
+ render(
9
+ <DocBrowserActionProvider>
10
+ <Button onClick={() => setOpen(true)}>Workspaces</Button>
11
+ <ActionListDialog
12
+ action={docWorkspaceList}
13
+ input={{}}
14
+ open={open}
15
+ onOpenChange={setOpen}
16
+ title="Workspaces"
17
+ description="Back-office"
18
+ actions={
19
+ <Button size="sm" variant="outline">
20
+ <Plus /> Workspace
21
+ </Button>
22
+ }
23
+ emptyText="Nenhum workspace."
24
+ >
25
+ {(workspaces) => (
26
+ <div className="space-y-2">
27
+ {workspaces.map((w) => (
28
+ <div key={w.id} className="flex items-center gap-2 rounded-md border border-border p-3">
29
+ <span className="text-sm font-semibold">{w.name}</span>
30
+ <Badge variant={w.status === 'active' ? 'success' : 'warning'}>{w.status}</Badge>
31
+ <span className="ml-auto text-xs text-muted-foreground">{w.agents} agentes</span>
32
+ </div>
33
+ ))}
34
+ </div>
35
+ )}
36
+ </ActionListDialog>
37
+ </DocBrowserActionProvider>,
38
+ )
39
+ ```
40
+
41
+ ## O que o contrato não sabe
42
+
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).
44
+
45
+ ```tsx
46
+ <ActionListDialog
47
+ action={agentListContract}
48
+ input={{ workspaceId }}
49
+ loading={rolesLoading} // A query irmã.
50
+ empty={(repos) => repos.length === 0 && !adding} // Form inline aberto ⇒ children.
51
+ ...
52
+ />
53
+ ```
54
+
55
+ ## Props
56
+
57
+ | Prop | Tipo | Default | Descrição |
58
+ |---|---|---|---|
59
+ | `action / input` | `ListAction / TInput` | | O contrato e os filtros — mudou o input, re-busca; `invalidates` de forms/triggers refaz sozinho. |
60
+ | `open / onOpenChange` | `boolean / (open) => void` | | Controle do modal — de quem orquestra. |
61
+ | `title / description` | `string` | | O DialogHeader fixo (em geral, o que é a lista e de quem). |
62
+ | `children` | `(items, refetch) => ReactNode` | | O layout dos itens (cards/linhas) — os estados já saíram daqui. |
63
+ | `note` | `ReactNode \| (items) => ReactNode` | `"N no total"` | Nota à esquerda da toolbar — o default é derivado dos itens. |
64
+ | `actions` | `ReactNode` | | Ação à direita da toolbar — em geral o botão de criar. |
65
+ | `empty` | `(items) => boolean` | `items.length === 0` | Sobrepõe o vazio derivado. |
66
+ | `loading` | `boolean` | | Carga extra agregada à do fetch (query irmã). |
67
+ | `emptyText / errorText` | `string` | | Textos dos estados (DataState). |
68
+ | `className` | `string` | `sm:max-w-3xl` | Largura do DialogContent. |
@@ -0,0 +1,194 @@
1
+ ## Declarativo pelo contrato (a diagramação da casa)
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.
4
+
5
+ ```tsx preview col
6
+ render(
7
+ <DocBrowserActionProvider>
8
+ <div className="w-full">
9
+ <ActionList action={docWorkspaceList} input={{}} emptyMessage="Nenhum workspace." />
10
+ </div>
11
+ </DocBrowserActionProvider>,
12
+ )
13
+ ```
14
+
15
+ ## Células custom (cells)
16
+
17
+ As colunas do contrato descrevem DADOS; apresentação especial entra por cima com `cells` (chave = key da coluna). É onde vivem chips coloridos por dict, links e a coluna de ações.
18
+
19
+ ```tsx preview col
20
+ const STATUS_VARIANT = { active: 'success', onboarding: 'warning', paused: 'outline' }
21
+
22
+ render(
23
+ <DocBrowserActionProvider>
24
+ <div className="w-full">
25
+ <ActionList
26
+ action={docWorkspaceList}
27
+ input={{}}
28
+ cells={{
29
+ status: (w) => <Badge variant={STATUS_VARIANT[w.status]}>{w.status}</Badge>,
30
+ }}
31
+ />
32
+ </div>
33
+ </DocBrowserActionProvider>,
34
+ )
35
+ ```
36
+
37
+ ## Período (o campo dinâmico)
38
+
39
+ 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.
40
+
41
+ ```tsx
42
+ periods: [
43
+ { value: 'today', label: 'Hoje' },
44
+ { value: 'last7', label: 'Últimos 7 dias' },
45
+ { value: 'thisMonth', label: 'Este mês' },
46
+ ]
47
+ // Presets computáveis: today · yesterday · last7 · last30 · thisMonth · lastMonth.
48
+ ```
49
+
50
+ ## Paginação e loading
51
+
52
+ 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") 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 refetch com a lista já na tela, o corpo esmaece (`aria-busy`) até os dados chegarem — o skeleton é só do primeiro load.
53
+
54
+ ## Multi-seleção com can (batch)
55
+
56
+ `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.
57
+
58
+ ```tsx
59
+ <ActionList action={runList} input={{}}
60
+ batch={[{
61
+ label: 'Reprocessar',
62
+ can: (run) => run.status === 'failed',
63
+ confirm: { title: 'Reprocessar as execuções?' },
64
+ run: async (runs) => { await Promise.all(runs.map((r) => api.retry(r.id))) },
65
+ }]}
66
+ />
67
+ ```
68
+
69
+ ## Views (a mesma listagem, outro renderer)
70
+
71
+ 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.
72
+
73
+ ```tsx preview col
74
+ render(
75
+ <DocBrowserActionProvider>
76
+ <div className="w-full">
77
+ <ActionList
78
+ action={docWorkspaceList}
79
+ input={{}}
80
+ views={{
81
+ gallery: {
82
+ label: 'Galeria',
83
+ icon: <LayoutGrid />,
84
+ render: (workspaces) => (
85
+ <div className="grid grid-cols-2 gap-3">
86
+ {workspaces.map((w) => (
87
+ <div key={w.id} className="rounded-lg border border-border p-3">
88
+ <div className="flex items-center gap-2">
89
+ <span className="text-sm font-semibold">{w.name}</span>
90
+ <Badge variant={w.status === 'active' ? 'success' : 'warning'}>{w.status}</Badge>
91
+ </div>
92
+ <p className="mt-1 text-xs text-muted-foreground">{w.agents} agentes.</p>
93
+ </div>
94
+ ))}
95
+ </div>
96
+ ),
97
+ },
98
+ }}
99
+ />
100
+ </div>
101
+ </DocBrowserActionProvider>,
102
+ )
103
+ ```
104
+
105
+ ## Filtros dependentes, dictionary e lookup
106
+
107
+ Três recursos do FilterSpec que a toolbar honra:
108
+
109
+ - **`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.
110
+ - **`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.
111
+ - **`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.
112
+
113
+ ```tsx
114
+ filters: {
115
+ project: { label: 'Projeto', type: 'select', options: { kind: 'static', items: [...] } },
116
+ sprint: {
117
+ label: 'Sprint',
118
+ type: 'lookup',
119
+ depends: ['project'],
120
+ options: { kind: 'lookup', source: 'sprint.lookup', depends: ['project'] },
121
+ },
122
+ }
123
+ ```
124
+
125
+ ## Exibição e URL sync
126
+
127
+ 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:
128
+
129
+ ```tsx
130
+ <ActionList
131
+ action={contract}
132
+ input={{}}
133
+ state={listParamsToState(params, contract)}
134
+ onStateChange={(next) => navigate(`/rota${listStateToParams(next, contract)}`)}
135
+ />
136
+ ```
137
+
138
+ Convenção compacta (defaults omitidos): `?q=…&sort=chave:dir&status=…&view=board&cols=a,b,c&limit=25`.
139
+
140
+ ## Composição (layout livre)
141
+
142
+ 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.
143
+
144
+ ```tsx preview col
145
+ render(
146
+ <DocBrowserActionProvider>
147
+ <div className="w-full">
148
+ <ActionList action={docWorkspaceList} input={{}}>
149
+ {(workspaces) => (
150
+ <div className="grid w-full grid-cols-2 gap-3">
151
+ {workspaces.map((w) => (
152
+ <div key={w.id} className="rounded-lg border border-border p-3">
153
+ <div className="flex items-center gap-2">
154
+ <span className="text-sm font-semibold">{w.name}</span>
155
+ <Badge variant={w.status === 'active' ? 'success' : 'warning'}>{w.status}</Badge>
156
+ </div>
157
+ <p className="mt-1 text-xs text-muted-foreground">{w.agents} agentes.</p>
158
+ </div>
159
+ ))}
160
+ </div>
161
+ )}
162
+ </ActionList>
163
+ </div>
164
+ </DocBrowserActionProvider>,
165
+ )
166
+ ```
167
+
168
+ ## No contrato (ListAction)
169
+
170
+ | Chave | O que declara |
171
+ |---|---|
172
+ | `columns` | `{ key, label, type?, sortable?, fit?, hidden?, dateFormat? }` — a tabela. `hidden` fica fora (base do futuro column picker). |
173
+ | `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. |
174
+ | `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. |
175
+ | `sort` | `{ fields, default }` — ordenação inicial; header ordenável escreve `sort: 'chave:dir'`. |
176
+ | `periods` | `{ value, label }[]` — o controle de período (presets + Personalizado com calendário); materializa em `from`/`to` no input. |
177
+
178
+ ## Props
179
+
180
+ | Prop | Tipo | Default | Descrição |
181
+ |---|---|---|---|
182
+ | `action` | `ListAction<TInput, TItem>` | | A ListAction do Opus (kind list, output = item, paginate cursor). |
183
+ | `input` | `TInput` | | O ESCOPO BASE (ex.: { workspaceId }) — a toolbar soma por cima, nunca sobrescreve. |
184
+ | `cells` | `Record<string, (item) => ReactNode>` | | Células custom por cima das colunas do contrato (chave = column.key). |
185
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Opções de runtime pros filtros select/lookup (chave = nome do filtro). |
186
+ | `columns` | `ActionListColumn<TItem>[]` | | Tabela EXPLÍCITA — sobrepõe as colunas do contrato (escape hatch). |
187
+ | `children` | `(items, refetch) => ReactNode` | | Modo COMPOSIÇÃO: layout livre; a toolbar segue. Tem precedência sobre columns. |
188
+ | `batch` | `ActionListBatchAction[]` | | Ações em lote: `{ label, can?, run, confirm?, destructive? }` — liga a multi-seleção. |
189
+ | `rowId` | `(item) => string` | `item.id` | Identidade da linha pra seleção. |
190
+ | `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. |
191
+ | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — pra quem embala sincronizar com a URL. |
192
+ | `emptyMessage` | `string` | `'Nenhum resultado.'` | O texto do estado vazio. |
193
+ | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar pro detalhe) — só na tabela. |
194
+ | `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`. |
@@ -0,0 +1,72 @@
1
+ ## Disparo direto
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.
5
+
6
+ ```tsx preview
7
+ <DocBrowserActionProvider>
8
+ <ActionTrigger action={docSessionArchive} input={{ id: 'onboarding-faturamento' }} variant="outline" />
9
+ </DocBrowserActionProvider>
10
+ ```
11
+
12
+ ## Confirmação declarada no contrato
13
+
14
+ O ConfirmSpec mora na action (title/message/destructive) — o pattern monta o Dialog sozinho e o
15
+ destructive já tonaliza o botão. Prop confirm sobrepõe quando a tela precisar de outro texto.
16
+
17
+ ```tsx preview
18
+ <DocBrowserActionProvider>
19
+ <ActionTrigger action={docSessionDelete} input={{ id: 'onboarding-faturamento' }} />
20
+ </DocBrowserActionProvider>
21
+ ```
22
+
23
+ ```tsx
24
+ // No contrato (spec):
25
+ confirm: {
26
+ title: 'Excluir a sessão?',
27
+ message: 'O worktree e o preview desta sessão serão removidos.',
28
+ confirmLabel: 'Excluir',
29
+ destructive: true,
30
+ }
31
+
32
+ // Na tela — nada além do disparo:
33
+ <ActionTrigger action={sessionDeleteContract} input={{ id: session.id }} />
34
+ ```
35
+
36
+ ## A ação que mora no item (icon)
37
+
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.
39
+
40
+ ```tsx
41
+ <ActionTrigger
42
+ action={unitDeleteContract}
43
+ input={{ id: unit.id }}
44
+ icon={<Trash2 />}
45
+ label="Excluir"
46
+ itemLabel={unit.name}
47
+ />
48
+ ```
49
+
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.
51
+
52
+ ## Erro que a pessoa entende
53
+
54
+ Quando a action falha, a frase do SERVIDOR só vence o rótulo do contrato se o erro for de negócio — `conflict`, `validation`, `not_found`:
55
+
56
+ > Este agente tem 3 conversas — desabilite em vez de excluir.
57
+
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.
59
+
60
+ ## Props
61
+
62
+ | Prop | Tipo | Default | Descrição |
63
+ |---|---|---|---|
64
+ | `action` | `SimpleContract<TInput, TData>` | | A SimpleAction do Opus — label, messages e confirm vêm do contrato. |
65
+ | `input` | `TInput` | | O que a action recebe — geralmente { id }. |
66
+ | `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 já escolhe sozinho. |
68
+ | `confirm` | `{ title, description?, actionLabel?, cancelLabel? }` | | Confirmação via prop — sobrepõe o ConfirmSpec do contrato. |
69
+ | `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. |
71
+ | `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). |
@@ -0,0 +1,47 @@
1
+ ## Detalhe com estados padronizados
2
+
3
+ Troque o workspace pra ver o skeleton; o inexistente mostra o erro padrão com o Tentar de novo. O
4
+ preview roda num client de mentira — no app, cada feature embrulha (TicketView…) delegando o
5
+ fetching pra cá.
6
+
7
+ ```tsx preview col
8
+ const [id, setId] = useState('empresa-x')
9
+
10
+ render(
11
+ <DocBrowserActionProvider>
12
+ <div className="w-full space-y-3">
13
+ <div className="flex flex-wrap gap-2">
14
+ <Button variant="outline" size="sm" onClick={() => setId('empresa-x')}>Empresa X</Button>
15
+ <Button variant="outline" size="sm" onClick={() => setId('softize-multica')}>Softize · Multica</Button>
16
+ <Button variant="outline" size="sm" onClick={() => setId('sessao-fantasma')}>Inexistente (erro)</Button>
17
+ </div>
18
+ <div className="rounded-lg border border-border p-4">
19
+ <ActionView action={docWorkspaceView} input={{ id }}>
20
+ {(ws) => (
21
+ <div className="space-y-1.5">
22
+ <div className="flex items-center gap-2">
23
+ <h3 className="text-sm font-semibold">{ws.name}</h3>
24
+ <Badge variant={ws.status === 'active' ? 'success' : 'warning'}>{ws.status}</Badge>
25
+ </div>
26
+ <p className="text-sm text-muted-foreground">
27
+ Cliente {ws.client} · {ws.agents} agentes vinculados.
28
+ </p>
29
+ </div>
30
+ )}
31
+ </ActionView>
32
+ </div>
33
+ </div>
34
+ </DocBrowserActionProvider>,
35
+ )
36
+ ```
37
+
38
+ ## Props
39
+
40
+ | Prop | Tipo | Default | Descrição |
41
+ |---|---|---|---|
42
+ | `action` | `ViewAction<TInput, TData>` | | A ViewAction do Opus (kind view, 1 recurso). |
43
+ | `input` | `TInput` | | Geralmente { id } — mudou, recarrega. |
44
+ | `children` | `(data: TData, refetch) => ReactNode` | | O estado feliz — layout 100% do consumidor; refetch pra recarregar por código. |
45
+ | `render` | `(data: TData, refetch) => ReactNode` | | Alias de children (a API original). Children tem precedência. |
46
+ | `loading / empty` | `ReactNode` | `3 skeletons / nada` | Sobrescreve os estados padrão quando o contexto pedir. |
47
+ | `error` | `(err, retry) => ReactNode` | `mensagem + Tentar de novo` | Sobrescreve o estado de erro padrão. |