@softize/opus 12.11.0 → 13.1.0

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 (213) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/PROMOTED.md +46 -0
  3. package/README.md +28 -19
  4. package/bin/cli.mjs +87 -216
  5. package/bin/lib/check.mjs +2 -7
  6. package/bin/lib/cli-shared.mjs +131 -0
  7. package/bin/lib/copy.mjs +1 -5
  8. package/bin/lib/db.mjs +16 -74
  9. package/bin/lib/gen-openapi.mjs +3 -3
  10. package/bin/lib/gen-runner.mjs +1 -1
  11. package/bin/lib/gen.mjs +14 -69
  12. package/bin/lib/mcp.mjs +3 -1
  13. package/bin/lib/seed.mjs +5 -62
  14. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +93 -10
  15. package/docs/adr/0007-toast-actions-form-an-ordered-collection.md +63 -0
  16. package/docs/adr/0008-hierarchical-navigation-is-composed-at-the-consumer-boundary.md +71 -0
  17. package/docs/ownership-vs-shadcn-lock.md +2 -3
  18. package/docs/protocol.md +7 -7
  19. package/docs/radius-scale.md +1 -1
  20. package/docs/releasing.md +8 -2
  21. package/package.json +7 -3
  22. package/registry/skills/maintain-opus-docs/SKILL.md +83 -0
  23. package/registry/skills/maintain-opus-docs/agents/openai.yaml +4 -0
  24. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +85 -0
  25. package/registry/skills/maintain-opus-docs/references/evaluations.md +34 -0
  26. package/registry/skills/maintain-opus-docs/scripts/audit-docs.mjs +81 -0
  27. package/registry/templates/app/package.json +1 -1
  28. package/registry/templates/app/src/main.tsx +4 -4
  29. package/src/audit/drivers/console.ts +1 -0
  30. package/src/auth/drivers/better-auth.ts +1 -0
  31. package/src/auth/drivers/jwt.ts +1 -0
  32. package/src/cache/drivers/memory.ts +1 -0
  33. package/src/client/drivers/fetch.ts +2 -1
  34. package/src/core/actions.ts +6 -1
  35. package/src/core/audit.ts +9 -3
  36. package/src/core/contracts.ts +7 -0
  37. package/src/core/domain.ts +1 -1
  38. package/src/core/errors.ts +18 -15
  39. package/src/core/index.ts +4 -2
  40. package/src/core/package-version.ts +26 -0
  41. package/src/core/reactions.ts +1 -1
  42. package/src/core/runtime.ts +33 -23
  43. package/src/core/schedules.ts +1 -1
  44. package/src/core/types.ts +2 -2
  45. package/src/dsl/eval.ts +2 -2
  46. package/src/dsl/kysely.ts +2 -2
  47. package/src/dsl/loads.ts +1 -1
  48. package/src/dsl/parser.ts +5 -5
  49. package/src/events/drivers/mitt.ts +1 -0
  50. package/src/mcp/index.ts +2 -1
  51. package/src/observability/drivers/opentelemetry.ts +1 -0
  52. package/src/queue/drivers/bullmq.ts +3 -3
  53. package/src/scheduler/drivers/node-cron.ts +3 -2
  54. package/src/scheduler/every.ts +7 -7
  55. package/src/schema/openapi.ts +3 -3
  56. package/src/seed/index.ts +29 -0
  57. package/src/server/drivers/fastify.ts +5 -2
  58. package/src/server/drivers/node.ts +9 -6
  59. package/src/server/index.ts +3 -1
  60. package/src/storage/drivers/fs.ts +1 -0
  61. package/src/testing/index.ts +3 -3
  62. package/src/ui/components/patterns/confirm.tsx +142 -42
  63. package/src/ui/components/patterns/content-header.tsx +7 -1
  64. package/src/ui/components/patterns/data-state.tsx +1 -1
  65. package/src/ui/components/patterns/dock.tsx +20 -3
  66. package/src/ui/components/patterns/form.tsx +12 -8
  67. package/src/ui/components/patterns/list.tsx +36 -41
  68. package/src/ui/components/patterns/page-state.tsx +2 -2
  69. package/src/ui/components/patterns/page.tsx +19 -1
  70. package/src/ui/components/patterns/shell-nav.tsx +10 -3
  71. package/src/ui/components/patterns/sidebar.tsx +43 -32
  72. package/src/ui/components/patterns/trigger.tsx +39 -38
  73. package/src/ui/components/patterns/view.tsx +26 -17
  74. package/src/ui/components/primitives/alert.tsx +14 -8
  75. package/src/ui/components/primitives/ask.tsx +3 -3
  76. package/src/ui/components/primitives/badge.tsx +11 -6
  77. package/src/ui/components/primitives/breadcrumb.tsx +2 -2
  78. package/src/ui/components/primitives/button.tsx +16 -3
  79. package/src/ui/components/primitives/calendar.tsx +28 -2
  80. package/src/ui/components/primitives/carousel.tsx +3 -3
  81. package/src/ui/components/primitives/chat.tsx +1 -1
  82. package/src/ui/components/primitives/checkbox.tsx +1 -1
  83. package/src/ui/components/primitives/command.tsx +2 -2
  84. package/src/ui/components/primitives/control.ts +12 -0
  85. package/src/ui/components/primitives/copyable.tsx +1 -1
  86. package/src/ui/components/primitives/dialog.tsx +202 -40
  87. package/src/ui/components/primitives/dot.tsx +5 -0
  88. package/src/ui/components/primitives/drawer.tsx +18 -8
  89. package/src/ui/components/primitives/empty.tsx +3 -3
  90. package/src/ui/components/primitives/field.tsx +3 -3
  91. package/src/ui/components/primitives/icon-picker.tsx +3 -1
  92. package/src/ui/components/primitives/input-group.tsx +1 -1
  93. package/src/ui/components/primitives/input-otp.tsx +1 -1
  94. package/src/ui/components/primitives/input.tsx +2 -2
  95. package/src/ui/components/primitives/item.tsx +3 -3
  96. package/src/ui/components/primitives/progress.tsx +32 -3
  97. package/src/ui/components/primitives/radio-group.tsx +1 -1
  98. package/src/ui/components/primitives/resizable.tsx +3 -1
  99. package/src/ui/components/primitives/select.tsx +5 -5
  100. package/src/ui/components/primitives/slider.tsx +5 -1
  101. package/src/ui/components/primitives/sonner.tsx +190 -8
  102. package/src/ui/components/primitives/switch.tsx +1 -0
  103. package/src/ui/components/primitives/tabs.tsx +1 -0
  104. package/src/ui/components/primitives/textarea.tsx +1 -1
  105. package/src/ui/components/primitives/toggle.tsx +1 -1
  106. package/src/ui/components/primitives/tooltip.tsx +1 -0
  107. package/src/ui/docs/DocBrowser.tsx +102 -23
  108. package/src/ui/docs/changelog.tsx +1 -1
  109. package/src/ui/docs/content/accordion.md +22 -16
  110. package/src/ui/docs/content/action-form-card.md +8 -8
  111. package/src/ui/docs/content/action-form-dialog.md +9 -9
  112. package/src/ui/docs/content/action-form.md +37 -36
  113. package/src/ui/docs/content/action-list-dialog.md +11 -6
  114. package/src/ui/docs/content/action-list.md +73 -39
  115. package/src/ui/docs/content/action-trigger.md +29 -15
  116. package/src/ui/docs/content/action-view.md +17 -10
  117. package/src/ui/docs/content/actions.md +9 -9
  118. package/src/ui/docs/content/ai.md +3 -3
  119. package/src/ui/docs/content/alert.md +14 -12
  120. package/src/ui/docs/content/ask.md +11 -0
  121. package/src/ui/docs/content/aspect-ratio.md +4 -4
  122. package/src/ui/docs/content/audit.md +2 -2
  123. package/src/ui/docs/content/auth.md +3 -3
  124. package/src/ui/docs/content/avatar.md +34 -14
  125. package/src/ui/docs/content/badge.md +3 -3
  126. package/src/ui/docs/content/breadcrumb.md +13 -8
  127. package/src/ui/docs/content/button.md +81 -6
  128. package/src/ui/docs/content/calendar.md +18 -5
  129. package/src/ui/docs/content/card.md +27 -1
  130. package/src/ui/docs/content/carousel.md +16 -11
  131. package/src/ui/docs/content/chat.md +23 -3
  132. package/src/ui/docs/content/checkbox.md +7 -7
  133. package/src/ui/docs/content/cli.md +74 -22
  134. package/src/ui/docs/content/collapsible.md +8 -8
  135. package/src/ui/docs/content/command.md +16 -8
  136. package/src/ui/docs/content/composer.md +17 -2
  137. package/src/ui/docs/content/content.md +17 -2
  138. package/src/ui/docs/content/copyable.md +12 -3
  139. package/src/ui/docs/content/customization.md +5 -5
  140. package/src/ui/docs/content/cycle.md +3 -3
  141. package/src/ui/docs/content/data-state.md +11 -12
  142. package/src/ui/docs/content/data.md +26 -33
  143. package/src/ui/docs/content/detail.md +22 -4
  144. package/src/ui/docs/content/dialog.md +339 -31
  145. package/src/ui/docs/content/dictionary-value.md +17 -10
  146. package/src/ui/docs/content/dock.md +11 -3
  147. package/src/ui/docs/content/dot.md +8 -0
  148. package/src/ui/docs/content/drawer.md +27 -14
  149. package/src/ui/docs/content/empty-value.md +2 -2
  150. package/src/ui/docs/content/empty.md +19 -12
  151. package/src/ui/docs/content/events.md +4 -4
  152. package/src/ui/docs/content/field.md +34 -12
  153. package/src/ui/docs/content/getting-started.md +3 -3
  154. package/src/ui/docs/content/icon-picker.md +19 -4
  155. package/src/ui/docs/content/input-otp.md +20 -12
  156. package/src/ui/docs/content/input.md +121 -9
  157. package/src/ui/docs/content/item.md +27 -13
  158. package/src/ui/docs/content/kbd.md +19 -11
  159. package/src/ui/docs/content/label.md +12 -3
  160. package/src/ui/docs/content/log.md +4 -4
  161. package/src/ui/docs/content/markdown.md +7 -6
  162. package/src/ui/docs/content/mcp.md +13 -15
  163. package/src/ui/docs/content/menu.md +40 -16
  164. package/src/ui/docs/content/metric-card.md +13 -0
  165. package/src/ui/docs/content/observability.md +2 -2
  166. package/src/ui/docs/content/page.md +59 -6
  167. package/src/ui/docs/content/pagination.md +22 -17
  168. package/src/ui/docs/content/popover.md +22 -8
  169. package/src/ui/docs/content/progress.md +15 -16
  170. package/src/ui/docs/content/queue.md +5 -5
  171. package/src/ui/docs/content/radio-group.md +20 -12
  172. package/src/ui/docs/content/router.md +11 -6
  173. package/src/ui/docs/content/scheduler.md +4 -5
  174. package/src/ui/docs/content/scroll-area.md +12 -7
  175. package/src/ui/docs/content/select.md +47 -34
  176. package/src/ui/docs/content/semantic-context.md +2 -2
  177. package/src/ui/docs/content/separator.md +5 -5
  178. package/src/ui/docs/content/sidebar.md +329 -54
  179. package/src/ui/docs/content/skeleton.md +9 -2
  180. package/src/ui/docs/content/slider.md +8 -7
  181. package/src/ui/docs/content/spinner.md +8 -8
  182. package/src/ui/docs/content/split.md +29 -5
  183. package/src/ui/docs/content/storage.md +6 -8
  184. package/src/ui/docs/content/switch.md +8 -7
  185. package/src/ui/docs/content/table.md +13 -3
  186. package/src/ui/docs/content/tabs.md +28 -14
  187. package/src/ui/docs/content/testing.md +9 -11
  188. package/src/ui/docs/content/textarea.md +12 -4
  189. package/src/ui/docs/content/toast.md +47 -13
  190. package/src/ui/docs/content/toggle.md +75 -7
  191. package/src/ui/docs/content/tokens.md +7 -7
  192. package/src/ui/docs/content/tooltip.md +19 -11
  193. package/src/ui/docs/content/truncate.md +15 -8
  194. package/src/ui/docs/content/ui.md +24 -9
  195. package/src/ui/docs/content/upgrading.md +7 -8
  196. package/src/ui/docs/doc-client.tsx +5 -5
  197. package/src/ui/docs/doc.tsx +26 -14
  198. package/src/ui/docs/registry.tsx +25 -42
  199. package/src/ui/docs/standalone.tsx +2 -2
  200. package/src/ui/drivers/react.tsx +17 -12
  201. package/src/ui/lib/action-errors.ts +45 -0
  202. package/src/ui/lib/zod-pt-br.ts +31 -4
  203. package/src/ui/meta.ts +65 -95
  204. package/src/ui/react.tsx +17 -16
  205. package/src/ui/theme.css +60 -8
  206. package/src/vite/design.ts +6 -18
  207. package/src/ui/components/primitives/alert-dialog.tsx +0 -192
  208. package/src/ui/docs/content/alert-dialog.md +0 -73
  209. package/src/ui/docs/content/button-group.md +0 -71
  210. package/src/ui/docs/content/confirm.md +0 -120
  211. package/src/ui/docs/content/input-group.md +0 -79
  212. package/src/ui/docs/content/page-state.md +0 -45
  213. package/src/ui/docs/content/toggle-group.md +0 -81
