@softize/opus 18.1.0 → 18.1.1

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 (96) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/PROMOTED.md +4 -5
  3. package/README.md +5 -4
  4. package/bin/cli.mjs +4 -0
  5. package/docs/adr/0004-page-content-state-is-composed.md +3 -0
  6. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +6 -2
  7. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +6 -1
  8. package/docs/adr/0014-structural-headers-do-not-carry-description.md +2 -2
  9. package/docs/adr/0015-action-size-follows-interaction-density.md +4 -3
  10. package/docs/adr/{0012-modal-header-only-names-the-surface.md → 0018-modal-header-only-names-the-surface.md} +4 -1
  11. package/docs/adr/{0016-productive-surfaces-use-compact-density.md → 0019-productive-surfaces-use-compact-density.md} +4 -1
  12. package/docs/code-style.md +2 -2
  13. package/docs/consumer-upgrade-propagation.md +1 -1
  14. package/docs/data-products.md +5 -3
  15. package/docs/protocol.md +6 -6
  16. package/docs/releasing.md +28 -4
  17. package/package.json +1 -1
  18. package/registry/skills/build-opus-ui/references/ui-patterns.md +11 -1
  19. package/src/auth/drivers/jwt.ts +2 -1
  20. package/src/core/runtime.ts +25 -4
  21. package/src/core/types.ts +4 -4
  22. package/src/mcp/index.ts +9 -0
  23. package/src/ui/components/patterns/content-header.tsx +1 -1
  24. package/src/ui/components/patterns/form.tsx +1 -1
  25. package/src/ui/components/patterns/sidebar.tsx +1 -1
  26. package/src/ui/components/primitives/card.tsx +1 -1
  27. package/src/ui/components/primitives/detail.tsx +7 -7
  28. package/src/ui/components/primitives/radio-group.tsx +1 -1
  29. package/src/ui/components/primitives/select.tsx +1 -1
  30. package/src/ui/docs/content/action-form-dialog.md +11 -4
  31. package/src/ui/docs/content/action-form.md +7 -16
  32. package/src/ui/docs/content/action-list-dialog.md +4 -6
  33. package/src/ui/docs/content/action-list.md +46 -4
  34. package/src/ui/docs/content/action-trigger.md +9 -5
  35. package/src/ui/docs/content/action-view.md +12 -8
  36. package/src/ui/docs/content/actions.md +36 -13
  37. package/src/ui/docs/content/ai.md +26 -7
  38. package/src/ui/docs/content/alert.md +4 -3
  39. package/src/ui/docs/content/aspect-ratio.md +2 -2
  40. package/src/ui/docs/content/auth.md +25 -10
  41. package/src/ui/docs/content/avatar.md +1 -1
  42. package/src/ui/docs/content/badge.md +2 -2
  43. package/src/ui/docs/content/breadcrumb.md +3 -2
  44. package/src/ui/docs/content/button.md +31 -7
  45. package/src/ui/docs/content/calendar.md +1 -1
  46. package/src/ui/docs/content/card.md +1 -1
  47. package/src/ui/docs/content/carousel.md +14 -3
  48. package/src/ui/docs/content/chat.md +1 -1
  49. package/src/ui/docs/content/cli.md +13 -7
  50. package/src/ui/docs/content/command.md +34 -2
  51. package/src/ui/docs/content/composer.md +1 -1
  52. package/src/ui/docs/content/content.md +5 -4
  53. package/src/ui/docs/content/customization.md +1 -1
  54. package/src/ui/docs/content/cycle.md +7 -5
  55. package/src/ui/docs/content/data-state.md +6 -5
  56. package/src/ui/docs/content/data.md +3 -3
  57. package/src/ui/docs/content/detail.md +3 -2
  58. package/src/ui/docs/content/dialog.md +2 -2
  59. package/src/ui/docs/content/dictionary-value.md +1 -1
  60. package/src/ui/docs/content/dock.md +23 -2
  61. package/src/ui/docs/content/dot.md +0 -2
  62. package/src/ui/docs/content/drawer.md +1 -1
  63. package/src/ui/docs/content/empty.md +1 -4
  64. package/src/ui/docs/content/events.md +1 -1
  65. package/src/ui/docs/content/field.md +20 -11
  66. package/src/ui/docs/content/getting-started.md +4 -2
  67. package/src/ui/docs/content/icon-picker.md +2 -2
  68. package/src/ui/docs/content/input-otp.md +2 -0
  69. package/src/ui/docs/content/input.md +2 -3
  70. package/src/ui/docs/content/item.md +5 -4
  71. package/src/ui/docs/content/kbd.md +2 -1
  72. package/src/ui/docs/content/mcp.md +10 -4
  73. package/src/ui/docs/content/menu.md +27 -0
  74. package/src/ui/docs/content/page.md +19 -5
  75. package/src/ui/docs/content/pagination.md +9 -2
  76. package/src/ui/docs/content/popover.md +2 -2
  77. package/src/ui/docs/content/presentation.md +46 -45
  78. package/src/ui/docs/content/progress.md +2 -6
  79. package/src/ui/docs/content/runtime.md +8 -5
  80. package/src/ui/docs/content/scheduler.md +1 -1
  81. package/src/ui/docs/content/select.md +13 -8
  82. package/src/ui/docs/content/sidebar.md +3 -2
  83. package/src/ui/docs/content/skeleton.md +1 -1
  84. package/src/ui/docs/content/slider.md +4 -4
  85. package/src/ui/docs/content/spinner.md +3 -3
  86. package/src/ui/docs/content/tabs.md +6 -6
  87. package/src/ui/docs/content/testing.md +4 -2
  88. package/src/ui/docs/content/toast.md +3 -5
  89. package/src/ui/docs/content/toggle.md +37 -0
  90. package/src/ui/docs/content/tooltip.md +4 -3
  91. package/src/ui/docs/content/truncate.md +3 -2
  92. package/src/ui/docs/content/ui.md +3 -1
  93. package/src/ui/docs/content/upgrading.md +43 -13
  94. package/src/ui/docs/doc-client.tsx +1 -1
  95. package/src/ui/docs/registry.tsx +30 -5
  96. package/src/ui/meta.ts +4 -4
