@softize/opus 13.0.0 → 14.0.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 (164) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/PROMOTED.md +46 -0
  3. package/README.md +28 -19
  4. package/bin/cli.mjs +87 -216
  5. package/bin/lib/cli-shared.mjs +131 -0
  6. package/bin/lib/copy.mjs +276 -6
  7. package/bin/lib/db.mjs +16 -74
  8. package/bin/lib/gen-openapi.mjs +3 -3
  9. package/bin/lib/gen-runner.mjs +1 -1
  10. package/bin/lib/gen.mjs +14 -69
  11. package/bin/lib/mcp.mjs +3 -1
  12. package/bin/lib/seed.mjs +5 -62
  13. package/docs/code-style.md +4 -1
  14. package/docs/ownership-vs-shadcn-lock.md +2 -3
  15. package/docs/protocol.md +7 -7
  16. package/docs/releasing.md +8 -2
  17. package/package.json +7 -3
  18. package/registry/instructions/opus.md +3 -3
  19. package/registry/templates/app/package.json +1 -1
  20. package/registry/templates/app/src/App.tsx +11 -6
  21. package/registry/templates/app/src/main.tsx +4 -4
  22. package/src/audit/drivers/console.ts +1 -0
  23. package/src/auth/drivers/better-auth.ts +1 -0
  24. package/src/auth/drivers/jwt.ts +1 -0
  25. package/src/cache/drivers/memory.ts +1 -0
  26. package/src/client/drivers/fetch.ts +2 -1
  27. package/src/core/actions.ts +6 -1
  28. package/src/core/audit.ts +9 -3
  29. package/src/core/contracts.ts +7 -0
  30. package/src/core/domain.ts +1 -1
  31. package/src/core/errors.ts +18 -15
  32. package/src/core/index.ts +4 -2
  33. package/src/core/package-version.ts +26 -0
  34. package/src/core/reactions.ts +1 -1
  35. package/src/core/runtime.ts +33 -23
  36. package/src/core/schedules.ts +1 -1
  37. package/src/core/types.ts +5 -6
  38. package/src/dsl/eval.ts +2 -2
  39. package/src/dsl/kysely.ts +2 -2
  40. package/src/dsl/loads.ts +1 -1
  41. package/src/dsl/parser.ts +5 -5
  42. package/src/events/drivers/mitt.ts +1 -0
  43. package/src/mcp/index.ts +2 -1
  44. package/src/observability/drivers/opentelemetry.ts +1 -0
  45. package/src/queue/drivers/bullmq.ts +3 -3
  46. package/src/scheduler/drivers/node-cron.ts +3 -2
  47. package/src/scheduler/every.ts +7 -7
  48. package/src/schema/openapi.ts +3 -3
  49. package/src/seed/index.ts +29 -0
  50. package/src/server/drivers/fastify.ts +5 -2
  51. package/src/server/drivers/node.ts +9 -6
  52. package/src/server/index.ts +3 -1
  53. package/src/storage/drivers/fs.ts +1 -0
  54. package/src/testing/index.ts +3 -3
  55. package/src/ui/components/patterns/action-list-dialog.tsx +10 -3
  56. package/src/ui/components/patterns/confirm.tsx +2 -31
  57. package/src/ui/components/patterns/content-header.tsx +44 -137
  58. package/src/ui/components/patterns/data-state.tsx +42 -68
  59. package/src/ui/components/patterns/dock.tsx +20 -3
  60. package/src/ui/components/patterns/form.tsx +26 -22
  61. package/src/ui/components/patterns/list.tsx +18 -12
  62. package/src/ui/components/patterns/page-state.tsx +39 -51
  63. package/src/ui/components/patterns/page.tsx +37 -54
  64. package/src/ui/components/patterns/sidebar.tsx +17 -6
  65. package/src/ui/components/patterns/state-surface.tsx +148 -0
  66. package/src/ui/components/patterns/surface-header.tsx +119 -0
  67. package/src/ui/components/patterns/trigger.tsx +21 -25
  68. package/src/ui/components/patterns/view.tsx +29 -22
  69. package/src/ui/components/primitives/alert.tsx +1 -27
  70. package/src/ui/components/primitives/ask.tsx +3 -3
  71. package/src/ui/components/primitives/avatar.tsx +15 -5
  72. package/src/ui/components/primitives/badge.tsx +5 -41
  73. package/src/ui/components/primitives/breadcrumb.tsx +2 -2
  74. package/src/ui/components/primitives/button.tsx +39 -30
  75. package/src/ui/components/primitives/calendar.tsx +28 -2
  76. package/src/ui/components/primitives/carousel.tsx +3 -3
  77. package/src/ui/components/primitives/chat.tsx +1 -1
  78. package/src/ui/components/primitives/checkbox.tsx +1 -1
  79. package/src/ui/components/primitives/command.tsx +2 -2
  80. package/src/ui/components/primitives/control.ts +72 -0
  81. package/src/ui/components/primitives/copyable.tsx +1 -1
  82. package/src/ui/components/primitives/dialog.tsx +12 -7
  83. package/src/ui/components/primitives/dot.tsx +1 -25
  84. package/src/ui/components/primitives/drawer.tsx +10 -3
  85. package/src/ui/components/primitives/field.tsx +3 -3
  86. package/src/ui/components/primitives/icon-picker.tsx +3 -1
  87. package/src/ui/components/primitives/input-group.tsx +12 -9
  88. package/src/ui/components/primitives/input-otp.tsx +1 -1
  89. package/src/ui/components/primitives/input.tsx +2 -2
  90. package/src/ui/components/primitives/item.tsx +3 -1
  91. package/src/ui/components/primitives/menu.tsx +1 -7
  92. package/src/ui/components/primitives/pagination.tsx +16 -8
  93. package/src/ui/components/primitives/progress.tsx +32 -3
  94. package/src/ui/components/primitives/radio-group.tsx +1 -1
  95. package/src/ui/components/primitives/resizable.tsx +3 -1
  96. package/src/ui/components/primitives/select.tsx +6 -6
  97. package/src/ui/components/primitives/slider.tsx +5 -1
  98. package/src/ui/components/primitives/sonner.tsx +3 -0
  99. package/src/ui/components/primitives/spinner.tsx +13 -16
  100. package/src/ui/components/primitives/switch.tsx +5 -1
  101. package/src/ui/components/primitives/tabs.tsx +6 -3
  102. package/src/ui/components/primitives/textarea.tsx +1 -1
  103. package/src/ui/components/primitives/toggle.tsx +9 -4
  104. package/src/ui/components/primitives/tooltip.tsx +1 -0
  105. package/src/ui/docs/changelog.tsx +1 -1
  106. package/src/ui/docs/content/action-form.md +36 -3
  107. package/src/ui/docs/content/action-list-dialog.md +2 -2
  108. package/src/ui/docs/content/action-list.md +13 -2
  109. package/src/ui/docs/content/action-trigger.md +12 -5
  110. package/src/ui/docs/content/action-view.md +11 -3
  111. package/src/ui/docs/content/ask.md +11 -0
  112. package/src/ui/docs/content/avatar.md +7 -3
  113. package/src/ui/docs/content/button.md +30 -14
  114. package/src/ui/docs/content/calendar.md +13 -0
  115. package/src/ui/docs/content/card.md +26 -0
  116. package/src/ui/docs/content/chat.md +20 -0
  117. package/src/ui/docs/content/cli.md +71 -19
  118. package/src/ui/docs/content/communication.md +36 -0
  119. package/src/ui/docs/content/composer.md +15 -0
  120. package/src/ui/docs/content/content.md +17 -1
  121. package/src/ui/docs/content/copyable.md +8 -0
  122. package/src/ui/docs/content/data-state.md +17 -13
  123. package/src/ui/docs/content/detail.md +19 -1
  124. package/src/ui/docs/content/dialog.md +1 -4
  125. package/src/ui/docs/content/dictionary-value.md +9 -2
  126. package/src/ui/docs/content/dock.md +8 -0
  127. package/src/ui/docs/content/dot.md +8 -0
  128. package/src/ui/docs/content/empty.md +2 -2
  129. package/src/ui/docs/content/getting-started.md +2 -2
  130. package/src/ui/docs/content/icon-picker.md +11 -0
  131. package/src/ui/docs/content/input.md +1 -1
  132. package/src/ui/docs/content/item.md +1 -1
  133. package/src/ui/docs/content/label.md +7 -0
  134. package/src/ui/docs/content/menu.md +6 -0
  135. package/src/ui/docs/content/metric-card.md +13 -0
  136. package/src/ui/docs/content/page.md +20 -4
  137. package/src/ui/docs/content/pagination.md +11 -9
  138. package/src/ui/docs/content/popover.md +6 -0
  139. package/src/ui/docs/content/progress.md +8 -11
  140. package/src/ui/docs/content/select.md +5 -5
  141. package/src/ui/docs/content/semantic-context.md +5 -4
  142. package/src/ui/docs/content/sidebar.md +13 -47
  143. package/src/ui/docs/content/skeleton.md +6 -0
  144. package/src/ui/docs/content/spinner.md +9 -6
  145. package/src/ui/docs/content/split.md +21 -0
  146. package/src/ui/docs/content/switch.md +1 -1
  147. package/src/ui/docs/content/tabs.md +1 -1
  148. package/src/ui/docs/content/textarea.md +7 -0
  149. package/src/ui/docs/content/toggle.md +1 -1
  150. package/src/ui/docs/content/tokens.md +4 -4
  151. package/src/ui/docs/content/truncate.md +8 -0
  152. package/src/ui/docs/content/ui.md +14 -0
  153. package/src/ui/docs/doc-client.tsx +5 -5
  154. package/src/ui/docs/doc.tsx +26 -14
  155. package/src/ui/docs/registry.tsx +5 -5
  156. package/src/ui/docs/standalone.tsx +2 -2
  157. package/src/ui/drivers/react.tsx +12 -12
  158. package/src/ui/lib/action-errors.ts +45 -0
  159. package/src/ui/lib/zod-pt-br.ts +31 -4
  160. package/src/ui/meta.ts +8 -8
  161. package/src/ui/react.tsx +10 -16
  162. package/src/ui/theme.css +10 -8
  163. package/src/vite/design.ts +6 -18
  164. package/src/ui/components/patterns/shell-nav.tsx +0 -147