@@ -4,11 +4,10 @@ title: Atualizar o Opus
4
4
 
5
5
  # Atualizar o Opus
6
6
 
7
- Projeto em produção fica meses sem tocar no Opus e um dia precisa pular várias
8
- versões de uma vez. O ritual é o mesmo para humano e IA, e não depende de memória:
9
- cada etapa está documentada ou é verificada por um gate que informa o que precisa ser corrigido.
7
+ Use este fluxo para atualizar o Opus por uma ou várias versões. O changelog explica as mudanças e os
8
+ gates apontam as adaptações necessárias no projeto.
10
9
 
11
- ## 1. Saber que está atrás
10
+ ## 1. Identificar a versão disponível
12
11
 
13
12
  O **Maestro** compara o pin (`opus.json`) com a versão instalada e com a fonte a cada
14
13
  sessão — o alerta no rodapé diz a distância (`v2.9.0 → v2.15.2`) e o **Atualizar** faz
@@ -16,7 +15,7 @@ a parte mecânica: bump da dependência, re-materialização da camada de IA (sk
16
15
  hooks, agentes) e re-sync do bloco gerenciado do CLAUDE.md. Fora do Maestro:
17
16
  `pnpm outdated @softize/opus`.
18
17
 
19
- ## 2. Saber o que mudou
18
+ ## 2. Ler as mudanças acumuladas
20
19
 