package/CHANGELOG.md CHANGED
@@ -7,6 +7,62 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
7
7
  `opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
8
8
  mudança cobra do seu código.
9
9
 
10
+ ## 18.1.1 — 2026-09-16
11
+
12
+ **Correção de segurança.** Quem executa tools de IA só roda o que `aiTools()` anunciou. Até aqui, o
13
+ servidor MCP executava em `CallTool` qualquer action registrada, e o agente co-locado executava
14
+ qualquer nome de tool que o modelo emitisse. Um cliente MCP, um modelo ou um prompt injetado podia
15
+ rodar uma action sem `ai.enabled` — inclusive destrutiva — dentro da autorização do contexto
16
+ resolvido. Os dois caminhos agora recusam o nome fora do conjunto exposto, com a mesma resposta
17
+ para action inexistente e para action registrada mas não exposta, sem revelar quais existem.
18
+ Nenhuma mudança é necessária em quem já marcava com `ai.enabled` as actions que expõe; quem
19
+ dependia de chamar pelo MCP uma action não marcada precisa marcá-la.
20
+
21
+ Uma action que não é `public` passa a exigir usuário ANTES de rodar os loaders. Antes, os
22
+ `loads` consultavam dados para um chamador anônimo, que recebia o erro do loader — um
23
+ `not_found`, por exemplo — e podia usar essa diferença para descobrir se um registro existe. O
24
+ `authorize` continua depois dos loaders, porque depende do que eles carregam.
25
+
26
+ `RadioGroupItem` passa a ter a classe `peer`, como `Checkbox` e `Switch`: o `Label` associado
27
+ esmaece quando o item está desabilitado, como a documentação já descrevia. Em `DetailGroup` com
28
+ moldura e orientação horizontal, a largura padrão da coluna de rótulos passa a ser uma classe, e
29
+ uma classe do consumidor (`[--detail-label-width:9rem]`) volta a substituí-la; antes, só `style`
30
+ conseguia.
31
+
32
+ A documentação publicada foi revisada por inteiro, e as 92 páginas foram abertas num navegador
33
+ headless sem erro de console nem de preview. O exemplo principal de Presentation voltou a
34
+ renderizar: ele usava funções que não estavam no escopo dos previews e lançava `ReferenceError`.
35
+ Um exemplo do `IconPicker` declarava `Icon`, nome que o escopo já fornece, e mostrava um erro de
36
+ sintaxe no lugar do preview. Um gate novo compila cada preview com o escopo da sua página, do jeito
37
+ que o site o executa, e reprova nome ausente ou redeclarado; o anterior só conferia tags JSX. Sessenta e quatro páginas foram
38
+ corrigidas contra o código: props, defaults e tipos que não batiam, exemplos que não compilavam ou ensinavam padrões
39
+ substituídos pelas versões 16 a 18, e afirmações que o código não sustenta. Entre elas, `auth.md`
40
+ deixa de sugerir que o runtime aplica um campo `auth` inexistente, e `upgrading.md` passa a incluir
41
+ `pnpm run setup` e os gates de copy, sem os quais o fluxo de upgrade falhava no primeiro gate.
42
+
43
+ `opus check --help` lista as regras de Produto de Dados. As ADRs que dividiam número foram
44
+ renumeradas: a do cabeçalho modal passa a 0018 e a da densidade compacta, a 0019; um gate impede
45
+ nova colisão. O guia de release passa a dizer que a documentação é publicada à parte, com
46
+ `pnpm deploy:docs`. A skill `build-opus-ui` passa a ensinar `placement` nos filtros e o
47
+ `ButtonGroup` nos footers modais.
48
+
49
+ ### Correções do histórico
50
+
51
+ Estas entradas publicadas descrevem algo diferente do que o código daquela versão fez. Elas não
52
+ foram reescritas; a correção fica registrada aqui.
53
+
54
+ - **17.2.1 nunca foi publicada.** O conteúdo daquela entrada — `renderMessageActions` nas mensagens
55
+ enviadas, `activity=""` e o `title` das tools MCP — saiu na 18.0.0.
56
+ - **17.0.0 e 17.2.0 mudaram a composição explícita dentro de `PageShell` sem aviso de quebra.** A
57
+ 17.0.0 passou a exigir `PageHeader` onde a 16.x exigia `PageIntro`, e a 17.2.0 voltou a exigir
58
+ `PageIntro`. Desde a 18.0.0, ambos são opcionais e a página exige um único heading principal.
59
+ - **18.1.0:** `Item size="sm"` reduz também o espaço entre regiões, para `0.625rem`, e não somente o
60
+ padding vertical. A mesma versão trocou o fechamento de Dialog e Drawer, que na 18.0.1 era um
61
+ `Button` `icon`, pelo `CloseButton` (`neutral`, `subtle`, forma de pílula e `icon-sm`), com
62
+ a nova prop `closeSize`; nenhum dos dois foi anunciado. Ela também removeu breakpoints responsivos
63
+ e mudou o footer padrão do `ActionFormDialog`; quem tinha testes visuais ou telas abaixo de
64
+ `64rem` precisa revisá-los.
65
+
10
66
  ## 18.1.0 — 2026-09-15
11
67
 
12
68
  A UI passa a assumir layout desktop fixo com largura mínima suportada de `64rem`. Breakpoints de
@@ -107,6 +163,8 @@ a API imperativa, usam `outline`; a decisão principal continua `solid`.
107
163
 
108
164
  ## 17.2.1 — 2026-09-12
109
165
 
166
+ > Esta versão nunca foi publicada no npm; o conteúdo abaixo saiu na 18.0.0.
167
+
110
168
  O `Chat` passa a renderizar `renderMessageActions` também nas mensagens enviadas. Data, cópia e
111
169
  outras ações contextuais podem usar a mesma extensão discreta nos dois lados da conversa.
112
170
  `activity=""` oferece o indicador compacto, com somente os pontos centralizados e “Pensando…”
package/PROMOTED.md CHANGED
@@ -1,11 +1,10 @@
1
1
  # Enhancements promovidas ao Opus
2
2
 
3
3
  O registro CURADO da régua "o Opus cresce por reincidência". O fluxo vivo NÃO passa por
4
- edição manual deste arquivo: o agente no projeto aponta em `.opus/issues.jsonl` do repo
5
- dele (instrução na skill `implement-opus-change`; `type` **enhancement** ou **bug**)
6
- o Maestro colhe no boot da sessão e abre a **Issue no repo do Opus**
7
- (`github.com/softize-dev/opus`, dedup por hash + ledger), onde a triagem e a decisão
8
- acontecem (aberta = pendente, fechada = decidida). Enhancement aceita com **n ≥ 2**
4
+ edição manual deste arquivo: cada apontamento vira uma **Issue no repo do Opus**
5
+ (`github.com/softize-dev/opus`), `type` **enhancement** ou **bug**, onde a triagem e a
6
+ decisão acontecem (aberta = pendente, fechada = decidida). O pacote não coleta nada do
7
+ projeto consumidor por conta própria. Enhancement aceita com **n 2**
9
8
  ocorrências reais — comportamento se paga na reincidência; estilo e conveniência não.
10
9
  Bug (defeito NO Opus) aciona com n=1: vira fix + CHANGELOG, **não** entra neste ledger.
11
10
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Opus
2
2
 
3
- > **Status:** v0 — pacote único `@softize/opus` com subpath exports (core + adapters). Uma versão, sem semver por módulo.
3
+ > Pacote único `@softize/opus` com subpath exports (core + adapters). Uma versão, sem semver por módulo.
4
4
 
5
5
  Protocolo de actions de ponta a ponta para TypeScript. Declare uma vez — input, autorização, execução, auditoria, feedback — e deixe os adapters materializarem a action na interface, no cliente, no servidor e no log.
6
6
 
@@ -43,6 +43,7 @@ Um pacote (`@softize/opus`), uma versão. Cada categoria abaixo é um **subpath
43
43
  | `@softize/opus/testing` | — | Harness de teste de actions pela fronteira do contrato (experimental) |
44
44
  | `@softize/opus/dsl` | — | Expressões declarativas avaliadas em `load` e em consultas |
45
45
  | `@softize/opus/seed` | — | Declaração e binding de datasets verificáveis operados pela CLI |
46
+ | `@softize/opus/presentation` | — | Presentations portáteis: `definePresentation`, invocação e rotas |
46
47
  | `@softize/opus/vite` | — | Plugins Vite: runtime em modo design e auth de sessão fixa |
47
48
 
48
49
  Os drivers são resolvidos por subpath ESM (no estilo do Drizzle): `@softize/opus/server/fastify`, `@softize/opus/data/kysely`.
@@ -90,7 +91,7 @@ await app.listen({ port: 3000 })
90
91
  src/
91
92
  core/ schema/ server/ client/ ui/ data/ auth/ audit/
92
93
  log/ queue/ events/ scheduler/ storage/ cache/ ai/ mcp/
93
- observability/ testing/ dsl/ seed/ vite/
94
+ observability/ testing/ dsl/ seed/ vite/ presentation/
94
95
  registry/ # scaffolds + skills Opus — geração e conhecimento do SDK, não runtime
95
96
  bin/ # CLI (opus create/setup/gen/copy/check/db/seed/pre-push/introspect/mcp) + libs
96
97
  docs/
@@ -117,8 +118,8 @@ O contrato não oferece reset/truncate, e `apply` precisa convergir quando repet
117
118
 
118
119
  ## Status
119
120
 
120
- - **v0**: 21 superfícies, suíte vitest com cobertura medida por `pnpm test:cov`. O threshold de 100% em `vitest.config.ts` é aspiracional e não faz parte do gate de release (ver [docs/releasing.md](docs/releasing.md)).
121
- - **v1+**: drivers adicionais (Hono, Drizzle, ArkType, Vue, Inngest, Redis events), camada de entidades (`defineEntity`), Conversya migration plan.
121
+ - **Hoje**: 22 superfícies, suíte vitest com cobertura medida por `pnpm test:cov`. O threshold de 100% em `vitest.config.ts` é aspiracional e não faz parte do gate de release (ver [docs/releasing.md](docs/releasing.md)).
122
+ - **Próximos**: drivers adicionais (Hono, Drizzle, ArkType, Vue, Inngest, Redis events) e o plano de migração da Conversya.
122
123
 
123
124
  ## Desenvolvimento
124
125
 
package/bin/cli.mjs CHANGED
@@ -426,6 +426,10 @@ Regras da UI (@softize/opus/ui):
426
426
  unpaired-ui-surface — superfície sem o foreground do par (bg-card sem
427
427
  text-card-foreground, bg-popover sem text-popover-foreground)
428
428
 
429
+ Regras dos Produtos de Dados (defineDataProduct):
430
+ data-product-* — declaração exportada, id e versão válidos, sem duplicata,
431
+ interfaces e entidades que existem no domínio
432
+
429
433
  Exit ≠ 0 se houver violação. Sem nenhuma action: projeto marcado (opus.json) passa
430
434
  vacuamente; sem marcador, falha — gate vazio não é aprovação. Default dir: cwd.
431
435
  `)
