@softize/opus 19.0.0 → 20.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 (142) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +5 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +15 -3
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/list.tsx +89 -78
  20. package/src/ui/components/patterns/page.tsx +20 -4
  21. package/src/ui/components/patterns/presentation.tsx +52 -37
  22. package/src/ui/components/patterns/split.tsx +44 -4
  23. package/src/ui/components/patterns/surface-anatomy.ts +35 -8
  24. package/src/ui/components/patterns/trigger.tsx +12 -2
  25. package/src/ui/components/primitives/alert.tsx +9 -1
  26. package/src/ui/components/primitives/avatar.tsx +7 -3
  27. package/src/ui/components/primitives/carousel.tsx +34 -5
  28. package/src/ui/components/primitives/checkbox.tsx +11 -3
  29. package/src/ui/components/primitives/command.tsx +2 -1
  30. package/src/ui/components/primitives/copyable.tsx +120 -10
  31. package/src/ui/components/primitives/dialog.tsx +41 -10
  32. package/src/ui/components/primitives/drawer.tsx +9 -1
  33. package/src/ui/components/primitives/empty.tsx +1 -1
  34. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  35. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  36. package/src/ui/components/primitives/input-group.tsx +9 -2
  37. package/src/ui/components/primitives/input-otp.tsx +1 -1
  38. package/src/ui/components/primitives/kbd.tsx +1 -1
  39. package/src/ui/components/primitives/label.tsx +3 -0
  40. package/src/ui/components/primitives/menu.tsx +5 -0
  41. package/src/ui/components/primitives/popover.tsx +1 -1
  42. package/src/ui/components/primitives/progress.tsx +9 -1
  43. package/src/ui/components/primitives/slider.tsx +62 -11
  44. package/src/ui/components/primitives/truncate.tsx +10 -1
  45. package/src/ui/docs/content/accordion.md +74 -27
  46. package/src/ui/docs/content/action-form-card.md +24 -15
  47. package/src/ui/docs/content/action-form-dialog.md +17 -7
  48. package/src/ui/docs/content/action-form.md +92 -77
  49. package/src/ui/docs/content/action-list-dialog.md +75 -38
  50. package/src/ui/docs/content/action-list.md +134 -52
  51. package/src/ui/docs/content/action-trigger.md +27 -10
  52. package/src/ui/docs/content/action-view.md +14 -14
  53. package/src/ui/docs/content/actions.md +161 -66
  54. package/src/ui/docs/content/ai.md +43 -44
  55. package/src/ui/docs/content/alert.md +83 -29
  56. package/src/ui/docs/content/ask.md +51 -26
  57. package/src/ui/docs/content/aspect-ratio.md +22 -24
  58. package/src/ui/docs/content/audit.md +23 -19
  59. package/src/ui/docs/content/auth.md +38 -19
  60. package/src/ui/docs/content/avatar.md +54 -5
  61. package/src/ui/docs/content/badge.md +29 -9
  62. package/src/ui/docs/content/breadcrumb.md +21 -8
  63. package/src/ui/docs/content/button.md +73 -43
  64. package/src/ui/docs/content/calendar.md +134 -26
  65. package/src/ui/docs/content/card.md +41 -15
  66. package/src/ui/docs/content/carousel.md +37 -6
  67. package/src/ui/docs/content/chat.md +118 -37
  68. package/src/ui/docs/content/checkbox.md +73 -7
  69. package/src/ui/docs/content/cli.md +94 -55
  70. package/src/ui/docs/content/collapsible.md +43 -19
  71. package/src/ui/docs/content/command.md +30 -8
  72. package/src/ui/docs/content/composer.md +23 -8
  73. package/src/ui/docs/content/content.md +31 -7
  74. package/src/ui/docs/content/copyable.md +39 -8
  75. package/src/ui/docs/content/customization.md +55 -46
  76. package/src/ui/docs/content/cycle.md +33 -18
  77. package/src/ui/docs/content/data-products.md +115 -0
  78. package/src/ui/docs/content/data-state.md +20 -6
  79. package/src/ui/docs/content/data.md +20 -19
  80. package/src/ui/docs/content/detail.md +12 -5
  81. package/src/ui/docs/content/dialog.md +107 -24
  82. package/src/ui/docs/content/dictionary-value.md +4 -4
  83. package/src/ui/docs/content/dock.md +54 -19
  84. package/src/ui/docs/content/dot.md +35 -5
  85. package/src/ui/docs/content/drawer.md +63 -19
  86. package/src/ui/docs/content/empty-value.md +20 -6
  87. package/src/ui/docs/content/empty.md +24 -3
  88. package/src/ui/docs/content/events.md +76 -16
  89. package/src/ui/docs/content/executions.md +9 -9
  90. package/src/ui/docs/content/field.md +21 -6
  91. package/src/ui/docs/content/getting-started.md +44 -34
  92. package/src/ui/docs/content/icon-picker.md +19 -13
  93. package/src/ui/docs/content/icon-tile.md +28 -5
  94. package/src/ui/docs/content/input-otp.md +58 -7
  95. package/src/ui/docs/content/input.md +22 -3
  96. package/src/ui/docs/content/item.md +53 -6
  97. package/src/ui/docs/content/kbd.md +5 -1
  98. package/src/ui/docs/content/label.md +13 -4
  99. package/src/ui/docs/content/log.md +5 -5
  100. package/src/ui/docs/content/markdown.md +5 -4
  101. package/src/ui/docs/content/mcp.md +49 -23
  102. package/src/ui/docs/content/menu.md +95 -10
  103. package/src/ui/docs/content/metric-card.md +12 -4
  104. package/src/ui/docs/content/observability.md +10 -9
  105. package/src/ui/docs/content/page.md +118 -12
  106. package/src/ui/docs/content/pagination.md +13 -5
  107. package/src/ui/docs/content/pane.md +55 -5
  108. package/src/ui/docs/content/popover.md +42 -5
  109. package/src/ui/docs/content/presentation.md +91 -47
  110. package/src/ui/docs/content/progress.md +27 -14
  111. package/src/ui/docs/content/queue.md +39 -21
  112. package/src/ui/docs/content/radio-group.md +26 -4
  113. package/src/ui/docs/content/router.md +29 -10
  114. package/src/ui/docs/content/runtime.md +90 -54
  115. package/src/ui/docs/content/scheduler.md +53 -21
  116. package/src/ui/docs/content/scroll-area.md +42 -35
  117. package/src/ui/docs/content/select.md +78 -33
  118. package/src/ui/docs/content/semantic-context.md +21 -2
  119. package/src/ui/docs/content/separator.md +15 -9
  120. package/src/ui/docs/content/sidebar.md +3 -2
  121. package/src/ui/docs/content/skeleton.md +6 -1
  122. package/src/ui/docs/content/slider.md +37 -9
  123. package/src/ui/docs/content/spinner.md +4 -3
  124. package/src/ui/docs/content/split.md +19 -0
  125. package/src/ui/docs/content/storage.md +8 -9
  126. package/src/ui/docs/content/switch.md +10 -7
  127. package/src/ui/docs/content/table.md +46 -8
  128. package/src/ui/docs/content/tabs.md +14 -4
  129. package/src/ui/docs/content/testing.md +76 -31
  130. package/src/ui/docs/content/textarea.md +5 -4
  131. package/src/ui/docs/content/toast.md +48 -7
  132. package/src/ui/docs/content/toggle.md +3 -3
  133. package/src/ui/docs/content/tokens.md +15 -26
  134. package/src/ui/docs/content/tooltip.md +18 -2
  135. package/src/ui/docs/content/truncate.md +36 -6
  136. package/src/ui/docs/content/ui.md +74 -22
  137. package/src/ui/docs/content/upgrading.md +45 -36
  138. package/src/ui/docs/plugin.ts +36 -2
  139. package/src/ui/docs/registry.tsx +6 -0
  140. package/src/ui/drivers/react.tsx +16 -8
  141. package/src/ui/meta.ts +31 -31
  142. package/src/vite/design.ts +16 -4