21
20
  O changelog **viaja no pacote** — depois do bump, está em
22
21
  `node_modules/@softize/opus/CHANGELOG.md`. Uma entrada por versão; quando quebra,
@@ -26,7 +25,7 @@ A esteira de release **recusa** publicar versão sem entrada — o arquivo não
26
25
  - **minor** (2.9 → 2.10): compatível; leia por curiosidade.
27
26
  - **major** (2.x → 3.0): pare nas seções Breaking de cada versão pulada, na ordem.
28
27
 
29
- ## 3. Deixar os gates cobrarem o resto
28
+ ## 3. Executar as verificações
30
29
 
31
30
  O Opus ship source: o que quebrou aparece com arquivo e linha. Rode, na ordem:
32
31
 
@@ -38,8 +37,8 @@ pnpm manifest:check # a spec projetada ficou fresca?
38
37
  opus db migrate # schema em dia (drift-check embutido)
39
38
  ```
40
39
 
41
- Verde em tudo = atualizado de verdade. O bump entra no PR como qualquer mudança de
42
- código — atualização de base passa por review, não por fé.
40
+ Com todas as verificações verdes, revise a atualização como qualquer outra mudança de código antes
41
+ de entregá-la.
43
42
 
44
43
  ## Pulou muitas versões?
45
44
 
@@ -8,7 +8,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
8
8
  import { z } from 'zod'
9
9
  import { attachLogicalType, defineContract, error } from '../../core/index.ts'
10
10
  import type { ActionDef, ActionResult, ClientAdapter, LogicalTypeMeta, Paginated } from '../../core/index.ts'
11
- import { TbdlibProvider } from '../react.tsx'
11
+ import { OpusProvider } from '../react.tsx'
12
12
 
13
13
  // — Dados enlatados (domínio da casa) —
14
14
  export interface DocBrowserWorkspace {
@@ -37,7 +37,7 @@ export const docWorkspaceStatus: LogicalTypeMeta = {
37
37
  keys: [...STATUS],
38
38
  entries: {
39
39
  active: { label: 'Ativo', context: 'success', description: 'Agentes em operação para o cliente.' },
40
- onboarding: { label: 'Onboarding', context: 'warning' },
40
+ onboarding: { label: 'Em integração', context: 'warning' },
41
41
  paused: { label: 'Pausado' },
42
42
  },
43
43
  presentation: 'status',
@@ -59,7 +59,7 @@ export const docWorkspaceCreate = defineContract({
59
59
  }),
60
60
  output: z.object({ id: z.string() }),
61
61
  fields: {
62
- name: { label: 'Nome', placeholder: 'Ex.: Empresa X', hint: 'Digite "Softize" pra ver o erro de servidor.' },
62
+ name: { label: 'Nome', placeholder: 'Ex.: Empresa X', help: 'Digite Softize para ver o erro de servidor.' },
63
63
  status: { label: 'Status' },
64
64
  contact: { label: 'Contato', placeholder: 'email@cliente.com' },
65
65
  notes: { label: 'Observações', placeholder: 'Contexto do onboarding…' },
@@ -100,7 +100,7 @@ export const docWorkspaceList = defineContract({
100
100
  kind: 'static',
101
101
  items: [
102
102
  { value: 'active', label: 'Ativo' },
103
- { value: 'onboarding', label: 'Onboarding' },
103
+ { value: 'onboarding', label: 'Em integração' },
104
104
  { value: 'paused', label: 'Pausado' },
105
105
  ],
106
106
  },
@@ -225,7 +225,7 @@ const queryClient = new QueryClient()
225
225
  export function DocBrowserActionProvider({ children }: { children: React.ReactNode }): React.ReactElement {
226
226
  return (
227
227
  <QueryClientProvider client={queryClient}>
228
- <TbdlibProvider client={docClient}>{children}</TbdlibProvider>
228
+ <OpusProvider client={docClient}>{children}</OpusProvider>
229
229
  </QueryClientProvider>
230
230
  )
231
231
  }
@@ -18,6 +18,10 @@ import {
18
18
  TableHead,
19
19
  TableHeader,
20
20
  TableRow,
21
+ Tooltip,
22
+ TooltipContent,
23
+ TooltipProvider,
24
+ TooltipTrigger,
21
25
  cn,
22
26
  } from '../react.tsx'
23
27
 
@@ -72,8 +76,8 @@ export function DocPage({
72
76
  </p>
73
77
  )}
74
78
  {meta?.deprecated !== undefined && (
75
- <div className="max-w-2xl rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm leading-relaxed text-foreground">
76
- <span className="font-medium">Deprecated{meta.deprecated.since !== undefined ? ` desde ${meta.deprecated.since}` : ''}.</span>{' '}
79
+ <div className="max-w-2xl rounded-lg border border-context-warning-border bg-context-warning-subtle px-3 py-2 text-sm leading-relaxed text-foreground">
80
+ <span className="font-medium">Descontinuado{meta.deprecated.since !== undefined ? ` desde ${meta.deprecated.since}` : ''}.</span>{' '}
77
81
  Use <InlineMd text={meta.deprecated.alternative} />.
78
82
  </div>
79
83
  )}
@@ -211,17 +215,25 @@ export function CodeBlock({
211
215
  // overflow-hidden: os filhos (fade incluso) são CLIPADOS no raio do contêiner — sem
212
216
  // isso, o overlay desenhava um segundo arco nos cantos (a "borda dobrada").
213
217
  <div className={cn('group relative overflow-hidden rounded-lg border border-border bg-muted/40', className)}>
214
- <button
215
- onClick={() => {
216
- void navigator.clipboard.writeText(clean)
217
- setCopied(true)
218
- setTimeout(() => setCopied(false), 1500)
219
- }}
220
- title="Copiar código"
221
- className="absolute right-2 top-2 z-20 rounded-md p-1.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:text-foreground group-hover:opacity-100"
222
- >
223
- {copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
224
- </button>
218
+ {/* Provider próprio: a doc também é montada fora do shell do app (standalone, testes). */}
219
+ <TooltipProvider delayDuration={200}>
220
+ <Tooltip>
221
+ <TooltipTrigger asChild>
222
+ <button
223
+ onClick={() => {
224
+ void navigator.clipboard.writeText(clean)
225
+ setCopied(true)
226
+ setTimeout(() => setCopied(false), 1500)
227
+ }}
228
+ aria-label="Copiar código"
229
+ className="absolute right-2 top-2 z-20 rounded-md p-1.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:text-foreground group-hover:opacity-100 focus-visible:opacity-100"
230
+ >
231
+ {copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
232
+ </button>
233
+ </TooltipTrigger>
234
+ <TooltipContent>{copied ? 'Copiado' : 'Copiar código'}</TooltipContent>
235
+ </Tooltip>
236
+ </TooltipProvider>
225
237
 
226
238
  <div ref={contentRef} className={cn('relative', collapsed && 'max-h-44 overflow-hidden')}>
227
239
  <Highlight code={clean} language={lang} theme={codeTheme}>
@@ -279,7 +291,7 @@ export function PropsTable({ rows }: { rows: PropRow[] }): React.ReactElement {
279
291
  <Table>
280
292
  <TableHeader>
281
293
  <TableRow>
282
- <TableHead className="w-[18%]">Prop</TableHead>
294
+ <TableHead className="w-[18%]">Propriedade</TableHead>
283
295
  <TableHead className="w-[30%]">Tipo</TableHead>
284
296
  <TableHead className="w-[12%]">Padrão</TableHead>
285
297
  <TableHead>Descrição</TableHead>
@@ -27,6 +27,7 @@ import authMd from "./content/auth.md?raw";
27
27
  import auditMd from "./content/audit.md?raw";
28
28
  import eventsMd from "./content/events.md?raw";
29
29
  import logMd from "./content/log.md?raw";
30
+ import observabilityMd from "./content/observability.md?raw";
30
31
  import queueMd from "./content/queue.md?raw";
31
32
  import schedulerMd from "./content/scheduler.md?raw";
32
33
  import actionsMd from "./content/actions.md?raw";
@@ -46,8 +47,6 @@ import customizationMd from "./content/customization.md?raw";
46
47
  // — Componentes —
47
48
  import accordionMd from "./content/accordion.md?raw";
48
49
  import alertMd from "./content/alert.md?raw";
49
- import alertDialogMd from "./content/alert-dialog.md?raw";
50
- import confirmMd from "./content/confirm.md?raw";
51
50
  import aspectRatioMd from "./content/aspect-ratio.md?raw";
52
51
  import avatarMd from "./content/avatar.md?raw";
53
52
  import badgeMd from "./content/badge.md?raw";
@@ -55,7 +54,6 @@ import dotMd from "./content/dot.md?raw";
55
54
  import dockMd from "./content/dock.md?raw";
56
55
  import breadcrumbMd from "./content/breadcrumb.md?raw";
57
56
  import buttonMd from "./content/button.md?raw";
58
- import buttonGroupMd from "./content/button-group.md?raw";
59
57
  import calendarMd from "./content/calendar.md?raw";
60
58
  import cardMd from "./content/card.md?raw";
61
59
  import metricCardMd from "./content/metric-card.md?raw";
@@ -77,7 +75,6 @@ import menuMd from "./content/menu.md?raw";
77
75
  import emptyMd from "./content/empty.md?raw";
78
76
  import fieldMd from "./content/field.md?raw";
79
77
  import inputMd from "./content/input.md?raw";
80
- import inputGroupMd from "./content/input-group.md?raw";
81
78
  import inputOtpMd from "./content/input-otp.md?raw";
82
79
  import itemMd from "./content/item.md?raw";
83
80
  import kbdMd from "./content/kbd.md?raw";
@@ -99,7 +96,6 @@ import tabsMd from "./content/tabs.md?raw";
99
96
  import textareaMd from "./content/textarea.md?raw";
100
97
  import toastMd from "./content/toast.md?raw";
101
98
  import toggleMd from "./content/toggle.md?raw";
102
- import toggleGroupMd from "./content/toggle-group.md?raw";
103
99
  import tooltipMd from "./content/tooltip.md?raw";
104
100
  import truncateMd from "./content/truncate.md?raw";
105
101
 
@@ -122,7 +118,6 @@ import actionTriggerMd from "./content/action-trigger.md?raw";
122
118
  import actionSearchDialogMd from "./content/action-list-dialog.md?raw";
123
119
  import pageMd from "./content/page.md?raw";
124
120
  import contentMd from "./content/content.md?raw";
125
- import pageStateMd from "./content/page-state.md?raw";
126
121
  import sidebarMd from "./content/sidebar.md?raw";
127
122
  import splitMd from "./content/split.md?raw";
128
123
  import routerMd from "./content/router.md?raw";
@@ -139,6 +134,8 @@ export interface DocEntry {
139
134
  export interface DocGroup {
140
135
  /** Sub-rótulo opcional dentro da seção (ex.: Primitivos). Ausente = páginas direto na seção. */
141
136
  label?: string;
137
+ /** Ícone opcional apresentado no nó expansível do grupo. */
138
+ icon?: React.ReactNode;
142
139
  pages: DocEntry[];
143
140
  }
144
141
 
@@ -178,7 +175,7 @@ const comp =
178
175
  </DocPage>
179
176
  );
180
177
 
181
- /** O palco dos patterns no scope dos previews: ícones + client fake + contratos de mentira. */
178
+ /** O palco dos patterns no scope dos previews: ícones, cliente simulado e contratos de exemplo. */
182
179
  const patternScope = {
183
180
  ...iconScope,
184
181
  DocBrowserActionProvider,
@@ -224,13 +221,13 @@ export const PROTOCOL_SECTIONS: DocSection[] = [
224
221
  pages: [
225
222
  {
226
223
  slug: "getting-started",
227
- title: "Getting started",
224
+ title: "Primeiros passos",
228
225
  render: doc(gettingStartedMd),
229
226
  },
230
227
  { slug: "cycle", title: "Evolução contínua", render: doc(cycleMd) },
231
228
  {
232
229
  slug: "upgrading",
233
- title: "Atualizar a base",
230
+ title: "Atualizar o Opus",
234
231
  render: doc(upgradingMd),
235
232
  },
236
233
  ],
@@ -291,6 +288,12 @@ export const PROTOCOL_SECTIONS: DocSection[] = [
291
288
  render: doc(eventsMd),
292
289
  },
293
290
  { slug: "log", title: "Log", badge: "log", render: doc(logMd) },
291
+ {
292
+ slug: "observability",
293
+ title: "Observabilidade",
294
+ badge: "observability",
295
+ render: doc(observabilityMd),
296
+ },
294
297
  {
295
298
  slug: "queue",
296
299
  title: "Filas",
@@ -334,7 +337,7 @@ export const UI_SECTIONS: DocSection[] = [
334
337
  pages: [
335
338
  {
336
339
  slug: "como-consumir",
337
- title: "Getting started",
340
+ title: "Como consumir",
338
341
  render: doc(uiMd),
339
342
  },
340
343
  ],
@@ -346,10 +349,10 @@ export const UI_SECTIONS: DocSection[] = [
346
349
  groups: [
347
350
  {
348
351
  pages: [
349
- { slug: "tokens", title: "Tokens & Tema", render: doc(tokensMd) },
352
+ { slug: "tokens", title: "Tokens e tema", render: doc(tokensMd) },
350
353
  {
351
354
  slug: "semantic-context",
352
- title: "Contexto & Variante",
355
+ title: "Contexto e variante",
353
356
  render: doc(semanticContextMd),
354
357
  },
355
358
  {
@@ -371,6 +374,7 @@ export const UI_SECTIONS: DocSection[] = [
371
374
  groups: [
372
375
  {
373
376
  label: "Fundamentos",
377
+ icon: <lucideIcons.Blocks className="size-4" />,
374
378
  pages: [
375
379
  {
376
380
  slug: "router",
@@ -381,6 +385,7 @@ export const UI_SECTIONS: DocSection[] = [
381
385
  },
382
386
  {
383
387
  label: "Layout",
388
+ icon: <lucideIcons.PanelsTopLeft className="size-4" />,
384
389
  pages: [
385
390
  {
386
391
  slug: "split",
@@ -402,11 +407,6 @@ export const UI_SECTIONS: DocSection[] = [
402
407
  title: "Content",
403
408
  render: pattern("Content", "content", contentMd),
404
409
  },
405
- {
406
- slug: "page-state",
407
- title: "Page State",
408
- render: pattern("Page State", "page-state", pageStateMd),
409
- },
410
410
  {
411
411
  slug: "accordion",
412
412
  title: "Accordion",
@@ -442,6 +442,7 @@ export const UI_SECTIONS: DocSection[] = [
442
442
  },
443
443
  {
444
444
  label: "Actions",
445
+ icon: <lucideIcons.Zap className="size-4" />,
445
446
  pages: [
446
447
  {
447
448
  slug: "action-form",
@@ -498,6 +499,7 @@ export const UI_SECTIONS: DocSection[] = [
498
499
  },
499
500
  {
500
501
  label: "IA",
502
+ icon: <lucideIcons.Sparkles className="size-4" />,
501
503
  pages: [
502
504
  { slug: "ask", title: "Ask", render: comp("Ask", "ask", askMd) },
503
505
  { slug: "chat", title: "Chat", render: comp("Chat", "chat", chatMd) },
@@ -510,6 +512,7 @@ export const UI_SECTIONS: DocSection[] = [
510
512
  },
511
513
  {
512
514
  label: "Formulário",
515
+ icon: <lucideIcons.TextCursorInput className="size-4" />,
513
516
  pages: [
514
517
  {
515
518
  slug: "calendar",
@@ -536,11 +539,6 @@ export const UI_SECTIONS: DocSection[] = [
536
539
  title: "Input",
537
540
  render: comp("Input", "input", inputMd),
538
541
  },
539
- {
540
- slug: "input-group",
541
- title: "Input Group",
542
- render: comp("Input Group", "input-group", inputGroupMd),
543
- },
544
542
  {
545
543
  slug: "input-otp",
546
544
  title: "Input OTP",
@@ -580,31 +578,23 @@ export const UI_SECTIONS: DocSection[] = [
580
578
  },
581
579
  {
582
580
  label: "Botões",
581
+ icon: <lucideIcons.MousePointerClick className="size-4" />,
583
582
  pages: [
584
583
  {
585
584
  slug: "button",
586
585
  title: "Button",
587
586
  render: comp("Button", "button", buttonMd),
588
587
  },
589
- {
590
- slug: "button-group",
591
- title: "Button Group",
592
- render: comp("Button Group", "button-group", buttonGroupMd),
593
- },
594
588
  {
595
589
  slug: "toggle",
596
590
  title: "Toggle",
597
591
  render: comp("Toggle", "toggle", toggleMd),
598
592
  },
599
- {
600
- slug: "toggle-group",
601
- title: "Toggle Group",
602
- render: comp("Toggle Group", "toggle-group", toggleGroupMd),
603
- },
604
593
  ],
605
594
  },
606
595
  {
607
596
  label: "Navegação",
597
+ icon: <lucideIcons.Compass className="size-4" />,
608
598
  pages: [
609
599
  {
610
600
  slug: "breadcrumb",
@@ -626,6 +616,7 @@ export const UI_SECTIONS: DocSection[] = [
626
616
  },
627
617
  {
628
618
  label: "Exibição",
619
+ icon: <lucideIcons.Eye className="size-4" />,
629
620
  pages: [
630
621
  {
631
622
  slug: "avatar",
@@ -689,17 +680,13 @@ export const UI_SECTIONS: DocSection[] = [
689
680
  },
690
681
  {
691
682
  label: "Feedback",
683
+ icon: <lucideIcons.MessageCircleWarning className="size-4" />,
692
684
  pages: [
693
685
  {
694
686
  slug: "alert",
695
687
  title: "Alert",
696
688
  render: comp("Alert", "alert", alertMd),
697
689
  },
698
- {
699
- slug: "confirm",
700
- title: "Dialog (imperativo)",
701
- render: comp("Dialog (imperativo)", "confirm", confirmMd),
702
- },
703
690
  {
704
691
  slug: "data-state",
705
692
  title: "Data State",
@@ -734,12 +721,8 @@ export const UI_SECTIONS: DocSection[] = [
734
721
  },
735
722
  {
736
723
  label: "Sobreposição",
724
+ icon: <lucideIcons.Layers className="size-4" />,
737
725
  pages: [
738
- {
739
- slug: "alert-dialog",
740
- title: "Alert Dialog",
741
- render: comp("Alert Dialog", "alert-dialog", alertDialogMd),
742
- },
743
726
  {
744
727
  slug: "dialog",
745
728
  title: "Dialog",
@@ -66,7 +66,7 @@ export interface MountDocsOptions {
66
66
  basePath?: string
67
67
  /** Elemento alvo. Default: `#root` (cria um se faltar). */