@@ -1,5 +1,8 @@
1
1
  # ADR 0004 — O estado integral do conteúdo é composto dentro de Page
2
2
 
3
+ - **Status:** aceita, com adendo.
4
+ - **Data:** 2026-09-04.
5
+
3
6
  > **Atualização (2026-09-09).** A decisão original preservava o cabeçalho da página em todos os
4
7
  > estados. Ela foi invertida: `PageState` passa a ocultá-lo em `loading`, `error` e `empty`, e a
5
8
  > restaurá-lo em `ready`. A lista de responsabilidades abaixo já descreve o comportamento novo; o
@@ -5,10 +5,14 @@
5
5
 
6
6
  > **Atualização (2026-09-09).** Parcialmente substituída pela ADR 0009 quanto a `PageMeta` e ao
7
7
  > `count` de `Page`, e complementada pela ADR 0010, que acrescenta as apresentações `default` e
8
- > `bar` ao mesmo header. O corpo abaixo já traz a anatomia vigente.
8
+ > `bar` ao mesmo header.
9
9
  >
10
- > **Atualização (2026-09-11).** As ADRs 0012 e 0014 removem Description dos headers de Page,
10
+ > **Atualização (2026-09-11).** As ADRs 0018 e 0014 removem Description dos headers de Page,
11
11
  > Dialog e Drawer. Contexto relevante começa no body.