@@ -4,94 +4,130 @@ title: Runtime & adapters
4
4
 
5
5
  # Runtime & adapters
6
6
 
7
- O Opus não é framework — é **protocolo + SDK**: você compõe o runtime a partir de
8
- adapters plugáveis, e cada capacidade é opcional. O litmus é a inversão de controle:
9
- o runtime não toma o seu app; o seu app monta o runtime com as peças que quer.
7
+ O runtime executa as actions registradas e entrega a elas as capacidades que o app configurou:
8
+ servidor HTTP, banco, autenticação, auditoria e as demais. Cada capacidade é um adapter opcional.
9
+ O app monta o runtime com as peças de que precisa, e o Opus não assume o controle do restante da
10
+ aplicação.
10
11
 
11
12
  ## O modelo
12
13
 
13
- > Todo adapter implementa `name`/`kind`; `init`/`dispose`/`healthCheck` são opcionais e
14
- > entram no ciclo de vida do runtime (start inicializa, dispose desliga em ordem reversa,
15
- > healthCheck agrega).
14
+ Todo adapter declara `name` e `kind`. `init`, `dispose` e `healthCheck` são opcionais e acompanham o
15
+ ciclo de vida do runtime: `start()` inicializa os adapters, `dispose()` os desliga em ordem inversa
16
+ e `healthCheck()` reúne o estado de todos.
16
17
 
