@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,110 @@
1
+ ---
2
+ title: Customização
3
+ ---
4
+
5
+ # Customização
6
+
7
+ Cinco alavancas, do global ao pontual — e um limite de propósito. O caminho previsto é compor
8
+ e configurar, nunca forkar componente: o que não cabe nas alavancas evolui no Opus (com
9
+ divergência declarada), pra valer pra casa toda.
10
+
11
+ ## 1 · Identidade por tokens
12
+
13
+ > O jeito canônico de vestir a marca. Sobrescreva as vars no `:root` depois de importar o
14
+ > `theme.css` — todos os componentes acompanham, sem forkar nada. Dark incluso (a classe `.dark`
15
+ > re-declara as vars).
16
+
17
+ ```css
18
+ /* index.css do app — depois do import do tema. */
19
+ @import '@softize/opus/ui/theme.css';
20
+
21
+ :root {
22
+ --primary: hsl(262 83% 58%); /* A cor da marca do projeto. */
23
+ --ring: hsl(262 83% 58%);
24
+ --radius: 0.5rem; /* Cantos mais retos, se for a praia do projeto. */
25
+ }
26
+ ```
27
+
28
+ ## 2 · className em tudo
29
+
30
+ > Todo componente termina em `cn(base, className)` com tailwind-merge: o utilitário do consumidor
31
+ > vence o conflito. Pra layout local (largura, margem, grid) — não pra repintar o visual da casa.
32
+
33
+ ```tsx
34
+ <Button className="w-full">Salvar</Button>
35
+ <Card className="max-w-sm" />
36
+ <DialogContent className="sm:max-w-2xl" />
37
+ ```
38
+
39
+ ## 3 · Recomposição estrutural
40
+
41
+ > Os componentes são explodidos em slots. `asChild` (Radix Slot) renderiza como outro elemento
42
+ > mantendo estilo e comportamento; os `*Variants` aplicam a cara da casa num elemento arbitrário.
43
+
44
+ ```tsx preview
45
+ <Button asChild variant="outline">
46
+ <a href="#" onClick={(e) => e.preventDefault()}>Âncora com cara de botão</a>
47
+ </Button>
48
+ ```
49
+
50
+ ```tsx
51
+ // Slots: só o que a tela pede.
52
+ <Card>
53
+ <CardHeader><CardTitle>Workspace</CardTitle></CardHeader>
54
+ <CardContent>…</CardContent>
55
+ </Card>
56
+
57
+ // asChild: o filho VIRA o botão (sem forkar estilo).
58
+ <Button asChild><a href="/docs">Abrir documentação</a></Button>
59
+
60
+ // buttonVariants: a cara da casa num elemento qualquer.
61
+ <a className={buttonVariants({ variant: 'outline' })}>Link estilizado</a>
62
+ ```
63
+
64
+ ## 4 · Comportamento por props
65
+
66
+ > Modo controlado em tudo que abre ou seleciona (`open`/`onOpenChange`, `value`/`onValueChange`),
67
+ > posicionamento (`align`/`side`/`sideOffset`), `showCloseButton`, `delayDuration`… A orquestração
68
+ > é do implementador; o visual continua o da casa.
69
+
70
+ ```tsx
71
+ const [open, setOpen] = useState(false)
72
+
73
+ <Dialog open={open} onOpenChange={setOpen}>…</Dialog>
74
+ <DialogContent showCloseButton={false} /> {/* modal que exige ação */}
75
+ <PopoverContent align="start" sideOffset={8} />
76
+ <TooltipProvider delayDuration={200}>…
77
+ ```
78
+
79
+ ## 5 · Nos patterns, o contrato é a customização
80
+
81
+ > Os patterns (ActionForm/Search/View/Trigger): o spec dirige. `fields`/`widget`/`showWhen`/
82
+ > `fieldOptions` no form, `columns` no search, render prop no view. O caminho previsto é a feature
83
+ > EMBRULHAR o pattern — nunca forkar.
84
+
85
+ ```tsx
86
+ // O form NASCE do contrato; a tela só ajusta o que é seu.
87
+ <ActionForm
88
+ action={workspaceCreateContract}
89
+ fieldOptions={{ skillIds: skillsDoWorkspace }}
90
+ onSuccess={(d) => navigate(`/workspaces/${d.id}`)}
91
+ />
92
+
93
+ // O search recebe as colunas; a célula é render livre.
94
+ <ActionList action={listAction} input={{ q }} columns={[
95
+ { key: 'name', header: 'Workspace', cell: (w) => w.name },
96
+ ]} />
97
+ ```
98
+
99
+ ## O limite — de propósito
100
+
101
+ > O look curado não é customizável no app: rodapé-faixa do dialog, elevação no dark, `active:scale`
102
+ > do botão, a seta do tooltip — é identidade da casa, igual em todo projeto.
103
+
104
+ ```tsx preview
105
+ <Badge variant="success">Cabe nas alavancas</Badge>
106
+ ```
107
+
108
+ Se uma necessidade real não cabe nas alavancas (tokens · className · slots/asChild · props ·
109
+ contrato), o movimento não é dialeto local: é evoluir o componente **no Opus**, com a divergência
110
+ declarada (skill `opus`) — assim a mudança vale pra casa toda, e esta doc passa a mostrá-la.
@@ -0,0 +1,34 @@
1
+ # Evolução contínua
2
+
3
+ O Opus cresce por **reincidência, nunca por especulação**. Quando o mesmo comportamento genérico reaparece em mais de um projeto, ele vira candidato a peça do Opus — e o projeto troca a implementação local pelo import. Estilo e conveniência não entram; comportamento que se repete, sim.
4
+
5
+ ## Onde você aponta
6
+
7
+ No seu projeto, uma linha por apontamento em `.opus/issues.jsonl` na raiz do repo. Dois tipos:
8
+
9
+ - **`enhancement`** — código genérico que o Opus deveria ganhar (serviria a qualquer projeto). Antes de apontar, confira o que o Opus já tem (o inventário de componentes, os exports, esta doc): se já existe, use.
10
+ - **`bug`** — um defeito ou limitação no próprio Opus, achado usando-o.
11
+
12
+ ```json
13
+ {"type": "enhancement", "behavior": "o comportamento em uma frase.", "file": "src/onde-vive.ts", "kind": "ui|sdk|driver|infra"}
14
+ {"type": "bug", "behavior": "o que quebra ou falta.", "file": "onde se manifesta.", "note": "repro + o workaround aplicado."}
15
+ ```
16
+
17
+ ## Não trave esperando
18
+
19
+ O ponto do ciclo é **não bloquear a entrega**. Bateu num gap ou num bug do Opus:
20
+
21
+ 1. **Contorne local** — componha um wrapper no seu projeto. O Opus entrega _source_, então dá pra embrulhar qualquer superfície dele. Nunca edite `node_modules` (some no próximo install).
22
+ 2. **Entregue** a feature com o workaround.
23
+ 3. **Aponte** no `.opus/issues.jsonl` e siga em frente.
24
+
25
+ O "depois" — o conserto no Opus — corre em paralelo. Ele não segura o seu trabalho; o único custo de demorar é o workaround viver um pouco mais.
26
+
27
+ ## Como fecha
28
+
29
+ O apontamento é colhido e abre uma **Issue no repositório do Opus**, onde a triagem acontece (deduplicada — re-apontar o mesmo é idempotente):
30
+
31
+ - **Enhancement** aceito (com reincidência) → implementado no Opus → sai num _bump_ → seu projeto atualiza o pin (`opus.json`) e troca o workaround pelo import.
32
+ - **Bug** → vira _fix_ + entrada no `CHANGELOG` → no _bump_, o workaround sai.
33
+
34
+ A régua e a decisão ficam com quem mantém o Opus — hoje, a **Softize**. O registro curado das promoções vive no `PROMOTED.md` do pacote.
@@ -0,0 +1,47 @@
1
+ ## Estados
2
+
3
+ Um lugar só pro erro/carregando/vazio/conteúdo de uma carga. Carregando = Spinner centralizado;
4
+ vazio = texto; erro = aviso calmo (a mensagem técnica não vai pra tela). É o "antes" do conteúdo —
5
+ pro "depois" (ação em andamento), use o busy do Button.
6
+
7
+ ```tsx preview col
8
+ <div className="w-full space-y-3">
9
+ <DataState loading>
10
+ <div />
11
+ </DataState>
12
+ <DataState empty emptyText="Nenhum papel.">
13
+ <div />
14
+ </DataState>
15
+ <DataState error={{ message: 'detalhe técnico fica no console' }}>
16
+ <div />
17
+ </DataState>
18
+ </div>
19
+ ```
20
+
21
+ ## Em tabela (colSpan)
22
+
23
+ Em lista de tabela, passe colSpan: o estado vira UMA linha de largura cheia (`<tr><td colSpan>`)
24
+ que cabe direto no `<tbody>`; o conteúdo são as `<tr>` dos itens.
25
+
26
+ ```tsx preview col
27
+ <table className="w-full overflow-hidden rounded-lg border border-border text-sm">
28
+ <tbody>
29
+ <DataState empty emptyText="Nenhum usuário." colSpan={3}>
30
+ <tr>
31
+ <td />
32
+ </tr>
33
+ </DataState>
34
+ </tbody>
35
+ </table>
36
+ ```
37
+
38
+ ## Props
39
+
40
+ | Prop | Tipo | Default | Descrição |
41
+ |---|---|---|---|
42
+ | `loading` | `boolean` | | Carregando (antes do conteúdo) — mostra o Spinner centralizado. |
43
+ | `empty` | `boolean` | | Sem itens — mostra o emptyText. |
44
+ | `emptyText` | `string` | | Texto do vazio (pt-BR, ex.: "Nenhum papel."). |
45
+ | `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica NÃO vai pra tela (use errorText). |
46
+ | `errorText` | `string` | `'Não foi possível carregar.'` | Aviso de erro, orientado ao usuário. |
47
+ | `colSpan` | `number` | | Em tabela: renderiza o estado como `<tr><td colSpan>` (cabe direto no tbody). |
@@ -0,0 +1,99 @@
1
+ ---
2
+ title: Camada de dados
3
+ ---
4
+
5
+ # Camada de dados
6
+
7
+ A entidade é a spec do armazenamento; o manifest a projeta. As queries falam Kysely tipado.
8
+ Uma regra firme atravessa tudo: o código fala camelCase, o banco fala snake — e o
9
+ `CamelCasePlugin` faz a ponte.
10
+
11
+ ## Entidade e manifest
12
+
13
+ > `defineEntity` declara o storage (campos + tipos lógicos `t.*`). A `description` de cada
14
+ > entidade/action é a spec de negócio, projetada no manifest pelo `opus gen`; `opus db check`
15
+ > acusa drift entidade ↔ banco.
16
+
17
+ ```ts
18
+ import { defineEntity } from '@softize/opus/schema'
19
+ import { t } from '@softize/opus/schema/zod'
20
+
21
+ export const SkillEntity = defineEntity({
22
+ name: 'skill',
23
+ fields: {
24
+ id: t.uuid().pk(),
25
+ slug: t.slug(),
26
+ name: t.string(),
27
+ content: t.text(),
28
+ createdAt: t.datetime(),
29
+ },
30
+ })
31
+ ```
32
+
33
+ ## Código camelCase, banco snake
34
+
35
+ > Regra dura (skill `code-style`): uma camada em snake e outra em camel é defeito — não tem meio-termo.
36
+
37
+ O schema tipado do Kysely e **toda** query (select/insert/update/where) usam camelCase
38
+ (`workspaceId`, `ghRepo`). O banco é snake (a DDL no schema idempotente — `db/schema.sql`).
39
+ O `CamelCasePlugin` no runtime faz a ponte camel↔snake nas queries E nos resultados — o
40
+ mesmo que o prepare já faz.
41
+ Única exceção: um sink escrito por fora do Kysely-com-plugin (ex.: o `audit_log` do Opus, pelo
42
+ pool) fica snake.
43
+
44
+ ```ts
45
+ import { CamelCasePlugin, Kysely, PostgresDialect } from 'kysely'
46
+
47
+ const db = new Kysely<AdminDB>({
48
+ dialect: new PostgresDialect({ pool }),
49
+ plugins: [new CamelCasePlugin()], // camel no código → snake no SQL → camel no resultado
50
+ })
51
+
52
+ await db.selectFrom('agents').select(['isDefault', 'roleId']).where('workspaceId', '=', id).execute()
53
+ ```
54
+
55
+ ## Migrações · prepare · seed
56
+
57
+ > Três coisas distintas — não confundir o que roda em prod.
58
+
59
+ - `opus db migrate` — aplica o **schema idempotente** (`config.schema`, um script SQL
60
+ evolutivo: `IF NOT EXISTS` + guards cobrem nascer do zero e upgrade no mesmo artefato)
61
+ e roda o drift-check entidade ↔ banco na sequência (exit ≠ 0 se divergir).
62
+ - `prepare` — backfill estrutural, prod-safe e idempotente; roda no deploy (depois do migrate).
63
+ - `seed` — fixtures de desenvolvimento. **Não** roda em prod.
64
+
65
+ ## Campo `t.json()`: objeto entra, objeto sai
66
+
67
+ > A dupla `ColumnType<unknown, string, never>` + `JSON.stringify` na mão está aposentada
68
+ > no caminho do `kyselyRepo`.
69
+
70
+ O `kyselyRepo` serializa campo `t.json()` na ESCRITA (insert/update), guiado pela
71
+ declaração. Sem isso, o pg até stringifica objeto plano — mas **array vira literal de
72
+ array do PG** (errado pra jsonb) sem quebrar typecheck. String passa direto (quem já
73
+ mandava pré-serializado segue valendo); na leitura o pg devolve objeto. Query à mão
74
+ (fora do repo) continua responsável pelo próprio stringify.
75
+
76
+ ## SQL escrito por LLM: `readOnlyContextPool`
77
+
78
+ > Action `ai: true` que executa SQL livre precisa das **quatro defesas** — todas no
79
+ > BANCO, nenhuma em regex sobre o texto da query.
80
+
81
+ ```ts
82
+ import { Pool } from 'pg'
83
+ import { readOnlyContextPool } from '@softize/opus/data/readonly-pool'
84
+
85
+ const bi = readOnlyContextPool({ pool: new Pool({ connectionString, max: 4 }) })
86
+ const { rows } = await bi.query(sqlDoLlm, { role: roleFor(ctx) })
87
+ ```
88
+
89
+ 1. **Pool com teto** (`max` do pool e/ou `maxConcurrent`) — query de LLM não esgota as
90
+ conexões do app;
91
+ 2. **`BEGIN TRANSACTION READ ONLY`** — escrita morre no servidor;
92
+ 3. **`SET LOCAL ROLE` por transação** — o alcance é do CONTEXTO (crie os roles com grants
93
+ default-fechado nas suas migrações: sem isso, `sales_read` leria `hr_employees`);
94
+ 4. **Protocolo estendido** — o SQL roda sempre com array de valores; multi-sentença
95
+ (`SELECT 1; DROP …`) é recusada pelo próprio protocolo.
96
+
97
+ Saindo, `DISCARD ALL` devolve a conexão limpa; se a limpeza falhar, a conexão é
98
+ destruída — nunca volta suja pro pool. `statement_timeout` local por transação
99
+ (default 15s) segura a query fugitiva.
@@ -0,0 +1,60 @@
1
+ ## Composição
2
+
3
+ Content é superfície pura; o espaço mora nos slots (body p-5; header/footer px-5 py-4): DialogHeader (fixo, com divisor), DialogBody (rola) e DialogFooter (faixa da casa — borda + fundo muted). DialogClose fecha sem estado manual.
4
+
5
+ ```tsx preview
6
+ <Dialog>
7
+ <DialogTrigger asChild>
8
+ <Button variant="outline">Novo workspace</Button>
9
+ </DialogTrigger>
10
+ <DialogContent>
11
+ <DialogHeader>
12
+ <DialogTitle>Novo workspace</DialogTitle>
13
+ <DialogDescription>O workspace agrupa os repositórios e agentes do cliente.</DialogDescription>
14
+ </DialogHeader>
15
+ <DialogBody className="space-y-2">
16
+ <Label htmlFor="ws-nome">Nome</Label>
17
+ <Input id="ws-nome" placeholder="Ex.: Empresa X" />
18
+ </DialogBody>
19
+ <DialogFooter>
20
+ <DialogClose asChild>
21
+ <Button variant="outline" size="sm">Cancelar</Button>
22
+ </DialogClose>
23
+ <Button size="sm">Criar</Button>
24
+ </DialogFooter>
25
+ </DialogContent>
26
+ </Dialog>
27
+ ```
28
+
29
+ ## Sem o X (ação obrigatória)
30
+
31
+ showCloseButton={false} esconde o X: o usuário decide pelos botões — pra confirmação que não pode ser dispensada no escuro.
32
+
33
+ ```tsx preview
34
+ <Dialog>
35
+ <DialogTrigger asChild>
36
+ <Button variant="destructive">Excluir sessão</Button>
37
+ </DialogTrigger>
38
+ <DialogContent showCloseButton={false}>
39
+ <DialogHeader>
40
+ <DialogTitle>Excluir a sessão?</DialogTitle>
41
+ <DialogDescription>O worktree e o preview desta sessão serão removidos.</DialogDescription>
42
+ </DialogHeader>
43
+ <DialogFooter>
44
+ <DialogClose asChild>
45
+ <Button variant="outline" size="sm">Cancelar</Button>
46
+ </DialogClose>
47
+ <DialogClose asChild>
48
+ <Button variant="destructive" size="sm">Excluir</Button>
49
+ </DialogClose>
50
+ </DialogFooter>
51
+ </DialogContent>
52
+ </Dialog>
53
+ ```
54
+
55
+ ## Props
56
+
57
+ | Prop | Tipo | Default | Descrição |
58
+ |---|---|---|---|
59
+ | `Dialog.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) — pra abrir por código (ex.: depois de uma action). |
60
+ | `DialogContent.showCloseButton` | `boolean` | `true` | Mostra o X de fechar no canto. Desligue pra modal que exige ação explícita. |
@@ -0,0 +1,55 @@
1
+ ## Painel de detalhe (à direita)
2
+
3
+ O painel que desliza de uma borda da tela — modal, com overlay, trap de foco e ESC (o motor é o Dialog do Radix). A régua contra o irmão: conteúdo curto e CENTRADO → `Dialog`; painel na borda que preserva o contexto atrás → `Drawer`.
4
+
5
+ side='right' desliza da borda direita. ESC, clique no overlay e o X fecham — sem estado manual. É o lugar de detalhe/edição lateral sem sair do contexto.
6
+
7
+ ```tsx preview
8
+ <Drawer>
9
+ <DrawerTrigger asChild>
10
+ <Button variant="outline">Ver detalhe</Button>
11
+ </DrawerTrigger>
12
+ <DrawerContent side="right">
13
+ <DrawerHeader>
14
+ <DrawerTitle>criarTicket</DrawerTitle>
15
+ <DrawerDescription>Ação · domínio Suporte</DrawerDescription>
16
+ </DrawerHeader>
17
+ <div className="px-4 text-sm text-muted-foreground">Campos, entrada/saída, arquivo…</div>
18
+ </DrawerContent>
19
+ </Drawer>
20
+ ```
21
+
22
+ ## Edição com rodapé (à esquerda)
23
+
24
+ side aceita top|right|bottom|left. DrawerFooter ancora as ações no rodapé; DrawerClose fecha sem estado manual.
25
+
26
+ ```tsx preview
27
+ <Drawer>
28
+ <DrawerTrigger asChild>
29
+ <Button variant="outline">Editar</Button>
30
+ </DrawerTrigger>
31
+ <DrawerContent side="left">
32
+ <DrawerHeader>
33
+ <DrawerTitle>Editar workspace</DrawerTitle>
34
+ </DrawerHeader>
35
+ <div className="space-y-2 px-4">
36
+ <Label htmlFor="ws-nome">Nome</Label>
37
+ <Input id="ws-nome" defaultValue="Empresa X" />
38
+ </div>
39
+ <DrawerFooter>
40
+ <DrawerClose asChild>
41
+ <Button variant="outline" size="sm">Cancelar</Button>
42
+ </DrawerClose>
43
+ <Button size="sm">Salvar</Button>
44
+ </DrawerFooter>
45
+ </DrawerContent>
46
+ </Drawer>
47
+ ```
48
+
49
+ ## Props
50
+
51
+ | Prop | Tipo | Default | Descrição |
52
+ |---|---|---|---|
53
+ | `DrawerContent.side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Borda de onde o painel desliza. |
54
+ | `DrawerContent.showCloseButton` | `boolean` | `true` | Mostra o X de fechar no canto. Desligue pra painel que exige ação explícita. |
55
+ | `Drawer.open / onOpenChange` | `boolean / (open: boolean) => void` | | Modo controlado (Radix) — pra abrir/fechar por código (ex.: detalhe de um item selecionado). |
@@ -0,0 +1,66 @@
1
+ ## Com ícone
2
+
3
+ EmptyMedia variant=icon desenha o quadrado de fundo muted atrás do ícone; o EmptyHeader agrupa mídia, título e descrição no centro.
4
+
5
+ ```tsx preview col
6
+ <Empty>
7
+ <EmptyHeader>
8
+ <EmptyMedia variant="icon">
9
+ <FolderGit2 />
10
+ </EmptyMedia>
11
+ <EmptyTitle>Nenhum repositório vinculado</EmptyTitle>
12
+ <EmptyDescription>
13
+ O workspace Empresa X ainda não tem repositórios. Vincule um pra abrir sessões.
14
+ </EmptyDescription>
15
+ </EmptyHeader>
16
+ </Empty>
17
+ ```
18
+
19
+ ## Com ação
20
+
21
+ EmptyContent guarda os botões abaixo do header — é o lugar da chamada pra ação que resolve o vazio.
22
+
23
+ ```tsx preview col
24
+ <Empty>
25
+ <EmptyHeader>
26
+ <EmptyMedia variant="icon">
27
+ <Sparkles />
28
+ </EmptyMedia>
29
+ <EmptyTitle>Sem skills neste agente</EmptyTitle>
30
+ <EmptyDescription>
31
+ O agente developer não tem skills anexadas. Adicione clean-code ou test pra começar.
32
+ </EmptyDescription>
33
+ </EmptyHeader>
34
+ <EmptyContent>
35
+ <Button>
36
+ <Plus />
37
+ Anexar skill
38
+ </Button>
39
+ </EmptyContent>
40
+ </Empty>
41
+ ```
42
+
43
+ ## Mídia default
44
+
45
+ EmptyMedia variant=default (o padrão) não desenha fundo — bom pra uma ilustração ou um ícone maior que se sustenta sozinho.
46
+
47
+ ```tsx preview col
48
+ <Empty>
49
+ <EmptyHeader>
50
+ <EmptyMedia className="text-muted-foreground">
51
+ <FolderGit2 className="size-12" />
52
+ </EmptyMedia>
53
+ <EmptyTitle>Nenhuma sessão por aqui</EmptyTitle>
54
+ <EmptyDescription>
55
+ Este repositório ainda não teve sessões. A primeira aparece assim que um agente começar a trabalhar.
56
+ </EmptyDescription>
57
+ </EmptyHeader>
58
+ </Empty>
59
+ ```
60
+
61
+ ## Props
62
+
63
+ | Prop | Tipo | Default | Descrição |
64
+ |---|---|---|---|
65
+ | `variant (EmptyMedia)` | `'default' \| 'icon'` | `'default'` | icon desenha o quadrado de fundo muted atrás da mídia; default não desenha fundo. |
66
+ | `className (Empty)` | `string` | | A moldura já vem tracejada e centrada — ajuste espaçamento ou borda por aqui. |
@@ -0,0 +1,61 @@
1
+ ---
2
+ title: Eventos
3
+ ---
4
+
5
+ # Eventos
6
+
7
+ O handler anuncia que algo aconteceu (`ctx.emit`) sem saber quem escuta. Reactions (e
8
+ integrações) assinam e reagem. O barramento é um adapter do core (`EventBusAdapter`); o
9
+ formato do evento é preservado de ponta a ponta.
10
+
11
+ ## O contrato
12
+
13
+ ```ts
14
+ interface EventBusAdapter {
15
+ publish(event: DomainEvent): Promise<void> | void
16
+ subscribe?(pattern: string, listener: (event: DomainEvent) => void): Unsubscribe
17
+ }
18
+
19
+ interface DomainEvent<T = unknown> {
20
+ type: string // 'nota.emitida'
21
+ id: string
22
+ timestamp: string
23
+ actor: { id: string | null; type?: 'user' | 'system' | 'integration' }
24
+ tenant?: string | null
25
+ data: T
26
+ source: { action: string; actionId: string; correlation?: string }
27
+ }
28
+ ```
29
+
30
+ ## Driver
31
+
32
+ ```ts
33
+ import { mittEvents } from '@softize/opus/events/mitt'
34
+
35
+ const bus = mittEvents() // in-process (mesmo processo do runtime)
36
+ ```
37
+
38
+ ## No runtime
39
+
40
+ ```ts
41
+ const runtime = createRuntime({
42
+ // …server/data…
43
+ eventBus: bus,
44
+ })
45
+
46
+ // No handler: emite pelo tipo declarado; o core preenche id/timestamp/actor/source.
47
+ handler: async (ctx, input) => {
48
+ const nota = await repo.create(input)
49
+ ctx.emit('nota.emitida', { id: nota.id, valor: nota.valor })
50
+ return nota
51
+ }
52
+ ```
53
+
54
+ Actions declaram no contrato o que emitem (`emits: ['nota.emitida']`); o runtime avisa se
55
+ o handler emitir algo não-declarado (`emitMode`). Uma reaction assina `nota.emitida` e roda
56
+ como sua própria action rastreável (provenance `reaction`).
57
+
58
+ ## Limites (por enquanto)
59
+
60
+ O driver `mitt` é in-process — sem durabilidade nem entre-processos. Barramentos duráveis/
61
+ distribuídos (Redis, NATS…) entram por reincidência de caso real.
@@ -0,0 +1,58 @@
1
+ ## Campo vertical
2
+
3
+ A composição base: FieldLabel (htmlFor↔id), o controle e FieldDescription como ajuda — empilhados, com o respiro do Field.
4
+
5
+ ```tsx preview col md
6
+ <Field>
7
+ <FieldLabel htmlFor="workspace-slug">Slug do workspace</FieldLabel>
8
+ <Input id="workspace-slug" defaultValue="empresa-x" />
9
+ <FieldDescription>Vira o subdomínio do preview: empresa-x.preview.softize.com.br.</FieldDescription>
10
+ </Field>
11
+ ```
12
+
13
+ ## Horizontal com toggle
14
+
15
+ orientation=horizontal põe o controle na lateral e a label à esquerda. FieldContent agrupa título e descrição num bloco; FieldTitle é o rótulo de texto quando o controle não é um <label>.
16
+
17
+ ```tsx preview col md
18
+ <Field orientation="horizontal">
19
+ <FieldContent>
20
+ <FieldTitle>Acionar o revisor no handoff</FieldTitle>
21
+ <FieldDescription>Ao abrir o handoff, o agente reviewer entra na sessão automaticamente.</FieldDescription>
22
+ </FieldContent>
23
+ <Switch id="auto-review" defaultChecked />
24
+ </Field>
25
+ ```
26
+
27
+ ## Conjunto com legenda e erro
28
+
29
+ FieldSet agrupa campos sob uma FieldLegend; FieldGroup dá o espaçamento entre eles; FieldSeparator marca uma divisão. FieldError mostra a mensagem só quando há erro (aria-invalid no controle casa o visual).
30
+
31
+ ```tsx preview col md
32
+ <FieldSet>
33
+ <FieldLegend>Novo agente</FieldLegend>
34
+ <FieldGroup>
35
+ <Field>
36
+ <FieldLabel htmlFor="agent-name">Nome</FieldLabel>
37
+ <Input id="agent-name" defaultValue="" aria-invalid placeholder="Ex.: developer" />
38
+ <FieldError>Informe um nome pro agente.</FieldError>
39
+ </Field>
40
+ <FieldSeparator />
41
+ <Field>
42
+ <FieldLabel htmlFor="agent-prompt">Prompt</FieldLabel>
43
+ <Textarea id="agent-prompt" rows={3} defaultValue="Veste o papel de developer no workspace Empresa X." />
44
+ <FieldDescription>A síntese vira a primeira mensagem da sessão.</FieldDescription>
45
+ </Field>
46
+ </FieldGroup>
47
+ </FieldSet>
48
+ ```
49
+
50
+ ## Props
51
+
52
+ | Prop | Tipo | Default | Descrição |
53
+ |---|---|---|---|
54
+ | `orientation` (Field) | `'vertical' \| 'horizontal' \| 'responsive'` | `'vertical'` | Direção do campo: vertical empilha; horizontal põe o controle ao lado (bom pra toggle); responsive vira horizontal a partir de @md. |
55
+ | `variant` (FieldLegend) | `'legend' \| 'label'` | `'legend'` | Tamanho da legenda do FieldSet — legend é o título da seção; label encolhe pro porte de rótulo. |
56
+ | `errors` (FieldError) | `Array<{ message?: string }>` | | Lista de erros (ex.: do react-hook-form) — deduplica e vira uma lista. Sem children e sem erros, o FieldError não renderiza. |
57
+ | `children` (FieldError) | `React.ReactNode` | | Mensagem de erro literal — tem prioridade sobre errors quando passada. |
58
+ | `children` (FieldSeparator) | `React.ReactNode` | | Rótulo opcional no meio da linha divisória (ex.: "ou") — sem filhos, é só a linha. |