12
+ >
13
+ > **Atualização (2026-09-16).** O corpo abaixo descreve a anatomia da época em que foi escrito, não a
14
+ > vigente: a apresentação `bar` da ADR 0010 saiu com a ADR 0011, e nenhum header estrutural carrega
15
+ > mais descrição (ADRs 0018 e 0014). A anatomia atual está na documentação de cada família.
12
16
 
13
17
  ## Contexto
14
18
 
@@ -2,11 +2,16 @@
2
2
 
3
3
  - **Status:** aceita.
4
4
  - **Data:** 2026-09-10.
5
- - **Revisa:** ADR 0010.
5
+ - **Substitui:** ADR 0010.
6
6
 
7
7
  > **Atualização (2026-09-13).** A ADR 0016 separa o chrome persistente da introdução e do
8
8
  > cabeçalho de uma coleção. `PageHeader` passa a declarar navegação e ações globais para a barra;
9
9
  > `PageIntro` permanece opcional no conteúdo e `Content` nomeia coleções.
10
+ >
11
+ > **Atualização (2026-09-16).** Desde a 18.0.0, a composição explícita dentro do shell aceita
12
+ > `PageHeader`, `PageIntro` e `PageFooter` como opcionais e exige exatamente um heading principal —
13
+ > `PageTitle`, `Content level={1}` ou um `PageState` ativo. As frases abaixo que pedem `PageIntro`
14
+ > "sem `PageHeader`" descrevem a regra da 17.2 e não valem mais.
10
15
 
11
16
  ## Contexto
12
17
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  - **Status:** aceita.
4
4
  - **Data:** 2026-09-11.
5
- - **Complementa:** ADRs 0005, 0011 e 0012.
5
+ - **Complementa:** ADRs 0005, 0011 e 0018.
6
6
 
7
7
  ## Contexto
8
8
 
@@ -13,7 +13,7 @@ superfície e misturava o nome do recurso com instruções e consequências da t
13
13
  ## Decisão
14
14
 
15
15
  Headers estruturais contêm navegação opcional, título e actions. Page não oferece a propriedade
16
- `description` nem `PageDescription`; Dialog e Drawer seguem a mesma regra conforme a ADR 0012.
16
+ `description` nem `PageDescription`; Dialog e Drawer seguem a mesma regra conforme a ADR 0018.
17
17
 
18
18
  Contexto que altera compreensão ou decisão aparece no início do body. Ele pode ser texto, `Alert`,
19
19
  `Content` ou outra composição adequada. `PageState`, `Alert`, `Empty`, campos, métricas e seções
@@ -21,10 +21,11 @@ risco.
21
21
 
22
22
  - ações textuais que decidem a superfície usam `default`: header de Page e footer de Page, Dialog
23
23
  ou Drawer;
24
- - ações operacionais em toolbar, header de seção ou coleção densa usam `sm`;
24
+ - ações operacionais em toolbar, header de seção ou coleção densa usam `sm`; a criação no header
25
+ de uma coleção é a exceção e usa `default` (ADR 0016);
25
26
  - ações internas de linha, célula ou campo usam `xs` ou `icon-xs`;
26
- - ações somente com ícone que pertencem ao chrome da superfície, como voltar e fechar, usam
27
- `icon`;
27
+ - ações somente com ícone que pertencem ao chrome da superfície, como voltar, usam `icon`; desde a
28
+ 18.1.0 o fechamento de Dialog e Drawer é o `CloseButton`, em `icon-sm` por padrão (`closeSize`);
28
29
  - `lg` fica reservado a chamadas que deliberadamente precisam de uma área de toque maior, não a
29
30
  uma ação primária comum.
30
31
 
@@ -1,4 +1,7 @@
1
- # ADR 0012 — O cabeçalho modal apenas nomeia a superfície
1
+ # ADR 0018 — O cabeçalho modal apenas nomeia a superfície
2
+
3
+ > Publicada originalmente com o número 0012, que colidia com a ADR de Produtos de Dados do mesmo
4
+ > dia. Renumerada em 2026-09-16; o conteúdo não mudou.
2
5
 
3
6
  - **Status:** aceita.
4
7
  - **Data:** 2026-09-11.
@@ -1,4 +1,7 @@
1
- # ADR 0016 — Superfícies produtivas usam densidade compacta
1
+ # ADR 0019 — Superfícies produtivas usam densidade compacta
2
+
3
+ > Publicada originalmente com o número 0016, que colidia com a ADR do cabeçalho da coleção.
4
+ > Renumerada em 2026-09-16; o conteúdo não mudou.
2
5
 
3
6
  - **Status:** aceita.
4
7
  - **Data:** 2026-09-14.
@@ -98,8 +98,8 @@ Base instalada é dona do catálogo, dos kinds aceitos e do fundamento de cada r
98
98
  | `Select.options[].hint/triggerLabel/group` | `label`, `label`, `heading` |
99
99
  | `ActionTrigger.confirm.*` | papel correspondente do diálogo |
100
100
  | `t.dict` — `label`, `description`, `doc` das entradas e `doc` do dicionário | `label`, `description` |
101
- | `DataState`/`PageState`/`ActionList`/`ActionListDialog` — `emptyMessage`, `errorMessage`, `retryLabel`; `PageState`/`ActionListDialog` — `title`, `description`; `ActionView.emptyMessage` | `empty-state`, `error`, `button`, `title`, `description` |
102
- | `dialog.alert/confirm/prompt/choose()` — `title`, `description`, `body`, `action`, `cancel`, `placeholder`, `actions[].label` | papel correspondente do diálogo |
101
+ | `DataState`/`PageState`/`ActionList`/`ActionListDialog` — `emptyMessage`, `errorMessage`, `retryLabel`; `PageState` — `title`, `description`; `ActionListDialog` — `title`, `intro`; `ActionView.emptyMessage` | `empty-state`, `error`, `button`, `title`, `description` |
102
+ | `dialog.alert/confirm/prompt/choose()` — `title`, `body`, `action`, `cancel`, `placeholder`, `actions[].label` | papel correspondente do diálogo |
103
103
  | `TooltipContent` (children), `LabelHelp.help` | `label`, `helper-text` |