68
68
  el?: HTMLElement
69
- /** Título no topo. Default `Components` (o site-base passa `opus`). */
69
+ /** Título no topo. Default `Componentes` (o site-base passa `opus`). */
70
70
  title?: string
71
71
  /** Legenda ao lado do título. Default `a verdade visual deste projeto`. */
72
72
  subtitle?: string
@@ -78,7 +78,7 @@ export interface MountDocsOptions {
78
78
  /** Monta a doc na página. Chamado pelo entry do plugin opusDocs (depois de importar o CSS do app). */
79
79
  export function mountDocs(options: MountDocsOptions = {}): void {
80
80
  const basePath = options.basePath ?? '/__docs'
81
- const title = options.title ?? 'Components'
81
+ const title = options.title ?? 'Componentes'
82
82
  const subtitle = options.subtitle ?? 'a verdade visual deste projeto'
83
83
  // Tema persistido pela própria doc; senão, a preferência do sistema.
84
84
  let saved: string | null = null
@@ -2,11 +2,11 @@
2
2
  * @softize/opus/ui/react — React driver
3
3
  *
4
4
  * Hooks + Provider pra invocar actions client-side.
5
- * - `TbdlibProvider` — injeta ClientAdapter no contexto
5
+ * - `OpusProvider` — injeta ClientAdapter no contexto (`TbdlibProvider` é alias em retirada)
6
6
  * - `useAction(action)` — invoca actions simple/form/view
7
7
  * - `useLookupAction(action)` — variante pra search (output Paginated)
8
8
  *
9
- * Compatível com React 18 e 19. Não dona estado global de cache —
9
+ * Requer React 19 (peer `react@^19`). Não dona estado global de cache —
10
10
  * cada hook mantém o seu (suficiente pra v0). Integração com
11
11
  * TanStack Query fica em `@softize/opus/client/tanstack` no futuro.
12
12
  */
@@ -58,14 +58,14 @@ export interface DictLike {
58
58
  options(): Array<{ value: string; label: string }>
59
59
  }
60
60
 
61
- interface TbdlibContextValue {
61
+ interface OpusContextValue {
62
62
  client: ClientAdapter
63
63
  dicts: Record<string, DictLike>
64
64
  }
65
65
 
66
- const TbdlibContext = createContext<TbdlibContextValue | null>(null)
66
+ const OpusContext = createContext<OpusContextValue | null>(null)
67
67
 
68
- export interface TbdlibProviderProps {
68
+ export interface OpusProviderProps {
69
69
  client: ClientAdapter
70
70
  /** Dicionários por ref — resolvem `options: { kind: 'dictionary', ref }` de
71
71
  * filters/fields (ActionList/ActionForm). Campo de dict sem registro aqui ainda
@@ -74,20 +74,25 @@ export interface TbdlibProviderProps {
74
74
  children: ReactNode
75
75
  }
76
76
 
77
- export function TbdlibProvider({
77
+ export function OpusProvider({
78
78
  client,
79
79
  dicts,
80
80
  children,
81
- }: TbdlibProviderProps): ReactNode {
82
- const value = useMemo<TbdlibContextValue>(() => ({ client, dicts: dicts ?? {} }), [client, dicts])
83
- return <TbdlibContext.Provider value={value}>{children}</TbdlibContext.Provider>
81
+ }: OpusProviderProps): ReactNode {
82
+ const value = useMemo<OpusContextValue>(() => ({ client, dicts: dicts ?? {} }), [client, dicts])
83
+ return <OpusContext.Provider value={value}>{children}</OpusContext.Provider>
84
84
  }
85
85
 
86
+ /** @deprecated Renomeado para `OpusProvider`; o alias segue até a próxima série. */
87
+ export const TbdlibProvider = OpusProvider
88
+ /** @deprecated Use `OpusProviderProps`. */
89
+ export type TbdlibProviderProps = OpusProviderProps
90
+
86
91
  function useClient(): ClientAdapter {
87
- const ctx = useContext(TbdlibContext)
92
+ const ctx = useContext(OpusContext)
88
93
  if (ctx === null) {
89
94
  throw new Error(
90
- 'useAction/useLookupAction must be used inside <TbdlibProvider>',
95
+ 'useAction/useLookupAction precisam estar dentro de <OpusProvider>.',
91
96
  )
92
97
  }
93
98
  return ctx.client
@@ -98,7 +103,7 @@ const NO_DICTS: Record<string, DictLike> = {}
98
103
  /** Dicts do provider. Default {} — inclusive fora do provider: o resolvedor cai
99
104
  * nos fallbacks (estáticas do spec, meta do `t.dict`) sem exigir registry. */
100
105
  export function useDicts(): Record<string, DictLike> {
101
- return useContext(TbdlibContext)?.dicts ?? NO_DICTS
106
+ return useContext(OpusContext)?.dicts ?? NO_DICTS
102
107
  }
103
108
 
104
109
  // =============================================================================
@@ -0,0 +1,45 @@
1
+ /**
2
+ * O que a pessoa pode ler quando uma action falha.
3
+ *
4
+ * Categorias cuja frase do SERVIDOR vence o rótulo do contrato: "Este agente tem 3 conversas —
5
+ * desabilite em vez de excluir" é acionável; "Falha ao excluir" não. É ALLOWLIST de propósito —
6
+ * erro inesperado carrega texto técnico cru (uma violação de FK viraria
7
+ * `violates foreign key constraint "user_unit_id_fkey"` para quem só clicou no botão), então
8
+ * categoria desconhecida cai no texto genérico, o lado seguro de errar. `authorization` e
9
+ * `authentication` entram porque o runtime já responde em pt-BR ("Acesso negado",
10
+ * "Autenticação necessária") e a pessoa precisa saber que o problema é de permissão, não de rede.
11
+ */
12
+ export const BUSINESS_ERRORS: readonly string[] = [
13
+ 'conflict',
14
+ 'validation',
15
+ 'not_found',
16
+ 'authorization',
17
+ 'authentication',
18
+ ]
19
+
20
+ /** Erro que tentar de novo não resolve: falta de permissão ou de sessão. */
21
+ export function isRetryable(err: HumanizableError): boolean {
22
+ return err.category !== 'authorization' && err.category !== 'authentication'
23
+ }
24
+
25
+ export interface HumanizableError {
26
+ category?: string
27
+ message?: string
28
+ }
29
+
30
+ /** A frase do servidor é de negócio (allowlist) e tem conteúdo. */
31
+ export function isBusinessError(err: HumanizableError): boolean {
32
+ return (
33
+ err.category !== undefined &&
34
+ BUSINESS_ERRORS.includes(err.category) &&
35
+ (err.message ?? '').trim() !== ''
36
+ )
37
+ }
38
+
39
+ /**
40
+ * Texto para a pessoa: a frase do servidor quando ela é de negócio; `fallback` no resto. Código
41
+ * técnico (`internal.unhandled`, `db.constraint`) nunca chega à tela por aqui.
42
+ */
43
+ export function humanizeActionError(err: HumanizableError, fallback: string): string {
44
+ return isBusinessError(err) ? (err.message as string).trim() : fallback
45
+ }
@@ -10,28 +10,55 @@
10
10
  */
11
11
  import { z } from 'zod'
12
12
 
13
+ const INVALID = 'Valor inválido.'
14
+
15
+ function options(count: number | bigint): string {
16
+ return Number(count) === 1 ? '1 opção' : `${count} opções`
17
+ }
18
+
13
19
  export const zodErrorMapPtBr: z.ZodErrorMap = (issue, ctx) => {
14
20
  switch (issue.code) {
15
21
  case z.ZodIssueCode.invalid_type:
16
22
  if (issue.received === 'undefined' || issue.received === 'null') return { message: 'Obrigatório.' }
17
- return { message: 'Valor inválido.' }
23
+ return { message: INVALID }
18
24
  case z.ZodIssueCode.too_small:
19
25
  if (issue.type === 'string') {
20
26
  return { message: Number(issue.minimum) <= 1 ? 'Obrigatório.' : `Mínimo de ${issue.minimum} caracteres.` }
21
27
  }
22
- if (issue.type === 'array') return { message: `Selecione pelo menos ${issue.minimum}.` }
28
+ if (issue.type === 'array') return { message: `Selecione pelo menos ${options(issue.minimum)}.` }
29
+ if (issue.type === 'date') return { message: 'Data anterior ao permitido.' }
23
30
  return { message: `Valor mínimo: ${issue.minimum}.` }
24
31
  case z.ZodIssueCode.too_big:
25
32
  if (issue.type === 'string') return { message: `Máximo de ${issue.maximum} caracteres.` }
26
- if (issue.type === 'array') return { message: `Selecione no máximo ${issue.maximum}.` }
33
+ if (issue.type === 'array') return { message: `Selecione no máximo ${options(issue.maximum)}.` }
34
+ if (issue.type === 'date') return { message: 'Data posterior ao permitido.' }
27
35
  return { message: `Valor máximo: ${issue.maximum}.` }
28
36
  case z.ZodIssueCode.invalid_string:
29
- if (issue.validation === 'email') return { message: 'Email inválido.' }
37
+ if (issue.validation === 'email') return { message: 'E-mail inválido.' }
30
38
  if (issue.validation === 'url') return { message: 'URL inválida.' }
31
39
  if (issue.validation === 'uuid') return { message: 'Identificador inválido.' }
32
40
  return { message: 'Formato inválido.' }
33
41
  case z.ZodIssueCode.invalid_enum_value:
42
+ case z.ZodIssueCode.invalid_union_discriminator:
34
43
  return { message: 'Escolha uma das opções.' }
44
+ case z.ZodIssueCode.invalid_literal:
45
+ case z.ZodIssueCode.invalid_union:
46
+ case z.ZodIssueCode.invalid_intersection_types:
47
+ case z.ZodIssueCode.invalid_arguments:
48
+ case z.ZodIssueCode.invalid_return_type:
49
+ case z.ZodIssueCode.custom:
50
+ return { message: INVALID }
51
+ case z.ZodIssueCode.invalid_date:
52
+ return { message: 'Data inválida.' }
53
+ case z.ZodIssueCode.not_multiple_of:
54
+ return { message: `Use múltiplos de ${issue.multipleOf}.` }
55
+ case z.ZodIssueCode.unrecognized_keys:
56
+ return {
57
+ message:
58
+ issue.keys.length === 1
59
+ ? `Campo não reconhecido: ${issue.keys[0]}.`
60
+ : `Campos não reconhecidos: ${issue.keys.join(', ')}.`,
61
+ }
35
62
  default:
36
63
  return { message: ctx.defaultError }
37
64
  }