17
18
  ```ts
18
19
  import { createRuntime } from '@softize/opus/core'
19
20
 
20
21
  const runtime = createRuntime({
21
- server: fastifyServer({ app }), // monta as actions como endpoints
22
+ server: fastifyServer({ app }), // expõe as actions como endpoints
22
23
  data: kyselyData({ db }), // fornece ctx.db
23
- auth: makeAuthAdapter(), // resolve user/tenant/can
24
- audit: [pgAudit({ pool })], // trilha de auditoria (N sinks)
25
- observability: makeObservability(), // span ativo + TraceContext vendor-neutral
24
+ auth: jwtAuth({ secret, mapUser }), // resolve user, tenant e can
25
+ audit: [pgAudit({ pool })], // trilha de auditoria; aceita vários sinks
26
+ observability: openTelemetryObservability(), // span ativo e trace sem fornecedor fixo
26
27
  storage: fsStorage({ root }), // fornece ctx.storage (experimental)
27
28
  config: { env, i18n: { defaultLocale: 'pt-BR' } },
28
29
  })
29
30
 
30
- runtime.register([dealsDomain, billingDomain]) // não registrado = não existe
31
+ runtime.register([workspaceDomain])
31
32
  await runtime.start()
32
33
  ```
33
34
 
35
+ ## Registrar domínios
36
+
37
+ Só o que foi registrado existe para o runtime: uma action fora do registro não é montada no servidor
38
+ nem pode ser executada. `runtime.register` aceita domínios criados com `defineDomain` e também
39
+ actions, reactions e schedules soltos, em uma mesma lista. Um domínio é achatado com seus
40
+ subdomínios; um nome repetido de action, reaction, schedule ou Produto de Dados lança erro, inclusive
41
+ entre domínios registrados separadamente.
42
+
43
+ O `start()` falha quando há reactions registradas sem `eventBus` (`runtime.eventbus_required`) ou
44
+ schedules sem `scheduler` (`runtime.scheduler_required`). A declaração de um domínio está em
45
+ [Actions & contratos](/docs/actions).
46
+
34
47
  ## As capacidades
35
48
 
36
- > Um subpath por driver; quem não usa, não instala (peers opcionais onde há dependência
37
- > pesada). A tabela é o mapa — cada linha é um recurso do protocolo.
49
+ Cada driver fica num subpath próprio do pacote. Drivers que dependem de bibliotecas maiores as
50
+ declaram como dependências opcionais: só quem usa o driver as instala.
38
51
 
39
52
  | Capacidade | Fornece | Driver pronto |
40
53
  |---|---|---|
41
- | `server` | monta as actions (HTTP) | `fastifyServer` — `@softize/opus/server/fastify` · `nodeServer` — `…/server/node` |
42
- | `data` | `ctx.db` + drift-check + CRUD | `kyselyData`, `kyselyRepo`, `crudActions` — `@softize/opus/data/kysely` |
43
- | `auth` | `user`/`tenantId`/`can` do contexto | `jwtAuth` — `…/auth/jwt` · `betterAuthSession` — `…/auth/better-auth` |
54
+ | `server` | expõe as actions por HTTP | `fastifyServer` — `@softize/opus/server/fastify` · `nodeServer` — `…/server/node` |
55
+ | `data` | `ctx.db`, verificação de drift e CRUD | `kyselyData`, `kyselyRepo`, `crudActions` — `@softize/opus/data/kysely` |
56
+ | `auth` | `user`, `tenantId` e `can` do contexto | `jwtAuth` — `…/auth/jwt` · `betterAuthSession` — `…/auth/better-auth` |
44
57
  | `audit` | trilha por execução de action | `pgAudit` — `…/audit/pg` · `consoleAudit` — `…/audit/console` |
45
58
  | `logger` | `ctx.log` estruturado | `pinoLogger` — `@softize/opus/log/pino` |
46
- | `observability` | span ativo + propagação de trace | `openTelemetryObservability` — `@softize/opus/observability/opentelemetry` |
47
- | `eventBus` | `ctx.emit` + **reactions** | `mittEvents` — `@softize/opus/events/mitt` |
59
+ | `observability` | span ativo e propagação de trace | `openTelemetryObservability` — `@softize/opus/observability/opentelemetry` |
60
+ | `eventBus` | `ctx.emit` e reactions | `mittEvents` — `@softize/opus/events/mitt` |
48
61
  | `queue` | jobs em background | `bullmqQueue` — `@softize/opus/queue/bullmq` |