104
104
 
105
105
  ## Cobertura e significado do gate verde
@@ -52,6 +52,6 @@ Há três sinais diferentes e eles não devem ser confundidos:
52
52
 
53
53
  ## Verificação
54
54
 
55
- - o status do Maestro deve distinguir versão instalada, aplicada e adotada;
55
+ - o status do Maestro deve distinguir versão disponível, adotada e aplicada;
56
56
  - fixtures com tokens removidos devem reprovar no detector de migração, enquanto texto de
57
57
  documentação e dependências geradas são ignorados.
@@ -58,9 +58,11 @@ consumidores usam o nome qualificado; o alias só poderá ser removido numa vers
58
58
  ## Projeções
59
59
 
60
60
  `opus gen` publica os produtos no `.opus/manifest.json`. Actions expostas como tools carregam os
61
- IDs em `metadata.dataProducts`; no MCP, a chave é
62
- `_meta['com.softize.opus/data-products']`. Consumidores devem registrar apenas produtos associados
63
- a chamadas concluídas com sucesso.
61
+ IDs em `metadata.dataProducts` e os rótulos legíveis em `metadata.dataProductLabels`, um mapa de ID
62
+ para `label`. No MCP, as chaves são `_meta['com.softize.opus/data-products']` e
63
+ `_meta['com.softize.opus/data-product-labels']`. Hosts mostram o rótulo às pessoas e usam o ID na
64
+ execução e na auditoria. Consumidores devem registrar apenas produtos associados a chamadas
65
+ concluídas com sucesso.
64
66
 
65
67
  O Opus Lens usa o manifest para mostrar a linhagem declarada entre Fontes, entities, produtos e
66
68
  Actions. Nenhuma dessas projeções é fonte autoritativa: corrija a declaração e gere novamente.
package/docs/protocol.md CHANGED
@@ -604,7 +604,7 @@ Não vai ter `compose(authA, authB)` no core. Se sentir falta, é regra de negó
604
604
 
605
605
  O Opus **não autentica** — não valida token, não emite sessão. O adapter (server) recebe a request, valida o token via auth provider externo (Better Auth, Clerk, próprio), monta `ctx.user`, e entrega pro Opus. A partir daí, `authorize` decide.
606
606
 
607
- Se `ctx.user` é `null` e `public !== true`, runtime rejeita com `{ code: 'auth.unauthenticated', category: 'authentication' }` **antes** de chamar `authorize`.
607
+ Se `ctx.user` é `null` e `public !== true`, runtime rejeita com `{ code: 'auth.unauthenticated', category: 'authentication' }` **antes** dos loaders e de `authorize`. Assim um chamador anônimo não dispara consultas nem descobre, pelo erro de um loader, se um registro existe.
608
608
 
609
609
  ---
610
610
 
@@ -1186,9 +1186,9 @@ ctx = { user, tenantId, can, db, emit, meta, ... }
1186
1186
 
1187
1187
  Runtime.execute(action, input, ctx)
1188
1188
  ↓ (1) validate input contra schema
1189
- ↓ (2) load (se action declara `loads`)
1190
- ↓ (3) check public OR authenticate (ctx.user != null)
1191
- ↓ (4) authorize (chama action.authorize)
1189
+ ↓ (2) check public OR authenticate (ctx.user != null)
1190
+ ↓ (3) load (se action declara `loads`)
1191
+ ↓ (4) authorize (chama action.authorize, que pode usar o que foi carregado)
1192
1192
  ↓ (5) handler executa
1193
1193
  (durante: handler pode chamar ctx.emit(event, data)
1194
1194
  → runtime publica via EventBusAdapter)
@@ -1426,14 +1426,14 @@ type BackgroundResult<T> =
1426
1426
  ```
1427
1427
  HTTP request → Server adapter
1428
1428
  ↓ (1) validate input
1429
- ↓ (2) load + authenticate + authorize
1429
+ ↓ (2) authenticate + load + authorize
1430
1430
  ↓ (3) enqueue job via QueueAdapter + audit do enqueue
1431
1431
 
1432
1432
  Server retorna JobHandle síncrono → client polla ou subscribe
1433
1433
 
1434
1434
  Worker (processo separado) consome job:
1435
1435
  ↓ (a) reidrata auth/can e chama runtime.executeJob(spec, ctx, progress)
1436
- ↓ (b) revalida input + load + authenticate + authorize
1436
+ ↓ (b) revalida input + authenticate + load + authorize
1437
1437
  ↓ (c) handler (recebe ProgressReporter se config.progress)
1438
1438
  ↓ (d) validate output + audit com provenance background
1439
1439
  ↓ (e) processor retorna/lança; BullMQ atualiza status done/failed
package/docs/releasing.md CHANGED
@@ -13,6 +13,13 @@ configuração de escopo ou token de leitura.
13
13
 
14
14
  ## Release pela estação (o caminho normal)
15
15
 
16
+ Antes de rodar, escreva no `CHANGELOG.md` a entrada `## X.Y.Z` da versão que vai sair. O script
17
+ confere essa entrada só DEPOIS do bump, então esquecê-la aborta a release com o `package.json` já
18
+ alterado — e a tentativa seguinte para na guarda de árvore limpa. Para recuperar, desfaça o bump
19
+ (`git checkout -- package.json`), escreva a entrada, commite e rode de novo.
20
+
21
+ Os comandos rodam em `packages/opus`; o `package.json` da raiz não tem `release`.
22
+
16
23
  ```bash
17
24
  pnpm release # patch bump + publica no npm público
18
25
  pnpm release minor # minor
@@ -53,7 +60,7 @@ pra testar publicação seria a guarda atrapalhando quem está experimentando.
53
60
 
54
61
  **Gate de qualidade**: depois do bump e da materialização, mas antes de publicar, roda
55
62
  `pnpm typecheck` + `pnpm test` + `pnpm copy:check` + `base copy check` e **aborta a release
56
- se qualquer um falhar**. O Opus declara `@softize/base ^2.2.1` em `dependencies`, pois usa
63
+ se qualquer um falhar**. O Opus declara `@softize/base ^2.3.0` em `dependencies`, pois usa
57
64
  suas APIs públicas de filesystem em runtime, e materializa os artefatos Base no próprio repo;
58
65
  a release não baixa uma política ad hoc. Como o
59
66
  Opus **ship source** (`.ts`, sem build), essa é a última barreira antes do tarball — sem ela,
@@ -67,7 +74,7 @@ não é bloqueada por cobertura abaixo de 100%; a cobertura é acompanhada com `
67
74
  **Smoke do esqueleto**: depois do bump e antes do publish, o `release.sh` gera um app com
