@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,44 @@
1
+ ---
2
+ title: MCP
3
+ ---
4
+
5
+ # MCP
6
+
7
+ Expõe as actions `ai:enabled` do runtime como **tools MCP** — a porta pra uma IA de **fora**
8
+ (Claude Desktop, o agente de um parceiro, um hub próprio) alcançar o app pelo protocolo. É o
9
+ mesmo bridge do agente co-locado (`runtime.aiTools`) e a mesma execução (`runtime.execute`,
10
+ como o usuário resolvido). O MCP é só o transporte.
11
+
12
+ ## Montar
13
+
14
+ ```ts
15
+ import { createOpusMcpServer } from '@softize/opus/mcp'
16
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
17
+
18
+ const server = createOpusMcpServer(runtime, {
19
+ name: 'meu-app',
20
+ // Valida a identidade da requisição (IdP) → contexto do runtime. Sem isso, roda anônimo
21
+ // (o `can` nega → só actions públicas). Com isso, o agente age COMO O USUÁRIO.
22
+ resolveContext: async (extra) => auth.resolveFromMcp(extra),
23
+ })
24
+
25
+ await server.connect(new StdioServerTransport()) // local; HTTP/SSE pra remoto
26
+ ```
27
+
28
+ `ListTools` devolve as actions `ai:enabled` (nome + descrição + JSON Schema do input);
29
+ `CallTool` executa a action pelo `runtime.execute` — validação, auth (`ctx.can`) e audit,
30
+ tudo igual a uma chamada normal. Read-only? Marque só actions de leitura com `ai:enabled`.
31
+
32
+ ## Interno × MCP
33
+
34
+ - **Monolito / chat co-locado:** não precisa de MCP — `runtime.aiFor(base).run()` chama as
35
+ actions direto, in-process (ver o recurso **IA generativa**).
36
+ - **IA por fora / ecossistema:** o agente vive num serviço próprio e alcança N apps pela
37
+ mesma porta MCP. É aqui que ele se paga.
38
+
39
+ Mesmas tools, transportes diferentes.
40
+
41
+ ## Limites (por enquanto)
42
+
43
+ Você monta o transporte (stdio/HTTP) — o server é agnóstico. A confirmação de action
44
+ `destructive` sobre MCP (elicitation) entra quando um caso real cobrar; comece read-only.
@@ -0,0 +1,114 @@
1
+ ## Menu de ações
2
+
3
+ Ícone à esquerda, atalho à direita (MenuShortcut), separador antes da zona perigosa e variant destructive na ação que destrói.
4
+
5
+ ```tsx preview
6
+ <Menu>
7
+ <MenuTrigger asChild>
8
+ <Button variant="outline">Ações <ChevronDown /></Button>
9
+ </MenuTrigger>
10
+ <MenuContent align="start">
11
+ <MenuItem><Pencil /> Editar <MenuShortcut>⌘E</MenuShortcut></MenuItem>
12
+ <MenuItem><Copy /> Duplicar</MenuItem>
13
+ <MenuSeparator />
14
+ <MenuItem variant="destructive"><Trash2 /> Excluir</MenuItem>
15
+ </MenuContent>
16
+ </Menu>
17
+ ```
18
+
19
+ ## Contexto: clique direito
20
+
21
+ O papel do ContextMenu (aposentado na 5.0.0) com o MESMO componente — só muda o
22
+ gatilho: controlado, ancorado no ponteiro. `onContextMenu` guarda a posição e abre;
23
+ um gatilho invisível `position: fixed` naquele ponto ancora o conteúdo.
24
+
25
+ ```tsx preview
26
+ const [pos, setPos] = useState(null)
27
+
28
+ render(
29
+ <div>
30
+ <div
31
+ onContextMenu={(e) => {
32
+ e.preventDefault()
33
+ setPos({ x: e.clientX, y: e.clientY })
34
+ }}
35
+ className="flex h-28 items-center justify-center rounded-md border border-dashed text-sm text-muted-foreground select-none"
36
+ >
37
+ clique direito aqui
38
+ </div>
39
+ <Menu open={pos !== null} onOpenChange={(open) => { if (!open) setPos(null) }}>
40
+ <MenuTrigger asChild>
41
+ <span style={{ position: 'fixed', left: pos?.x ?? 0, top: pos?.y ?? 0 }} />
42
+ </MenuTrigger>
43
+ <MenuContent align="start">
44
+ <MenuItem><Pencil /> Renomear</MenuItem>
45
+ <MenuItem><Copy /> Duplicar</MenuItem>
46
+ <MenuSeparator />
47
+ <MenuItem variant="destructive"><Trash2 /> Excluir</MenuItem>
48
+ </MenuContent>
49
+ </Menu>
50
+ </div>,
51
+ )
52
+ ```
53
+
54
+ ## Seleção: checkbox e radio
55
+
56
+ CheckboxItem pra liga/desliga, RadioGroup pra escolha exclusiva — estado fica no consumidor (controlado).
57
+
58
+ ```tsx preview
59
+ const [showArchived, setShowArchived] = useState(false)
60
+ const [order, setOrder] = useState('recent')
61
+
62
+ render(
63
+ <Menu>
64
+ <MenuTrigger asChild>
65
+ <Button variant="outline">Exibição <ChevronDown /></Button>
66
+ </MenuTrigger>
67
+ <MenuContent align="start">
68
+ <MenuLabel>Sessões</MenuLabel>
69
+ <MenuCheckboxItem checked={showArchived} onCheckedChange={setShowArchived}>
70
+ Mostrar arquivadas
71
+ </MenuCheckboxItem>
72
+ <MenuSeparator />
73
+ <MenuLabel>Ordenar por</MenuLabel>
74
+ <MenuRadioGroup value={order} onValueChange={setOrder}>
75
+ <MenuRadioItem value="recent">Mais recentes</MenuRadioItem>
76
+ <MenuRadioItem value="name">Nome</MenuRadioItem>
77
+ </MenuRadioGroup>
78
+ </MenuContent>
79
+ </Menu>,
80
+ )
81
+ ```
82
+
83
+ ## Submenu e inset
84
+
85
+ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
86
+
87
+ ```tsx preview
88
+ <Menu>
89
+ <MenuTrigger asChild>
90
+ <Button variant="outline">Sessão <ChevronDown /></Button>
91
+ </MenuTrigger>
92
+ <MenuContent align="start">
93
+ <MenuItem><ExternalLink /> Abrir preview</MenuItem>
94
+ <MenuSub>
95
+ <MenuSubTrigger><ArrowDownAZ /> Mover pra</MenuSubTrigger>
96
+ <MenuSubContent>
97
+ <MenuItem>Empresa X</MenuItem>
98
+ <MenuItem>Softize</MenuItem>
99
+ </MenuSubContent>
100
+ </MenuSub>
101
+ <MenuItem inset>Renomear</MenuItem>
102
+ </MenuContent>
103
+ </Menu>
104
+ ```
105
+
106
+ ## Props
107
+
108
+ | Prop | Tipo | Default | Descrição |
109
+ |---|---|---|---|
110
+ | `MenuItem.variant` | `'default' \| 'destructive'` | `'default'` | destructive tonaliza texto e foco em vermelho — só pra ação que destrói. |
111
+ | `MenuItem.inset` | `boolean` | | Recuo à esquerda pra alinhar item sem ícone com os que têm. |
112
+ | `MenuContent.align / sideOffset` | `'start' \| 'center' \| 'end' / number` | `'center' / 4` | Alinhamento e distância em relação ao gatilho (Radix). |
113
+ | `MenuCheckboxItem.checked / onCheckedChange` | `boolean / (checked: boolean) => void` | | Estado do liga/desliga — controlado pelo consumidor. |
114
+ | `MenuRadioGroup.value / onValueChange` | `string / (value: string) => void` | | Escolha exclusiva entre os MenuRadioItem filhos. |
@@ -0,0 +1,83 @@
1
+ ---
2
+ title: Microcopy
3
+ ---
4
+
5
+ # Microcopy
6
+
7
+ Código em inglês; o que humano lê (UI, comentário, doc) em pt-BR. Frases começam com
8
+ maiúscula e terminam com ponto; labels curtos em sentence case, sem ponto. **Esta página
9
+ é a fonte** — a skill `code-style` aponta pra cá (regra duplicada em vez de apontada é
10
+ defeito).
11
+
12
+ ## Língua por camada
13
+
14
+ > Regra dura — não tem caso a caso.
15
+
16
+ | Camada | Língua |
17
+ |---|---|
18
+ | Identificadores de código (vars, funções, tipos, arquivos, dirs, domínios) | Inglês |
19
+ | Nomes de action / rota | Inglês (`resource.verb`) |
20
+ | Colunas e tabelas de banco | Inglês (snake_case) |
21
+ | Valores de código (enums, status, chaves de dict) | Inglês — mesmo em exemplos e fixtures (`active`, não `ativo`); a UI mostra o label pt |
22
+ | Texto de UI visível (labels, headings, botões, placeholders, mensagens) | pt-BR |
23
+ | Comentários e docs (`.md`) | pt-BR |
24
+
25
+ ## Frase vs label
26
+
27
+ > Frase (mensagem, descrição, tooltip, help): maiúscula no início, ponto no fim. Label
28
+ > (botão, heading, item de menu, chip): sentence case, sem ponto.
29
+
30
+ | Tipo | Assim | Assim não |
31
+ |---|---|---|
32
+ | Frase | Opus não inicializado. | ~~opus nao inicializado~~ |
33
+ | Frase | O Multica não respondeu — tente de novo. | ~~ERRO: request failed (500)~~ |
34
+ | Label | Publicar | ~~PUBLICAR~~ |
35
+ | Label | Nova sessão | ~~Nova sessão.~~ |
36
+ | Placeholder | Selecione um cliente… | ~~Selecionar~~ · ~~digite o nome…~~ |
37
+
38
+ ## As regras que mais pegam
39
+
40
+ > Erros recorrentes de revisão — atenção redobrada nestes.
41
+
42
+ - **Maiúscula é ortografia, não estilo.** Toda frase começa com maiúscula — em UI,
43
+ hint/help **e comentário de código** — e nome próprio idem (Engineer, Multica, GitHub).
44
+ Vale com interpolação: mensagem que começaria com valor ganha um artigo na frente
45
+ (`O workspace '${slug}' não existe…`).
46
+ - **Frase fluida, não telegrama.** Símbolo não é conector: nada de `—`/`+`/`≈` amarrando
47
+ ideias em label ou help. `Staff — acesso ao back-office + todos os workspaces` vira
48
+ label `Staff` + help `Tem acesso ao back-office e a todos os workspaces.`.
49
+ - **"Opus" e "Softize" são nomes próprios.** Em prosa, maiúscula (`o Opus`, `a Softize`);
50
+ os tokens técnicos ficam minúsculos: `opus check`, `@softize/opus`, `opus.json`,
51
+ `softize.com.br`. É o nome → maiúscula; é código → minúscula.
52
+ - **Abreviação é último caso.** Por extenso: `mínimo`, `máximo`, `configuração`.
53
+ `Config indisponível.` é duplamente errado (seco E abreviado): `A configuração não
54
+ carregou.`. `Ex.:` em placeholder é tolerado (com `E` maiúsculo).
55
+ - **Placeholder de seleção segue UM padrão** (select simples, buscável, múltiplo):
56
+ `Selecione <artigo> <coisa>…` — com `…` (o caractere único), nunca o genérico
57
+ `Selecionar`. Declare no contrato (`fields.<campo>.placeholder`) — o default do
58
+ componente é rede de segurança, não estilo; dois pickers na mesma tela com padrões
59
+ diferentes é defeito.
60
+ - **Nada de maiúsculo deliberado.** Sem ALL-CAPS — nem a classe `uppercase` — em labels,
61
+ headers ou badges.
62
+ - **Exemplos são neutros e duráveis.** Nada de cliente real ou com cara de real: use
63
+ Empresa X / Empresa Y (`contato@empresa-x.com.br`). Exemplo "de verdade" envelhece,
64
+ vaza contexto e vira dívida.
65
+
66
+ ## Tom
67
+
68
+ > Fale com o usuário, não com o log.
69
+
70
+ - **Calmo e informativo, não alarmista.** "Indisponível" em vez de "ERRO"/"FALHOU".
71
+ Não culpe o usuário.
72
+ - **Orientado à ação.** Quando dá, diga o próximo passo: `Inicie o ambiente`,
73
+ `Crie a primeira acima`.
74
+ - **Conciso, mas natural — não telegráfico.** Prefira a forma completa do verbo a um
75
+ corte abrupto. O meio-termo é a régua: nem seco demais (`rode no projeto`) nem manual
76
+ técnico (jargão de arquitetura num help).
77
+ - **Explique pelo efeito, não pelo mecanismo.** Se precisou citar sistema interno pra
78
+ explicar (`o push sobrescreve o runtime`), reescreva pelo que acontece pro usuário
79
+ (`os agentes deste workspace passam a usar esta skill`). Mecanismo é doc/comentário,
80
+ não tooltip.
81
+ - **Nada de endpoint, status ou stack na tela** — detalhe técnico vai pro console/log.
82
+ - **Termos do produto** (sessão, ambiente, cliente, agente), não do código. E o mesmo
83
+ termo sempre: se a UI fala "cliente", nenhum label diz "client".
@@ -0,0 +1,34 @@
1
+ ## Esqueleto de página
2
+
3
+ O que toda página do back-office repete, num pattern só: `<main>` + container de largura cheia (o caso comum; `className="max-w-5xl"` estreita e centra) + header com título, descrição e a ação à direita. O conteúdo é children, sem nada imposto. Presentacional de propósito — uma página agrega N fontes; o que é action-driven (listas, forms) mora dentro.
4
+
5
+ ```tsx preview col
6
+ render(
7
+ <div className="w-full overflow-hidden rounded-lg border border-border">
8
+ <Page
9
+ title="Workspaces"
10
+ count={3}
11
+ actions={
12
+ <Button>
13
+ <Plus /> Novo workspace
14
+ </Button>
15
+ }
16
+ >
17
+ <div className="rounded-lg border border-dashed border-border p-10 text-center text-sm text-muted-foreground">
18
+ O conteúdo da página (tabela, cards, grid…).
19
+ </div>
20
+ </Page>
21
+ </div>,
22
+ )
23
+ ```
24
+
25
+ ## Props
26
+
27
+ | Prop | Tipo | Default | Descrição |
28
+ |---|---|---|---|
29
+ | `title` | `string` | | O h1 da página. |
30
+ | `count` | `number` | | Total de itens ao lado do título (mono, esmaecido). |
31
+ | `description` | `ReactNode` | | Linha de contexto sob o título (ex.: "N no total · X ativos"). |
32
+ | `actions` | `ReactNode` | | Ação à direita do header — em geral o botão de criar. |
33
+ | `className` | `string` | largura cheia | Classes do container (`max-w-5xl` estreita e centra). |
34
+ | `children` | `ReactNode` | | O conteúdo — espaçamento e diagramação são seus. |
@@ -0,0 +1,99 @@
1
+ ## Básico
2
+
3
+ É composição: Pagination embrulha PaginationContent, e cada PaginationItem segura um link. isActive no PaginationLink marca a página atual (vira outline).
4
+
5
+ ```tsx preview
6
+ <Pagination>
7
+ <PaginationContent>
8
+ <PaginationItem>
9
+ <PaginationPrevious href="#" />
10
+ </PaginationItem>
11
+ <PaginationItem>
12
+ <PaginationLink href="#">1</PaginationLink>
13
+ </PaginationItem>
14
+ <PaginationItem>
15
+ <PaginationLink href="#" isActive>
16
+ 2
17
+ </PaginationLink>
18
+ </PaginationItem>
19
+ <PaginationItem>
20
+ <PaginationLink href="#">3</PaginationLink>
21
+ </PaginationItem>
22
+ <PaginationItem>
23
+ <PaginationNext href="#" />
24
+ </PaginationItem>
25
+ </PaginationContent>
26
+ </Pagination>
27
+ ```
28
+
29
+ ## Com elipse
30
+
31
+ PaginationEllipsis é o atalho decorativo (aria-hidden) entre blocos de páginas distantes — útil quando a lista de sessões do workspace tem páginas demais pra caber na barra.
32
+
33
+ ```tsx preview
34
+ <Pagination>
35
+ <PaginationContent>
36
+ <PaginationItem>
37
+ <PaginationPrevious href="#" />
38
+ </PaginationItem>
39
+ <PaginationItem>
40
+ <PaginationLink href="#">1</PaginationLink>
41
+ </PaginationItem>
42
+ <PaginationItem>
43
+ <PaginationEllipsis />
44
+ </PaginationItem>
45
+ <PaginationItem>
46
+ <PaginationLink href="#" isActive>
47
+ 8
48
+ </PaginationLink>
49
+ </PaginationItem>
50
+ <PaginationItem>
51
+ <PaginationLink href="#">9</PaginationLink>
52
+ </PaginationItem>
53
+ <PaginationItem>
54
+ <PaginationNext href="#" />
55
+ </PaginationItem>
56
+ </PaginationContent>
57
+ </Pagination>
58
+ ```
59
+
60
+ ## Controlado
61
+
62
+ Os links são <a> — sem href, vire onClick e guarde a página no estado. Aqui a barra pagina os repositórios do workspace Empresa X.
63
+
64
+ ```tsx preview col
65
+ const [page, setPage] = useState(2)
66
+ const pages = [1, 2, 3]
67
+
68
+ render(
69
+ <Pagination>
70
+ <PaginationContent>
71
+ <PaginationItem>
72
+ <PaginationPrevious
73
+ onClick={() => setPage((p) => Math.max(1, p - 1))}
74
+ />
75
+ </PaginationItem>
76
+ {pages.map((n) => (
77
+ <PaginationItem key={n}>
78
+ <PaginationLink isActive={n === page} onClick={() => setPage(n)}>
79
+ {n}
80
+ </PaginationLink>
81
+ </PaginationItem>
82
+ ))}
83
+ <PaginationItem>
84
+ <PaginationNext
85
+ onClick={() => setPage((p) => Math.min(pages.length, p + 1))}
86
+ />
87
+ </PaginationItem>
88
+ </PaginationContent>
89
+ </Pagination>,
90
+ )
91
+ ```
92
+
93
+ ## Props
94
+
95
+ | Prop | Tipo | Default | Descrição |
96
+ |---|---|---|---|
97
+ | `isActive (PaginationLink)` | `boolean` | `false` | Marca a página atual: vira outline e ganha aria-current="page". Os demais ficam ghost. |
98
+ | `size (PaginationLink)` | `'default' \| 'sm' \| 'lg' \| 'icon'` | `'icon'` | Tamanho do link, herdado do Button. Previous/Next já forçam default pra caber o texto. |
99
+ | `href (PaginationLink)` | `string` | | Cada link é um <a>: passe href pra navegação real, ou use onClick no modo controlado. |
@@ -0,0 +1,49 @@
1
+ ## Estrutura
2
+
3
+ PopoverHeader agrupa título e descrição; o corpo é livre (form curto, detalhes). Superfície bg-popover — a elevação da casa.
4
+
5
+ ```tsx preview
6
+ <Popover>
7
+ <PopoverTrigger asChild>
8
+ <Button variant="outline">Renomear sessão</Button>
9
+ </PopoverTrigger>
10
+ <PopoverContent align="start">
11
+ <PopoverHeader>
12
+ <PopoverTitle>Renomear</PopoverTitle>
13
+ <PopoverDescription>O slug técnico não muda — só o título.</PopoverDescription>
14
+ </PopoverHeader>
15
+ <div className="mt-3 space-y-2">
16
+ <Label htmlFor="pop-titulo">Título</Label>
17
+ <Input id="pop-titulo" defaultValue="Onboarding do faturamento" />
18
+ <Button size="sm" className="w-full">Salvar</Button>
19
+ </div>
20
+ </PopoverContent>
21
+ </Popover>
22
+ ```
23
+
24
+ ## Alinhamento
25
+
26
+ align posiciona o painel em relação ao gatilho; sideOffset afasta. O padrão (center) serve pra quase tudo.
27
+
28
+ ```tsx preview
29
+ <Popover>
30
+ <PopoverTrigger asChild><Button variant="ghost">start</Button></PopoverTrigger>
31
+ <PopoverContent align="start" className="w-56">
32
+ <PopoverDescription>Alinhado à borda esquerda do gatilho.</PopoverDescription>
33
+ </PopoverContent>
34
+ </Popover>
35
+ <Popover>
36
+ <PopoverTrigger asChild><Button variant="ghost">end</Button></PopoverTrigger>
37
+ <PopoverContent align="end" className="w-56">
38
+ <PopoverDescription>Alinhado à borda direita do gatilho.</PopoverDescription>
39
+ </PopoverContent>
40
+ </Popover>
41
+ ```
42
+
43
+ ## Props
44
+
45
+ | Prop | Tipo | Default | Descrição |
46
+ |---|---|---|---|
47
+ | `PopoverContent.align` | `'start' \| 'center' \| 'end'` | `'center'` | Alinhamento do painel em relação ao gatilho. |
48
+ | `PopoverContent.sideOffset` | `number` | `4` | Distância (px) entre gatilho e painel. |
49
+ | `Popover.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) — pra fechar por código depois de salvar. |
@@ -0,0 +1,69 @@
1
+ ## Básico
2
+
3
+ value vai de 0 a 100 — o preenchimento anima a cada mudança. Sem value (ou null) a barra fica vazia.
4
+
5
+ ```tsx preview col
6
+ <div className="w-full max-w-sm space-y-2">
7
+ <div className="flex items-center justify-between text-sm">
8
+ <span>opus check</span>
9
+ <span className="text-muted-foreground">65%</span>
10
+ </div>
11
+ <Progress value={65} />
12
+ </div>
13
+ ```
14
+
15
+ ## Controlado
16
+
17
+ Guarde o value no estado e atualize conforme a tarefa avança — aqui cada clique soma um passo na sincronização do workspace Empresa X.
18
+
19
+ ```tsx preview col
20
+ const [step, setStep] = useState(40)
21
+ const advance = (): void => setStep((prev) => Math.min(prev + 20, 100))
22
+
23
+ render(
24
+ <div className="w-full max-w-sm space-y-3">
25
+ <div className="flex items-center justify-between text-sm">
26
+ <span>Sincronizar Empresa X</span>
27
+ <span className="text-muted-foreground">{step}%</span>
28
+ </div>
29
+ <Progress value={step} />
30
+ <button
31
+ type="button"
32
+ onClick={advance}
33
+ className="rounded-md border px-3 py-1.5 text-sm hover:bg-muted"
34
+ >
35
+ Avançar etapa
36
+ </button>
37
+ </div>,
38
+ )
39
+ ```
40
+
41
+ ## Altura e cor
42
+
43
+ className compõe sobre o padrão: ajuste a altura no Progress e tinja o preenchimento mirando o slot do indicador (&_[data-slot=progress-indicator]) para sinalizar bom/atenção.
44
+
45
+ ```tsx preview col
46
+ <div className="w-full max-w-sm space-y-4">
47
+ <div className="space-y-1.5">
48
+ <span className="text-sm">Cobertura de testes</span>
49
+ <Progress
50
+ value={92}
51
+ className="h-1.5 [&_[data-slot=progress-indicator]]:bg-emerald-500"
52
+ />
53
+ </div>
54
+ <div className="space-y-1.5">
55
+ <span className="text-sm">Skills cobertas pelo revisor</span>
56
+ <Progress
57
+ value={45}
58
+ className="h-3 [&_[data-slot=progress-indicator]]:bg-amber-500"
59
+ />
60
+ </div>
61
+ </div>
62
+ ```
63
+
64
+ ## Props
65
+
66
+ | Prop | Tipo | Default | Descrição |
67
+ |---|---|---|---|
68
+ | `value` | `number \| null` | | O progresso de 0 a 100. O preenchimento anima a cada mudança; null/ausente deixa a barra vazia. |
69
+ | `className` | `string` | | Compõe sobre o padrão — ajuste a altura (h-1.5/h-3) ou tinja o indicador via [&_[data-slot=progress-indicator]]:bg-*. |
@@ -0,0 +1,62 @@
1
+ ---
2
+ title: Filas
3
+ ---
4
+
5
+ # Filas
6
+
7
+ Action pesada não segura o request: marcada como background, ela é enfileirada e processada
8
+ fora da linha, e o cliente acompanha por um handle. O contrato é o `QueueAdapter`.
9
+
10
+ ## O contrato
11
+
12
+ ```ts
13
+ interface QueueAdapter {
14
+ enqueue(spec: JobSpec): Promise<JobHandle>
15
+ status(jobId: string): Promise<JobHandle | null>
16
+ cancel(jobId: string): Promise<boolean>
17
+ subscribe?(jobId: string, listener: (h: JobHandle) => void): Unsubscribe
18
+ }
19
+
20
+ interface JobHandle<T = unknown> {
21
+ jobId: string
22
+ action: string
23
+ status: 'queued' | 'running' | 'done' | 'failed' | 'cancelled'
24
+ progress?: { percent?: number; message?: string }
25
+ data?: T
26
+ error?: ActionError
27
+ attempts: number
28
+ }
29
+ ```
30
+
31
+ O core monta o `JobSpec` (action, input, ctx, retry/priority/timeout); o adapter persiste e
32
+ processa.
33
+
34
+ ## Driver
35
+
36
+ ```ts
37
+ import { Queue } from 'bullmq'
38
+ import { bullmqQueue } from '@softize/opus/queue/bullmq'
39
+
40
+ const queue = bullmqQueue({ queue: new Queue('opus', { connection: { host: 'localhost' } }) })
41
+ ```
42
+
43
+ Redis-backed (durável, entre-processos, retries). Peers opcionais (`bullmq`, `ioredis`) só
44
+ pra quem usa o driver.
45
+
46
+ ## No runtime
47
+
48
+ ```ts
49
+ const runtime = createRuntime({
50
+ // …server/data…
51
+ queue,
52
+ })
53
+ ```
54
+
55
+ A action declara `background: true` (+ retry/priority no contrato); chamá-la devolve um
56
+ `JobHandle` em vez do resultado, e o cliente pola `status(jobId)` (ou `subscribe`) até
57
+ `done`/`failed`.
58
+
59
+ ## Limites (por enquanto)
60
+
61
+ Driver hoje: `bullmq` (Redis). In-memory pra dev e outros backends (SQS, pg-boss…) entram
62
+ por reincidência.
@@ -0,0 +1,77 @@
1
+ ## Básico
2
+
3
+ Cada RadioGroupItem tem um value; o item escolhido é o value do RadioGroup. defaultValue deixa o estado com o componente. Pareie cada item com um Label (htmlFor↔id).
4
+
5
+ ```tsx preview col-start
6
+ <RadioGroup defaultValue="balanced">
7
+ <div className="flex items-center gap-2">
8
+ <RadioGroupItem id="speed" value="speed" />
9
+ <Label htmlFor="speed">Priorizar velocidade</Label>
10
+ </div>
11
+ <div className="flex items-center gap-2">
12
+ <RadioGroupItem id="balanced" value="balanced" />
13
+ <Label htmlFor="balanced">Equilibrado</Label>
14
+ </div>
15
+ <div className="flex items-center gap-2">
16
+ <RadioGroupItem id="thorough" value="thorough" />
17
+ <Label htmlFor="thorough">Priorizar profundidade</Label>
18
+ </div>
19
+ </RadioGroup>
20
+ ```
21
+
22
+ ## Controlado
23
+
24
+ value + onValueChange no RadioGroup levam o estado pra fora — o padrão pra escolher quem recebe o handoff de uma task.
25
+
26
+ ```tsx preview col-start
27
+ const [agent, setAgent] = useState('reviewer')
28
+
29
+ render(
30
+ <RadioGroup value={agent} onValueChange={setAgent}>
31
+ <div className="flex items-center gap-2">
32
+ <RadioGroupItem id="developer" value="developer" />
33
+ <Label htmlFor="developer">developer</Label>
34
+ </div>
35
+ <div className="flex items-center gap-2">
36
+ <RadioGroupItem id="reviewer" value="reviewer" />
37
+ <Label htmlFor="reviewer">reviewer</Label>
38
+ </div>
39
+ <div className="flex items-center gap-2">
40
+ <RadioGroupItem id="designer" value="designer" />
41
+ <Label htmlFor="designer">designer</Label>
42
+ </div>
43
+ </RadioGroup>,
44
+ )
45
+ ```
46
+
47
+ ## Item desabilitado
48
+
49
+ disabled num RadioGroupItem esmaece e tira a opção da escolha; o Label em par esmaece junto (peer-disabled). Pra bloquear o grupo inteiro, ponha disabled no RadioGroup.
50
+
51
+ ```tsx preview col-start
52
+ <RadioGroup defaultValue="empresa-x-web">
53
+ <div className="flex items-center gap-2">
54
+ <RadioGroupItem id="empresa-x-web" value="empresa-x-web" />
55
+ <Label htmlFor="empresa-x-web">empresa-x-web</Label>
56
+ </div>
57
+ <div className="flex items-center gap-2">
58
+ <RadioGroupItem id="empresa-x-api" value="empresa-x-api" />
59
+ <Label htmlFor="empresa-x-api">empresa-x-api</Label>
60
+ </div>
61
+ <div className="flex items-center gap-2">
62
+ <RadioGroupItem id="empresa-x-mobile" value="empresa-x-mobile" disabled />
63
+ <Label htmlFor="empresa-x-mobile">empresa-x-mobile (sem worktree)</Label>
64
+ </div>
65
+ </RadioGroup>
66
+ ```
67
+
68
+ ## Props
69
+
70
+ | Prop | Tipo | Default | Descrição |
71
+ |---|---|---|---|
72
+ | `value (RadioGroup)` | `string` | | A opção escolhida, no modo controlado — pareie com onValueChange. |
73
+ | `onValueChange (RadioGroup)` | `(value: string) => void` | | Chamado quando o usuário escolhe outra opção. |
74
+ | `defaultValue (RadioGroup)` | `string` | | A opção inicial no modo não controlado. |
75
+ | `disabled (RadioGroup)` | `boolean` | `false` | Bloqueia e esmaece o grupo inteiro. |
76
+ | `value (RadioGroupItem)` | `string` | | O valor que este item representa — vira o value do grupo quando escolhido. |
77
+ | `disabled (RadioGroupItem)` | `boolean` | `false` | Esmaece e tira só este item da escolha — o Label em par esmaece junto. |