49
- | `scheduler` | **schedules** (cron/intervalo) | `nodeCronScheduler` — `@softize/opus/scheduler/node-cron` |
50
- | `storage` | `ctx.storage` (arquivos) | `fsStorage` — `…/storage/fs` · `s3Storage` — `…/storage/s3` |
51
- | `cache` | `ctx.cache` (leitura, experimental) | `memoryCache` — `@softize/opus/cache/memory` |
52
- | `ai` | `ctx.ai` (complete/extract) | `anthropicAi` — `@softize/opus/ai/anthropic` |
53
- | `client` | chamar actions de fora (stubs) | `fetchClient` — `@softize/opus/client/fetch` |
62
+ | `scheduler` | schedules por cron ou intervalo | `nodeCronScheduler` — `@softize/opus/scheduler/node-cron` |
63
+ | `storage` | `ctx.storage` para arquivos | `fsStorage` — `…/storage/fs` · `s3Storage` — `…/storage/s3` |
64
+ | `cache` | `ctx.cache` de leitura (experimental) | `memoryCache` — `@softize/opus/cache/memory` |
65
+ | `ai` | `ctx.ai`: `complete`, `extract`, `run` e `runStream` | `anthropicAi` — `@softize/opus/ai/anthropic` |
66
+ | `client` | chamar actions de fora do servidor | `fetchClient` — `@softize/opus/client/fetch` |
54
67
 
55
- Fora do runtime, mas parte do protocolo: o harness de teste (`runAction`/`testContext`
56
- — `@softize/opus/testing`) roda uma action em unidade com o mesmo pipeline do
57
- `execute`. Ver **Testes de action**.
68
+ `fastifyServer` devolve o adapter diretamente. `nodeServer` devolve `{ adapter, handler, hasRoute }`:
69
+ passe `adapter` ao runtime e ligue `handler` a um servidor `node:http`, ao Vite ou a um middleware no
70
+ estilo connect.
71
+
72
+ ```ts
73
+ import { createServer } from 'node:http'
74
+ import { nodeServer } from '@softize/opus/server/node'
75
+
76
+ const node = nodeServer()
77
+ const runtime = createRuntime({ server: node.adapter })
78
+ createServer(node.handler).listen(3000)
79
+ ```
80
+
81
+ Fora do runtime, o harness de teste (`runAction` e `testContext`, de `@softize/opus/testing`) executa
82
+ uma action em unidade com as etapas centrais do `execute`: valida a entrada, exige autenticação
83
+ quando a action não é pública, avalia o `authorize`, roda o handler e valida a saída. Ele não roda os
84
+ `loads` (os dados carregados entram pela opção `loaded`) nem grava audit. Veja
85
+ [Testes de action](/docs/testing).
58
86
 
59
87
  ## O contexto do handler
60
88
 
61
- > O `ctx` é a soma do que os adapters fornecem — sem adapter, o campo é `null`/`unknown`
62
- > e o handler decide como degradar.
89
+ O `ctx` recebido pelo handler reúne o que os adapters fornecem. Sem o adapter correspondente, o
90
+ campo chega como `null`, ou como `undefined` no caso de `db`, e o handler decide como seguir.
91
+
92
+ - `user`, `tenantId` e `can`, do adapter `auth`.
93
+ - `db`, do adapter `data`.
94
+ - `log`, do `logger`; sem ele, o runtime usa um logger de console.
95
+ - `emit`, que publica no `eventBus`.
96
+ - `storage`, `cache` e `ai`, dos adapters homônimos.
97
+ - `provenance`, que identifica a origem da execução: HTTP, schedule, reaction, job, agente de IA ou
98
+ integração.
99
+ - `trace`, quando há contexto de trace.
100
+ - `meta`, com dados livres da chamada.
63
101
 
64
- `user`/`tenantId`/`can` (auth) · `db` (data) · `log` (logger) · `emit` (eventBus) ·
65
- `storage` (storage) · `cache` (cache) · `ai` (ai) · `provenance` (quem disparou: http, schedule,
66
- reaction…) · `trace` (quando configurado) · `meta`.
102
+ ## Trace e observabilidade
67
103
 