68
75
  `opus create`, instala o **tarball exato** que vai ser publicado e roda os gates dele
69
76
  (typecheck · test · `opus check` · `opus copy --check` · `base copy check` · manifest ·
70
- build). O template exige `@softize/base ^2.2.1`; publique a Base compatível antes do Opus.
77
+ build). O template exige `@softize/base ^2.3.0`; publique a Base compatível antes do Opus.
71
78
  O `minimumReleaseAgeExclude` do template inclui os dois pacotes, e o smoke executa os
72
79
  fragmentos de pre-push para provar que o layout pnpm instalado resolve ambos os CLIs.
73
80
  É o que pega o que typecheck+test não
@@ -80,7 +87,24 @@ O script bumpa, publica **e leva o bump pra `origin/main`** — não sobra passo
80
87
  `--local` ele não commita nada (Verdaccio é sandbox). `pnpm release none` não cria diff de
81
88
  versão, mas ainda commita qualquer derivado que a materialização legitimamente atualizar.
82
89
  `--dry-run` encerra depois das mesmas materializações/gates/smoke, sem publish, commit ou
83
- push; use `none` para validar a versão atual sem sujar o manifesto.
90
+ push; use `none` para validar a versão atual sem sujar o manifesto. Sem `--local`, o
91
+ `--dry-run` também exige a `main` limpa e igual a `origin/main`.
92
+
93
+ ## A documentação é publicada à parte
94
+
95
+ A release publica o pacote, não o site. `opus.softize.com.br` só muda quando alguém roda, na raiz
96
+ do repositório:
97
+
98
+ ```bash
99
+ pnpm deploy:docs
100
+ ```
101
+
102
+ O script aplica as mesmas guardas de procedência da release, roda `typecheck`, `test`,
103
+ `copy:check` e o `opus check` de `apps/opus`, builda o site e envia o resultado. Ele não roda o
104
+ `base copy check` nem o smoke do esqueleto, que ficam com a release. Rode-o depois de cada
105
+ release; sem isso, o site continua mostrando a versão anterior.
106
+ Em setembro de 2026, o site ficou onze dias preso numa versão anterior à 13.0.0 enquanto o
107
+ pacote chegava à 18.1.0. `pnpm deploy:docs --dry-run` valida sem enviar.
84
108
 
85
109
  ## Verificar
86
110
 
@@ -111,7 +135,7 @@ pnpm add @softize/opus@8.7.0-rc.0 --registry http://127.0.0.1:6873/
111
135
 
112
136
  Valide o app normalmente. Ao encerrar a sessão, restaure o intervalo de versão do
113
137
  consumidor para o npm e rode `pnpm install`; a prerelease local nunca é enviada ao npm.
114
- Para publicar outra tentativa, incremente o sucesso (`8.6.0-rc.1`, por exemplo) e repita
138
+ Para publicar outra tentativa, incremente o sufixo (`8.7.0-rc.1`, por exemplo) e repita
115
139
  o mesmo fluxo.
116
140
 
117
141
  ## Auth
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "18.1.0",
3
+ "version": "18.1.1",
4
4
  "description": "End-to-end action protocol for TypeScript. Single package with subpath exports (core + adapters).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -73,6 +73,11 @@ ContentDescription, ContentMeta, ContentActions) + ContentBody`. `ContentHeader`
73
73
  - `ActionFilterBar` renderiza busca, filtros, período e ações declarados por uma list action
74
74
  quando a tela precisa da toolbar sem entregar os resultados a `ActionList`. Manter seu
75
75
  `state` e `onStateChange` ligados à mesma projeção de URL usada pela superfície.
76
+ - Filtros de list action declaram onde aparecem com `placement`: `inline` fica na barra;
77
+ `advanced` vai para o painel de filtros avançados, agrupado por `section` e distribuído em até
78
+ três colunas com `advancedFilters.columns`; `external` continua no estado navegável e no input,
79
+ mas a tela apresenta o controle por conta própria. `advanced: true` segue aceito só durante a
80
+ migração; em código novo, declarar `placement`.
76
81
  - `ActionForm` mantém validação, execução e estados do contrato nos dois modos: sem `children`,
77
82
  renderiza os campos declarados; com `children`, o consumidor diagrama `ActionFormField` e
78
83
  controles customizados pelo contexto. `ActionFormCard` e `ActionFormDialog` acrescentam a