package/src/seed/index.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  * implementação server-only que a CLI carrega pelo `opus.config.ts`.
6
6
  */
7
7
 
8
+ /** Variante de dados que um seed sabe aplicar (ex.: `minimal`, `demo`). */
8
9
  export interface SeedProfile {
9
10
  /** Situação que este perfil representa para quem vai aplicá-lo. */
10
11
  description: string
@@ -12,13 +13,16 @@ export interface SeedProfile {
12
13
  expected?: Readonly<Record<string, number>>
13
14
  }
14
15
 
16
+ /** Limites de onde o seed pode rodar. */
15
17
  export interface SeedSafety {
16
18
  /** Escopos de dados em que o seed pode abrir conexão. */
17
19
  scopes: readonly SeedScope[]
18
20
  }
19
21
 
22
+ /** Escopos de banco em que seeds podem abrir conexão; nunca produção. */
20
23
  export type SeedScope = 'local' | 'test' | 'isolated-preview'
21
24
 
25
+ /** Declaração pura de um seed — o que `defineSeed` valida e o manifesto publica. */
22
26
  export interface SeedDefinition {
23
27
  /** Identidade estável, em segmentos lowercase separados por ponto. */
24
28
  name: string
@@ -31,12 +35,14 @@ export interface SeedDefinition {
31
35
  safety: SeedSafety
32
36
  }
33
37
 
38
+ /** Contexto entregue a `plan`, `apply` e `verify` do binding. */
34
39
  export interface SeedContext<Database = unknown> {
35
40
  database: Database | null
36
41
  profile: string
37
42
  scope: SeedScope
38
43
  }
39
44
 
45
+ /** Contexto da asserção de alvo, executada antes de qualquer binding. */
40
46
  export interface SeedTargetContext<Database = unknown> {
41
47
  database: Database | null
42
48
  scope: SeedScope
@@ -50,28 +56,34 @@ export type SeedTargetAssertion<Database = unknown> = (
50
56
  context: SeedTargetContext<Database>,
51
57
  ) => boolean | Promise<boolean>
52
58
 
59
+ /** Resultado de `plan`: o que a aplicação faria, sem tocar o banco. */
53
60
  export interface SeedPlan {
54
61
  summary: string
55
62
  operations: readonly string[]
56
63
  }
57
64
 
65
+ /** Resultado de `apply` e `verify`: resumo e métricas observadas. */
58
66
  export interface SeedReport {
59
67
  summary: string
60
68
  metrics: Readonly<Record<string, number>>
61
69
  }
62
70
 
71
+ /** Implementação server-only de um seed, ligada à declaração por `bindSeed`. */
63
72
  export interface SeedBinding<Database = unknown> {
64
73
  plan(context: SeedContext<Database>): SeedPlan | Promise<SeedPlan>
65
74
  apply(context: SeedContext<Database>): SeedReport | Promise<SeedReport>
66
75
  verify(context: SeedContext<Database>): SeedReport | Promise<SeedReport>
67
76
  }
68
77
 
78
+ /** Seed com declaração e binding — a forma que o registro em `opus.config.ts` consome. */
69
79
  export interface BoundSeed<Database = unknown> extends SeedDefinition {
70
80
  readonly binding: SeedBinding<Database>
71
81
  }
72
82
 
83
+ /** Item aceito no registro; declaração sem binding é diagnosticada como `seed.unbound`. */
73
84
  export type SeedRegistryItem = SeedDefinition | BoundSeed
74
85
 
86
+ /** Problema encontrado ao verificar o registro de seeds. */
75
87
  export interface SeedDiagnostic {
76
88
  code:
77
89
  | 'seed.duplicate'
@@ -82,6 +94,7 @@ export interface SeedDiagnostic {
82
94
  seed: string
83
95
  }
84
96
 
97
+ /** Resultado de `checkSeedRegistry`. */
85
98
  export interface SeedRegistryCheck {
86
99
  ok: boolean
87
100
  diagnostics: readonly SeedDiagnostic[]
@@ -94,6 +107,7 @@ const PROFILE_RE = /^[a-z][a-z0-9-]*$/
94
107
  const SCOPE_RE = /^[a-z][a-z0-9-]*$/
95
108
  const SEED_SCOPES = new Set<SeedScope>(['local', 'test', 'isolated-preview'])
96
109
 
110
+ /** Valida e devolve a declaração de um seed, preservando o tipo literal. Lança em declaração inválida. */
97
111
  export function defineSeed<const Definition extends SeedDefinition>(
98
112
  definition: Definition,
99
113
  ): Definition {
@@ -129,6 +143,7 @@ export function publicSeedDefinition(seed: SeedDefinition): SeedDefinition {
129
143
  }
130
144
  }
131
145
 
146
+ /** Liga a implementação server-only (`plan`, `apply`, `verify`) a uma declaração validada. */
132
147
  export function bindSeed<Database = unknown>(
133
148
  definition: SeedDefinition,
134
149
  binding: SeedBinding<Database>,
@@ -138,6 +153,7 @@ export function bindSeed<Database = unknown>(
138
153
  return { ...definition, binding }
139
154
  }
140
155
 
156
+ /** Type guard: `true` se o valor é uma declaração de seed válida. */
141
157
  export function isSeedDefinition(value: unknown): value is SeedDefinition {
142
158
  try {
143
159
  validateDefinition(value)
@@ -147,6 +163,7 @@ export function isSeedDefinition(value: unknown): value is SeedDefinition {
147
163
  }
148
164
  }
149
165
 
166
+ /** Type guard: `true` se o valor é um seed declarado e com binding completo. */
150
167
  export function isBoundSeed(value: unknown): value is BoundSeed {
151
168
  if (!isSeedDefinition(value)) return false
152
169
  try {
@@ -157,6 +174,10 @@ export function isBoundSeed(value: unknown): value is BoundSeed {
157
174
  }
158
175
  }
159
176
 
177
+ /**
178
+ * Verifica o registro de seeds: duplicidade, binding ausente, dependência
179
+ * inexistente e ciclo. Devolve a ordem topológica quando não há ciclo.
180
+ */
160
181
  export function checkSeedRegistry(items: readonly SeedRegistryItem[]): SeedRegistryCheck {
161
182
  const diagnostics: SeedDiagnostic[] = []
162
183
  const byName = new Map<string, BoundSeed>()
@@ -237,6 +258,7 @@ export function checkSeedRegistry(items: readonly SeedRegistryItem[]): SeedRegis
237
258
  }
238
259
  }
239
260
 
261
+ /** Resolve o perfil a aplicar (`requested` ou `defaultProfile`); lança se não existir no seed. */
240
262
  export function resolveSeedProfile(seed: SeedDefinition, requested?: string): string {
241
263
  const profile = requested ?? seed.defaultProfile
242
264
  if (!Object.hasOwn(seed.profiles, profile)) {
@@ -247,6 +269,10 @@ export function resolveSeedProfile(seed: SeedDefinition, requested?: string): st
247
269
  return profile
248
270
  }
249
271
 
272
+ /**
273
+ * Falha fechado antes de abrir conexão: exige `NODE_ENV` de desenvolvimento ou
274
+ * teste, escopo informado e permitido por `safety.scopes`.
275
+ */
250
276
  export function assertSeedExecutionAllowed(
251
277
  seed: SeedDefinition,
252
278
  scope: string | undefined,
@@ -274,6 +300,7 @@ function runtimeNodeEnvironment(): string | undefined {
274
300
  ).process?.env?.NODE_ENV
275
301
  }
276
302
 
303
+ /** Garante que o retorno de `plan` tem a forma `{ summary, operations }`. */
277
304
  export function validateSeedPlan(value: unknown): SeedPlan {
278
305
  if (!isRecord(value) || !isNonEmptyString(value.summary) || !Array.isArray(value.operations)) {
279
306
  throw new Error('plan precisa retornar { summary, operations }')
@@ -284,6 +311,7 @@ export function validateSeedPlan(value: unknown): SeedPlan {
284
311
  return value as unknown as SeedPlan
285
312
  }
286
313
 
314
+ /** Garante que o retorno de `apply`/`verify` tem a forma `{ summary, metrics }` com números finitos. */
287
315
  export function validateSeedReport(value: unknown): SeedReport {
288
316
  if (!isRecord(value) || !isNonEmptyString(value.summary) || !isMetrics(value.metrics)) {
289
317
  throw new Error('apply/verify precisa retornar { summary, metrics } com valores numéricos finitos')
@@ -291,6 +319,7 @@ export function validateSeedReport(value: unknown): SeedReport {
291
319
  return value as unknown as SeedReport
292
320
  }
293
321
 
322
+ /** Compara as métricas do relatório com `profiles[profile].expected`; lança listando as divergências. */
294
323
  export function assertSeedExpectedMetrics(
295
324
  seed: SeedDefinition,
296
325
  profile: string,
@@ -28,6 +28,7 @@ import type {
28
28
  RuntimeRef,
29
29
  ServerAdapter,
30
30
  } from '../../core/index.ts'
31
+ import { readPackageVersion } from '../../core/package-version.ts'
31
32
  import {
32
33
  toOpenAPISpec,
33
34
  type OpenAPIInfo,
@@ -58,7 +59,8 @@ export interface FastifyServerOptions {
58
59
 
59
60
  /**
60
61
  * Info do OpenAPI document (title, version, description). Quando ausente,
61
- * usa um default genérico — sobrescreva pra publicar API decente.
62
+ * usa `{ title: 'Opus API', version: <versão do SDK> }` — sobrescreva pra
63
+ * publicar a identidade da sua API.
62
64
  */
63
65
  openapiInfo?: OpenAPIInfo
64
66
 
@@ -73,11 +75,12 @@ export interface FastifyServerOptions {
73
75
  // Adapter factory
74
76
  // =============================================================================
75
77
 
78
+ /** ServerAdapter que monta as actions como rotas numa instância Fastify existente. */
76
79
  export function fastifyServer(options: FastifyServerOptions): ServerAdapter {
77
80
  const {
78
81
  app,
79
82
  apiPrefix = '/api',
80
- openapiInfo = { title: 'tbdlib API', version: '0.0.0' },
83
+ openapiInfo = { title: 'Opus API', version: readPackageVersion() },
81
84
  openapiServers,
82
85
  traceContext = false,
83
86
  } = options
@@ -34,7 +34,8 @@ import type {
34
34
  RuntimeRef,
35
35
  ServerAdapter,
36
36
  } from '../../core/index.ts'
37
- import { error } from '../../core/index.ts'
37
+ import { error, UNHANDLED_ERROR_MESSAGE } from '../../core/index.ts'
38
+ import { readPackageVersion } from '../../core/package-version.ts'
38
39
  import {
39
40
  toOpenAPISpec,
40
41
  type OpenAPIInfo,
@@ -68,7 +69,8 @@ export interface NodeServerOptions {
68
69
 
69
70
  /**
70
71
  * Info do OpenAPI document (title, version, description). Quando ausente,
71
- * usa um default genérico — sobrescreva pra publicar API decente.
72
+ * usa `{ title: 'Opus API', version: <versão do SDK> }` — sobrescreva pra
73
+ * publicar a identidade da sua API.
72
74
  */
73
75
  openapiInfo?: OpenAPIInfo
74
76
 
@@ -97,11 +99,12 @@ export interface NodeServerHandle {
97
99
 
98
100
  type RouteFn = (req: IncomingMessage, res: ServerResponse, url: URL) => Promise<void>
99
101
 
102
+ /** ServerAdapter sobre `node:http` puro, sem framework; expõe `handler` pra plugar em qualquer servidor. */
100
103
  export function nodeServer(options: NodeServerOptions = {}): NodeServerHandle {
101
104
  const {
102
105
  apiPrefix = '/api',
103
106
  endpoints,
104
- openapiInfo = { title: 'tbdlib API', version: '0.0.0' },
107
+ openapiInfo = { title: 'Opus API', version: readPackageVersion() },
105
108
  openapiServers,
106
109
  traceContext = false,
107
110
  } = options
@@ -164,7 +167,7 @@ export function nodeServer(options: NodeServerOptions = {}): NodeServerHandle {
164
167
  res,
165
168
  400,
166
169
  driverResult(
167
- { code: 'server.invalid_json', category: 'validation', message: 'Request body is not valid JSON' },
170
+ { code: 'server.invalid_json', category: 'validation', message: 'O corpo da requisição não é um JSON válido' },
168
171
  action.name,
169
172
  requestId,
170
173
  ),
@@ -205,7 +208,7 @@ export function nodeServer(options: NodeServerOptions = {}): NodeServerHandle {
205
208
  res,
206
209
  500,
207
210
  driverResult(
208
- { code: 'server.unhandled', category: 'internal', message: String(err) },
211
+ { code: 'server.unhandled', category: 'internal', message: UNHANDLED_ERROR_MESSAGE, cause: String(err) },
209
212
  deriveActionName(url.pathname, apiPrefix),
210
213
  crypto.randomUUID(),
211
214
  ),
@@ -319,7 +322,7 @@ function driverResult(
319
322
 
320
323
  function routeNotFound(pathname: string, apiPrefix: string): ActionResult<never> {
321
324
  return driverResult(
322
- { code: 'server.route_not_found', category: 'not_found', message: `No route for ${pathname}` },
325
+ { code: 'server.route_not_found', category: 'not_found', message: `Rota não encontrada: ${pathname}` },
323
326
  deriveActionName(pathname, apiPrefix),
324
327
  crypto.randomUUID(),
325
328
  )
@@ -181,7 +181,9 @@ function serializeError(err: ActionError): Record<string, unknown> {
181
181
  if (err.i18nParams !== undefined) out.i18nParams = err.i18nParams
182
182
  if (err.field !== undefined) out.field = err.field
183
183
  if (err.issues !== undefined) out.issues = err.issues
184
- if (err.cause !== undefined) out.cause = String(err.cause)
184
+ // Erro interno não leva `cause` para a wire: é o texto cru da exceção (driver, SQL), que
185
+ // pertence ao log do servidor, não à pessoa nem ao cliente.
186
+ if (err.cause !== undefined && err.category !== 'internal') out.cause = String(err.cause)
185
187
  if (err.meta !== undefined) out.meta = err.meta
186
188
  return out
187
189
  }
@@ -29,6 +29,7 @@ function metaPathOf(filePath: string): string {
29
29
  return `${filePath}.meta.json`
30
30
  }
31
31
 
32
+ /** StorageAdapter que grava objetos no sistema de arquivos local, com metadados em `<arquivo>.meta.json`. */
32
33
  export function fsStorage(options: FsStorageOptions): StorageAdapter {
33
34
  const root = path.resolve(options.root)
34
35
  const baseUrl = options.baseUrl?.replace(/\/+$/, '')
@@ -250,7 +250,7 @@ async function validate<T>(
250
250
  throw error({
251
251
  code: kind === 'input' ? 'validation.invalid_input' : 'validation.invalid_output',
252
252
  category: kind === 'input' ? 'validation' : 'internal',
253
- message: kind === 'input' ? 'Input validation failed' : 'Output validation failed (dev)',
253
+ message: kind === 'input' ? 'Dados de entrada inválidos' : 'Dados de saída inválidos (validação em dev)',
254
254
  issues: result.issues.map((iss) => ({
255
255
  path: (iss.path ?? []).join('.'),
256
256
  code: 'invalid',
@@ -294,7 +294,7 @@ export async function runAction<In, Out, ParsedIn = In>(
294
294
  throw error({
295
295
  code: 'auth.unauthenticated',
296
296
  category: 'authentication',
297
- message: 'Authentication required',
297
+ message: 'Autenticação necessária',
298
298
  })
299
299
  }
300
300
 
@@ -305,7 +305,7 @@ export async function runAction<In, Out, ParsedIn = In>(
305
305
  ? Boolean(evalExpression(parseExpression(spec), { user: ctx.user, input: parsed, ctx, ...(loaded ?? {}) }))
306
306
  : await spec(ctx, parsed, loaded)
307
307
  if (decision === false) {
308
- throw error({ code: 'auth.forbidden', category: 'authorization', message: 'Forbidden' })
308
+ throw error({ code: 'auth.forbidden', category: 'authorization', message: 'Acesso negado' })
309
309
  }
310
310
  if (decision !== true) throw decision
311
311
  }
@@ -37,7 +37,10 @@ export interface ActionListDialogProps<TItem, TInput extends Record<string, unkn
37
37
  note?: ReactNode
38
38
  /** Ação à direita da linha — em geral o botão de criar. */
39
39
  actions?: ReactNode
40
- emptyText?: string
40
+ /** Textos dos estados, repassados ao ActionList (e dele ao DataState). */
41
+ emptyMessage?: string
42
+ errorMessage?: string
43
+ retryLabel?: string
41
44
  /** Sobrepõe o vazio derivado (items.length === 0) — ex.: form inline aberto. */
42
45
  empty?: (items: TItem[]) => boolean
43
46
  /** Carga EXTRA agregada à do fetch (ex.: a query irmã que os children precisam). */
@@ -57,7 +60,9 @@ export function ActionListDialog<TItem, TInput extends Record<string, unknown> =
57
60
  description,
58
61
  note,
59
62
  actions,
60
- emptyText,
63
+ emptyMessage,
64
+ errorMessage,
65
+ retryLabel,
61
66
  empty,
62
67
  loading,
63
68
  className,
@@ -80,7 +85,9 @@ export function ActionListDialog<TItem, TInput extends Record<string, unknown> =
80
85
  <ActionList<TInput, TItem>
81
86
  action={action as unknown as ListAction<TInput, TItem>}
82
87
  input={(input ?? {}) as TInput}
83
- {...(emptyText !== undefined ? { emptyMessage: emptyText } : {})}
88
+ {...(emptyMessage !== undefined ? { emptyMessage } : {})}
89
+ {...(errorMessage !== undefined ? { errorMessage } : {})}
90
+ {...(retryLabel !== undefined ? { retryLabel } : {})}
84
91
  {...(empty !== undefined ? { empty } : {})}
85
92
  {...(loading !== undefined ? { loading } : {})}
86
93
  >
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Namespace `dialog` de propósito: `window.alert/confirm/prompt` são globais do browser, e
14
14
  * um import esquecido cairia no nativo (cinza, sem tema) sem erro. `window.dialog` não
15
- * existe — a família fica imune ao footgun. `confirm()` segue exportado como alias.
15
+ * existe — a família fica imune ao footgun.
16
16
  *
17
17
  * Por baixo é o <Dialog mode="alert">: `role="alertdialog"`, não fecha no clique fora, foco preso
18
18
  * — a semântica que uma interrupção que EXIGE resposta pede. ESC/Cancelar resolve o "não"
@@ -59,16 +59,12 @@ export interface ConfirmOptions extends DialogBase {
59
59
  cancel?: string
60
60
  /** Contexto do botão de confirmação. */
61
61
  context?: Extract<UiContext, 'primary' | 'danger'>
62
- /** @deprecated Use `context="danger"`. */
63
- variant?: 'default' | 'destructive'
64
62
  }
65
63
 
66
64
  /** `prompt` — uma string livre. Form de verdade (campos, contrato) → ActionFormDialog. */
67
65
  export interface PromptOptions extends DialogBase {
68
66
  cancel?: string
69
67
  context?: Extract<UiContext, 'primary' | 'danger'>
70
- /** @deprecated Use `context="danger"`. */
71
- variant?: 'default' | 'destructive'
72
68
  placeholder?: string
73
69
  defaultValue?: string
74
70
  }
@@ -107,16 +103,6 @@ function requireHost(fn: string): Error | null {
107
103
  )
108
104
  }
109
105
 
110
- function rejectAmbiguousContext(
111
- fn: string,
112
- options: ConfirmOptions | PromptOptions,
113
- ): Error | null {
114
- if (options.context === undefined || options.variant === undefined) return null
115
- return new Error(
116
- `${fn} não permite combinar context com uma variante semântica legada.`,
117
- )
118
- }
119
-
120
106
  function validateChoose<TResult extends string>(options: ChooseOptions<TResult>): Error | null {
121
107
  if (options.actions.length === 0) {
122
108
  return new Error('dialog.choose() exige pelo menos uma ação.')
@@ -145,8 +131,6 @@ export const dialog = {
145
131
  },
146
132
  /** Pergunta sim/não. `Promise<boolean>` — fechar/ESC resolve `false`. */
147
133
  confirm(options: ConfirmOptions): Promise<boolean> {
148
- const ambiguous = rejectAmbiguousContext('dialog.confirm()', options)
149
- if (ambiguous !== null) return Promise.reject(ambiguous)
150
134
  const err = requireHost('dialog.confirm()')
151
135
  if (err !== null) return Promise.reject(err)
152
136
  return new Promise<boolean>((resolve) => {
@@ -155,8 +139,6 @@ export const dialog = {
155
139
  },
156
140
  /** Pede uma string. `Promise<string | null>` — fechar/ESC/Cancelar resolve `null`. */
157
141
  prompt(options: PromptOptions): Promise<string | null> {
158
- const ambiguous = rejectAmbiguousContext('dialog.prompt()', options)
159
- if (ambiguous !== null) return Promise.reject(ambiguous)
160
142
  const err = requireHost('dialog.prompt()')
161
143
  if (err !== null) return Promise.reject(err)
162
144
  return new Promise<string | null>((resolve) => {
@@ -180,11 +162,6 @@ export const dialog = {
180
162
  },
181
163
  }
182
164
 
183
- /** Alias retrocompatível — `confirm()` é `dialog.confirm()`. */
184
- export function confirm(options: ConfirmOptions): Promise<boolean> {
185
- return dialog.confirm(options)
186
- }
187
-
188
165
  /**
189
166
  * O host — um por app, no shell. Enfileira: uma por vez (duas caixas empilhadas seriam
190
167
  * ambíguas sobre qual clique respondeu o quê).
@@ -228,11 +205,8 @@ export function DialogHost(): React.ReactElement {
228
205
  else p.resolve(value)
229
206
  }
230
207
 
231
- const variant = current !== undefined && (current.kind === 'confirm' || current.kind === 'prompt')
232
- ? current.variant
233
- : undefined
234
208
  const context = current !== undefined && (current.kind === 'confirm' || current.kind === 'prompt')
235
- ? current.context ?? (variant === 'destructive' ? 'danger' : 'primary')
209
+ ? current.context ?? 'primary'
236
210
  : 'primary'
237
211
  const defaultAction = current?.kind === 'alert' ? 'OK' : 'Confirmar'
238
212
 
@@ -344,6 +318,3 @@ export function DialogHost(): React.ReactElement {
344
318
  </Dialog>
345
319
  )
346
320
  }
347
-
348
- /** Alias retrocompatível — `<ConfirmHost />` é `<DialogHost />`. */
349
- export const ConfirmHost = DialogHost