68
- O core não depende de OpenTelemetry. O `ObservabilityAdapter` envolve actions e reactions
69
- com `runInSpan`; o `TraceContext` efetivo também segue para resultado, audit e eventos. Os
70
- envelopes de job recebem o trace da execução que enfileirou; `executeJob()` cria a execução
71
- filha no worker sem reenfileirar a action.
72
- Falha de instrumentação é lenient e nunca repete o handler.
104
+ O core não depende de OpenTelemetry. O `ObservabilityAdapter` envolve actions e reactions com
105
+ `runInSpan`, e o `TraceContext` efetivo segue para o resultado, o audit e os eventos. Um job recebe
106
+ o trace da execução que o enfileirou, e `executeJob()` cria a execução filha no worker sem
107
+ enfileirar a action de novo. Uma falha de instrumentação é tratada de forma tolerante e nunca
108
+ executa o handler outra vez.
73
109
 
74
- O core não extrai headers HTTP. Os drivers Fastify e Node podem fornecer
75
- `ContextBase.trace` com `traceContext: 'w3c'`; o opt-in é desativado por default. Outros
76
- transportes continuam responsáveis por sua fronteira. Em audit, o contexto novo usa
77
- `traceContext`; o campo `trace` legado continua disponível sem mudança.
110
+ O core não lê headers HTTP. Os drivers Fastify e Node preenchem `ContextBase.trace` com a opção
111
+ `traceContext: 'w3c'`, desativada por padrão; outros transportes continuam responsáveis pela própria
112
+ fronteira. O registro de audit traz dois campos: `traceContext`, com o contexto de trace (`traceId`, `spanId` e flags), e `trace`, com o
113
+ `requestId` e a action de origem (`parentActionId`). Os detalhes estão em [Observabilidade](/docs/observability).
78
114
 
79
- Para actions, o callback observável resolve com `ActionResult`: `resultKind: 'action-result'`
80
- indica que o driver deve inspecionar `result.ok`. Uma Promise resolvida com `ok:false` ainda
81
- representa span de erro. Reactions usam `resultKind: 'void'` e rejeitam em falha.
115
+ Para actions, a execução observada resolve com `ActionResult` e declara `resultKind:
116
+ 'action-result'`: o driver inspeciona `result.ok`, e uma Promise resolvida com `ok: false` ainda
117
+ representa um span de erro. Reactions usam `resultKind: 'void'` e rejeitam em caso de falha.
82
118
 
83
- ## Reactions e schedules
119
+ ## Execução sem requisição
84
120
 
85
- > Os dois jeitos de código rodar SEM request: evento de domínio dispara reaction;
86
- > tempo dispara schedule (que executa uma action, com provenance própria).
121
+ Duas formas de código rodam sem uma requisição: um evento de domínio dispara uma
122
+ [reaction](/docs/events), e o tempo dispara um [schedule](/docs/scheduler), que executa uma action
123
+ com provenance própria. Ambos são registrados junto com as actions, e o manifest projeta os três.
87
124
 
88
- Declarados como as actions e registrados no mesmo `runtime.register` — o manifest
89
- projeta os três. `opus introspect` lista reactions, schedules e o wiring entre eles, mas por
90
- enquanto só reconhece actions declaradas com `defineAction`: as do split `defineContract` +
91
- `bindAction` ficam de fora do modelo que ele monta.
125
+ `opus introspect` lista reactions, schedules e as ligações entre eles, mas só reconhece
126
+ declarações feitas com `defineAction`, `defineReaction` e `defineSchedule`. Actions do par
127
+ `defineContract` e `bindAction` e reactions do par `defineReactionContract` e `bindReaction` ficam de
128
+ fora do modelo que ele monta.
92
129
 
93
- ## Referência profunda
130
+ ## Referência completa
94
131
 
95
- O contrato completo (tipos, garantias, ordem de execução, erros) vive no
96
- `docs/protocol.md` **dentro do pacote** — como o changelog, viaja com a versão
97
- que você instalou.
132
+ O contrato completo, com tipos, garantias, ordem de execução e erros, está em `docs/protocol.md`,
133
+ dentro do pacote instalado. Como o changelog, ele acompanha a versão que o projeto usa.
@@ -4,62 +4,94 @@ title: Agendador
4
4
 
5
5
  # Agendador
6
6
 
7
- Use um schedule para executar uma action por cron ou intervalo. O `SchedulerAdapter` registra o
8
- agendamento e o runtime executa a action com provenance `schedule`, preservando sua rastreabilidade.
7
+ Use um schedule para executar uma action por cron ou em intervalos, sem uma requisição. O
8
+ `SchedulerAdapter` registra o agendamento, e o runtime executa a action com provenance `schedule`,
9
+ que aparece no audit e no contexto do handler.
9
10
 
10
11
  ## O contrato
11
12
 
