@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,85 @@
1
+ ## Básico
2
+
3
+ Compõe Carousel>CarouselContent>CarouselItem, com CarouselPrevious/CarouselNext pra navegar. As setas ficam fora do trilho (-left-12/-right-12), então reserve a margem lateral no entorno.
4
+
5
+ ```tsx preview
6
+ <Carousel className="mx-12 w-full max-w-xs">
7
+ <CarouselContent>
8
+ <CarouselItem>
9
+ <div className="flex h-32 items-center justify-center rounded-lg border bg-card text-sm">
10
+ Sessão #128
11
+ </div>
12
+ </CarouselItem>
13
+ <CarouselItem>
14
+ <div className="flex h-32 items-center justify-center rounded-lg border bg-card text-sm">
15
+ Sessão #131
16
+ </div>
17
+ </CarouselItem>
18
+ <CarouselItem>
19
+ <div className="flex h-32 items-center justify-center rounded-lg border bg-card text-sm">
20
+ Sessão #134
21
+ </div>
22
+ </CarouselItem>
23
+ </CarouselContent>
24
+ <CarouselPrevious />
25
+ <CarouselNext />
26
+ </Carousel>
27
+ ```
28
+
29
+ ## Vários por vista
30
+
31
+ O basis do CarouselItem decide quantos cabem na vista — basis-1/3 mostra três slides por vez. Bom pra galeria de workspaces ou repositórios.
32
+
33
+ ```tsx preview
34
+ <Carousel className="mx-12 w-full max-w-sm" opts={{ align: 'start' }}>
35
+ <CarouselContent>
36
+ {['Sessão #128', 'Sessão #131', 'Sessão #134', 'Sessão #137', 'Sessão #140'].map((session) => (
37
+ <CarouselItem key={session} className="basis-1/3">
38
+ <div className="flex h-24 items-center justify-center rounded-lg border bg-card text-xs">
39
+ {session}
40
+ </div>
41
+ </CarouselItem>
42
+ ))}
43
+ </CarouselContent>
44
+ <CarouselPrevious />
45
+ <CarouselNext />
46
+ </Carousel>
47
+ ```
48
+
49
+ ## Vertical
50
+
51
+ orientation=vertical empilha os slides; as setas migram pra cima e pra baixo (-top-12/-bottom-12). Dê uma altura ao CarouselContent pra delimitar a vista.
52
+
53
+ ```tsx preview
54
+ <Carousel className="w-full max-w-xs" orientation="vertical">
55
+ <CarouselContent className="h-48">
56
+ <CarouselItem>
57
+ <div className="flex h-24 items-center justify-center rounded-lg border bg-card text-sm">
58
+ developer
59
+ </div>
60
+ </CarouselItem>
61
+ <CarouselItem>
62
+ <div className="flex h-24 items-center justify-center rounded-lg border bg-card text-sm">
63
+ reviewer
64
+ </div>
65
+ </CarouselItem>
66
+ <CarouselItem>
67
+ <div className="flex h-24 items-center justify-center rounded-lg border bg-card text-sm">
68
+ designer
69
+ </div>
70
+ </CarouselItem>
71
+ </CarouselContent>
72
+ <CarouselPrevious />
73
+ <CarouselNext />
74
+ </Carousel>
75
+ ```
76
+
77
+ ## Props
78
+
79
+ | Prop | Tipo | Default | Descrição |
80
+ |---|---|---|---|
81
+ | `orientation (Carousel)` | `'horizontal' \| 'vertical'` | `'horizontal'` | Eixo do deslize — vertical empilha os slides e gira as setas pro topo/base. |
82
+ | `opts (Carousel)` | `CarouselOptions` | | Opções do embla (ex.: { loop: true }, { align: "start" }). Repassadas direto pro motor. |
83
+ | `setApi (Carousel)` | `(api: CarouselApi) => void` | | Recebe a instância do embla pra controlar de fora (scrollTo, ler o slide ativo). |
84
+ | `plugins (Carousel)` | `CarouselPlugin` | | Plugins do embla (ex.: autoplay) anexados ao carousel. |
85
+ | `className (CarouselItem)` | `string` | | O basis decide quantos slides cabem na vista (basis-full, basis-1/2, basis-1/3). |
@@ -0,0 +1,69 @@
1
+ ## Básico
2
+
3
+ Um chat mínimo: lista de mensagens + composer. A conversa é gerenciada por dentro (estado, loading, auto-scroll; **Enter** envia, **Shift+Enter** quebra linha) — a inteligência vem da prop `send`. O `greeting` é o estado vazio (centrado; some quando a conversa começa e NÃO entra no transcript). Dê altura ao container.
4
+
5
+ ```tsx preview
6
+ <div className="h-96 rounded-lg border">
7
+ <Chat
8
+ greeting="Oi! Sou o assistente. Pergunte algo."
9
+ send={async (messages) => {
10
+ await new Promise((r) => setTimeout(r, 500))
11
+ const last = messages[messages.length - 1]
12
+ return `Você disse: "${last.content}". (Num app real, aqui rodaria o agente.)`
13
+ }}
14
+ />
15
+ </div>
16
+ ```
17
+
18
+ ## O backend (o agente)
19
+
20
+ O `send` posta a conversa no seu endpoint, que resolve o usuário e roda o agente sobre as actions `ai:enabled` — **como o usuário logado**:
21
+
22
+ ```ts
23
+ // Rota do app (server): recebe { messages }, devolve { reply }.
24
+ async function chatRoute(req) {
25
+ const base = await auth.resolveContext(req) // quem é o usuário
26
+ const { messages } = req.body
27
+ const { text } = await runtime.aiFor(base)!.run(messages) // o agente age nas actions
28
+ return { reply: text }
29
+ }
30
+ ```
31
+
32
+ ```tsx
33
+ // No cliente, o send bate nessa rota:
34
+ <Chat send={async (messages) => (await api.post('/chat', { messages })).reply} />
35
+ ```
36
+
37
+ O modelo só faz o que o usuário pode (`ctx.can`); o que é destrutivo pede `confirm`. Para expor as actions ao agente, marque-as com `ai: { enabled: true }` no contrato — ver o recurso **IA generativa**.
38
+
39
+ ## Streaming — eventos de conversa
40
+
41
+ O `send` também aceita devolver um **`AsyncIterable<ChatEvent>`** (o protocolo de
42
+ `docs/chat-event-protocol.md`): o texto chega incremental, o tool em uso vira o
43
+ **indicador vivo** (pontinhos + rótulo humano — nunca uma mensagem no transcript) e
44
+ artefatos produzidos na conversa rendem pelo `renderArtifact` do app. O backend liga em
45
+ `runtime.aiFor(base).runStream(...)` e streama por SSE; o `Promise<string>` de sempre
46
+ continua valendo — é o caso degenerado do protocolo.
47
+
48
+ ```tsx preview
49
+ <div className="h-96 rounded-lg border">
50
+ <Chat
51
+ greeting="Oi! Pergunte algo — a resposta chega em streaming."
52
+ send={async function* (messages) {
53
+ yield { type: 'tool', name: 'query' }
54
+ await new Promise((r) => setTimeout(r, 600))
55
+ const last = messages[messages.length - 1]
56
+ for (const word of `Você disse: "${last.content}".`.split(' ')) {
57
+ yield { type: 'text', delta: word + ' ' }
58
+ await new Promise((r) => setTimeout(r, 80))
59
+ }
60
+ yield { type: 'artifact', kind: 'url', ref: 'https://opus.softize.com.br', title: 'Um artefato da conversa' }
61
+ yield { type: 'done', ok: true }
62
+ }}
63
+ />
64
+ </div>
65
+ ```
66
+
67
+ Reidratação: passe `initialMessages` com o histórico persistido e troque a `key` do
68
+ componente ao trocar de conversa. O rótulo do indicador é customizável por
69
+ `humanizeTool={(name) => '…'}`.
@@ -0,0 +1,75 @@
1
+ ## Básico
2
+
3
+ Sempre em par com Label (htmlFor↔id) — clicar no texto alterna a caixa. defaultChecked pro modo não controlado.
4
+
5
+ ```tsx preview
6
+ <div className="flex items-center gap-2">
7
+ <Checkbox id="run-check" defaultChecked />
8
+ <Label htmlFor="run-check">Rodar opus check antes do handoff</Label>
9
+ </div>
10
+ ```
11
+
12
+ ## Controlado
13
+
14
+ onCheckedChange recebe boolean | 'indeterminate' — compare com true pra guardar um boolean.
15
+
16
+ ```tsx preview
17
+ const [autoReview, setAutoReview] = useState(true)
18
+
19
+ render(
20
+ <div className="flex items-center gap-2">
21
+ <Checkbox
22
+ id="auto-review"
23
+ checked={autoReview}
24
+ onCheckedChange={(v) => setAutoReview(v === true)}
25
+ />
26
+ <Label htmlFor="auto-review">Acionar o revisor ao abrir o handoff</Label>
27
+ </div>,
28
+ )
29
+ ```
30
+
31
+ ## Lista de opções
32
+
33
+ Várias caixas, um estado: o conjunto marcado é a lista de valores — o padrão pra anexar skills a um agente.
34
+
35
+ ```tsx preview col-start
36
+ const [skills, setSkills] = useState(['clean-code', 'test'])
37
+ const toggleSkill = (skill: string): void => {
38
+ setSkills((prev) => (prev.includes(skill) ? prev.filter((s) => s !== skill) : [...prev, skill]))
39
+ }
40
+
41
+ render(
42
+ <>
43
+ {['clean-code', 'code-style', 'ddd', 'test'].map((skill) => (
44
+ <div key={skill} className="flex items-center gap-2">
45
+ <Checkbox id={skill} checked={skills.includes(skill)} onCheckedChange={() => toggleSkill(skill)} />
46
+ <Label htmlFor={skill}>{skill}</Label>
47
+ </div>
48
+ ))}
49
+ </>,
50
+ )
51
+ ```
52
+
53
+ ## Desabilitado
54
+
55
+ disabled esmaece a caixa e o rótulo em par (peer-disabled no Label) — marcado ou não.
56
+
57
+ ```tsx preview col-start
58
+ <div className="flex items-center gap-2">
59
+ <Checkbox id="sync-multica" disabled />
60
+ <Label htmlFor="sync-multica">Sincronizar com o Multica</Label>
61
+ </div>
62
+ <div className="flex items-center gap-2">
63
+ <Checkbox id="opus" disabled defaultChecked />
64
+ <Label htmlFor="opus">Opus inicializado</Label>
65
+ </div>
66
+ ```
67
+
68
+ ## Props
69
+
70
+ | Prop | Tipo | Default | Descrição |
71
+ |---|---|---|---|
72
+ | `checked` | `boolean \| 'indeterminate'` | | O estado, no modo controlado — parear com onCheckedChange. |
73
+ | `onCheckedChange` | `(checked: boolean \| 'indeterminate') => void` | | Chamado a cada alternância. Pra guardar um boolean, compare com true. |
74
+ | `defaultChecked` | `boolean` | `false` | Estado inicial no modo não controlado. |
75
+ | `disabled` | `boolean` | `false` | Esmaece e bloqueia — o Label em par esmaece junto (peer-disabled). |
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: CLI opus
3
+ ---
4
+
5
+ # CLI opus
6
+
7
+ O Opus traz um CLI que cobre o ciclo: faz o bootstrap, gera artefatos a partir das declarações,
8
+ valida as convenções e expõe o estado vivo pros agentes via MCP.
9
+
10
+ ## Gates
11
+
12
+ > A régua de entrega: 0 violação antes de qualquer handoff. Rode no CI e antes de commitar.
13
+
14
+ ```bash
15
+ opus check src # valida as convenções das actions (exit ≠ 0 se violar)
16
+ opus db check # drift entidade ↔ banco (read-only)
17
+ opus db migrate # aplica o schema idempotente + drift-check na sequência
18
+ ```
19
+
20
+ ## Geração e introspecção
21
+
22
+ > As declarações (`description` de entidades/actions) são a fonte; o `gen` as projeta. O manifest
23
+ > é um lockfile commitado — diff de manifest é ouro pra review.
24
+
25
+ ```bash
26
+ opus gen # manifest / openapi / docs / stubs a partir do opus.config.ts
27
+ opus introspect # modelo da estrutura (actions/reactions/schedules + wiring)
28
+ opus introspect --json
29
+ ```
30
+
31
+ ## Bootstrap e templates
32
+
33
+ > `create` scaffolda um app novo com os pré-requisitos plugados; `setup` é per-app
34
+ > (idempotente, nunca sobrescreve o seu); `add` copia um template do registry pro consumer.
35
+
36
+ ```bash
37
+ opus create meu-app # app canônico do zero: protocolo + UI + preview + dia zero
38
+ opus create meu-cliente --monorepo # a RAIZ de um workspace (apps/* + packages/*)
39
+ opus create apps/portal # dentro de um workspace: só o app (modo detectado)
40
+ opus setup # grava opus.json, mantém o bloco do CLAUDE.md e o dia zero
41
+ opus list # lista os templates disponíveis
42
+ opus add action-form # copia um template do registry pro projeto
43
+ ```
44
+
45
+ O esqueleto do `create` versiona com o Opus (sai do mesmo pacote que o SDK que ele
46
+ configura) e nasce com os gates verdes: domínio-exemplo canônico, teste, manifest,
47
+ CI de fábrica e o dev server pronto pro preview do Maestro. Dois modos, por detecção:
48
+ repo standalone (template inteiro) ou **app em monorepo** (dentro de um workspace pnpm:
49
+ só os arquivos do app; o que a raiz precisa ter vira aviso, sem clobber).
50
+
51
+ ## MCP — estado vivo pros agentes
52
+
53
+ > O server MCP expõe `introspect`/`check`/`create-action`. É como um agente lê a estrutura e cria
54
+ > action no formato canônico sem decorar convenção.
55
+
56
+ `opus mcp` sobe o server (configurado em `.mcp.json`): o agente consulta a estrutura, roda o
57
+ check e gera o esqueleto de uma action nova pela skill `create-action` — a mesma régua do
58
+ `opus check`, por construção.
@@ -0,0 +1,64 @@
1
+ ## Básico
2
+
3
+ CollapsibleTrigger alterna o CollapsibleContent — o Trigger já é o <button>. defaultOpen deixa o estado com o componente.
4
+
5
+ ```tsx preview col
6
+ <Collapsible defaultOpen className="w-full max-w-sm space-y-2">
7
+ <CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted">
8
+ Repositórios do workspace
9
+ <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />
10
+ </CollapsibleTrigger>
11
+ <CollapsibleContent className="space-y-1 px-3 text-sm text-muted-foreground">
12
+ <p>empresa-x-api</p>
13
+ <p>empresa-x-web</p>
14
+ <p>empresa-x-infra</p>
15
+ </CollapsibleContent>
16
+ </Collapsible>
17
+ ```
18
+
19
+ ## Controlado
20
+
21
+ open + onOpenChange põem o estado nas suas mãos — dá pra refletir no gatilho (aqui o chevron gira) ou guardar a preferência.
22
+
23
+ ```tsx preview col
24
+ const [open, setOpen] = useState(false)
25
+
26
+ render(
27
+ <Collapsible open={open} onOpenChange={setOpen} className="w-full max-w-sm space-y-2">
28
+ <CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium hover:bg-muted">
29
+ Skills do agente developer
30
+ <ChevronDown className={open ? 'h-4 w-4 rotate-180 transition-transform' : 'h-4 w-4 transition-transform'} />
31
+ </CollapsibleTrigger>
32
+ <CollapsibleContent className="space-y-1 px-3 text-sm text-muted-foreground">
33
+ <p>clean-code</p>
34
+ <p>code-style</p>
35
+ <p>ddd</p>
36
+ </CollapsibleContent>
37
+ </Collapsible>,
38
+ )
39
+ ```
40
+
41
+ ## Desabilitado
42
+
43
+ disabled no Collapsible trava o gatilho — a seção fica fixa no estado atual (aqui, fechada).
44
+
45
+ ```tsx preview col
46
+ <Collapsible disabled className="w-full max-w-sm space-y-2">
47
+ <CollapsibleTrigger className="flex w-full items-center justify-between rounded-md border border-border px-3 py-2 text-sm font-medium disabled:cursor-not-allowed disabled:opacity-50">
48
+ Sessões arquivadas
49
+ <ChevronsUpDown className="h-4 w-4 text-muted-foreground" />
50
+ </CollapsibleTrigger>
51
+ <CollapsibleContent className="px-3 text-sm text-muted-foreground">
52
+ <p>Nada para mostrar enquanto o workspace está em sincronização.</p>
53
+ </CollapsibleContent>
54
+ </Collapsible>
55
+ ```
56
+
57
+ ## Props
58
+
59
+ | Prop | Tipo | Default | Descrição |
60
+ |---|---|---|---|
61
+ | `defaultOpen (Collapsible)` | `boolean` | `false` | Estado inicial no modo não controlado. |
62
+ | `open (Collapsible)` | `boolean` | | Estado no modo controlado — pareie com onOpenChange. |
63
+ | `onOpenChange (Collapsible)` | `(open: boolean) => void` | | Chamado a cada abertura ou fechamento. |
64
+ | `disabled (Collapsible)` | `boolean` | `false` | Trava o gatilho — a seção fica presa no estado atual. |
@@ -0,0 +1,56 @@
1
+ ## Lista filtrável inline
2
+
3
+ Digite pra filtrar — navegação por teclado, grupos e CommandEmpty de graça (cmdk). É a base do Select buscável; pra escolha em form, use o Select.
4
+
5
+ ```tsx preview
6
+ <Command className="max-w-sm rounded-lg border border-border">
7
+ <CommandInput placeholder="Buscar sessão ou ação…" />
8
+ <CommandList>
9
+ <CommandEmpty>Nada encontrado.</CommandEmpty>
10
+ <CommandGroup heading="Sessões">
11
+ <CommandItem><FolderGit2 /> Onboarding do faturamento</CommandItem>
12
+ <CommandItem><FolderGit2 /> Integração com o ERP</CommandItem>
13
+ </CommandGroup>
14
+ <CommandSeparator />
15
+ <CommandGroup heading="Ações">
16
+ <CommandItem><Plus /> Nova sessão <CommandShortcut>⌘N</CommandShortcut></CommandItem>
17
+ <CommandItem><Play /> Iniciar ambiente</CommandItem>
18
+ </CommandGroup>
19
+ </CommandList>
20
+ </Command>
21
+ ```
22
+
23
+ ## Palette modal (CommandDialog)
24
+
25
+ O Command embrulhado num Dialog, com header sr-only pra a11y. O atalho ⌘K (keydown no app) só troca o open — o conteúdo é o mesmo do inline.
26
+
27
+ ```tsx preview
28
+ const [open, setOpen] = useState(false)
29
+
30
+ render(
31
+ <>
32
+ <Button variant="outline" onClick={() => setOpen(true)}>
33
+ Abrir palette <CommandShortcut>⌘K</CommandShortcut>
34
+ </Button>
35
+ <CommandDialog open={open} onOpenChange={setOpen}>
36
+ <CommandInput placeholder="Buscar comando…" />
37
+ <CommandList>
38
+ <CommandEmpty>Nada encontrado.</CommandEmpty>
39
+ <CommandGroup heading="IA">
40
+ <CommandItem onSelect={() => setOpen(false)}><Bot /> Chamar agente</CommandItem>
41
+ <CommandItem onSelect={() => setOpen(false)}><Sparkles /> Importar skill</CommandItem>
42
+ </CommandGroup>
43
+ </CommandList>
44
+ </CommandDialog>
45
+ </>,
46
+ )
47
+ ```
48
+
49
+ ## Props
50
+
51
+ | Prop | Tipo | Default | Descrição |
52
+ |---|---|---|---|
53
+ | `CommandDialog.open / onOpenChange` | `boolean / (open: boolean) => void` | | Controle do modal — ligue ao atalho de teclado do app. |
54
+ | `CommandDialog.title / description` | `string` | `'Comandos' / 'Busque um comando pra executar.'` | Texto sr-only do header (a11y do Dialog) — não aparece na tela. |
55
+ | `CommandDialog.showCloseButton` | `boolean` | `true` | Mostra o X do Dialog — desligue se o Esc/clique fora bastarem. |
56
+ | `CommandItem.onSelect` | `(value: string) => void` | | Dispara ao escolher (Enter ou clique) — feche o palette aqui. |
@@ -0,0 +1,50 @@
1
+ ## Básico
2
+
3
+ A caixa de escrever da casa: textarea numa pílula elevada (`rounded-card` + `ring-edge` + `shadow-card`), **Enter** envia / **Shift+Enter** quebra linha, enviar dentro. É o composer do [Chat](/components/chat) extraído — use SOZINHO quando há entrada de texto mas não um chat (ex.: criar uma sessão). Controlado: o dono do texto é você.
4
+
5
+ ```tsx preview col
6
+ const [text, setText] = React.useState('')
7
+
8
+ render(
9
+ <Composer
10
+ value={text}
11
+ onChange={setText}
12
+ onSubmit={() => setText('')}
13
+ placeholder="Escreva uma mensagem…"
14
+ />,
15
+ )
16
+ ```
17
+
18
+ ## Com seletores (barra de ações)
19
+
20
+ Passe `actions` e o composer vira **duas linhas**: textarea em cima, uma barra limpa embaixo com os seletores à ESQUERDA e o enviar à direita. Estilize os controles como discretos (ghost, sem borda) — a barra não é um formulário. É onde o Maestro prende **app + task** e a GB, o **agente**. O `submitDisabled` trava o enviar além de vazio/busy (ex.: falta escolher o app), sem esconder a barra.
21
+
22
+ ```tsx preview col
23
+ const [text, setText] = React.useState('')
24
+ const [app, setApp] = React.useState('admin')
25
+
26
+ render(
27
+ <Composer
28
+ value={text}
29
+ onChange={setText}
30
+ onSubmit={() => setText('')}
31
+ submitDisabled={app === ''}
32
+ rows={3}
33
+ placeholder="Descreva o que essa sessão vai fazer…"
34
+ actions={
35
+ <Menu>
36
+ <MenuTrigger asChild>
37
+ <Button variant="ghost" size="sm" className="gap-1 text-muted-foreground">
38
+ {app === 'admin' ? 'Admin' : 'Copilot'}
39
+ <ChevronDown className="size-3.5 opacity-60" />
40
+ </Button>
41
+ </MenuTrigger>
42
+ <MenuContent align="start">
43
+ <MenuItem onSelect={() => setApp('admin')}>Admin</MenuItem>
44
+ <MenuItem onSelect={() => setApp('copilot')}>Copilot</MenuItem>
45
+ </MenuContent>
46
+ </Menu>
47
+ }
48
+ />,
49
+ )
50
+ ```
@@ -0,0 +1,120 @@
1
+ ## O trio imperativo: `dialog`
2
+
3
+ `dialog.alert` · `dialog.confirm` · `dialog.prompt` — o trio do browser (`alert`/`confirm`/`prompt`), mas **assíncrono, com tema e a11y-correto**. Cada um pergunta e **espera a resposta**:
4
+
5
+ ```tsx
6
+ await dialog.alert({ title: 'Sessão expirada', description: 'Você será desconectado.' }) // Promise<void>
7
+ const ok = await dialog.confirm({ title: 'Excluir a sessão?', variant: 'destructive' }) // Promise<boolean>
8
+ const nome = await dialog.prompt({ title: 'Nome do snapshot', placeholder: 'ex.: v1-final' }) // Promise<string | null>
9
+ ```
10
+
11
+ É a mesma forma do `toast` (superfície imperativa, host no shell), mas que **responde** — e é isso que tira as ~15 linhas de JSX que cada tela montava. Por baixo é o `AlertDialog`: `role="alertdialog"`, foco preso, **não fecha no clique fora** — a semântica de uma interrupção que EXIGE resposta. Fechar por ESC/Cancelar resolve o "não" de cada tipo (`void`, `false`, `null`).
12
+
13
+ ```tsx preview
14
+ function Demo() {
15
+ const [last, setLast] = useState('—')
16
+ return (
17
+ <div className="flex flex-wrap items-center gap-3">
18
+ <DialogHost />
19
+ <Button variant="outline" size="sm" onClick={async () => {
20
+ await dialog.alert({ title: 'Sessão expirada', description: 'Você será desconectado.' })
21
+ setLast('alert → OK')
22
+ }}>alert</Button>
23
+ <Button variant="outline" size="sm" onClick={async () => {
24
+ const ok = await dialog.confirm({ title: 'Excluir a sessão?', description: 'Some o worktree e o preview.', action: 'Excluir', variant: 'destructive' })
25
+ setLast('confirm → ' + (ok ? 'excluída' : 'mantida'))
26
+ }}>confirm</Button>
27
+ <Button variant="outline" size="sm" onClick={async () => {
28
+ const name = await dialog.prompt({ title: 'Nome do snapshot', placeholder: 'ex.: v1-final' })
29
+ setLast('prompt → ' + (name === null ? 'cancelado' : name))
30
+ }}>prompt</Button>
31
+ <span className="text-sm text-muted-foreground">Última: {last}</span>
32
+ </div>
33
+ )
34
+ }
35
+ render(<Demo />)
36
+ ```
37
+
38
+ ## Por que namespace, e não `confirm()` solto
39
+
40
+ `alert`/`confirm`/`prompt` são **globais do browser**. Como função solta, esquecer o import não daria erro — o TypeScript resolveria pro global e o código abriria a caixa cinza do sistema (sem tema, que webview suprime). `window.dialog` não existe, então o namespace **imuniza** a família contra esse footgun. `confirm()` segue exportado como alias (retrocompat), mas o novo código usa `dialog.confirm`.
41
+
42
+ ## `body`: corpo com conteúdo próprio
43
+
44
+ Os três aceitam `body` — conteúdo que a `description` de texto não expressa (uma lista do que será afetado, um detalhe destacado). É o que antes obrigava a compor o `AlertDialog` à mão:
45
+
46
+ ```tsx preview
47
+ function Demo() {
48
+ const [status, setStatus] = useState('—')
49
+ return (
50
+ <div className="flex items-center gap-3">
51
+ <DialogHost />
52
+ <Button variant="outline" size="sm" onClick={async () => {
53
+ const ok = await dialog.confirm({
54
+ title: 'Encerrar 3 sessões?',
55
+ description: 'Os agentes abaixo serão interrompidos:',
56
+ body: (
57
+ <ul className="list-disc space-y-1 pl-5 text-sm text-muted-foreground">
58
+ <li><span className="font-medium text-foreground">developer</span> — empresa-x-api</li>
59
+ <li><span className="font-medium text-foreground">reviewer</span> — empresa-x-web</li>
60
+ </ul>
61
+ ),
62
+ action: 'Encerrar todas',
63
+ })
64
+ setStatus(ok ? 'encerradas' : 'mantidas')
65
+ }}>Encerrar sessões</Button>
66
+ <span className="text-sm text-muted-foreground">Resposta: {status}</span>
67
+ </div>
68
+ )
69
+ }
70
+ render(<Demo />)
71
+ ```
72
+
73
+ ## O host
74
+
75
+ Monte **um** `<DialogHost />` no shell do app, ao lado do `<Toaster />`:
76
+
77
+ ```tsx
78
+ <AppShell sidebar={…}>
79
+ {rotas}
80
+ <Toaster />
81
+ <DialogHost />
82
+ </AppShell>
83
+ ```
84
+
85
+ Sem ele, os três **lançam** — em vez de devolver uma promise que nunca resolve. Promise pendurada viraria clique sem efeito, o pior desfecho pra uma interrupção que exige resposta: a pessoa acha que respondeu, ou clica de novo. (`<ConfirmHost />` segue valendo como alias de `<DialogHost />`.)
86
+
87
+ Várias perguntas seguidas entram numa **fila** e aparecem uma por vez — cada `await` recebe a resposta do SEU diálogo (duas caixas empilhadas seriam ambíguas sobre qual clique respondeu o quê).
88
+
89
+ ## Quando NÃO usar
90
+
91
+ - **Excluir uma entidade por contrato** → `ActionTrigger` com `icon={<Trash2 />}`: lê o `action.confirm`, dispara a action e fala o erro de negócio.
92
+ - **Um form de verdade** (validação, vários campos, submete uma action) → `ActionFormDialog`. O `dialog.prompt` é só pra UMA string livre.
93
+ - **Mais de duas ações** (ex.: Salvar / Descartar / Cancelar) ou **layout totalmente custom** → componha o `AlertDialog` à mão.
94
+ - **Só avisar, sem cobrar decisão** → `toast` (passageiro) ou `Alert` (fica na página). O `dialog.alert` é pro aviso que **bloqueia** e exige um "OK".
95
+
96
+ ## Opções
97
+
98
+ Comuns aos três (`dialog.alert`/`confirm`/`prompt`):
99
+
100
+ | Opção | Tipo | Default | Descrição |
101
+ |---|---|---|---|
102
+ | `title` | `React.ReactNode` | | O enunciado. Obrigatório. |
103
+ | `description` | `React.ReactNode` | | O que a pessoa precisa saber pra decidir — a CONSEQUÊNCIA, não o óbvio. |
104
+ | `body` | `React.ReactNode` | | Conteúdo próprio entre a descrição e os botões (lista, detalhe). |
105
+ | `media` | `React.ReactNode` | | Ícone grande acima do título (o quadro do `AlertDialogMedia`). |
106
+ | `action` | `string` | `'OK'` / `'Confirmar'` | Rótulo do botão que confirma (default `'OK'` no alert, `'Confirmar'` nos outros). |
107
+
108
+ Só `confirm` e `prompt`:
109
+
110
+ | Opção | Tipo | Default | Descrição |
111
+ |---|---|---|---|
112
+ | `cancel` | `string` | `'Cancelar'` | Rótulo do botão que recusa. |
113
+ | `variant` | `'default' \| 'destructive'` | `'default'` | `destructive` pinta o confirmar — o que apaga tem que parecer o que apaga. |
114
+
115
+ Só `prompt`:
116
+
117
+ | Opção | Tipo | Default | Descrição |
118
+ |---|---|---|---|
119
+ | `placeholder` | `string` | | Texto-fantasma do input. |
120
+ | `defaultValue` | `string` | | Valor inicial (o input abre focado e selecionado). |
@@ -0,0 +1,30 @@
1
+ ## Básico
2
+
3
+ Sem filhos, é um botão-ícone: copia `value` e o ícone vira um check por ~1.5s. Bom pra toolbar ou célula estreita, ao lado de um ID/token/slug.
4
+
5
+ ```tsx preview
6
+ <Copyable value="opus_sk_1a2b3c4d5e6f" className="text-muted-foreground hover:text-foreground" />
7
+ ```
8
+
9
+ ## Com rótulo
10
+
11
+ Com filhos, o valor visível fica à esquerda e o ícone à direita — clicar no conjunto copia. Componha o chrome (borda, mono) por fora, via `className`.
12
+
13
+ ```tsx preview
14
+ <Copyable
15
+ value="softize-dev/opus"
16
+ className="rounded-md border px-2 py-1 font-mono text-xs text-muted-foreground hover:text-foreground"
17
+ >
18
+ softize-dev/opus
19
+ </Copyable>
20
+ ```
21
+
22
+ ## Duração do feedback
23
+
24
+ `feedbackMs` ajusta quanto o check dura (default 1500). Some no-op silencioso se o clipboard não existir (contexto inseguro/SSR).
25
+
26
+ ```tsx preview
27
+ <Copyable value="copiado devagar" feedbackMs={3000} className="text-muted-foreground hover:text-foreground">
28
+ copiar (3s)
29
+ </Copyable>
30
+ ```