@@ -81,8 +86,13 @@ ContentDescription, ContentMeta, ContentActions) + ContentBody`. `ContentHeader`
81
86
  a ação principal mantém `solid`. Criação e edição comuns preservam o rótulo padrão `Salvar`;
82
87
  `submitLabel` fica para efeitos específicos, como `Renomear` ou `Criar nova versão`. Em
83
88
  `Dialog mode="alert"`, a saída segura também usa `outline`.
89
+ - `DialogFooter` e `DrawerFooter` cuidam só da faixa. Agrupar e distribuir as decisões cabe a um
90
+ `ButtonGroup` dentro deles, com `distribution="equal"` para o footer 50/50; botões soltos no
91
+ footer não são distribuídos. `ActionFormDialog` já compõe isso: por padrão o footer acompanha o
92
+ conteúdo com o cancelamento em `ghost`, e `footerDistribution="equal"` divide a faixa e passa o
93
+ cancelamento a `outline`.
84
94
  - Cabeçalhos de `Dialog` e `Drawer` nomeiam a superfície com o título e organizam suas ações. O close
85
- padrão é uma action ghost somente com ícone no final do header; não crie uma posição flutuante
95
+ padrão é o `CloseButton` (`neutral`, `subtle`, pílula) no final do header, sem posição flutuante
86
96
  alternativa. Não preencher uma segunda linha por hábito. Consequência, restrição ou instrução que
87
97
  realmente mude a tarefa entra no início de `DialogBody`/`DrawerBody`, como texto ou `Alert`;
88
98
  wrappers usam `intro` e a API imperativa usa `body`. Relacione texto conciso por
@@ -34,7 +34,8 @@ import type { AuthAdapter, CanFn, User } from '../../core/index.ts'
34
34
  // =============================================================================
35
35
 
36
36
  export interface JwtAuthOptions<P extends JwtPayload = JwtPayload> {
37
- /** Secret HMAC ou public key. Pode ser string sync ou async resolver. */
37
+ /** Secret HMAC ou public key, fixo ou por resolver (sync ou async). O resolver é chamado
38
+ * sem argumentos: a escolha da chave pelo `kid` do token ainda não é suportada. */
38
39
  secret: string | Buffer | ((kid?: string) => Promise<string | Buffer> | string | Buffer)
39
40
 
40
41
  /** Algorithms aceitos (default ['HS256']). */
@@ -75,6 +75,23 @@ import type {
75
75
 
76
76
  /* eslint-disable @typescript-eslint/no-explicit-any */
77
77
 
78
+ /**
79
+ * Erro devolvido a um cliente de IA que chama uma tool fora do conjunto exposto.
80
+ *
81
+ * Quem executa tools (o agente co-locado e o servidor MCP) só pode rodar o que `aiTools()`
82
+ * anunciou. O modelo, ou um prompt injetado, pode emitir qualquer nome; sem este corte, uma
83
+ * action sem `ai.enabled` — inclusive destrutiva — rodaria dentro da autorização do contexto.
84
+ * A resposta é a mesma para nome inexistente e para action registrada mas não exposta, para
85
+ * que o cliente não descubra quais actions existem.
86
+ */
87
+ export function aiToolNotFound(name: string): ReturnType<typeof error> {
88
+ return error({
89
+ code: 'runtime.action_not_found',
90
+ category: 'not_found',
91
+ message: `Tool "${name}" não está disponível.`,
92
+ })
93
+ }
94
+
78
95
  /** Normaliza o `ai` da action (`boolean | AIConfig`) → config, ou null se não exposta à IA. */
79
96
  function normalizeAiConfig(ai: boolean | AIConfig | undefined): AIConfig | null {
80
97
  if (ai === undefined || ai === false) return null
@@ -620,10 +637,10 @@ export class Runtime {
620
637
  // — 1. Validate input ————————————————————————————————————————————————
621
638
  const validatedInput = await this.validate(action.input, input, 'input')
622
639
 
623
- // — 2. Load —————————————————————————————————————————————————————————
624
- const loaded = await this.runLoaders(action, ctx, validatedInput)
625
-
626
- // 3. Authenticate (public-or-user) ——————————————————————————————————
640
+ // — 2. Authenticate (public-or-user) ——————————————————————————————————
641
+ // Antes dos loaders: eles consultam dados, e um chamador anônimo não pode nem disparar essas
642
+ // consultas nem distinguir, pelo erro de um loader, se um registro existe. A autenticação
643
+ // não depende de nada carregado; a autorização, sim, e por isso vem depois.
627
644
  if (action.public !== true && ctx.user === null) {
628
645
  throw error({
629
646
  code: 'auth.unauthenticated',
@@ -632,6 +649,9 @@ export class Runtime {
632
649
  })
633
650
  }
634
651
 
652
+ // — 3. Load —————————————————————————————————————————————————————————
653
+ const loaded = await this.runLoaders(action, ctx, validatedInput)
654
+
635
655
  // — 4. Authorize ——————————————————————————————————————————————————————
636
656
  if (action.authorize !== undefined) {
637
657
  const authorizeFn = this.compileAuthorize(action.authorize)
@@ -848,6 +868,7 @@ export class Runtime {
848
868
  ...opts,
849
869
  tools,
850
870
  execute: async (name, toolInput) => {
871
+ if (!tools.some((tool) => tool.name === name)) return { error: aiToolNotFound(name) }
851
872
  const cfg = normalizeAiConfig(this.actions.get(name)?.ai)
852
873
  if (cfg !== null && (cfg.destructive === true || cfg.requiresConfirmation === true)) {
853
874
  const approved = opts?.confirm ? await opts.confirm({ name, input: toolInput }) : false
package/src/core/types.ts CHANGED
@@ -620,9 +620,9 @@ export interface FilterSpec {
620
620
 
621
621
  /**
622
622
  * Coluna declarativa de uma `ListAction` — a UI (ActionList) deriva a tabela
623
- * daqui; células custom entram POR CIMA na UI (prop `cells`, chave = key). É a base
624
- * do futuro column picker (o usuário escolher colunas): a lista completa vive no
625
- * contrato, `hidden` marca as que nascem fora.
623
+ * daqui; células custom entram POR CIMA na UI (prop `cells`, chave = key). O seletor
624
+ * de colunas parte desta lista: ela vive completa no contrato, e `hidden` marca as que
625
+ * nascem fora.
626
626
  */
627
627
  export interface ListColumnSpec {
628
628
  /** Chave do item (`Out`) que a coluna mostra. */
@@ -1385,7 +1385,7 @@ export interface ScheduleDef {
1385
1385
  cron?: string
1386
1386
  /** Shorthand interval — ex: '1h', '30m', '15s'. */
1387
1387
  every?: string
1388
- /** Timezone IANA — ex: 'America/Sao_Paulo'. Default: UTC. */
1388
+ /** Timezone IANA — ex: 'America/Sao_Paulo'. Sem ela, vale a hora local do processo. */
1389
1389
  timezone?: string
1390
1390
 
1391
1391
  /** Input passado ao runtime.execute(); estático ou dinâmico. */
package/src/mcp/index.ts CHANGED
@@ -14,6 +14,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
14
14
  import { zodToJsonSchema } from 'zod-to-json-schema'
15
15
  import type { ContextBase, Runtime } from '../core/index.ts'
16
16
  import { readPackageVersion } from '../core/package-version.ts'
17
+ import { aiToolNotFound } from '../core/runtime.ts'
17
18
 
18
19
  export interface OpusMcpOptions {
19
20
  name?: string
@@ -55,6 +56,14 @@ export function createOpusMcpServer(runtime: Runtime, opts: OpusMcpOptions = {})
55
56
  }))
56
57
 
57
58
  server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
59
+ // Executa só o que ListTools anunciou. Sem este corte, qualquer action registrada — sem
60
+ // `ai.enabled`, inclusive destrutiva — rodaria dentro da autorização do contexto.
61
+ if (!runtime.aiTools().some((tool) => tool.name === req.params.name)) {
62
+ return {
63
+ content: [{ type: 'text', text: JSON.stringify({ error: aiToolNotFound(req.params.name) }) }],
64
+ isError: true,
65
+ }
66
+ }
58
67
  const base = opts.resolveContext ? await opts.resolveContext(extra) : ANON
59
68
  const result = await runtime.execute(req.params.name, req.params.arguments ?? {}, base)
60
69
  const payload = result.ok ? result.data : { error: result.error }
@@ -50,7 +50,7 @@ interface ContentBaseProps extends Omit<
50
50
 
51
51
  interface ContentShorthandProps extends ContentBaseProps {
52
52
  title: ReactNode;
53
- /** Total de itens ao lado do título o mesmo `count` de Page. */
53
+ /** Total de itens ao lado do título da seção. A página não carrega contador (ADR 0009). */
54
54
  count?: number;
55
55
  description?: ReactNode;
56
56
  actions?: ReactNode;
@@ -702,7 +702,7 @@ export interface ActionFormProps<
702
702
  onSuccess?: (data: TData) => void;
703
703
  submitLabel?: string;
704
704
  cancelLabel?: string;
705
- /** Tratamento visual do cancelamento. Use outline quando ele dividir o footer com a ação principal. */
705
+ /** Tratamento visual do cancelamento. `ghost` por padrão; `outline` quando o footer distribui as ações por igual. */
706
706
  cancelVariant?: Extract<ButtonVariant, "ghost" | "outline">;
707
707
  onCancel?: () => void;
708
708
  /** Bloqueia campos e ações sem desmontar o formulário. */
@@ -94,7 +94,7 @@ export function PaneBody({
94
94
  );
95
95
  }
96
96
 
97
- /** @deprecated Use PaneBody. Mantido durante a versão 12 para migração gradual. */
97
+ /** @deprecated Use PaneBody. Alias mantido para migração; sai numa próxima versão major. */
98
98
  export function PaneContent({
99
99
  className,
100
100
  children,
@@ -101,7 +101,7 @@ export function CardBody({
101
101
  );
102
102
  }
103
103
 
104
- /** @deprecated Use CardBody. Mantido durante a versão 12 para migração gradual. */
104
+ /** @deprecated Use CardBody. Alias mantido para migração; sai numa próxima versão major. */
105
105
  export function CardContent({
106
106
  className,
107
107
  ...props
@@ -8,11 +8,11 @@ export type DetailGroupOrientation = 'vertical' | 'horizontal'
8
8
  export type DetailGroupColumns = 1 | 2 | 3 | 4 | 'auto'
9
9
 
10
10
  export interface DetailGroupProps extends React.ComponentProps<'dl'> {
11
- /** `framed` aplica a superfície e a moldura canônicas ao conjunto. */
11
+ /** `framed` aplica a moldura canônica ao conjunto; na orientação horizontal, a coluna de rótulos ganha fundo sutil. */
12
12
  variant?: DetailGroupVariant
13
13
  /** Exibe hairlines somente entre os campos, sem exigir moldura externa. */
14
14
  dividers?: boolean
15
- /** Número responsivo de colunas ou distribuição automática por largura mínima. */
15
+ /** Número fixo de colunas ou distribuição automática por largura mínima. */
16
16
  columns?: DetailGroupColumns
17
17
  /** Organiza a chave sobre o valor ou ao lado dele em cada campo. */
18
18
  orientation?: DetailGroupOrientation
@@ -59,13 +59,13 @@ function DetailGroup({
59
59
  '[&>[data-slot=detail-field]]:p-3',
60
60
  variant === 'framed' &&
61
61
  'overflow-hidden rounded-lg border border-border bg-transparent text-foreground',
62
+ // Default da coluna de rótulos como CLASSE, antes do `className`: assim uma classe do
63
+ // consumidor (`[--detail-label-width:9rem]`) a substitui. Em `style` inline, o default
64
+ // vencia qualquer classe e só `style` conseguia ajustar a largura.
65
+ splitHorizontalField && '[--detail-label-width:8.5rem]',
62
66
  className,
63
67
  )}
64
- style={
65
- splitHorizontalField
66
- ? ({ '--detail-label-width': '8.5rem', ...style } as React.CSSProperties)
67
- : style
68
- }
68
+ style={style}
69
69
  {...props}
70
70
  >
71
71
  {children}
@@ -25,7 +25,7 @@ function RadioGroupItem({
25
25
  <RadioGroupPrimitive.Item
26
26
  data-slot="radio-group-item"
27
27
  className={cn(
28
- "aspect-square size-4 shrink-0 rounded-full border border-input text-primary transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:aria-invalid:ring-context-danger/40",
28
+ "peer aspect-square size-4 shrink-0 rounded-full border border-input text-primary transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-context-danger aria-invalid:ring-context-danger/20 dark:bg-input/30 dark:aria-invalid:ring-context-danger/40",
29
29
  className
30
30
  )}
31
31
  {...props}