12
13
  ```ts
13
- interface SchedulerAdapter {
14
+ interface SchedulerAdapter extends Adapter {
15
+ name: string // herdado de Adapter; identifica o driver
16
+ kind: 'scheduler'
14
17
  register(spec: ScheduleDef, fire: () => Promise<void> | void): Unsubscribe
15
18
  }
16
19
 
17
20
  interface ScheduleDef {
18
21
  name: string
19
- action: string // action do registry, executada quando dispara
20
- cron?: string // '0 9 * * *' (9h todo dia)
21
- every?: string // '1h', '30m', '15s' — atalho de intervalo
22
- timezone?: string // IANA, ex.: 'America/Sao_Paulo' (sem ele: hora local do processo)
23
- input?: unknown | (() => unknown | Promise<unknown>) // estático ou dinâmico
24
- enabled?: boolean
22
+ action: string // action registrada, executada a cada disparo
23
+ cron?: string // '0 9 * * *' (todo dia às 9h)
24
+ every?: string // '15s', '5m', '1h', '1d': atalho de intervalo
25
+ timezone?: string // IANA, como 'America/Sao_Paulo'; sem ela, vale a hora local do processo
26
+ input?: unknown | (() => unknown | Promise<unknown>) // fixo ou calculado a cada disparo
27
+ enabled?: boolean // padrão true
28
+ description?: string
29
+ tags?: string[]
25
30
  }
26
31
  ```
27
32
 
28
- O timing é exclusivo: `cron` **ou** `every`. A action referenciada tem que existir no
29
- registry.
33
+ Declare `cron` ou `every`, não os dois. `every` aceita segundos e minutos até 59, horas até 23 e
34
+ somente `1d` para dias; intervalos maiores usam `cron`. Sem `input`, a action recebe `{}`.
30
35
 
31
36
  ## Driver
32
37
 
33
38
  ```ts
34
39
  import { nodeCronScheduler } from '@softize/opus/scheduler/node-cron'
35
40
 
36
- const scheduler = nodeCronScheduler() // in-process
41
+ const scheduler = nodeCronScheduler() // no mesmo processo do runtime
37
42
  ```
38
43
 
39
- ## No runtime
44
+ ## Declarar no domínio
45
+
46
+ Declare o schedule com `defineSchedule` e registre-o no domínio, ao lado da action que ele executa:
40
47
 
41
48
  ```ts
42
- import { defineSchedule } from '@softize/opus/core'
49
+ import { createRuntime, defineDomain, defineSchedule } from '@softize/opus/core'
50
+ import { caixaFechar } from './caixa-fechar.ts' // a action 'caixa.fechar', definida com bindAction
43
51
 
44
- // Declarado no domínio, junto das actions:
45
52
  const fecharDia = defineSchedule({
46
- name: 'fechar-dia',
53
+ name: 'caixa.fechar-dia',
47
54
  action: 'caixa.fechar',
48
55
  cron: '0 23 * * *',
49
56
  timezone: 'America/Sao_Paulo',
50
57
  })
51
58
 
59
+ export const caixaDomain = defineDomain({
60
+ name: 'caixa',
61
+ actions: { caixaFechar },
62
+ schedules: [fecharDia],
63
+ })
64
+
52
65
  const runtime = createRuntime({
53
66
  // …server/data…
54
67
  scheduler,
55
68
  })
69
+ runtime.register([caixaDomain])
70
+ await runtime.start()
56
71
  ```
57
72
 
58
- O runtime registra os schedules do domínio no `start()` e chama a action quando o tempo bate
59
- — nada de disparar no handler.
73
+ No `start()`, o runtime confere se cada schedule aponta para uma action registrada no mesmo runtime
74
+ e depois o registra no adapter. Um nome sem action registrada interrompe o `start()` com
75
+ `runtime.schedule_action_not_found`, antes de inicializar os adapters; a mensagem lista cada
76
+ schedule e a action que ele procurou. Com schedules registrados e sem `scheduler`, o `start()`
77
+ falha com `runtime.scheduler_required`. Um schedule com `enabled: false` não é registrado nem
78
+ conferido, então pode apontar para uma action que ainda não existe no ambiente.
79
+
80
+ ## Preparar a action agendada
81
+
82
+ O schedule executa a action sem usuário: `ctx.user` é `null` e `ctx.can` nega tudo. Por isso, a
83
+ action alvo precisa declarar `public: true`; sem isso, cada disparo termina em
84
+ `auth.unauthenticated`. Para que ela não fique acessível por HTTP, declare também `internal: true`:
85
+ os servidores do Opus não expõem actions internas, e o schedule continua executando-a pelo runtime.
86
+
87
+ Quando um disparo não conclui a action, o runtime registra um erro no log com o schedule, a action,
88
+ o código e a mensagem do erro; o agendamento continua ativo e o próximo disparo executa a action de
89
+ novo. Uma falha dentro do pipeline da action, como `auth.unauthenticated` ou um erro do handler,
90
+ também fica registrada no audit. O callback `onError` do driver recebe somente erros lançados antes
91
+ da execução, como uma falha na função `input`.
60
92
 
