@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,1616 @@
1
+ # Changelog do @softize/opus
2
+
3
+ O que muda em cada versão — e, quando quebra, **o que fazer**. Regras de leitura:
4
+ minor = novidade compatível; major = breaking (a seção **Breaking** diz a migração).
5
+ Este arquivo viaja no pacote: num projeto, leia `node_modules/@softize/opus/CHANGELOG.md`.
6
+ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
7
+ `manifest:check`) — eles apontam o que a mudança cobra do seu código.
8
+
9
+ > As entradas 2.30.1–2.31.5 foram **reconstruídas do git** (o release publicava sem
10
+ > passar por aqui). Uma delas — a 2.31.5, que mudou o rem base — é visual GLOBAL e
11
+ > tinha ficado sem registro nenhum, o que é exatamente o caso que este arquivo existe
12
+ > pra cobrir.
13
+
14
+ ## 8.6.6 — 2026-07-30
15
+
16
+ **O pacote passa a ser publicado no npm PÚBLICO.** O registry próprio
17
+ (`registry.softize.com.br`) vivia na VPS, e a VPS não volta — em vez de reconstruir o
18
+ serviço, ele sai da infra. Consequência prática boa: máquina nova instala **sem token
19
+ nenhum**, o que é o que faz um servidor poder ser destruído e recriado à vontade.
20
+
21
+ Nos projetos, apague a linha `@softize:registry=` do `.npmrc` — sem ela o npm público
22
+ é o padrão, e é onde o pacote está.
23
+
24
+ **Nome de cliente saiu do que é publicado.** Como o pacote virou público, as menções a um
25
+ cliente real no CHANGELOG, no `shellnav.md`, no `section-shell`, no `router` e no
26
+ `theme.css` viraram **Empresa X** — o placeholder que a documentação já usava. Nenhum
27
+ comportamento muda; é texto. O repositório segue privado, então o histórico não vai junto.
28
+
29
+ ## 8.6.5 — 2026-07-28
30
+
31
+ **`<Chat>`: a fala do usuário ganha teto de altura.** O card do usuário é `sticky` — gruda
32
+ no topo enquanto o turno está em vista. Sem teto, uma mensagem longa (o caso normal quando
33
+ a primeira fala é um enunciado de tarefa inteiro) ocupava a tela toda e ficava lá, cobrindo
34
+ a resposta. Agora corta em `max-h-40`, com esmaecido e um **Mostrar mais**.
35
+
36
+ O expandido TAMBÉM tem teto, e ele sai do scroll container (60% da altura dele, medida),
37
+ não da viewport: soltar a altura devolvia o defeito atrás de um clique, e um teto em `vh`
38
+ devolvia o mesmo num `<Chat>` embutido em painel mais baixo que a tela — nos dois casos o
39
+ botão de fechar sai de vista e não volta enquanto o turno estiver em cima.
40
+
41
+ **Vale pra todo consumidor no bump** — é o comportamento do componente, não há prop pra
42
+ desligar. Ganchos novos: `data-slot` `chat-user`, `chat-user-body` e `chat-scroll`.
43
+
44
+ Um contrato que já existia de fato passa a ter consequência: **o `<Chat>` precisa de altura
45
+ limitada pelo pai**. Sem isso o container passa a ser dimensionado pelo conteúdo e o teto
46
+ se realimenta. Se o seu `<Chat>` está num pai de altura automática, limite-o no bump.
47
+
48
+ ## 8.6.4 — 2026-07-26
49
+
50
+ **`Split resizable` volta a respeitar percentuais.** `react-resizable-panels` v4
51
+ interpreta valores numéricos como pixels; o adaptador agora envia
52
+ `initialSize`/`minSize`/`maxSize` com a unidade `%` explícita. Um pane declarado com
53
+ `initialSize={30}` deixa de nascer com 30 px e volta a ocupar 30% do split.
54
+
55
+ ## 8.6.3 — 2026-07-26
56
+
57
+ **Rail colapsado segue a Empresa X.** `SidebarItem` usa um botão centralizado de
58
+ `size-9` quando a sidebar está recolhida, em vez de ocupar toda a largura disponível.
59
+ Ícone, área ativa e raio ficam idênticos ao padrão que originou o rail.
60
+
61
+ ## 8.6.2 — 2026-07-26
62
+
63
+ **Docs de `Sidebar` migradas para slots de pane.** Os exemplos agora usam
64
+ `PaneHeader`, `PaneContent` e `PaneFooter`, e explicam a compatibilidade temporária dos
65
+ aliases `Sidebar*`.
66
+
67
+ ## 8.6.1 — 2026-07-26
68
+
69
+ **Cabeçalho, conteúdo e rodapé pertencem ao pane, não à sidebar.** Entram
70
+ `PaneHeader`, `PaneContent` e `PaneFooter`: os três organizam qualquer pane, inclusive
71
+ um `Sidebar` encaixado em um `Split`. `SidebarHeader`, `SidebarContent` e
72
+ `SidebarFooter` continuam exportados como aliases deprecated para uma migração gradual.
73
+
74
+ ## 8.6.0 — 2026-07-26
75
+
76
+ **Estrutura composicional de tela: `Split`/`Pane` e `Sidebar`.** `Split` concentra a
77
+ divisão horizontal ou vertical e recebe `resizable`; `Pane` define os espaços. `Sidebar`
78
+ cuida apenas do chrome de navegação, com `SidebarHeader`, `SidebarContent`,
79
+ `SidebarFooter`, `SidebarNav` e modo `collapsed`. `AppShell`, `SectionShell` e
80
+ `Resizable*` passam a estar marcados como deprecated nas docs, com os caminhos de migração.
81
+
82
+ **`opusDesign` deixa de perder domínio novo — watcher mais largo, 404 que se cura e entry
83
+ glob.** O episódio real: numa sessão de design o agente criou um domínio, levou 404 e
84
+ concluiu "precisa de restart" (não precisava — faltou registrar no entry). Três fixes
85
+ matam a classe:
86
+
87
+ - **`add`/`unlink` invalidam sempre** (sob o root, fora node_modules/.git): arquivo novo
88
+ nunca importado não está no module graph — e é exatamente ele que um entry glob precisa
89
+ enxergar. `change` mantém o critério do graph. Rebuild segue lazy; over-invalidation é
90
+ barata.
91
+ - **404 que se cura e ensina.** Request sob o `/api` sem rota força UM rebuild por ciclo
92
+ de staleness (mutex no boot — sem rebuild-storm de 404 legítimo) e re-tenta o match;
93
+ persistindo, o envelope vem com mensagem-guia: action não registrada no runtime —
94
+ confira o entry (path incluso) ou use entry glob. O driver node ganhou
95
+ `hasRoute(method, pathname)` no handle pra viabilizar o pre-match.
96
+ - **`entry` aceita glob** (ex.: `'src/domains/*/contract.ts'`; pode misturar com entries
97
+ estáticos no array): re-expandido a CADA rebuild — domínio novo entra no ar ao salvar o
98
+ arquivo, sem editar entry nem reiniciar o dev server. Dos módulos casados entram os
99
+ exports (default e named) com shape de registrável; schemas/helpers exportados junto
100
+ ficam de fora. Matcher próprio sem dep nova: `*` casa um segmento, `**` qualquer
101
+ profundidade. Entry estático segue funcionando exatamente como antes.
102
+
103
+ ## 8.5.1 — 2026-07-24
104
+
105
+ **Um X só de clear (fantasma) e o trailing-botão balanceado no canto.** Dois acertos de
106
+ consistência nos adornos de campo:
107
+
108
+ - **O clear do Select vira X fantasma.** Era um badge circular CHEIO (`bg-muted-foreground`,
109
+ X branco) — destoava de todo o resto dos adornos, que são ghost-muted no slot. Agora é um
110
+ X `text-muted-foreground hover:text-foreground`, igual ao remover-chip do multi e ao que um
111
+ `trailing` de limpar renderia num Input. Um tratamento de X, não três.
112
+ - **Trailing que é BOTÃO encosta no canto (~6px), não flutua a 12px.** Um botão-ícone num
113
+ campo `h-9` tem 6px de folga em cima/embaixo; a da direita era o `px-3` do texto (12px),
114
+ deixando o botão torto. Agora o slot trailing puxa `has-[button]:-mr-1.5` (Input sempre;
115
+ Select só no buscável — com chevron o `gap` fica) pra o inset direito casar com o vertical.
116
+ Mesmo balanceamento do `InputGroup`. Trailing de texto puro segue alinhado ao texto (12px).
117
+
118
+ ## 8.5.0 — 2026-07-24
119
+
120
+ **`Input` adornado (`icon`/`trailing`) passa pro modelo flex — o mesmo do `InputGroup`/`Select`.**
121
+ Antes o adorno era SOBREPOSTO (`absolute`) e o texto abria espaço com `pl-9`/`pr-9`; a
122
+ distância do trailing à borda (6px) divergia da do Select (12px). Agora, com adorno, a
123
+ BORDA e o anel de foco moram no WRAPPER (disparados pelo `:focus-visible` do `<input>`
124
+ interno — foco de teclado), o `<input>` fica sem borda e CRESCE (`flex-1`), e os adornos
125
+ são irmãos ao lado: o `px-3`/`gap` posicionam sozinhos (trailing a 12px, casando com o
126
+ Select). Some a conta de `pl-9`/`pr-9`. **Nota de call site:** o `className` de um Input
127
+ ADORNADO agora estiliza o CAMPO (o wrapper — largura, raio, fundo, fonte), como no Select;
128
+ a fonte/cor cascateiam pro texto digitado. O Input CRU (sem `icon`/`trailing`) segue com o
129
+ `className` no próprio `<input>`, borda e ref nele — zero mudança. O adorno era novidade
130
+ recente (8.3.x) e só a barra de endereço do Maestro o usava.
131
+
132
+ **O preview de design vira in-process: driver `server/node` + plugin `opusDesign()`.** A
133
+ Trilha A do isolamento preview×prod (ADR 0004 da softize): desenhar tela não pode depender
134
+ de backend de pé — nem, pior, falar com prod por um proxy esquecido no vite.config.
135
+
136
+ - **`@softize/opus/server/node`** — `nodeServer()`: `ServerAdapter` connect-style sem
137
+ framework. Devolve `{ adapter, handler }`: o adapter vai no `createRuntime({ server })`;
138
+ o handler `(req, res, next?)` encaixa em qualquer stack node — middlewares do vite,
139
+ express, `http.createServer` direto. Mesmo protocolo do driver fastify (helpers
140
+ compartilhados de `@softize/opus/server`): rotas por convenção, envelopes, status por
141
+ categoria, endpoints health/ready/openapi.
142
+ - **`@softize/opus/vite`** — `opusDesign()`: com `vite --mode design`, carrega o entry
143
+ (`./opus.config.ts` por default, via SSR do vite — aliases do app valem), registra as
144
+ actions num runtime local (SÓ actions: reactions/schedules ficam de fora — o runtime de
145
+ design é request/response) e serve `/api` dentro do próprio dev server, com
146
+ `OPUS_MODE=design` fazendo os `mockHandler` responderem e `designAuth` dando a sessão
147
+ fixa (`{ id: 'design' }`). Qualquer `server.proxy` do config é DESLIGADO — prefixo
148
+ ex-proxy sem cobertura responde 503 em envelope (`design.no_backend`), nunca vaza pra
149
+ fora. `stubs` por prefixo cobrem o que não é opus (ex.: `/api/auth`, podem streamar);
150
+ `GET /__opus/design` é a sonda do Maestro (versão, contagem de actions/mocks, stubs);
151
+ HMR reconstrói o runtime quando qualquer módulo carregado muda. `devBackend: true`
152
+ monta o `/api` real também no dev normal (sem modo design).
153
+ - **`runAction` (testing) respeita o modo design.** Mesma régua do `execute()` (fonte
154
+ única): com `OPUS_MODE=design` e `mockHandler` presente, o mock roda; sem o env, o
155
+ handler real. Contrato puro (sem handler) vira TESTÁVEL em design — os testes-de-contrato
156
+ da sessão de design deixam de ser impossíveis; fora de design, erro claro
157
+ (`testing.no_handler`) em vez de estouro obscuro.
158
+ - **O esqueleto do `opus create` nasce contract-first.** `dev:design` vira
159
+ `vite --mode design`, o vite.config traz `opusDesign({ devBackend: true })` (dev normal
160
+ serve o handler real via /api; design roteia pro mock) e o domínio-exemplo mostra o
161
+ split canônico: `defineContract` com `mockHandler` alimentado pelo schema (`fakeMany`)
162
+ + `bindAction` com o handler in-memory — o App consome o CONTRATO via `useListAction`
163
+ (fim do "chama o handler direto, sem servidor ainda"), e o teste cobre o bound E o
164
+ contrato puro em design. O plugin também marca `@softize/opus` como `ssr.noExternal`:
165
+ o pacote embarca TS source e, externalizado, o node recusa TS sob node_modules no
166
+ load SSR do entry.
167
+ - **`options: { kind: 'dictionary', ref }` agora resolve.** O `TbdlibProvider` ganha
168
+ `dicts` (`Record<ref, DictLike>` — o `DictType` do `t.dict` encaixa direto) e a UI
169
+ resolve a origem declarada no contrato: no `ActionList`, filtro e chips mostram os
170
+ labels do vocabulário; no `ActionForm`, a precedência é prop `options` do campo >
171
+ `fieldOptions` do form > `options` do FieldSpec (dictionary/static) > **meta do
172
+ `t.dict` no schema** (zero-config: campo dict — ou multiselect de dict — resolve
173
+ value→label pela meta que viaja no contrato, sem registry) > chaves cruas do z.enum.
174
+ Campo texto com opções declaradas vira single-select por-id. Habilitador por baixo:
175
+ `attachLogicalType`/`getLogicalType` moveram pro core (o `@softize/opus/schema`
176
+ re-exporta — API igual) porque a fronteira SPA só deixa a UI tocar ui/lib/core.
177
+
178
+ **Diálogos imperativos: o trio `dialog`.** `dialog.alert` (Promise<void>, reconhecimento
179
+ obrigatório) · `dialog.confirm` (Promise<boolean>, agora com slot `body` pra corpo próprio) ·
180
+ `dialog.prompt` (Promise<string|null>, um input). O `confirm()` vira o namespace `dialog` —
181
+ `window.alert/confirm/prompt` são globais do browser e um import esquecido cai no nativo;
182
+ `window.dialog` não existe. Tudo sobre o mesmo `AlertDialog` (role=alertdialog, não fecha
183
+ fora) e a mesma fila; `confirm()` e `<ConfirmHost/>` seguem como aliases de `dialog.confirm`
184
+ e `<DialogHost/>` (zero migração). A doc do AlertDialog passa a mostrar o caso manual real
185
+ (mais de duas ações), já que o `body` cobriu o "corpo próprio".
186
+
187
+ ## 8.4.1 — 2026-07-24
188
+
189
+ **O tom da ação `trailing` mora no SLOT, não no call site.** Uma ação no fim do campo é
190
+ SECUNDÁRIA — então o `text-muted-foreground` passou pro slot `trailing` (do Input e do
191
+ Select), e o botão ghost herda. Antes cada call site sprinklava `className="text-muted-foreground"`
192
+ no botão (inerte quando o slot já daria o tom); agora o trailing nasce muted e acende no
193
+ hover pelo próprio ghost, sem cor no call site.
194
+
195
+ ## 8.4.0 — 2026-07-24
196
+
197
+ **`Button` ganha `icon-xs` (24px) — a ação DENTRO de um campo.** Fecha a escala dos botões
198
+ só-ícone: `icon` (36) · `icon-sm` (32) · `icon-xs` (24). O `icon-xs` é o par do `icon-xs`
199
+ que o `InputGroupButton` já tinha — pra um clear/mostrar-senha no `trailing` do Input/Select
200
+ caber folgado num h-9. Antes o exemplo virava `className="size-6"` (override na mão, fora
201
+ da escala); agora é `size="icon-xs"`.
202
+
203
+ Doc afinada junto: o exemplo de "limpar" do `InputGroup` deixou de ensinar a fazer à mão o
204
+ que o `<Input trailing={…} />` já resolve — o InputGroup agora mostra o caso que é DELE
205
+ (ícone leading + botão no fim juntos, a busca do ActionList). Uma ação simples é prop;
206
+ adornos que se acumulam, InputGroup.
207
+
208
+ ## 8.3.2 — 2026-07-24
209
+
210
+ **`Input` ganha `trailing` — fecha a simetria com o Select.** O 8.3.0 unificou o LEADING
211
+ (`icon` no Input, Select e Alert); este fecha o TRAILING, que estava pela metade (Select
212
+ fazia ação-no-fim por prop, Input só por `InputGroup`). Agora `<Input trailing={<Button/>} />`,
213
+ o par do `icon`. Regra: **um ícone e/ou uma ação → props (`icon`/`trailing`); mais de um
214
+ adorno junto, prefixo de texto ou adorno em bloco → `InputGroup`.** Sem breaking.
215
+
216
+ (Saiu como 8.3.2 — o `release patch` num momento de versionamento cruzado com outra sessão;
217
+ por conteúdo é feature, valeria um minor, mas está publicada e é o que vale.)
218
+
219
+ ## 8.3.1 — 2026-07-24
220
+
221
+ **Destructive legível no escuro + a ação do Alert no lugar.** Duas correções que vieram
222
+ de olhar o dark com atenção:
223
+
224
+ - **`--destructive` do dark clareia (`hsl(0 62.8% 30.6%)` → `hsl(0 72% 58%)`).** O valor
225
+ antigo era calibrado só pra FUNDO (com texto branco por cima), mas menu, alert, field
226
+ e erros de form usam `text-destructive` como FOREGROUND — e um vermelho de 30,6% de
227
+ luminosidade sobre o popover dava ~1,7:1, abaixo do AA (medido). Agora passa como texto
228
+ e alinha o destructive ao que o success já fazia (`dark:text-emerald-400` pula pro tom
229
+ claro). **Efeito nos fundos sólidos** (Button/Badge/toast destructive): ficam mais vivos
230
+ no dark — o branco por cima segue perfeitamente legível (validado no visual), e o
231
+ resultado é consistente com o light e com o shadcn novo. Light **não muda** (só o bloco
232
+ `.dark` foi tocado).
233
+ - **Ação do Alert alinha na coluna de conteúdo.** No Alert com ícone (grid de 2 colunas),
234
+ `AlertTitle`/`AlertDescription` têm `col-start-2`, mas a AÇÃO (children depois da frase,
235
+ ex.: um `<Button>Reiniciar</Button>`) não tinha — caía na coluna do ícone (~1rem) e era
236
+ espremida pra esquerda. Agora vai num wrapper `col-start-2`, alinhada sob o texto.
237
+
238
+ ## 8.3.0 — 2026-07-24
239
+
240
+ **`Input` ganha `icon` — adorno de campo vira prop, unificado com Select e Alert.** O ícone
241
+ leading passa a ser a MESMA prop nos três: `<Input icon={<Building2 />} />`. Antes o Input só
242
+ tinha o `InputGroup` (compositivo, verboso, divergente do Select/Alert); agora adorno simples
243
+ é prop e o `InputGroup` fica pro avançado (botão no fim, prefixo, bloco, múltiplos). Sem
244
+ breaking: prop opcional, o `<input>` cru segue igual sem ela.
245
+
246
+ ## 8.2.1 — 2026-07-24
247
+
248
+ **Remove a auto-dependência `file:` do `package.json` do próprio opus (publish quebrado).**
249
+ Uma sessão paralela injetou `"@softize/opus": "file:…/scratchpad/opus.tgz"` no package.json
250
+ do pacote (provável `pnpm add` do tarball local pra testar), e isso foi publicado da 8.0.3 até
251
+ a 8.2.0: todo consumidor que instalava puxava um `file:` pra um scratchpad que só existia
252
+ naquela máquina — deploy quebrava com ENOENT. 8.2.1 sai limpo.
253
+
254
+ ## 8.2.0 — 2026-07-24
255
+
256
+ **`Select` ganha `trailing`.** Um slot pra uma ação custom DENTRO do controle, no fim (antes
257
+ do chevron) — um botão que age sobre o valor escolhido, em vez de ficar como irmão solto ao
258
+ lado do campo. O clique no trailing **não** abre a lista (o slot para a propagação pro
259
+ controle). Pareia com o `icon` (leading) que já existia.
260
+
261
+ ## 8.1.0 — 2026-07-24
262
+
263
+ **`Tabs` ganha `size`, `Button` ganha `icon-sm` — o `sm` que faltava pra fileira densa.**
264
+ Button e Select tinham `sm` (h-8, 32px); o `Tabs` era h-9 cravado e o `Button` só-ícone só
265
+ tinha `icon` (36px). Numa toolbar/header onde os controles são `sm`, o segmento de abas e um
266
+ botão de ícone ficavam 4px mais altos que os irmãos — e a saída era override de altura na
267
+ mão, que foge da escala do sistema.
268
+
269
+ - `<Tabs size="sm">` baixa a lista pra h-8 (o size flui pra TabsList por contexto). Isto
270
+ **ejetou** o Tabs (o shadcn não tem size aqui) — divergência declarada no lock.
271
+ - `<Button size="icon-sm">` é o quadrado de 32px (`size-8`), par do `sm`.
272
+
273
+ Sem breaking: props novas, opcionais. Quem quiser uma fileira toda em 32px agora usa só
274
+ props de size, sem tocar em altura.
275
+
276
+ ## 8.0.4 — 2026-07-24
277
+
278
+ **A "dançada" do menu, resolvida DE VERDADE — o culpado era o botão.** A pista final
279
+ veio do João: o `active:scale-95` do Button (toque da casa, com `transition-all`).
280
+ No clique HUMANO — que pressiona e segura ~100ms, diferente do clique sintético dos
281
+ testes — o menu abre e se ancora enquanto o gatilho ainda está ENCOLHENDO; o
282
+ posicionador do Radix acompanha o rect a cada frame, então o painel recém-aberto
283
+ **persegue o botão** em passos de 0,5px (~2px de deriva) e desanda na soltura.
284
+ Medido com clique segurado; com o scale removido, âncora imóvel do press à soltura.
285
+
286
+ - **Press do Button vira `active:brightness-90`.** Feedback de clique continua
287
+ (escurece de leve), mas NÃO-GEOMÉTRICO: o rect não muda, nenhum popover ancorado
288
+ (Menu, Popover, Select…) tem por que dançar. Regra que fica: press feedback em
289
+ gatilho não pode mexer em geometria.
290
+ - **A entrada do menu volta a fade + zoom ancorado** (a escolha da 8.0.2 — sem
291
+ slide — fica; o fade-only experimental não chegou a publicar). O `will-change`
292
+ da 8.0.3 também fica: animação no compositor é robustez de graça.
293
+ - As 8.0.2/8.0.3 não eram a causa, mas seguem valendo como melhorias: coreografia
294
+ mais simples e animação imune a stall de main thread na 1ª abertura.
295
+
296
+ ## 8.0.3 — 2026-07-24
297
+
298
+ **A "dançada" de verdade: era só na PRIMEIRA abertura — e agora tem causa medida e
299
+ correção.** A 8.0.2 simplificou a coreografia (decisão que fica), mas o sintoma
300
+ persistia, e o detalhe "só na primeira vez" fechou o diagnóstico: a 1ª abertura da
301
+ página paga a inicialização única (portal + focus-scope + scroll-lock + primeiro
302
+ raster do painel), que trava o main thread NO MEIO dos 150ms — medido em Chrome com
303
+ janela real/Retina: **congelamento de até 219ms** com o menu parado no meio do voo,
304
+ que completava depois com um tranco. Da 2ª abertura em diante, tudo quente → suave.
305
+ É por isso que nenhum teste sintético "médio" pegava.
306
+
307
+ - **Correção: `will-change: transform, opacity` no conteúdo do menu e do submenu.**
308
+ Promove a layer ao compositor ANTES de animar — com a animação rodando no
309
+ compositor, o main thread pode travar que ela segue tocando. Medido pós-fix
310
+ (4 rodadas, browser frio): zero stalls no meio da animação, cadência 16-17ms.
311
+ O resto do custo de init vira só ~50ms até o primeiro paint — e como o fade começa
312
+ em opacity 0, esse início nem é visível.
313
+ - Custo: uma layer composited por menu aberto — irrelevante nesse tamanho.
314
+ - Nota: Popover/Select/Tooltip têm a mesma anatomia de animação e podem herdar o
315
+ mesmo tratamento — ficam pra quando (se) o sintoma aparecer lá; Select e Popover
316
+ são componentes TRAVADOS (byte-fiéis), então a mudança lá passa por ejetar.
317
+
318
+ ## 8.0.2 — 2026-07-24
319
+
320
+ **Menu abre sem "dançada" + a doc ganha o clique-direito.** Dois refinamentos no Menu:
321
+
322
+ - **Entrada sem slide.** A coreografia herdada do shadcn compunha TRÊS movimentos em
323
+ 150ms — descer 7,5px (slide) + inchar 5% (zoom) + fade — e o conjunto lia como
324
+ "dançada" na abertura. Investigação instrumentada (posição frame a frame, câmera
325
+ lenta, screencast em velocidade real, reopen no meio do animate-out) confirmou: não
326
+ há pulo de posição nem bug de layout — a impressão vinha da própria coreografia.
327
+ Agora a entrada é **fade + zoom ancorado no gatilho** (o que o Radix Themes faz);
328
+ a saída já era simétrica e fica como está. Vale pro conteúdo do menu E do submenu.
329
+ - **Doc: "Contexto: clique direito".** O papel do ContextMenu (aposentado na 5.0.0)
330
+ documentado com preview funcional: gatilho CONTROLADO ancorado no ponteiro
331
+ (`onContextMenu` guarda a posição; um span `position: fixed` invisível naquele ponto
332
+ ancora o conteúdo). A página do Menu agora cobre o caso que justificou a unificação.
333
+
334
+ ## 8.0.1 — 2026-07-24
335
+
336
+ **Menu: o hover do item destructive volta a tingir o fundo.** A decisão de 17/jul
337
+ (padrão Notion: hover muda SÓ a cor do texto/ícone, bg segue o accent) foi testada no
338
+ uso real e revertida: **no dark, texto destructive sobre o accent do hover não lia bem**
339
+ — o vermelho perdia contraste justamente no item que mais precisa ser inconfundível.
340
+ Volta o comportamento anterior, byte-fiel ao que era antes da decisão:
341
+
342
+ - Em repouso o item segue NEUTRO (a decisão de 2.24.1, essa fica);
343
+ - No hover/focus, texto/ícone destructive **sobre `bg-destructive/10`**
344
+ (dark: `bg-destructive/20`) — o fundo tingido devolve a leitura no escuro.
345
+
346
+ Visual, só no `<Menu>`; nenhuma API muda. O delta do lock registra o teste e a reversão
347
+ — se o padrão Notion voltar, volta sabendo por que saiu.
348
+
349
+ ## 8.0.0 — 2026-07-24
350
+
351
+ **A densidade do markdown sai — por ora.** O `size` do `<Markdown>` e a regra
352
+ `.prose[data-size='sm']` do theme.css foram revertidos: no uso real quase toda mensagem
353
+ tem um parágrafo só, e ali o `[&>:first-child]:mt-0 [&>:last-child]:mb-0` já zerava tudo —
354
+ a regra só atuava nas mensagens com vários blocos, que são minoria. O que de fato apertava
355
+ o transcript era o vão ENTRE mensagens (resolvido no 7.2.0, que fica). Sobrar uma prop de
356
+ efeito quase nulo é pior que não ter: vira decisão a mais em cada call site.
357
+
358
+ Fica o que se pagou: **motor único** (markdown-it no chat e na doc), a correção do respiro
359
+ fantasma e a hierarquia de gaps do Chat.
360
+
361
+ Se a densidade voltar, volta com o problema entendido — e a discussão em aberto é se o
362
+ `prose` (régua de artigo) deve mesmo estar no chat, ou se ali cabe estilo próprio.
363
+
364
+ ### Breaking
365
+
366
+ `MarkdownProps.size` saiu. Quem passava `size="sm"` só remove a prop — o render é o mesmo
367
+ de antes, já que a regra que dava efeito a ela também saiu.
368
+
369
+ ## 7.2.0 — 2026-07-24
370
+
371
+ **Hierarquia no espaço do transcript.** O `Chat` usava o MESMO `gap-2` nos dois níveis:
372
+ entre turnos e entre as falas de dentro de um turno. Como o agente narra o que vai fazendo
373
+ ("Vou ler o arquivo…", "A estrutura está correta…"), um turno junta oito falas curtas — e
374
+ com o gap igual nos dois níveis o transcript virava uma parede uniforme, sem começo nem fim
375
+ visíveis, gastando ~56px só de vão.
376
+
377
+ Agora: **`gap-1` dentro do turno** (as falas são um raciocínio contínuo, andam juntas) e
378
+ **`gap-3` entre turnos** (aí sim separa pergunta de pergunta). Num turno de oito falas são
379
+ ~28px a menos, e de quebra dá pra bater o olho e ver onde cada turno começa.
380
+
381
+ Isto é o par da densidade do 7.1.0, e ataca o que aquela mudança NÃO alcançava: mensagem de
382
+ um parágrafo só já tinha margem interna zerada — o que ocupava espaço ali era o vão entre
383
+ mensagens, que é do Chat, não do markdown.
384
+
385
+ ## 7.1.1 — 2026-07-24
386
+
387
+ **`@types/markdown-it` vai pra `dependencies`.** O pacote distribui o SOURCE (`.tsx`), então
388
+ o typecheck de quem consome atravessa nossos arquivos: com os tipos só em `devDependencies`,
389
+ todo app quebrava com `TS7016: Could not find a declaration file for module 'markdown-it'` —
390
+ e agora atinge todos, porque quem importa é o primitivo `Markdown`, que o `Chat` usa. Regra
391
+ que fica: **dependência que aparece em `import` de arquivo publicado precisa dos tipos em
392
+ `dependencies`**, não em dev.
393
+
394
+ ## 7.1.0 — 2026-07-24
395
+
396
+ **Um motor de markdown só.** Havia dois: o `Markdown` (primitivo, usado pelo `Chat`) era um
397
+ parser de **regex à mão**, e a doc renderizava com **markdown-it**. Mesma mensagem, render
398
+ diferente conforme onde caísse — e o argumento que justificava o parser caseiro ("sem
399
+ dependência de runtime") não valia mais: o markdown-it já era `dependency` do pacote por
400
+ causa da doc. Agora o primitivo usa markdown-it, e a doc importa a **mesma instância** (não
401
+ duas com opções próprias, que divergiriam no primeiro edge case).
402
+
403
+ Em prática, o chat ganha o que o regex não cobria: itálico, listas aninhadas, referências,
404
+ ênfase combinada, todo o CommonMark. `html: false` segue valendo — tag HTML no fonte é
405
+ escapada, o que é o que torna seguro renderizar texto de usuário e de modelo.
406
+
407
+ **`<Markdown size="sm">`** encolhe o RESPIRO entre blocos mantendo o corpo em 14px — mesma
408
+ semântica do `size` no Button (encolhe o espaço, não a letra). O `<Chat>` passou a usar.
409
+ A régua do `prose` é de artigo: 16px em volta de cada parágrafo, 20px no bloco de código e
410
+ **40px em volta de cada `<hr>`** — numa bolha, uma linha divisória custa mais que a mensagem.
411
+
412
+ O espaçamento mora no `theme.css` (`.prose[data-size='sm']`), junto do resto da tipografia,
413
+ e o seletor é **universal** (`> *`), não uma lista de tags — a primeira versão desta regra
414
+ enumerava p/ul/ol/pre/blockquote/h1-h4/li e deixava passar justamente o `hr`, o maior de
415
+ todos. Elemento novo (table, figure, dl) entra sozinho.
416
+
417
+ ### Corrigido
418
+
419
+ **O respiro fantasma nas mensagens.** As bolhas do `Chat` tentavam zerar a margem do
420
+ primeiro e do último bloco com `[&>*:first-child]:mt-0`, mas o primeiro filho da bolha é o
421
+ *wrapper* do `prose` — que não tem margem; quem tem é o `<p>` um nível abaixo. A
422
+ compensação nunca surtiu efeito, e cada mensagem carregava ~16px invisíveis em cima e
423
+ embaixo. Agora mora dentro do `Markdown`, onde alcança o bloco certo.
424
+
425
+ ## 7.0.1 — 2026-07-23
426
+
427
+ **Ponteiros mortos pro `DeleteButton`.** Duas páginas (`confirm` e `alert-dialog`) ainda
428
+ mandavam usá-lo pra excluir por contrato, um release depois de ele sair. O gate de doc
429
+ (`content-scope`) só enxerga JSX nos previews — prosa que cita componente inexistente passa,
430
+ e essa é a terceira vez no dia que a varredura à mão é que pega.
431
+
432
+ ## 7.0.0 — 2026-07-23
433
+
434
+ **O `DeleteButton` durou um release.** Ele entrou no 6.0.0 e sai agora: era o
435
+ `ActionTrigger` com uma lixeira dentro — mesmas props (`action`, `input`, `label`,
436
+ `onSuccess`, `className`), e o trigger já tinha `variant`, `size: 'icon'` e a confirmação
437
+ do contrato. Pior: o nome furava a convenção da casa, onde contract-driven com `action` na
438
+ primeira prop é a família `Action*`. Criar componente onde cabia prop é o que os releases 3
439
+ a 5 desfizeram — repeti o erro e desfaço.
440
+
441
+ As três coisas que só ele tinha subiram pro `ActionTrigger`:
442
+
443
+ - **`icon`** — o botão vira icon-only, com o rótulo no tooltip e no `aria-label`, visual
444
+ discreto (vermelho no hover quando o contrato marca `destructive`) e clique que **não
445
+ vaza** pro item em volta.
446
+ - **`itemLabel`** — nomeia o alvo na pergunta, entre aspas, antes da mensagem do contrato.
447
+ - **Erro de negócio falado** — a frase do servidor vence o rótulo genérico em
448
+ `conflict`/`validation`/`not_found`; o resto cai no rótulo, pra não vazar
449
+ `violates foreign key constraint …` pra quem só clicou num botão. Agora vale pra QUALQUER
450
+ ação de item (arquivar, duplicar, reprocessar), não só excluir.
451
+
452
+ ### Breaking
453
+
454
+ `DeleteButton` e `DeleteButtonProps` saíram.
455
+
456
+ ```tsx
457
+ // Antes
458
+ <DeleteButton action={unitDelete} input={{ id: u.id }} itemLabel={u.name} />
459
+ // Depois
460
+ <ActionTrigger action={unitDelete} input={{ id: u.id }} icon={<Trash2 />} label="Excluir" itemLabel={u.name} />
461
+ ```
462
+
463
+ **Confira se o contrato declara `confirm`.** O `DeleteButton` perguntava SEMPRE (montava a
464
+ caixa com textos default); o `ActionTrigger` segue a regra da casa — sem `action.confirm`
465
+ (ou a prop `confirm`), o disparo é **direto**. Um delete sem `confirm` declarado passaria a
466
+ apagar sem perguntar. A confirmação é spec: declare no contrato.
467
+
468
+ ## 6.0.3 — 2026-07-23
469
+
470
+ **A página do AlertDialog mostra o caminho curto.** Ela dizia em texto que `confirm()` faz
471
+ a mesma pergunta em uma linha, mas não mostrava — quem chegasse por ali montava as 15
472
+ linhas assim mesmo. Agora tem preview vivo e clicável.
473
+
474
+ ## 6.0.2 — 2026-07-23
475
+
476
+ **`confirm()` avisa da colisão com o `window.confirm`.** Esquecer o import não dá erro: o
477
+ TypeScript resolve pro global e o código passa a abrir a caixa cinza do sistema (que
478
+ webview suprime e não tem tema). A doc agora abre com esse aviso e sugere
479
+ `no-restricted-globals` no ESLint.
480
+
481
+ ## 6.0.1 — 2026-07-23
482
+
483
+ **Import órfão que quebrava o typecheck de quem consome.** A migração da confirmação do
484
+ `ActionList` pro AlertDialog deixou `DialogDescription` importado sem uso — inofensivo
485
+ aqui, erro (`TS6133`) em todo app com `noUnusedLocals`, que é o caso dos nossos. O gate do
486
+ pacote não pegava porque o tsconfig DELE não tinha a regra: agora tem
487
+ (`noUnusedLocals` + `noUnusedParameters`), então o próximo órfão morre antes de publicar.
488
+
489
+ ## 6.0.0 — 2026-07-23
490
+
491
+ **`confirm()` — a confirmação em uma linha.**
492
+
493
+ ```tsx
494
+ if (await confirm({ title: 'Excluir a sessão?', action: 'Excluir', variant: 'destructive' })) …
495
+ ```
496
+
497
+ Mesma forma do `toast` (superfície imperativa + host no shell), com a diferença de que esta
498
+ **responde** (`Promise<boolean>`). Nasceu de um levantamento constrangedor: a casa
499
+ confirmava exclusão de QUATRO jeitos — um `DeleteButton` copiado entre dois repos (116 e
500
+ 130 linhas, já divergentes), dialogs montados à mão em três telas, um botão de "dois
501
+ cliques" e o confirm declarativo dos patterns. Todos porque montar a pergunta custava ~15
502
+ linhas de JSX.
503
+
504
+ Requer `<ConfirmHost />` no shell, ao lado do `<Toaster />`. Sem ele **lança** em vez de
505
+ devolver promise pendurada — clique sem efeito é o pior desfecho pra uma pergunta
506
+ destrutiva (mesma guarda do `onAsk` no driver de IA).
507
+
508
+ **`<DeleteButton action input />` sobe pro pacote.** É a cópia que existia nos dois repos,
509
+ na versão mais madura: allowlist de erro de NEGÓCIO (a frase do servidor só vence o rótulo
510
+ genérico em `conflict`/`validation`/`not_found` — erro inesperado traria
511
+ `violates foreign key constraint …` pro toast de quem só clicou em excluir).
512
+
513
+ **Toda confirmação da casa passa a ser o AlertDialog compacto.** `ActionTrigger` e
514
+ `ActionList` confirmavam com `Dialog` comum — agora usam `AlertDialog`: `role="alertdialog"`,
515
+ não fecha no clique fora. O compacto virou o desenho ÚNICO.
516
+
517
+ **`<Alert>` ganha forma curta:** `<Alert icon={<Info />} title="…" description="…" />` numa
518
+ linha; a composição segue pro conteúdo rico.
519
+
520
+ ### Corrigido: o alert saía uma palavra por linha
521
+
522
+ `<Alert>Workspace Empresa X sincronizado.</Alert>` renderizava a frase quebrada, uma
523
+ palavra por linha — e a doc ensinava exatamente esse uso. Duas causas somadas: o alert era
524
+ SEMPRE `grid grid-cols-[0_1fr]`, então texto solto virava item anônimo na 1ª coluna, de
525
+ largura **zero**; e o `AlertDescription` era `grid justify-items-start`, onde cada nó de
526
+ uma frase interpolada (`Workspace {cliente} {nome} sincronizado.`) virava um item empilhado.
527
+ Agora o grid só existe quando há ícone, a descrição não é grid, e texto cru como filho cai
528
+ no slot de descrição sozinho.
529
+
530
+ ### Breaking
531
+
532
+ **`size` saiu do `AlertDialogContent`** — o compacto (`max-w-xs`, footer em duas colunas) é
533
+ o único desenho. Quem passava `size="sm"` só apaga a prop; quem dependia do largo
534
+ (`sm:max-w-lg`) precisa decidir: confirmação curta cabe no compacto, e conteúdo que não
535
+ cabe provavelmente não era uma confirmação — é `Dialog`.
536
+
537
+ ```tsx
538
+ // Antes // Depois
539
+ <AlertDialogContent size="sm"> <AlertDialogContent>
540
+ ```
541
+
542
+ O `Alert` deixou de aceitar o atributo `title` do HTML (agora `title` é o título do alert).
543
+ Era tooltip nativo, que a casa já bania em favor do `Tooltip`.
544
+
545
+ ## 5.0.1 — 2026-07-23
546
+
547
+ **Nome morto na doc, e o gate que faltava.** O preview do Composer ainda montava
548
+ `<DropdownMenu>` — transpila igual, e explodia no browser. Sete `whenToUse` do catálogo
549
+ também apontavam pra ele ("pra menu de ações, use DropdownMenu"), mandando quem lesse
550
+ importar um nome que não existe mais.
551
+
552
+ O `content-fences` provava que todo fence TRANSPILA; agora um irmão (`content-scope`)
553
+ prova que **todo componente citado num preview existe no scope** — foi ele que teria
554
+ pegado isto sozinho, e é o que cobre o próximo rename.
555
+
556
+ ## 5.0.0 — 2026-07-23
557
+
558
+ **Cinco menus viram um: `Menu`.** O catálogo tinha `DropdownMenu`, `ContextMenu`,
559
+ `Menubar`, `NavigationMenu` e `Command` — e a decisão "qual dos cinco" caía na tela toda
560
+ vez. Medindo o uso real (softize + cliente + o próprio Opus): DropdownMenu em 4 telas,
561
+ Command como motor do Select e do IconPicker, e **zero** para os outros três.
562
+
563
+ - **Saem `ContextMenu` e `Menubar`**: é o mesmo menu com outro gatilho — o ContextMenu é o
564
+ dropdown no botão direito; o Menubar são N dropdowns numa barra, vocabulário de app
565
+ desktop (Arquivo/Editar/Ver) que nenhuma tela nossa tem.
566
+ - **Sai `NavigationMenu`**: mega-menu de LINKS de site (semântica `<nav>`, não
567
+ `role="menu"`). Papel legítimo — que a casa não exerce.
568
+ - **`Command` fica**: não é menu, é busca por teclado (e é o motor do Select).
569
+ - **`DropdownMenu` vira `Menu`**: sem irmãos pra distinguir, o qualificador não
570
+ distinguia de nada. "Dropdown" ainda por cima descreve como abre, não o que é — e no uso
571
+ corrente costuma significar o `Select`.
572
+
573
+ Componente portado não se cria, se baixa: no dia que existir a primeira tela com menu de
574
+ contexto, `scripts/port-shadcn.mjs context-menu` traz o ContextMenu byte-fiel de volta em
575
+ um comando. Até lá, cada entrada a mais no catálogo só cobra pedágio na decisão.
576
+
577
+ ### Breaking
578
+
579
+ `DropdownMenu*` → `Menu*` (rename puro, mesma API):
580
+
581
+ ```tsx
582
+ // Antes // Depois
583
+ <DropdownMenu> <Menu>
584
+ <DropdownMenuTrigger asChild> <MenuTrigger asChild>
585
+ <DropdownMenuContent align="end"> <MenuContent align="end">
586
+ <DropdownMenuItem … <MenuItem …
587
+ <DropdownMenuCheckboxItem … <MenuCheckboxItem …
588
+ <DropdownMenuSeparator /> <MenuSeparator />
589
+ ```
590
+
591
+ Vale pros 15 subcomponentes (`Trigger`, `Content`, `Item`, `CheckboxItem`, `RadioGroup`,
592
+ `RadioItem`, `Label`, `Separator`, `Shortcut`, `Group`, `Portal`, `Sub`, `SubTrigger`,
593
+ `SubContent`). `data-slot`: `dropdown-menu-*` → `menu-*`.
594
+
595
+ `ContextMenu*`, `Menubar*` e `NavigationMenu*` **não existem mais**. Menu de contexto e
596
+ barra de menus se fazem com `Menu`; navegação de links, com `<nav>` + `Button`/`Link` (ou
597
+ re-porte o componente do registry shadcn se o caso for mesmo um mega-menu).
598
+
599
+ ## 4.0.0 — 2026-07-23
600
+
601
+ **Sheet e Drawer eram o mesmo componente com dois motores.** Painel que desliza de uma
602
+ borda: o `Sheet` fazia isso com Radix Dialog, o `Drawer` com [vaul](https://vaul.emilkowal.ski/)
603
+ (que acrescenta gesto de arrastar e snap points). Mesmo papel, duas entradas no catálogo —
604
+ e a decisão "qual dos dois" caindo na tela toda vez. Prova de que não funcionava: o único
605
+ consumidor da casa importava o `Sheet` e batizou o wrapper local de `Drawer`.
606
+
607
+ Fica **um**, com o motor Radix (mesma família do Dialog: overlay, trap de foco, ESC) e o
608
+ nome **`Drawer`** — que é como MUI, Ant Design, Chakra e Mantine chamam o painel de borda;
609
+ "sheet" é vocabulário do shadcn/Apple. O `vaul` sai das dependências. Se um dia aparecer o
610
+ caso real de bottom sheet arrastável, ele volta como PROP do Drawer, não como componente
611
+ irmão.
612
+
613
+ Limpeza junto: `@radix-ui/react-select` continuava declarado desde o 3.0.0, sem ninguém
614
+ importar. Saiu.
615
+
616
+ ### Breaking
617
+
618
+ `Sheet`, `SheetTrigger`, `SheetClose`, `SheetContent`, `SheetHeader`, `SheetFooter`,
619
+ `SheetTitle` e `SheetDescription` **saíram**: troque o prefixo por `Drawer*`. A API é a
620
+ mesma (`side`, `showCloseButton`, `open`/`onOpenChange`, `asChild`) — é rename, não
621
+ redesenho.
622
+
623
+ ```tsx
624
+ // Antes // Depois
625
+ <Sheet open={o} onOpenChange={setO}> <Drawer open={o} onOpenChange={setO}>
626
+ <SheetContent side="right"> <DrawerContent side="right">
627
+ <SheetHeader><SheetTitle>… <DrawerHeader><DrawerTitle>…
628
+ ```
629
+
630
+ Quem usava o `Drawer` do vaul: a API de composição é a mesma, mas **`direction` virou
631
+ `side`**, e `DrawerPortal`/`DrawerOverlay` deixaram de ser exportados (o `DrawerContent`
632
+ já os inclui). O gesto de arrastar e os snap points não existem mais.
633
+
634
+ `data-slot`: `sheet-*` → `drawer-*`.
635
+
636
+ ## 3.0.1 — 2026-07-23
637
+
638
+ **A lista do `variant="ghost"` ganha largura própria.** Ela seguia a largura do controle
639
+ (certo no campo de form, errado na barra do composer: o gatilho ali é do tamanho de uma
640
+ chave curta como `GB-42`, e a lista saía com 160px cortando o título inteiro). Agora o
641
+ ghost abre em `min-w-56 max-w-80` — o resto do modo default segue acompanhando o campo.
642
+
643
+ ## 3.0.0 — 2026-07-23
644
+
645
+ **Um Select só.** Escolher em lista era QUATRO componentes com TRÊS APIs diferentes
646
+ (`Select` composicional do Radix, `NativeSelect` com `NativeSelectOption` como filho,
647
+ `Combobox` data-driven, `ComposerSelect` data-driven com outro nome de props) — e a
648
+ decisão "qual dos quatro" caía na tela, toda vez. Agora é **um componente, modos por
649
+ prop**: `<Select>` com `options` (dado, nunca JSX de item) + `searchable` · `multiple` ·
650
+ `native` · `variant="ghost"`. A régua que importava (achabilidade: lista longa precisa de
651
+ busca) virou UMA prop, não a escolha do componente.
652
+
653
+ **Corrigido junto: o `id` agora chega ao campo.** O cmdk sobrescrevia o `id` do input, e
654
+ o `htmlFor` do Label não achava controle nenhum — clicar no rótulo não focava. O Select
655
+ reaplica o próprio id; o `ActionFormField` perdeu o fallback que existia só por causa
656
+ disso.
657
+
658
+ **Nav da doc por categoria.** O catálogo de UI (`/ui`) deixou de ser uma lista alfabética
659
+ de 67 itens + uma seção "Patterns" à parte: são dez grupos (Estrutura, Actions, IA,
660
+ Formulário, Botões, Navegação, Exibição, Feedback, Sobreposição, Layout), e o item de
661
+ subgrupo rotulado agora **recua** sob o rótulo no `SectionShell` (três níveis na mesma
662
+ margem não eram hierarquia).
663
+
664
+ ### Breaking
665
+
666
+ `Combobox`, `NativeSelect`, `NativeSelectOption`, `NativeSelectOptGroup`, `ComposerSelect`,
667
+ `SelectClear` e toda a API composicional do Radix (`SelectTrigger`, `SelectValue`,
668
+ `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, `SelectSeparator`,
669
+ `SelectScrollUpButton`, `SelectScrollDownButton`) **saíram do pacote**. `ComboboxOption`
670
+ virou `SelectOption`.
671
+
672
+ ```tsx
673
+ // Antes — Select do Radix (composicional)
674
+ <Select value={v} onValueChange={setV}>
675
+ <SelectTrigger id="papel"><SelectValue placeholder="Selecione" /></SelectTrigger>
676
+ <SelectContent>
677
+ <SelectItem value="dev">Developer</SelectItem>
678
+ </SelectContent>
679
+ </Select>
680
+ // Depois
681
+ <Select id="papel" value={v} onChange={setV} placeholder="Selecione"
682
+ options={[{ value: 'dev', label: 'Developer' }]} />
683
+
684
+ // Antes — NativeSelect
685
+ <NativeSelect value={v} onChange={(e) => setV(e.target.value)}>
686
+ <NativeSelectOption value="dev">Developer</NativeSelectOption>
687
+ </NativeSelect>
688
+ // Depois
689
+ <Select native value={v} onChange={setV} options={[{ value: 'dev', label: 'Developer' }]} />
690
+
691
+ // Antes — Combobox → Depois
692
+ <Combobox value={v} onChange={setV} options={o} />
693
+ <Select searchable value={v} onChange={setV} options={o} />
694
+
695
+ // Antes — ComposerSelect → Depois
696
+ <ComposerSelect value={v} onChange={setV} options={o} />
697
+ <Select variant="ghost" value={v} onChange={setV} options={o} />
698
+ ```
699
+
700
+ Ponto a ponto:
701
+
702
+ - **`onChange` recebe o VALUE**, não o evento — inclusive no `native` (era
703
+ `e.target.value`).
704
+ - **Grupo é dado**: `group` na opção (vira `<optgroup>` no native, heading no custom);
705
+ `SelectGroup`/`SelectLabel`/`SelectSeparator` não existem mais.
706
+ - **`clearable` é prop** (o `SelectClear` por composição saiu).
707
+ - **Item rico**: `label` é sempre string (campo, busca, a11y) e o JSX vai em `content`.
708
+ `triggerLabel` (do ComposerSelect) segue na opção.
709
+ - **`defaultValue` saiu**: o Select é controlado (`value` + `onChange`).
710
+ - **Largura**: o trigger do Radix era `w-fit`; o Select é `min-w-40` e cresce com o pai —
711
+ quem passava `w-full` em form pode tirar.
712
+ - **`data-slot`**: `combobox-*` → `select-*` (`select-control`, `select-input`,
713
+ `select-item`, `select-chip`, `select-clear`, `select-toggle-all`). Teste E2E que mirava
714
+ `[data-slot="select-trigger"]` passa a mirar `[data-slot="select-control"]`.
715
+ - **Visual**: o campo não-buscável agora é um input readOnly (mesma altura e moldura de
716
+ antes); o Radix abria a lista alinhada ao item selecionado, o novo abre ancorado no
717
+ campo.
718
+
719
+ Os patterns da casa (`ActionForm`, `ActionList`, filtros, `Composer`) já vêm migrados —
720
+ `fieldOptions`/`filterOptions` agora tipam `SelectOption[]`.
721
+
722
+ ## 2.45.0 — 2026-07-23
723
+
724
+ **SectionShell: nav redimensionável (`resizable`).** A divisa arrastável que o `rail` do
725
+ AppShell já tinha desce pro terceiro nível — quando os rótulos da nav variam de tamanho,
726
+ o `w-56` fixo ora aperta ora sobra. Opt-in (default segue a coluna fixa); ligado, a
727
+ largura vira % (`navDefaultSize` 18, `navMinSize` 12).
728
+
729
+ **O Combobox volta pra régua de altura da casa** — e ganha `size`. Ele era o único
730
+ controle de form em `min-h-10`: numa toolbar ao lado de Button/Select/Input (todos h-9)
731
+ ficava visivelmente mais alto, "pegando carona" no flex. Agora o default é **`min-h-9`**
732
+ (a altura do DS) e existe **`size="sm"`** (h-8) pra barra de filtros densa — o mesmo par
733
+ que `Select` e `NativeSelect` já tinham. **Sem breaking de API** (prop opcional), mas é
734
+ **mudança VISUAL**: todo Combobox encolhe 4px por padrão. Se alguma tela dependia dos
735
+ 40px, passe `className` com a altura desejada.
736
+
737
+ ## 2.44.0 — 2026-07-23
738
+
739
+ **Ícone DENTRO do campo, uniforme.** `Select` (via `SelectTrigger`), `NativeSelect` e
740
+ `Combobox` ganham o prop **`icon`** — um ícone leading dentro do controle, herdando
741
+ `size-4` + `text-muted-foreground` como o resto (mesma linguagem do `InputGroupAddon`,
742
+ que já cobria os `<Input>` de texto). Nasceu de uma tela real construída no Maestro que
743
+ ficou com os ícones (calendário/prédio/pessoa) **jogados ao lado** dos filtros porque os
744
+ selects não tinham onde pôr — n≥2 (todo filtro faz isso). **Sem breaking** (prop
745
+ opcional; sem `icon`, nada muda). Uso: `<NativeSelect icon={<Building2 />}>…`,
746
+ `<SelectTrigger icon={<CalendarDays />}>`, `<Combobox icon={<Ticket />} … />`. Pros
747
+ inputs de texto, siga usando `InputGroup` + `InputGroupAddon align="inline-start"`.
748
+
749
+ ## 2.43.0 — 2026-07-23
750
+
751
+ **Terceira leva: os promovidos por reincidência.** `<Truncate>`, marshalling de `t.json()`
752
+ no repo, e o `readOnlyContextPool` — a candidatura de segurança do "SQL escrito por LLM"
753
+ (n=2 na casa) vira primitivo da base. **Sem breaking** (exports/comportamentos aditivos;
754
+ ver a nota de migração do t.json()).
755
+
756
+ - **`<Truncate>` (novo primitivo, `ui/react`).** Texto truncado com tooltip SÓ quando
757
+ transborda (medição do overflow via ResizeObserver, re-medida em resize) — aposenta a
758
+ composição `block truncate` + `title` sempre presente, que mostra dica até em texto
759
+ que não corta. `tooltip` sobrepõe o conteúdo da dica (default: os children). Requer
760
+ `TooltipProvider` na raiz (o esqueleto já monta). Promovido por reincidência (Ai.tsx
761
+ do Maestro, duas tabelas).
762
+ - **`kyselyRepo` marshalla campo `t.json()` (objeto→jsonb, guiado pela declaração).**
763
+ A dupla `ColumnType<unknown, string, never>` + `JSON.stringify` na mão morre no
764
+ caminho do repo: objeto E array são serializados na escrita (insert/update). O caso
765
+ traiçoeiro era o ARRAY — o pg stringifica objeto plano sozinho, mas array vira
766
+ literal de array do PG (errado pra jsonb) sem quebrar typecheck. **Migração:** quem
767
+ já passava STRING pré-serializada segue valendo (string passa direto — sem
768
+ double-encode); quem usava o repo com objeto ganha o conserto de graça. Query à mão
769
+ (fora do repo) continua responsável pelo próprio stringify.
770
+ - **`readOnlyContextPool` (`@softize/opus/data/readonly-pool`).** SQL escrito por LLM
771
+ com as QUATRO defesas dos ADRs 0003/0007 — todas no BANCO, nenhuma em regex: pool com
772
+ teto (`max` do pool e/ou `maxConcurrent` com fila própria), `BEGIN TRANSACTION READ
773
+ ONLY`, `SET LOCAL ROLE` por transação (alcance do contexto; roles/grants
774
+ default-fechado são migração sua) e protocolo estendido (o SQL roda sempre com array
775
+ de valores — multi-sentença é recusada pelo protocolo). Saindo, `DISCARD ALL` devolve
776
+ a conexão limpa; limpeza que falha DESTRÓI a conexão (nunca volta suja ao pool).
777
+ `statement_timeout` local por transação (default 15s). Zero-dep: aceita qualquer
778
+ pool pg-like (`connect()`), sem dependência direta de `pg`.
779
+
780
+ ## 2.42.1 — 2026-07-23
781
+
782
+ Destrava a publicação da 2.42.0, que o gate do release barrou ANTES do registry (a
783
+ tag existe; a versão nunca publicou — **instale esta**). Mesmo conteúdo, mais o conserto:
784
+
785
+ - **Import órfão reprovava o smoke do esqueleto.** A ampliação do `useTriggerAction`
786
+ deixou `SimpleContract` importado sem uso em `ui/drivers/react.tsx`. O tsconfig do
787
+ PACOTE não cobra unused — o do app escafoldado cobra (`noUnusedLocals`), e como o
788
+ Opus ship source, o tsc do consumidor compila a base: o smoke do release
789
+ (`opus create` + typecheck) barrou o tarball, exatamente como devia. Import
790
+ removido; o smoke roda limpo de ponta a ponta.
791
+
792
+ ## 2.42.0 — 2026-07-23
793
+
794
+ > ⚠️ **Não publicou** — o smoke do release barrou o tarball (import órfão; o
795
+ > diagnóstico está na 2.42.1). O conteúdo abaixo vale e saiu na **2.42.1**.
796
+
797
+ **Segunda leva do 1º app real: UI composável + audit pronto pra dado sensível.** Fecha
798
+ quatro issues do consumidor (GB). **Sem breaking** (tipos ampliados e opções/exports
799
+ novos, todos aditivos).
800
+
801
+ - **`useTriggerAction` aceita QUALQUER contrato.** Era tipado só pra `SimpleContract`,
802
+ mas roda qualquer um (o hook usa name/kind e lê `invalidates`) — disparar um
803
+ FormContract fora de form (modal composto que submete `role.update`) exigia
804
+ `as unknown as SimpleContract<...>`. Agora o parâmetro é `ActionContract<TInput,
805
+ TData>`; o cast morre. Nenhuma chamada existente muda.
806
+ - **`useActionFormContext()` (novo export, `ui/react`).** A válvula de escape pra
807
+ CONTROLE CUSTOM no modo composição do `<ActionForm>`: campo com UI própria (a grade
808
+ de permissões que forçou abandonar o form inteiro) participa do form do contrato —
809
+ `form.watch`/`form.setValue`/`formState.errors` — mantendo zod-resolver, erro inline
810
+ e submit do contrato. Type `ActionFormContextValue` exportado junto.
811
+ - **Redator global plugável nos sinks de audit (`redact` em `pgAudit`/`consoleAudit`) +
812
+ `redactDeep` (`@softize/opus/audit`).** O pgAudit persistia input E output CRUS —
813
+ `user.create`/`setPassword` gravaria senha em claro; o `AuditConfig.redact` por action
814
+ só cobre dot-paths estáticos. O `redactDeep` é o redator recursivo por nome de chave
815
+ (default `/password|passwd|secret|token|authorization|api[-_]?key|credential/i`, em
816
+ qualquer profundidade, arrays inclusos): `pgAudit({ pool, redact: redactDeep })` e o
817
+ sink fica utilizável com dado sensível. `redactRecord` e `SENSITIVE_KEY_PATTERN`
818
+ exportados pra sink próprio reusar.
819
+ - **`AuditRecord` carrega `actionKind` e `actor.meta` populado.** Sink que filtra
820
+ leitura (kind list/view = ruído) decidia recebendo os DOMAINS por fora pra montar Set
821
+ de nomes — agora o record diz o kind. E `actor.meta` (que existia no tipo mas nunca
822
+ vinha) chega com `name`/`email` do user resolvido, quando presentes — só os dois
823
+ campos de identificação; o resto do `User` (shape aberto) não vaza pro log.
824
+
825
+ ## 2.41.0 — 2026-07-23
826
+
827
+ **O `opus check` volta a ser gate de verdade + a leva de correções do 1º app real.** Fecha
828
+ seis issues do consumidor (GB): o check enxerga o split contrato/bind e não passa mais
829
+ verde vazio, `requires` sem `authorize` vira violação, o `gen` projeta o `requires` fiel
830
+ e poda órfãos, o `create` tolera repo recém-iniciado e o template nasce com os providers.
831
+ **Sem breaking de API** — mas o check pode ficar VERMELHO onde antes passava vacuamente
832
+ (é o conserto; ver o 1º e o 2º bullets).
833
+
834
+ - **`opus check` enxerga `defineContract` + `bindAction`.** Era cego: só `defineAction`,
835
+ e app refatorado pro split saía INTEIRO do radar ("Nenhuma action encontrada", exit 0 —
836
+ gate vazio passando verde por 200+ arquivos). Contrato passa pelas mesmas regras
837
+ (name/kind/ordem/`export const`); bind conta como action e cobra `export const`.
838
+ ⚠️ Comportamento: **0 actions agora é exit ≠ 0** — gate sem nada pra checar não é
839
+ aprovação; a mensagem diz o que ele procura (e acusa contrato sem bind no diretório).
840
+ O hook `opus-check-on-stop` herda isso (bloqueia gate vazio).
841
+ - **Nova regra `requires-sem-authorize`.** `requires` é DECLARATIVO (o runtime 2.x não o
842
+ executa — e dev que confia nele deixa a action ABERTA; aconteceu de verdade na
843
+ graduação do reports). No `defineAction` acusa direto; no split, o join contrato↔bind é
844
+ cross-file por identificador exportado — `authorize` no contrato OU no binding
845
+ satisfaz; referência ambígua/não encontrada não acusa (sem falso-positivo). Runtime
846
+ executar `requires` como authorize default segue em discussão; esta régua fecha o
847
+ buraco sem mudar runtime.
848
+ - **`opus gen` poda órfãos.** `.md`/`.ts` que a rodada anterior gerou e esta não gerou
849
+ saem de `docs/`, `client-stubs/` e `client-stubs/dicts/` — removido um domínio, a doc
850
+ que o AGENTE lê parava anunciando endpoint 404. Só as extensões geradas nos dirs
851
+ gerenciados; qualquer outro arquivo fica. O sumário lista o que podou.
852
+ - **`opus gen` projeta o `requires` FIEL no manifest.** `requires: ['sales','hr']`
853
+ (semântica ANY) saía `"permission": "sales"` — a spec mentia por omissão. Agora lista
854
+ sai lista (`permission: string | string[] | null`); a doc mostra
855
+ `` `sales` | `hr` (qualquer uma) ``; os stubs já emitiam o JSON certo.
856
+ - **`opus create` tolera repo recém-iniciado.** Dir com só `.git` (e/ou README/LICENSE)
857
+ não é mais "já existe e não está vazio" — `git init` + remote ANTES do scaffold é o
858
+ ponto de partida normal de repo-cliente. Vale pro `--monorepo` e pro modo app; os
859
+ templates não têm esses arquivos, então segue sem clobber por construção.
860
+ - **Template do app nasce com o trio de providers e favicon.** `QueryClientProvider` →
861
+ `TbdlibProvider` → `TooltipProvider` no `main.tsx` (qualquer Tooltip da base explodia
862
+ no 1º uso; os hooks de action exigem os outros dois) e `public/favicon.svg` + link no
863
+ `index.html` (some o 404 do console).
864
+
865
+ ## 2.40.0 — 2026-07-22
866
+
867
+ **Modo design: `fake`/`fakeMany` + split de reação.** Duas peças aditivas que fundam o preview
868
+ UI-only-com-dado — o `mockHandler`/`OPUS_MODE=design` (que já existia) agora tem com o que ser
869
+ alimentado — e deixam a REAÇÃO autorável como contrato. **Sem breaking** (só exports novos).
870
+
871
+ - **`fake(schema)` / `fakeMany(schema, n)` (`@softize/opus/testing`).** Dado sintético
872
+ DETERMINÍSTICO (PRNG semeado, sem `Math.random` → preview reproduzível) que SATISFAZ o schema
873
+ zod — matéria-prima do `mockHandler` no modo design e de fixtures de teste. Heurística por
874
+ check do zod (email length-aware, uuid, url, datetime) e por nome de campo (id, data,
875
+ telefone, cpf/cnpj, nome…), sabor pt-BR; respeita min/max/int/length/enum/literal (formato por
876
+ NOME cede pro length quando há restrição).
877
+ - **Split `defineReactionContract` → `bindReaction` (`@softize/opus/core`).** Espelha
878
+ `defineContract`/`bindAction` pras reações: a DECLARAÇÃO (`name`/`on`/`description`/`authorize`)
879
+ é isomorfa e autorável no design; `handler` + entrega (`retry`/`timeout`/`dedup`/`concurrency`)
880
+ vão no `bindReaction`, que produz uma `ReactionDef` normal (o runtime não sabe do split).
881
+ - **Docs.** `mockHandler`/`OPUS_MODE=design` e `fake` documentados (estavam implementados mas
882
+ invisíveis). Nota: o `mockHandler` roda no server de design; tirá-lo do bundle web é transform
883
+ deferido (o contrato é isomorfo — gate por `import.meta.env` estouraria no server).
884
+
885
+ ## 2.39.0 — 2026-07-22
886
+
887
+ **Elicitação: o agente pergunta à pessoa no meio do turno, seguro por construção.** Fecha a
888
+ pegadinha vivida no chat do Maestro — o `AskUserQuestion` embutido do Claude Code, em modo
889
+ headless, volta VAZIO e a pergunta some pro usuário (o agente "infere sozinho"). O contrato +
890
+ o guard agora moram na base, então qualquer consumidor de IA do Opus nasce protegido. **Sem
891
+ breaking** (tipos/campos/exports novos, aditivos).
892
+
893
+ - **Contrato de pergunta (novos tipos, `@softize/opus`).** `AskQuestion` (enunciado + `header`
894
+ + `options` + `multiSelect`), `AskOption` e `AskAnswer` (rótulos escolhidos + texto livre) —
895
+ a forma única que todo mundo fala. Espelha o AskUserQuestion do Claude Code, mas servida por
896
+ um round-trip REAL.
897
+ - **`AiRunOptions.onAsk` / `BoundAiRunOptions.onAsk` (novo hook).** O round-trip de "perguntar
898
+ à pessoa": com ele, o driver INTERCEPTA a tool reservada `ask_user` (injetada automaticamente
899
+ no catálogo) e chama o handler, devolvendo as respostas ao modelo. Passa direto por `ctx.ai`
900
+ (o `bindAi` já espalha as opções).
901
+ - **Guard anti dead-end no driver `ai/anthropic` (`run` + `runStream`).** SEM `onAsk`, uma
902
+ chamada a `ask_user` é RECUSADA com erro acionável ("pergunte em texto e siga") em vez de
903
+ cair no `execute` do consumidor — que, num loop headless/one-shot (o caso dos copilots
904
+ `services/ai`), resolveria contra o servidor MCP e morreria em silêncio. Segurança por
905
+ construção: um ask-tool não vira dead-end silencioso.
906
+ - **Novos exports em `@softize/opus/ai`.** `ASK_USER_TOOL` (nome reservado), `ASK_INPUT_SCHEMA`
907
+ (JSON Schema da tool), `ASK_TOOL_DESCRIPTION` e `formatAnswers()` — pra quem serve o tool por
908
+ fora (ex.: o chat do Maestro, que roda via `claude` CLI e monta o `ask_user` como MCP).
909
+
910
+ ## 2.38.0 — 2026-07-22
911
+
912
+ O **menu vira primitivo pivotável** (`<ShellNav>`), fechando o gap "cada app copia a sidebar
913
+ à mão" — Maestro, back-office e GB reescreviam o `<button>` do item de menu (3 produtos).
914
+ **Sem breaking** (componentes/props novos).
915
+
916
+ - **`<ShellNav>` (novo primitivo PIVOTÁVEL).** O menu — grupos → itens, com `heading` e
917
+ âncora inferior (`footer`) — que serve os DOIS lares do chrome sem flag: na sidebar do
918
+ `<AppShell>` recolhe pra ícone-só (com tooltip) quando a sidebar recolhe; dentro do
919
+ conteúdo (num `<SectionShell>`) fica expandido. Sabe onde está pelo novo contexto de slot
920
+ do AppShell (`useSidebarSlot`) — o app não passa o estado de rail em duas mãos (fecha a
921
+ nota `n=2` que o `gb-nav-rail` deixou anotada). v1 PLANA; aninhamento/árvore + DnD COMPOSTO
922
+ vêm quando o 1º consumidor de árvore migrar. Acompanha `<ShellNavHeading>` (título + slot
923
+ de ação, o "+" de criar). Desenho em `docs/shellnav.md`.
924
+ - **`<AppShell>` publica o slot da sidebar (`useSidebarSlot`).** `useAppShell` não distinguia
925
+ sidebar × conteúdo (o colapso vale a árvore toda); o novo contexto existe SÓ dentro dos
926
+ slots da sidebar (header/nav/rodapé), `null` no conteúdo. É o que torna o `<ShellNav>`
927
+ pivotável de verdade — e o que o `gb-nav-rail` previu virar prop do Opus quando n=2.
928
+ - **`<Composer>`: textarea plana também no escuro.** Faltava `dark:bg-transparent` — o
929
+ `dark:bg-input/30` do primitivo `Textarea` vazava dentro da pílula, dando um fundo de input
930
+ à área de escrita no tema escuro. Agora a textarea herda o `bg-card` do composer nos dois temas.
931
+
932
+ ## 2.37.0 — 2026-07-22
933
+
934
+ Fecha o desenho da 2.36.0: os seletores da barra viram componente, e o `<Chat>` passa a
935
+ hospedá-los. **Sem breaking** (props novas, opcionais).
936
+
937
+ - **`<ComposerSelect>` (novo primitivo).** O seletor discreto que vive na barra de ações
938
+ do composer: gatilho ghost (rótulo + chevron) que abre um menu com `Check` no ativo.
939
+ Era o padrão que o **Maestro** escrevia à mão — DUAS vezes (app + task) — e que a GB ia
940
+ copiar pro agente: a mesma receita (trigger + DropdownMenu + Check), agora uma vez só.
941
+ Controlado (`value`/`onChange`), opções `{ value, label, hint?, triggerLabel? }` — `hint`
942
+ é o detalhe à direita (a chave da task em mono) e `triggerLabel` encurta o gatilho quando
943
+ o rótulo da lista é longo (a task mostra o título na lista, só a chave no gatilho). Pra um
944
+ select de formulário (moldura/label), siga com `Select`/`Combobox` — este é calibrado pra
945
+ barra: discreto, sem borda, some no fundo até o hover.
946
+ - **`<Chat composerActions>` — o chat também hospeda a barra.** A 2.36.0 pôs o slot
947
+ `actions` no `<Composer>`, mas deixou o `<Chat>` "inalterado por fora": quem tinha um chat
948
+ (não um composer nu) não alcançava a barra — o comentário mandava "usar o `<Composer>`
949
+ direto", o que num chat significaria reimplementar o transcript. Agora o `<Chat>` repassa
950
+ `composerActions` pro Composer interno. É o que faltava pro rail do Copilot da GB tirar o
951
+ seletor de agente do slot `notice` (acima, deslocado) e pô-lo na barra, onde o Maestro já
952
+ põe app + task. `notice` volta a ser só aviso; `composerActions`, só controle.
953
+
954
+ ## 2.36.0 — 2026-07-21
955
+
956
+ Novo `<Composer>` — a caixa de escrever extraída do `<Chat>`, reusável sozinha.
957
+
958
+ - **`<Composer>` (novo primitivo).** O composer do `<Chat>` (textarea numa pílula elevada,
959
+ Enter envia / Shift+Enter quebra linha, enviar dentro) virou componente próprio, pra usar
960
+ ONDE HÁ ENTRADA DE TEXTO MAS NÃO UM CHAT — o caso do composer de criação de sessão do
961
+ Maestro (sem histórico). Controlado (`value`/`onChange`/`onSubmit`); `submitDisabled` trava
962
+ o enviar além de vazio/busy (ex.: falta escolher o app); `busy` vira spinner no enviar.
963
+ - **Slot `actions` — seletores discretos na barra.** Com `actions`, o composer vira duas
964
+ linhas: textarea em cima, uma barra limpa embaixo com os controles à esquerda e o enviar à
965
+ direita. É onde o Maestro prende app + task e a GB, o agente — o padrão que os apps
966
+ resolviam cada um do seu jeito (GB no slot `notice` acima; Maestro à mão) agora tem lugar.
967
+ - **`<Chat>` inalterado por fora.** Ele passa a usar o `<Composer>` por dentro; sem `actions`,
968
+ o composer é byte-a-byte o de antes. Única mudança visível: o enviar mostra spinner enquanto
969
+ `busy` (era só desabilitado). Nenhuma migração.
970
+
971
+ ## 2.35.1 — 2026-07-21
972
+
973
+ Correções da 2.35.0 (que saiu antes de passar pelo review). **Prefira esta à 2.35.0.**
974
+
975
+ - **`AppShell` recolhido não estreitava se o app passasse `sidebarClassName` com largura.** O
976
+ `sidebarClassName` (ex.: `w-72`) vencia a largura do recolhido no `twMerge`, então recolher
977
+ escondia o rótulo por CSS mas o aside ficava largo — meio-recolhido, sem erro. Agora a
978
+ largura do recolhido é aplicada por último: `sidebarClassName` manda no expandido, o estado
979
+ manda no recolhido (quem quer outra largura recolhida usa `sidebarCollapsedClassName`).
980
+ - **Doc e registro do rem, alinhados ao CSS.** O `tokens.md` ainda dizia "14px / ~12,5%"
981
+ (contradizia o `theme.css`, que já publicava 15px), o comentário do `theme.css` contava uma
982
+ história falsa (dizia "revert" — 15px é valor inédito, antes da 2.31.5 valia o 16px do
983
+ browser) e a entrada da 2.31.5 tinha dois números derivados de uma baseline que nunca
984
+ existiu. Tudo conferido no git e corrigido. Sem efeito em runtime — o rem segue 15px.
985
+
986
+ ## 2.35.0 — 2026-07-21
987
+
988
+ Sidebar recolhível no `AppShell` e o **rem base passa a 15px**. *(Publicada com os defeitos
989
+ que a 2.35.1 conserta — use a 2.35.1.)*
990
+
991
+ - **Base do rem em 15px. Visual: GLOBAL — e o delta depende de onde você está.** Todo `rem`
992
+ deriva daqui, então tipografia, espaçamento e tamanho de controle mudam em toda tela no
993
+ bump. Confira sua origem:
994
+ - **vindo de 2.31.5–2.34.2** (base 14px): tudo **cresce ~7,1%** (0.875rem: 12.25px → 13.125px);
995
+ - **vindo de ≤ 2.31.4** (o pacote não fixava rem; valia o 16px do browser): tudo
996
+ **encolhe ~6,25%** — direção oposta, não pule este bullet.
997
+
998
+ Isto **não é um revert** da 2.31.5: 15px é valor inédito (antes dela não havia regra de
999
+ rem alguma). O número veio da prática — a Empresa X escolheu 15px por conta própria em
1000
+ **dois** apps, e a exceção reincidente virou o default (régua da casa: promove na
1001
+ reincidência). Quem quiser a escala densa fixa `html { font-size: 14px }` no próprio entry.
1002
+ **Consumidor que já sobrescrevia pra 15px pode remover o override** — vira redundante, não
1003
+ conflitante (mesmo valor), então dá pra limpar sem pressa.
1004
+ <br>*Correção de registro:* a entrada da 2.31.5 saiu com dois erros (chamava 15px de "a
1005
+ escala anterior" e dizia "encolhe ~6,7%"), ambos por supor uma baseline de 15px que nunca
1006
+ existiu. Corrigidos lá, com nota.
1007
+ - **`AppShell` ganha `collapsible` (opt-in).** Sem a prop, nada muda. Com ela, o shell
1008
+ controla a **largura** e publica o estado; **o que some é decisão do conteúdo**, porque o
1009
+ `sidebarNav` é do consumidor. Em vez de exigir estado no app, o aside expõe
1010
+ `data-collapsed` e o grupo `sidebar`: o rótulo some por CSS —
1011
+ `className="group-data-[collapsed=true]/sidebar:hidden"`. O botão é o novo
1012
+ **`<AppShellTrigger />`**, posicionado por você (some sozinho quando o shell não é
1013
+ `collapsible`, então dispensa condicional); **`useAppShell()`** dá o estado em JS. Pra
1014
+ persistir a preferência, controle de fora com `collapsed` + `onCollapsedChange`;
1015
+ `sidebarCollapsedClassName` ajusta a largura recolhida (default `w-14`).
1016
+
1017
+ ## 2.34.2 — 2026-07-21
1018
+
1019
+ O `opus setup` passa a conhecer o Tailwind v4. **Sem breaking** (o caminho v3 é o mesmo).
1020
+
1021
+ - **Não nasce mais `tailwind.config.js` fantasma.** O setup criava o config sempre que o
1022
+ app tinha `index.html` e nenhum config — sem nunca olhar a versão do Tailwind. Na v4 não
1023
+ existe config (tema e escaneamento moram no CSS: `@import`, `@theme`, `@source`), então o
1024
+ arquivo nascia órfão: a build ignora, mas ele parece oficial pra quem abrir. Agora o
1025
+ setup lê a geração do consumidor e só semeia o config até a v3.
1026
+ - **E o config que já nasceu agora é avisado.** Quem tomou o arquivo fantasma nas versões
1027
+ anteriores ouve, numa build v4, que ele é inerte e pode ser apagado. O setup não apaga
1028
+ nada — arquivo do projeto é do projeto —, mas parar de criar sem apontar o que já criou
1029
+ deixaria o problema no disco em silêncio. Config plugado de propósito com `@config` não
1030
+ é avisado, e a busca por esse `@config` varre **todos** os CSS do app, não só o de
1031
+ entrada: ele pode morar num parcial, e errar aí mandaria apagar um arquivo em uso.
1032
+ - **Some o aviso falso do tema.** A checagem do `@softize/opus/ui/theme.css` olhava só o
1033
+ entry `.tsx`. App v4 importa o tema pelo CSS, então quem já fazia certo ouvia "importe o
1034
+ tema" a cada install — aviso que ensina a duplicar o que já está feito. Na v4 a
1035
+ checagem passa a olhar o CSS de entrada, e o CSS é **encontrado varrendo `src/`**, não
1036
+ casando uma lista de nomes: `src/styles/app.css` vale tanto quanto `src/index.css`, e
1037
+ entre vários vence o que importa o tema (num app com reset + entry, é esse que responde).
1038
+ - **Ganha um aviso que faltava:** app v4 sem `@source` apontando
1039
+ `node_modules/@softize/opus/src/ui` renderiza cru, porque o Tailwind v4 não escaneia
1040
+ `node_modules`. Era o outro lado do `content` glob da v3, e ninguém avisava.
1041
+ - **Detecção pela versão INSTALADA**, com o range declarado só como palpite de reserva. A
1042
+ pergunta real é "que Tailwind essa build roda?", e o range não responde: `catalog:` (o
1043
+ protocolo de catálogo do pnpm), `workspace:*`, `latest` e `*` não têm dígito nenhum, e
1044
+ `>=3` pode ter resolvido 4.x. Ler o range primeiro erraria bem no formato de monorepo
1045
+ pnpm que originou este achado. A busca **sobe a árvore** (`node_modules` da raiz, para o
1046
+ monorepo com hoisting), e `peerDependencies`/`optionalDependencies` entram na conta do
1047
+ fallback. Só quando não há Tailwind instalado **e** o range não tem número nenhum
1048
+ (`catalog:`, `workspace:*`, `latest`, `*`) o setup mantém o caminho v3 — aí seria palpite.
1049
+ - Achado ao dar `postinstall: opus setup` aos apps de um monorepo pnpm: lá o `INIT_CWD` é
1050
+ a RAIZ, que não tem a dep, então o setup nunca rodava pros apps — e o `opus.json` de um
1051
+ deles envelhecia desde 2.28.0. Com o setup rodando de verdade, o defeito apareceu.
1052
+ - 41 testes novos em `tests/init` — a fundação de UI não tinha nenhum.
1053
+
1054
+ ## 2.34.1 — 2026-07-21
1055
+
1056
+ Conserta a regressão que a 2.34.0 introduziu no `DocBrowser`. **Suba da 2.33.x direto
1057
+ para cá.**
1058
+
1059
+ - **`navigate` volta a notificar por evento.** Ele avisava só uma lista de assinantes em
1060
+ escopo de módulo, então quem escuta `popstate` na unha — a forma de todas as cópias que
1061
+ a primitiva substituiu — deixou de ser avisado. Agora dispara
1062
+ `new PopStateEvent('popstate')` no `window`. Como o `window` é um só, isso também faz
1063
+ duas cópias do pacote no `node_modules` se enxergarem; a lista privada não fazia.
1064
+ - **Quem estava quebrado:** app que renderiza `<DocBrowser basePath="…" />` **sem** a prop
1065
+ `path` e guarda o path por conta própria. Clicar numa página trocava a URL e o corpo,
1066
+ mas o realce do menu do host congelava — sem erro, sem aviso.
1067
+ - **O no-op do `navigate` passa a normalizar o destino** (`new URL(...).href` dos dois
1068
+ lados) em vez de comparar strings cruas. Isso conserta dois furos de uma vez: o **hash**
1069
+ (estando em `/a#secao`, `navigate('/a')` virava no-op e a âncora nunca saía) e o
1070
+ **encoding** — `window.location` devolve `/relatórios` percent-encodado, então o no-op
1071
+ nunca disparava para rota acentuada ou query com espaço, e cada clique repetido
1072
+ empilhava uma entrada. Era o caso pt-BR inteiro.
1073
+ - Some a última cópia à mão do mecanismo, que tinha sobrado dentro do próprio
1074
+ `registry.tsx` (o redirect de `/docs/ui` pra `/ui` — agora `navigate('/ui', { replace:
1075
+ true })`, porque redirect não merece entrada no histórico).
1076
+
1077
+ ## 2.34.0 — 2026-07-21
1078
+
1079
+ > ⚠️ **Regressão — use a 2.34.1.** A frase "nada existente muda de comportamento" logo
1080
+ > abaixo é FALSA: o `DocBrowser` standalone parou de notificar o host. O diagnóstico e o
1081
+ > conserto estão na 2.34.1. A entrada fica porque a versão foi publicada.
1082
+
1083
+ Roteamento vira primitiva da casa. **Sem breaking** (módulo novo; nada existente muda de
1084
+ comportamento).
1085
+
1086
+ - **`navigate` / `usePathname` / `useSegments` / `useSearchParams`** (`ui/react`):
1087
+ history-based, sem dependência — o pathname É o estado, então deep-link, reload e o
1088
+ botão voltar funcionam sem um segundo lugar guardando "onde estou".
1089
+ - Nasceu por **reincidência**, não por gosto: o mesmo mecanismo estava reimplementado à
1090
+ mão em quatro lugares, TRÊS deles dentro da casa (o `DocBrowser` daqui, o site do Opus
1091
+ e o back-office da Empresa X). O `DocBrowser` e o site já consomem a primitiva — a
1092
+ doc é o primeiro consumidor, como no `SectionShell`.
1093
+ - As cópias divergiam no que importa: só uma usava `useSyncExternalStore` (as outras leem
1094
+ `window.location` em `useState`, que sofre **tearing** no modo concurrent), e o no-op de
1095
+ destino repetido — sem ele, clicar duas vezes no mesmo item empilha entradas idênticas e
1096
+ o "voltar" não sai do lugar — não estava em todas.
1097
+ - **Escopo pequeno de propósito**: não há tabela de rotas, `<Route>`, params tipados nem
1098
+ data loader. A doc traz a régua de quando isto NÃO serve (casamento de padrão, params,
1099
+ carregamento por rota → o caso pede uma biblioteca de rotas).
1100
+ - Compatibilidade: `subscribe` escuta `popstate`, então quem ainda notifica por evento
1101
+ sintético (`dispatchEvent(new PopStateEvent('popstate'))`) segue funcionando. ⚠️ A mão
1102
+ inversa é que faltava — ver 2.34.1.
1103
+ - SSR: snapshot de servidor constante (`/`), reconciliado no 1º render do cliente.
1104
+
1105
+ ## 2.33.0 — 2026-07-20
1106
+
1107
+ Conserta a regressão da 2.32.0 na nav da doc — **não use a 2.32.0** se você serve doc
1108
+ por pasta. **Sem breaking** (as props novas são aditivas).
1109
+
1110
+ ### O que quebrou na 2.32.0
1111
+
1112
+ Ao passar o `DocBrowser` pro `<SectionShell>`, o mapeamento achatou o tier
1113
+ `DocGroup.label`. A justificativa registrada era falsa: o tier parecia morto porque o
1114
+ catálogo hardcoded (`DOC_SECTIONS`) nunca o preenche — mas `docSectionsFromFolder` o
1115
+ preenche a partir de sub-pasta (ou do frontmatter `group:`), que é o caminho do
1116
+ `opusDocs({ source })`. **Sintoma:** projeto que serve `docs/` com sub-pasta perdia os
1117
+ sub-cabeçalhos, e as páginas de subgrupos distintos viravam uma lista indistinguível.
1118
+ A entrada da 2.32.0 diz "saída visual idêntica" — vale só pro catálogo do próprio Opus.
1119
+
1120
+ - **`SectionNavGroup.subgroups`** (novo): o 3º nível — rótulo mais fraco e mais
1121
+ indentado, renderizado DEPOIS dos `items` soltos do grupo. `items` virou opcional
1122
+ (quem já passava segue igual). O `DocBrowser` mapeia seção → grupo → subgrupo e a
1123
+ nav de 3 níveis volta ao que era.
1124
+ - **`navLabel`** (novo, default `Navegação da seção`): rotula a landmark `<nav>`. Sem
1125
+ isso, um app com nav na sidebar + nav de seção anuncia "navigation" duas vezes e o
1126
+ leitor de tela não distingue.
1127
+ - **`scrollResetKey` — a doc estava invertida.** Dizia pra passar "algo mais fino"
1128
+ quando a tela pagina no lugar, e o exemplo (`scrollResetKey={id}` com `id` = o
1129
+ `activeId`) era um no-op: paginar não muda o `activeId`, então o default já resolvia.
1130
+ Os dois usos reais estão documentados agora — chave CONSTANTE pra nunca remontar,
1131
+ chave MAIS FINA pra remontar dentro da mesma tela.
1132
+ - Rótulo vazio (`''`) passa a contar como ausente no grupo e no subgrupo (antes saía um
1133
+ cabeçalho com padding e sem texto); a normalização saiu dos consumidores.
1134
+ - `id` de item agora está documentado como único em TODA a nav — repetido, marca dois
1135
+ itens com `aria-current` e o painel não remonta ao alternar.
1136
+ - Testes: regressão do tier de grupo no nível do `DocBrowser` (inclusive pelo caminho
1137
+ real do `docSectionsFromFolder`), os dois sentidos do `scrollResetKey`, subgrupos,
1138
+ rótulo vazio, `icon`/`badge`/`navHeader`/`navFooter` e a landmark rotulada.
1139
+
1140
+ ## 2.32.0 — 2026-07-20
1141
+
1142
+ `<SectionShell>` — o nível que faltava entre o `AppShell` e a `Page`. **Sem breaking**
1143
+ (componente novo; nada existente muda de comportamento).
1144
+
1145
+ - **`<SectionShell>` (ui)**: uma SEÇÃO com navegação própria — nav `w-56` com filete +
1146
+ painel — pras telas irmãs de Configurações, Relatórios ou doc. Controlado
1147
+ (`activeId` + `onSelect`): o roteamento é do app, mesma regra do `AppShell`. Trocar de
1148
+ item REMONTA o painel (é o que zera o scroll); `scrollResetKey` fixa a chave quando a
1149
+ mesma tela pagina no lugar. Itens aceitam `icon`, `badge` e `disabled`; o ativo ganha
1150
+ `aria-current="page"`.
1151
+ - Nasceu por **reincidência**, não por gosto: o mesmo casco `nav + painel` já estava
1152
+ copiado à mão no `DocBrowser`, no site do Opus e no back-office da Empresa X — três
1153
+ vezes as mesmas classes. O `DocBrowser` agora **consome** o componente (a doc é o
1154
+ primeiro consumidor; saída visual idêntica — ⚠️ **falso**: regrediu a nav de quem
1155
+ serve doc por pasta, ver 2.33.0).
1156
+ - A doc do pattern traz a régua que faltava: **quando é sidebar do app, quando é seção,
1157
+ quando é tab**. Sintoma de seção que devia sair da sidebar: o item de topo não tem
1158
+ tela própria e só existe pra abrir um accordion.
1159
+
1160
+ ## 2.31.5 — 2026-07-17
1161
+
1162
+ **Base do rem em 14px** — a escala inteira densifica. **Visual: GLOBAL.** Todo `rem`
1163
+ encolhe ~12,5% (0.875rem = 12.25px, e assim por diante), então tipografia, espaçamento e
1164
+ tamanho de controle mudam em toda tela de todo consumidor no bump. Antes desta versão o
1165
+ pacote não fixava rem: valia o 16px do browser. Projeto que quiser a escala larga
1166
+ sobrescreve `html { font-size: 15px }` no próprio entry (é o que o back-office do Grand
1167
+ Brasil faz).
1168
+
1169
+ > **Corrigido em 2.35.0.** Esta entrada é do lote reconstruído do git e saiu com dois erros,
1170
+ > ambos derivados de supor uma baseline de 15px que nunca existiu: dizia "encolhe ~6,7%"
1171
+ > (é 14/15; contra o 16px real são ~12,5% — o comentário no código sempre disse 12,5%) e
1172
+ > chamava 15px de "a escala anterior" (a anterior era 16px; 15px é escolha da Empresa X).
1173
+
1174
+ ## 2.31.4 — 2026-07-17
1175
+
1176
+ Blur do backdrop volta pro degrau `xs`: a escala do Tailwind v4 renomeou os degraus (o
1177
+ antigo `sm` virou 8px), e o backdrop tinha herdado o valor errado no rename.
1178
+
1179
+ ## 2.31.3 — 2026-07-17
1180
+
1181
+ Backdrop de modal mais claro, com blur; header do `Dialog` com o mesmo padding do corpo.
1182
+
1183
+ ## 2.31.2 — 2026-07-17
1184
+
1185
+ Canvas quase-branco (tinte de 25% do muted) e bordas mais leves (92.5%) — a divergência
1186
+ nº 5 da casa em relação ao stock do shadcn.
1187
+
1188
+ ## 2.31.1 — 2026-07-17
1189
+
1190
+ `Dialog` se aproxima do `Card`: mesma forma (radius + 6px) e sombra pela metade.
1191
+
1192
+ ## 2.31.0 — 2026-07-17
1193
+
1194
+ **Forma por PAPEL**: `rounded-card` / `rounded-popover` / `rounded-dialog`, na mesma
1195
+ lógica dos tokens de elevação da 2.30.0 — papel novo ganha token novo. Entra junto a doc
1196
+ do sistema de elevação.
1197
+
1198
+ ## 2.30.2 — 2026-07-17
1199
+
1200
+ `shadow-card` menor na elevação de repouso (1px/3px).
1201
+
1202
+ ## 2.30.1 — 2026-07-17
1203
+
1204
+ Idem 2.30.2 — degrau intermediário do mesmo ajuste.
1205
+
1206
+ ## 2.30.0 — 2026-07-17
1207
+
1208
+ Elevação POR PAPEL + aresta — e os defaults do Tailwind de volta. **Visual: superfície
1209
+ elevada troca a borda pelo anel; `shadow-*` cru volta a ser o stock do TW.**
1210
+
1211
+ - Tokens em namespace PRÓPRIO (sem pegadinha): `shadow-card` / `shadow-popover` /
1212
+ `shadow-dialog` (família Notion — curta+longa, com dark de verdade via vars) e
1213
+ `ring-edge`/`border-edge` (`--color-edge`: o anel hairline que SUBSTITUI a borda em
1214
+ superfície elevada — não combine com `border`). Papel novo = token novo (ex.: um
1215
+ futuro `shadow-input` nasce com demanda).
1216
+ - A sobrescrita da escala `--shadow-*` (antiga divergência nº 4) foi APOSENTADA:
1217
+ `shadow-md` volta a significar exatamente o que a doc do Tailwind diz.
1218
+ - Migrados: popover, select, dropdown-menu, context-menu, menubar, navigation-menu
1219
+ (`ring-edge` + `shadow-popover`); dialog, alert-dialog, sheet (`shadow-dialog`);
1220
+ `Card` (rounded-2xl + `ring-edge` + `shadow-card` — o flat aposentado); composer e
1221
+ card de fala do `<Chat>` (`shadow-card`).
1222
+
1223
+ ## 2.29.5 — 2026-07-17
1224
+
1225
+ Degraus baixos da sombra com ALCANCE menor (geometria: xs 1px/4px, sm 3px/8px, base
1226
+ 5px/12px) — opacidade aprovada fica. **Sem breaking**; flutuantes intactos.
1227
+
1228
+ ## 2.29.4 — 2026-07-17
1229
+
1230
+ Degraus baixos da sombra MAIS CLAROS (correção de rumo: xs 2.5%/3.5%, abaixo até do
1231
+ original 3%/4%). **Sem breaking**; flutuantes intactos.
1232
+
1233
+ ## 2.29.3 — 2026-07-17
1234
+
1235
+ Degraus baixos da sombra: mais um ponto de presença (xs 5%/7%, sm 5.5%/8%). **Sem
1236
+ breaking** (só visual; flutuantes seguem intactos).
1237
+
1238
+ ## 2.29.2 — 2026-07-17
1239
+
1240
+ Degraus BAIXOS da sombra mais presentes. **Sem breaking** (só visual).
1241
+
1242
+ - `2xs`/`xs`/`sm`/`shadow` sobem de opacidade (xs 3.5%/5.5%, sm 4%/6%…) — cards e
1243
+ superfícies apoiadas ficavam invisíveis sobre o canvas. `md`/`lg`/`xl`/`2xl`
1244
+ (flutuantes) NÃO mudam — calibração aprovada; progressão da escala preservada.
1245
+
1246
+ ## 2.29.1 — 2026-07-17
1247
+
1248
+ O flush completa a promessa do shell transparente. **Visual: consumidores flush que
1249
+ querem miolo branco pintam `bg-background` no próprio contêiner.**
1250
+
1251
+ - `<AppShell flush>`: o main deixa de pintar `bg-background` — o shell NÃO pinta nada
1252
+ (canvas do body em tudo; o filete `border-l` fica). Superfície é decisão do conteúdo.
1253
+
1254
+ ## 2.29.0 — 2026-07-17
1255
+
1256
+ Conversa que começa pelo assistente. **Sem breaking.**
1257
+
1258
+ - `<Chat kickoff>`: no modo autogerenciado, roda uma vez no mount quando o transcript
1259
+ nasce vazio — o agente abre a conversa (proatividade: relatório recém-criado, sessão
1260
+ nova). Mesmo contrato do `send` (string ou stream de ChatEvent), sem mensagem de
1261
+ usuário; com `initialMessages` (histórico) não dispara. 2 testes.
1262
+
1263
+ ## 2.28.0 — 2026-07-17
1264
+
1265
+ O canvas é do BODY; o shell fica transparente. **Sem breaking** (mesmo tom).
1266
+
1267
+ - `theme.css`: o body ganha o cinza clarinho da casa (`color-mix` de muted 40% sobre
1268
+ background — o tom que o AppShell pintava); overscroll/áreas fora do shell param de
1269
+ aparecer brancas, e o canvas segue o tema (light/dark).
1270
+ - `<AppShell>`: perde o `bg-muted/40` da raiz (transparente) — as superfícies pintam
1271
+ `bg-background` por cima, como já faziam.
1272
+
1273
+ ## 2.27.2 — 2026-07-17
1274
+
1275
+ Escala de sombra COMPLETA da casa. **Sem breaking** (só visual).
1276
+
1277
+ - Todos os degraus (`2xs`→`2xl`) redefinidos no theme.css com a curva leve de duas
1278
+ camadas (estilo Notion) — antes só `md`/`lg`; agora QUALQUER `shadow-*` em qualquer
1279
+ consumidor sai sem o peso default do Tailwind, preservando o sentido da progressão.
1280
+
1281
+ ## 2.27.1 — 2026-07-17
1282
+
1283
+ Reset de borda no tema — bordas pretas em consumidor novo. **Sem breaking.**
1284
+
1285
+ - `theme.css` ganha o base layer canônico `* { border-color: var(--border) }` (o
1286
+ globals.css do shadcn): sem ele, todo consumidor NOVO do opus/ui nascia com bordas
1287
+ `currentColor` (pretas) até copiar o reset à mão no entry CSS. App com a cópia local
1288
+ segue funcionando (redundante, inofensivo — pode remover).
1289
+
1290
+ ## 2.27.0 — 2026-07-17
1291
+
1292
+ Chrome flush no `<AppShell>` (padrão Vetra BI). **Sem breaking** (default inalterado).
1293
+
1294
+ - `flush` (prop): sidebar sem o padding do shell e conteúdo full-bleed — encosta no
1295
+ browser, com filete à esquerda (`border-l`) sobre `bg-background`. O chrome clássico
1296
+ (miolo em card sobre o muted) segue sendo o default.
1297
+ - `<AppShellBar>`: a faixa h-12 com `border-b` — uma na sidebar (marca) e outra no topo
1298
+ do conteúdo (breadcrumb/ações); as alturas casam e a linha do header atravessa a tela
1299
+ inteira. Doc com a receita completa + 3 testes.
1300
+
1301
+ ## 2.26.2 — 2026-07-17
1302
+
1303
+ Sombra dos flutuantes: calibração fina pra baixo (md 3%/7.5%, lg 4%/9.5%). **Sem
1304
+ breaking** (só visual).
1305
+
1306
+ ## 2.26.1 — 2026-07-17
1307
+
1308
+ Tabela larga rola nela mesma. **Sem breaking.**
1309
+
1310
+ - `<Markdown>`: a `<table>` ganha invólucro `overflow-x-auto` — tabela mais larga que o
1311
+ bloco (ex.: ranking no `<Chat>`) rola horizontalmente NELA, sem arrastar o scroll do
1312
+ chat/doc inteiro.
1313
+
1314
+ ## 2.26.0 — 2026-07-17
1315
+
1316
+ Tabela GFM no `<Markdown>` (e, por tabela, no `<Chat>`). **Sem breaking.**
1317
+
1318
+ - O renderer nativo (zero-dep) ganha tabela GFM: linha de células `| a | b |` +
1319
+ separadora `|---|` viram `<table>` semântica (thead/tbody), células com inline
1320
+ (bold/code/link) — resposta de agente com ranking/tabela renderiza no chat. Linha
1321
+ com `|` sem separadora segue parágrafo.
1322
+
1323
+ ## 2.25.2 — 2026-07-17
1324
+
1325
+ Calibrações de UI (3ª rodada no olho do dono). **Sem breaking** (só visual).
1326
+
1327
+ - Sombras `md`/`lg` um pouco mais opacas (md 3.5%/8.5%, lg 4.5%/10.5%) — meio-termo
1328
+ entre a 2.24.3 (pesada) e a 2.25.1 (leve demais).
1329
+ - `DropdownMenuContent`/`ContextMenuContent` com `min-w` 10rem (era 8rem).
1330
+
1331
+ ## 2.25.1 — 2026-07-17
1332
+
1333
+ Sombras dos flutuantes ainda mais leves (2ª calibração no olho do dono). **Sem
1334
+ breaking** (só visual).
1335
+
1336
+ - `md` 3%/7% · `lg` 4%/9% — mesma expansão, menos peso.
1337
+
1338
+ ## 2.25.0 — 2026-07-17
1339
+
1340
+ Form contract-driven ganha o campo de ícone. **Sem breaking.**
1341
+
1342
+ - `widget: 'icon'` no vocabulário de `fields`: string com o nome kebab-case da paleta
1343
+ da casa — o `ActionForm`/`ActionFormDialog` renderiza o `<IconPicker>` (2.24.0) no
1344
+ campo. Fecha o ciclo: o seletor de ícone entra no form declarado no contrato, sem
1345
+ form à mão.
1346
+
1347
+ ## 2.24.3 — 2026-07-17
1348
+
1349
+ Sombras dos flutuantes mais leves. **Sem breaking** (só visual).
1350
+
1351
+ - `md`/`lg` com opacidade reduzida (camada curta 4–5%, longa 10–12%) — a 2.24.1 tinha
1352
+ ficado mais pesada que a referência (Notion); a expansão fica, o peso sai.
1353
+
1354
+ ## 2.24.2 — 2026-07-17
1355
+
1356
+ Destructive dos menus: hover muda **só a cor**. **Sem breaking** (só visual).
1357
+
1358
+ - `DropdownMenuItem`/`ContextMenuItem` `variant="destructive"`: no hover/focus o fundo
1359
+ segue o accent NORMAL dos itens — só texto/ícone pintam de destructive (refinamento
1360
+ do 2.24.1, que tingia o fundo de vermelho).
1361
+
1362
+ ## 2.24.1 — 2026-07-17
1363
+
1364
+ Elevação estilo Notion + destructive discreto. **Sem breaking** (só visual).
1365
+
1366
+ - Sombras `md`/`lg` viram duas camadas difusas e expandidas (curta pra descolar +
1367
+ longa pra flutuar) nos FLUTUANTES (menus, popovers, selects, dialogs) — divergência
1368
+ declarada nº 4 no `theme.css`. `xs`/`sm` ficam (superfícies apoiadas).
1369
+ - `DropdownMenuItem`/`ContextMenuItem` `variant="destructive"`: NEUTRO em repouso —
1370
+ vermelho (texto/ícone/fundo) só no hover/focus, como o "Move to Trash" do Notion.
1371
+ `dropdown-menu` e `context-menu` viram **ejected** no lock (delta declarado).
1372
+
1373
+ ## 2.24.0 — 2026-07-17
1374
+
1375
+ Seletor de ícone da casa. **Sem breaking.**
1376
+
1377
+ - `<IconPicker>` (ui): gatilho com o ícone corrente + lista buscável (Popover+Command,
1378
+ a receita do Combobox). O value é o **nome** do ícone (kebab-case) — renderize com a
1379
+ mesma paleta (`iconPickerIcons[name] ?? fallback`). Paleta default curada (~40,
1380
+ lucide); vocabulário próprio via prop `icons`; selecionar o corrente desmarca.
1381
+ Caso de uso: personalização de item criado pelo usuário (relatório, projeto, pasta).
1382
+
1383
+ ## 2.23.0 — 2026-07-17
1384
+
1385
+ `<Chat>` em modo CONTROLADO. **Sem breaking** (o modo autogerenciado segue igual).
1386
+
1387
+ - `messages` (transcript por prop, `ChatTranscriptItem[]` com item `error`), `onSend`
1388
+ (retornar `false` devolve o texto ao composer), `busy` e `activity` (`undefined`
1389
+ esconde o indicador, `null` = "Pensando…", string = rótulo) — pro app dono do estado
1390
+ (transporte próprio, ex.: SSE server-autoritativo com replay, como o ChatRail do
1391
+ Maestro). `notice` renderiza avisos do app acima do composer.
1392
+
1393
+ ## 2.22.1 – 2.22.4 — 2026-07-17
1394
+
1395
+ Absorção do design do ChatRail no `<Chat>` (patches; o design do Maestro virou o
1396
+ default do componente):
1397
+
1398
+ - **2.22.1** — `greeting` vira estado vazio centrado (✦, some quando a conversa
1399
+ começa); era falsa 1ª fala do assistente e entrava no transcript do `send`.
1400
+ - **2.22.2** — composer pílula flutuante: enviar dentro do campo, focus ring.
1401
+ - **2.22.3** — transcript em turnos: mensagem do usuário em card com Markdown,
1402
+ cabeçalho do turno sticky; balão preenchido aposentado.
1403
+ - **2.22.4** — linha do composer centralizada verticalmente (py-2 fecha os 36px do
1404
+ botão).
1405
+
1406
+ ## 2.22.0 — 2026-07-17
1407
+
1408
+ Protocolo de eventos de conversa (streaming). **Sem breaking.**
1409
+
1410
+ - `ChatEvent` no core: `text{delta}` · `tool{name,detail?}` · `artifact{kind,ref,title?}`
1411
+ · `done{ok,error?}` — contrato em `docs/chat-event-protocol.md`.
1412
+ - `runStream` aditivo no driver anthropic (token a token com `client.messages.stream`;
1413
+ fallback `create` = delta único) e no `BoundAi` (mesmo execute-como-usuário/confirm).
1414
+ - `<Chat>` aceita os dois modos no `send`: `Promise<string>` (request/response) OU
1415
+ `AsyncIterable<ChatEvent>` (streaming) — tool vira indicador vivo humanizado
1416
+ (`humanizeTool`), artifact via `renderArtifact`, `initialMessages` reidrata (troque a
1417
+ `key` ao trocar de conversa).
1418
+
1419
+ ## 2.21.0 — 2026-07-16
1420
+
1421
+ Chrome de aplicação. **Sem breaking.**
1422
+
1423
+ - `<AppShell>` (ui): sidebar (header/nav/footer) + conteúdo + rail opcional
1424
+ redimensionável (Resizable) — o esqueleto que admin, Maestro e Vetra BI copiavam à
1425
+ mão, agora slot-based (`sidebarHeader`/`sidebarNav`/`sidebarFooter`/`rail`).
1426
+
1427
+ ## 2.20.1 — 2026-07-14
1428
+
1429
+ Controles flat, agora HONESTO: a sombra sai de DENTRO dos componentes, não por token
1430
+ mentiroso. **Sem breaking, mesmo visual da 2.20.0.**
1431
+
1432
+ - Reverte o `--shadow-xs: 0 0 #0000` da 2.20.0 — redefinir um token de escala pra "nada"
1433
+ mentia sobre o que a variável contém. O token volta ao valor real do shadcn.
1434
+ - `shadow-xs` removido de dentro dos 16 controles (button, input, select, textarea,
1435
+ checkbox, switch, radio-group, toggle, toggle-group, input-group, input-otp,
1436
+ native-select, calendar, menubar, button-group, combobox) — a intenção "flat" mora
1437
+ onde o componente é definido.
1438
+ - Os 14 que eram byte-fiéis ao shadcn viram **ejected** (assumidos pela casa). O lock
1439
+ guarda a proveniência (`upstreamHash` = merge-base) pro re-sync futuro por diff.
1440
+
1441
+ ## 2.20.0 — 2026-07-14
1442
+
1443
+ Controles **flat**: a sombra sutil (`shadow-xs`) dos controles zerada por token do tema.
1444
+ **Sem breaking.**
1445
+
1446
+ - `--shadow-xs: 0 0 #0000` no tema — button, input, select (trigger), input-group,
1447
+ textarea, native-select e o toggle-group perdem a sombra, pareando com os Cards flat
1448
+ da casa e com o ToggleGroup "junto" (que já era flat). Divergência declarada vs shadcn.
1449
+ - Superfícies elevadas (dialog, popover, dropdown, card — `shadow-md`/`lg`) NÃO mudam.
1450
+
1451
+ ## 2.19.0 — 2026-07-14
1452
+
1453
+ Toolbar do `ActionList` em **altura default (36px)**, alinhada com a ação primária da
1454
+ página. **Sem breaking.**
1455
+
1456
+ - Os controles da toolbar (período, filtros, busca, segment de views, recarregar,
1457
+ exibição) saem de `sm` (32px) pra `default` (36px) — mais respiro e legibilidade; a
1458
+ ação primária da página (o `Page` header) e a toolbar passam a ter o mesmo peso.
1459
+ - Simplifica: a busca deixa de precisar do nivelamento manual (`h-8`/`h-full`) que o
1460
+ `sm` exigia (o `Input` não tem variante `sm`) — em default tudo alinha nativamente.
1461
+
1462
+ ## 2.18.0 — 2026-07-13
1463
+
1464
+ Novo subpath **`@softize/opus/mcp`** — o MCP server de runtime. **Sem breaking.**
1465
+
1466
+ - `createOpusMcpServer(runtime, { resolveContext })`: expõe as actions `ai:enabled` como
1467
+ **tools MCP** (ListTools) e as executa via `runtime.execute` **como o usuário resolvido**
1468
+ (CallTool) — a porta pra uma IA de FORA alcançar o app pelo protocolo. Mesmo bridge do
1469
+ agente co-locado; o MCP é só o transporte (você monta stdio/HTTP).
1470
+ - `runtime.aiTools()` agora é **público** — o bridge actions `ai:enabled` → tool specs,
1471
+ reusado pela cola do `ctx.ai` E pelo MCP server.
1472
+
1473
+ ## 2.17.0 — 2026-07-13
1474
+
1475
+ Novo componente **`Chat`** (`@softize/opus/ui/react`) — **sem breaking**.
1476
+
1477
+ - `Chat`: a UI reutilizável do agente — lista de mensagens + composer, self-managed
1478
+ (estado, loading, auto-scroll; Enter envia, Shift+Enter quebra linha). A inteligência
1479
+ vem da prop `send`; no Opus, o backend liga numa rota que chama `runtime.aiFor(base).run`
1480
+ (o agente da 2.16 sobre as actions `ai:enabled`, como o usuário). Tipos: `ChatProps`,
1481
+ `ChatMessage`.
1482
+
1483
+ ## 2.16.0 — 2026-07-13
1484
+
1485
+ Loop **AGÊNTICO** no recurso `ai` — as actions viram tools do modelo. **Sem breaking.**
1486
+
1487
+ - `AiAdapter` ganha `run?(input, { tools, execute, maxSteps })` (opcional, aditivo): o loop
1488
+ de tool-use multi-turn, PURO (o driver não conhece o registry — tools e execute vêm por
1489
+ DI). Implementado no driver `anthropic`.
1490
+ - Uma action com `ai: { enabled: true }` no contrato vira uma **tool**. `ctx.ai.run(prompt)`
1491
+ — ou `runtime.aiFor(base).run(historico)` fora do handler — roda o agente sobre essas
1492
+ actions, executando-as **COMO O USUÁRIO** (limitado pelo `ctx.can`). O que é `destructive`
1493
+ ou pede `requiresConfirmation` não roda sem `run(prompt, { confirm })`.
1494
+ - `ctx.ai` passa a ser `BoundAi` (complete/extract iguais; `run` com tools+execute
1495
+ pré-injetados). Novos tipos: `AiTool`, `AiMessage`, `AiRunOptions`, `AiRunResult`,
1496
+ `BoundAi`, `BoundAiRunOptions`.
1497
+
1498
+ ## 2.15.0 — 2026-07-13
1499
+
1500
+ Novo componente de UI **`Copyable`** (nativo) — **sem breaking**.
1501
+
1502
+ - **`Copyable`** (`@softize/opus/ui/react`): clicar-pra-copiar com feedback — copia `value`
1503
+ pro clipboard e o ícone vira um check por ~1.5s (`feedbackMs` ajusta). Sem filhos é um
1504
+ botão-ícone (toolbar/célula); com filhos, o rótulo visível + o ícone. Pra IDs, tokens,
1505
+ slugs, URLs. No-op silencioso sem clipboard (contexto inseguro/SSR).
1506
+ - Promovido pela régua da reincidência (o funil "Issues do Opus"): apontado como enhancement
1507
+ num projeto-cliente (issue #2/#4 em `softize-dev/opus`), aceito na triagem — sai da espera
1508
+ do `PROMOTED.md` pras Promovidas. Projeto que compunha um copiar-com-feedback à mão troca
1509
+ pelo import.
1510
+
1511
+ ## 2.14.1 — 2026-07-12
1512
+
1513
+ Primeiro release do repo próprio do Opus (`github.com/softize-dev/opus`). Só correções
1514
+ de doc/vocabulário desde 2.14.0 — **sem mudança de API**.
1515
+
1516
+ - Vocabulário "a base Opus" → "**o Opus**" no bloco gerenciado do CLAUDE.md e nas docs
1517
+ (o `opus setup` re-sincroniza o bloco; nada quebra); "camada-base" → "camada materializada".
1518
+ - Correção de doc: o kind `search` (morto desde o rename pra `list`) sobrevivia em
1519
+ `actions.md`, `action-list.md` e na skill `create-action` (prosa + exemplos + `scaffold.mjs`)
1520
+ → tudo `list`.
1521
+ - Doc self-contained: o exemplo de `bindAction` deixou de importar de pacote interno;
1522
+ `create-action` aponta a página Actions em vez de reproduzir a ordem canônica dos campos.
1523
+
1524
+ ## 2.14.0 — 2026-07-11
1525
+
1526
+ Primeira rodada do funil de feedback da base: os 4 apontamentos do projeto-exercício
1527
+ (`fieldnotes`, via `.opus/base-feedback.jsonl` → Radar) triados e resolvidos.
1528
+
1529
+ - Handler (e `authorize`) agora recebem o tipo PARSEADO do schema de input: campo com
1530
+ `.default()` deixa de chegar `| undefined` — remova os `?? fallback` redundantes.
1531
+ - `ViewAction.projection` virou opcional: view simples sem expand não paga mais o
1532
+ boilerplate `projection: []`.
1533
+ - `opus create`/`setup` semeiam as skills DO PACOTE (`registry/skills`) em
1534
+ `.claude/skills/` — esqueleto standalone não nasce mais sem o que o tarball carrega
1535
+ (untracked via `info/exclude`; o Maestro segue sendo o reconciliador). O bloco do
1536
+ CLAUDE.md agora diz o que só chega quando o Maestro rege.
1537
+ - Átomos do catálogo em input de action: `t.slug().zod()` (e afins) documentado na
1538
+ página de actions e travado por teste como superfície pública — não re-escreva
1539
+ regex do que a base valida.
1540
+
1541
+ ## 2.13.1 — 2026-07-11
1542
+
1543
+ - Fix visual no DocBrowser: as regras custom de `code`/`pre` do prose (theme.css)
1544
+ vazavam pra dentro de `not-prose` — o `<pre>` do CodeBlock ganhava uma segunda
1545
+ borda colada na do contêiner (a "borda duplicada", gritante no dark). Guard
1546
+ `[class~='not-prose']` espelhando o escopo do plugin typography.
1547
+
1548
+ ## 2.13.0 — 2026-07-11
1549
+
1550
+ - `@softize/opus/testing` (EXPERIMENTAL): harness de teste do protocolo —
1551
+ `runAction` roda uma action em UNIDADE com o mesmo pipeline do runtime (valida
1552
+ input, cobra `public`/`authorize` com semântica idêntica incl. DSL e `loaded`,
1553
+ valida output, mesmos `ActionError`); `testContext` (ctx com emit/log capturados,
1554
+ `can` configurável) e `memStorage` (StorageAdapter em memória). O template do
1555
+ `opus create` já testa com ele.
1556
+ - `ctx.ai` (EXPERIMENTAL): IA generativa no protocolo — `AiAdapter`
1557
+ (`complete`/`extract`) com driver `@softize/opus/ai/anthropic` (peer
1558
+ `@anthropic-ai/sdk` opcional, client injetável, default haiku). O diferencial do
1559
+ `extract`: o mesmo `Schema` das actions vira o contrato da resposta do modelo —
1560
+ saída estruturada forçada e VALIDADA pelo schema antes de devolver.
1561
+ - Docs novas na surface SDK: "Testes de action" e "IA generativa"; a skill `test`
1562
+ da metodologia passa a transcluir a página do harness.
1563
+
1564
+ ### Breaking
1565
+
1566
+ - `ActionContext`/`ReactionContext` ganharam `ai` — só afeta quem CONSTRÓI o
1567
+ contexto à mão: adicione `ai: null`, ou troque pra `testContext()` do novo
1568
+ `@softize/opus/testing` (que já vem completo e observável).
1569
+
1570
+ ## 2.12.0 — 2026-07-11
1571
+
1572
+ - `opus create` ficou workspace-aware: dentro de um monorepo gera só os arquivos do app
1573
+ (nada de `.npmrc`/workspace yaml aninhado) e avisa o que a raiz precisa ter.
1574
+ - `opus create <dir> --monorepo`: cria a RAIZ canônica de um workspace (apps/* +
1575
+ packages/*, allowBuilds da base, escopo do registry); o primeiro app vem de
1576
+ `opus create apps/<nome>` na raiz.
1577
+
1578
+ ## 2.11.0 — 2026-07-11
1579
+
1580
+ - `ctx.storage` (EXPERIMENTAL): storage de arquivos no protocolo — `StorageAdapter`
1581
+ (put/get/delete/url) com drivers `@softize/opus/storage/fs` (disco local) e
1582
+ `@softize/opus/storage/s3` (S3-compatível; peers `@aws-sdk/*` opcionais).
1583
+ - `opus db migrate` aplica o SCHEMA IDEMPOTENTE (config `schema`, script SQL evolutivo)
1584
+ e roda o drift-check entidade ↔ banco na sequência (exit ≠ 0 se divergir).
1585
+
1586
+ ### Breaking
1587
+
1588
+ - **`opus db migrate` não roda mais migrations Kysely** (`migrations/*.ts` com
1589
+ `up`/`down`) e **`migrate down` foi removido**. Migração: converta o schema num
1590
+ script SQL idempotente (`CREATE IF NOT EXISTS` + guards `DO $$ IF EXISTS`), salve
1591
+ em `db/schema.sql` (ou aponte via `schema:` no opus.config.ts) e delete os
1592
+ `migrations/*.ts`. Rollback passa a ser: editar o script e re-rodar.
1593
+ - `ActionContext`/`ReactionContext` ganharam `storage` — só afeta quem CONSTRÓI o
1594
+ contexto à mão (testes): adicione `storage: null`.
1595
+
1596
+ ## 2.10.0 — 2026-07-10
1597
+
1598
+ - `opus create <dir>`: scaffold do app canônico (vite + react + tema v4 CSS-first,
1599
+ domínio-exemplo com gates verdes, dia zero completo, dev server na porta do preview).
1600
+ - Template com `.prettierrc.json` (espelho executável da seção Formatação da skill
1601
+ code-style) + `format`/`format:check` no CI de fábrica.
1602
+ - Correção de empacotamento: `@tailwindcss/typography` virou dependency (a theme.css
1603
+ o exige — consumidor standalone quebrava no build).
1604
+
1605
+ ## 2.9.0 — 2026-07-10
1606
+
1607
+ - Hooks da base no registry (`registry/hooks`): `opus-check-on-stop` (gate do
1608
+ `opus check` no fim do turno, escopado aos apps alterados) e `link-memory-on-start`
1609
+ (liga a memória versionada do repo à sessão). Materializados pelo Maestro.
1610
+ - `opus setup`: CLAUDE.md com BLOCO GERENCIADO (`<!-- opus:base -->…`) — o setup
1611
+ re-sinca o bloco quando a base evolui; fora dele o arquivo é seu. Dia zero:
1612
+ semente de `.claude/memory/` + CI de fábrica + aviso de script `test` ausente.
1613
+
1614
+ ## 2.8.0 e anteriores
1615
+
1616
+ Sem changelog (a disciplina começou na 2.9.0). Referência: o histórico do monorepo.