61
93
  ## Limites atuais
62
94
 
63
- Driver hoje: `node-cron` (in-process — some se o processo cair; em um cluster, cada nó
64
- dispararia). Backends duráveis/distribuídos (BullMQ repeatable, Temporal…) entram por
65
- reincidência.
95
+ O único driver é o `node-cron`, que roda no mesmo processo: o agendamento some se o processo cair e,
96
+ em um cluster, cada nó dispara o mesmo schedule. O modo design do Vite não registra schedules.
97
+ Backends duráveis ou distribuídos, como jobs repetidos do BullMQ ou Temporal, ainda não têm driver.
@@ -1,26 +1,28 @@
1
1
  ## Lista de altura fixa
2
2
 
3
- Dê a altura ao ScrollArea (h-48) e ponha o conteúdo dentro — a barra vertical já vem por padrão, sem precisar de ScrollBar.
3
+ Use `ScrollArea` quando um bloco precisa manter a própria altura e rolar o conteúdo que passar dela.
4
+ Dê a altura ao `ScrollArea`, como `h-48`, e coloque o conteúdo dentro. A barra vertical já vem
5
+ incluída; não é preciso acrescentar `ScrollBar`.
4
6
 
5
7
  ```tsx preview col
6
- const sessions = [
7
- { id: 'empresa-x-1842', title: 'Migrar billing para o novo schema', agent: 'developer' },
8
- { id: 'empresa-x-1839', title: 'Revisar handoff do checkout', agent: 'reviewer' },
9
- { id: 'empresa-x-1835', title: 'Redesenhar o painel de rotas', agent: 'designer' },
10
- { id: 'empresa-x-1830', title: 'Corrigir flaky no teste de webhook', agent: 'developer' },
11
- { id: 'empresa-x-1827', title: 'Vincular skill clean-code ao agente', agent: 'reviewer' },
12
- { id: 'empresa-x-1821', title: 'Subir preview do worktree', agent: 'developer' },
13
- { id: 'empresa-x-1818', title: 'Ajustar copy do empty state', agent: 'designer' },
14
- { id: 'empresa-x-1814', title: 'Rodar opus check no monorepo', agent: 'reviewer' },
8
+ const orders = [
9
+ { id: 'OS-1842', title: 'Revisão dos 20.000 km', vehicle: 'ABC-1D23' },
10
+ { id: 'OS-1839', title: 'Troca das pastilhas de freio', vehicle: 'BRA-2E19' },
11
+ { id: 'OS-1835', title: 'Alinhamento e balanceamento', vehicle: 'RIO-4F56' },
12
+ { id: 'OS-1830', title: 'Diagnóstico de ruído na suspensão', vehicle: 'SPO-7G88' },
13
+ { id: 'OS-1827', title: 'Troca de óleo e filtro', vehicle: 'ABC-1D23' },
14
+ { id: 'OS-1821', title: 'Substituição da bateria', vehicle: 'CWB-3H45' },
15
+ { id: 'OS-1818', title: 'Higienização do ar-condicionado', vehicle: 'BRA-2E19' },
16
+ { id: 'OS-1814', title: 'Revisão dos 40.000 km', vehicle: 'POA-9J12' },
15
17
  ]
16
18
 
17
19
  render(
18
20
  <ScrollArea className="h-48 w-full rounded-md border border-border">
19
21
  <div className="p-3">
20
- {sessions.map((s) => (
21
- <div key={s.id} className="rounded-md px-2 py-1.5 text-sm hover:bg-muted">
22
- <p className="font-medium">{s.title}</p>
23
- <p className="text-xs text-muted-foreground">{s.id} · {s.agent}</p>
22
+ {orders.map((order) => (
23
+ <div key={order.id} className="rounded-md px-2 py-1.5 text-sm hover:bg-muted">
24
+ <p className="font-medium">{order.title}</p>
25
+ <p className="text-xs text-muted-foreground">{order.id} · {order.vehicle}</p>
24
26
  </div>
25
27
  ))}
26
28
  </div>
@@ -30,17 +32,18 @@ render(
30
32
 
31
33
  ## Trilho horizontal
32
34
 
33
- Para rolar na horizontal, acrescente <ScrollBar orientation="horizontal" /> como filho e deixe o conteúdo em uma linha que não quebra (flex + w-max).
35
+ Para rolar na horizontal, acrescente `<ScrollBar orientation="horizontal" />` como filho e mantenha
36
+ o conteúdo em uma linha que não quebra, com `flex` e `w-max`.
34
37
 
35
38
  ```tsx preview col
36
- const skills = ['implement-opus-change', 'create-opus-action', 'test-opus-action', 'build-opus-ui', 'upgrade-opus']
39
+ const services = ['Troca de óleo', 'Filtro de ar', 'Alinhamento', 'Balanceamento', 'Pastilhas de freio', 'Velas de ignição']
37
40
 
38
41
  render(
39
42
  <ScrollArea className="w-full rounded-md border border-border whitespace-nowrap">
40
43
  <div className="flex w-max gap-2 p-3">
41
- {skills.map((skill) => (
42
- <span key={skill} className="rounded-md border border-border bg-muted px-3 py-1.5 text-sm">
43
- {skill}
44
+ {services.map((service) => (
45
+ <span key={service} className="rounded-md border border-border bg-muted px-3 py-1.5 text-sm">
46
+ {service}
44
47
  </span>
45
48
  ))}
46
49
  </div>
@@ -49,28 +52,30 @@ render(
49
52
  )
50
53
  ```
51
54
 
52
- ## Painel com divisórias
55
+ ## Histórico com divisórias
53
56
 
54
- O conteúdo é livre: aqui o log do agente, com Separator entre as linhas. A barra só aparece quando o conteúdo passa da altura.
57
+ O conteúdo interno é livre. Neste exemplo, o histórico de uma ordem de serviço usa `Separator`
58
+ entre as linhas. A barra só aparece quando o conteúdo passa da altura e o ponteiro está sobre a
59
+ área; para mantê-la sempre visível, passe `type="always"`.
55
60
 
56
61
  ```tsx preview col
57
- const log = [
58
- '12:04 developer clonou o worktree de empresa-x-api',
59
- '12:05 developer rodou pnpm install',
60
- '12:07 developer abriu a sessão empresa-x-1842',
61
- '12:11 developer aplicou o diff no schema de billing',
62
- '12:14 reviewer foi acionado no handoff',
63
- '12:16 reviewer apontou 2 ajustes de microcopy',
64
- '12:19 developer corrigiu a microcopy',
65
- '12:22 opus check passou em todos os pacotes',
66
- '12:24 reviewer aprovou o handoff',
62
+ const history = [
63
+ '08:02 Veículo recebido na oficina',
64
+ '08:15 Checklist de entrada concluído',
65
+ '08:40 Diagnóstico iniciado',
66
+ '09:10 Orçamento enviado ao cliente',
67
+ '09:32 Orçamento aprovado',
68
+ '10:05 Peças separadas no estoque',
69
+ '11:20 Serviço concluído',
70
+ '11:45 Teste de rodagem aprovado',
71
+ '12:00 Veículo liberado para retirada',
67
72
  ]
68
73
 
69
74
  render(
70
75
  <ScrollArea className="h-40 w-full rounded-md border border-border">
71
76
  <div className="p-3 text-sm">
72
- {log.map((line, i) => (
73
- <div key={i}>
77
+ {history.map((line, i) => (
78
+ <div key={line}>
74
79
  {i > 0 && <Separator className="my-1.5" />}
75
80
  <p className="text-muted-foreground">{line}</p>
76
81
  </div>
@@ -84,11 +89,13 @@ render(
84
89
 
85
90
  | Propriedade | Tipo | Padrão | Descrição |
86
91
  |---|---|---|---|
87
- | `className` | `string` | | Classes que definem as dimensões da janela rolável. Sem uma dimensão limitada, não há conteúdo a recortar. |
88
- | `children` | `React.ReactNode` | | Conteúdo da janela, incluindo uma `ScrollBar` horizontal quando necessária. |
92
+ | `className` | `string` | | Define as dimensões da janela rolável. Sem uma dimensão limitada, não há conteúdo a recortar. |
93
+ | `type` | `'hover' \| 'scroll' \| 'auto' \| 'always'` | `'hover'` | Quando a barra aparece: sob o ponteiro, durante a rolagem, sempre que houver excedente ou sempre. |
94
+ | `children` | `ReactNode` | | Conteúdo da janela, incluindo uma `ScrollBar` horizontal quando necessária. |
89
95
 
90
96
  ## Propriedades de ScrollBar
91
97
 
92
98
  | Propriedade | Tipo | Padrão | Descrição |
93
99
  |---|---|---|---|
94
100
  | `orientation` | `'vertical' \| 'horizontal'` | `'vertical'` | Direção da barra. A barra vertical já faz parte de `ScrollArea`. |
101
+ | `className` | `string` | | Ajusta o trilho da barra. |