@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
@@ -5,53 +5,105 @@ title: Como consumir
5
5
  # Como consumir a UI
6
6
 
7
7
  Esta documentação é a referência visual e de uso de `@softize/opus/ui`. Os exemplos são renderizados
8
- com o tema e o contrato entregues aos aplicativos.
8
+ com o mesmo tema e os mesmos componentes entregues aos aplicativos.
9
9
 
10
- ## Por que esta doc existe
10
+ ## Por que esta documentação existe
11
11
 
12
- O catálogo do shadcn documenta seu próprio código e pode mostrar composições que não fazem parte do
12
+ O catálogo do shadcn documenta o próprio código e pode mostrar composições que não fazem parte do
13
13
  Opus. Consulte estas páginas para conhecer os componentes, as variações e os comportamentos
14
14
  suportados pelo pacote.
15
15
 
16
16
  Cada página combina exemplos vivos, código e uma referência de propriedades por componente. A
17
- orientação inicial vem de `componentMeta`, a mesma fonte usada pelo catálogo e pelas ferramentas.
17
+ orientação inicial de cada componente vem de `componentMeta`, a mesma fonte usada pelo catálogo e
18
+ pelas ferramentas.
18
19
 
19
- ## Como consumir
20
+ ## Importar componentes e tema
20
21
 
21
22
  Importe componentes e hooks de `@softize/opus/ui/react` e o tema por CSS. Não copie a implementação
22
23
  para o aplicativo quando a API pública já atender ao caso.
23
24
 
24
25
  ```tsx
25
- // Componentes e hooks — tudo do mesmo barrel.
26
+ // Componentes e hooks vêm do mesmo ponto de entrada.
26
27
  import { Button, Dialog, useAction } from '@softize/opus/ui/react'
27
28
  ```
28
29
 
29
30
  ```css
30
- /* index.css — o tema canônico + os componentes do Opus no scan do Tailwind. */
31
+ /* index.css — o tema do Opus e os componentes do pacote no scan do Tailwind. */
31
32
  @import '@softize/opus/ui/theme.css';
32
33
  @source '../node_modules/@softize/opus/src/ui/**/*.{ts,tsx}';
33
34
  ```
34
35
 
36
+ ## Montar os providers
37
+
38
+ Os hooks de action e os componentes que executam actions dependem de três providers na raiz da
39
+ aplicação. O app criado por `opus create` já nasce com eles:
40
+
41
+ ```tsx
42
+ // main.tsx
43
+ import { StrictMode } from 'react'
44
+ import { createRoot } from 'react-dom/client'
45
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
46
+ import { OpusProvider, TooltipProvider } from '@softize/opus/ui/react'
47
+ import { fetchClient } from '@softize/opus/client/fetch'
48
+ import { App } from './App.tsx'
49
+
50
+ const queryClient = new QueryClient()
51
+ // Mesma origem: as actions respondem em /api. Aponte baseUrl para outro servidor quando houver.
52
+ const client = fetchClient({ baseUrl: '' })
53
+
54
+ createRoot(document.getElementById('root')!).render(
55
+ <StrictMode>
56
+ <QueryClientProvider client={queryClient}>
57
+ <OpusProvider client={client}>
58
+ <TooltipProvider>
59
+ <App />
60
+ </TooltipProvider>
61
+ </OpusProvider>
62
+ </QueryClientProvider>
63
+ </StrictMode>,
64
+ )
65
+ ```
66
+
67
+ - `QueryClientProvider` guarda o cache das consultas; `useListAction` e as invalidações entre
68
+ actions dependem dele.
69
+ - `OpusProvider` informa como chamar as actions. Fora dele, os hooks de action lançam erro ao
70
+ montar.
71
+ - `TooltipProvider` é exigido pelos tooltips da aplicação e por componentes do Opus que usam
72
+ tooltip, como `Dock` e `Truncate`.
73
+
74
+ `fetchClient` chama cada action por HTTP em `<baseUrl><apiPrefix>`; `apiPrefix` tem padrão `/api`,
75
+ o mesmo dos servidores do Opus. `headers` e `beforeRequest` acrescentam cabeçalhos fixos ou
76
+ calculados a cada chamada, como um token de acesso.
77
+
78
+ ## Propriedades de OpusProvider
79
+
80
+ | Propriedade | Tipo | Padrão | Descrição |
81
+ | --- | --- | --- | --- |
82
+ | `client` | `ClientAdapter` | obrigatório | Executa as actions chamadas pelos hooks e componentes; normalmente `fetchClient`. |
83
+ | `dicts` | `Record<string, DictLike>` | `{}` | Dicionários por referência, usados por `options: { kind: 'dictionary', ref }` em campos e filtros. Sem registro, o campo usa os dados do `t.dict` que viajam no schema do contrato. |
84
+ | `children` | `ReactNode` | obrigatório | A árvore que usa as actions. |
85
+
35
86
  ## Prebundle do Vite
36
87
 
37
- A entrada `@softize/opus/ui/react` é um `.tsx`, e o prebundle do Vite só considera entradas
38
- `.js`/`.ts`. Sem ajuste, o dev server avisa `Cannot optimize dependency` e serve a árvore do Opus
39
- arquivo a arquivo na carga fria. Há duas saídas, cada uma com um custo conhecido:
88
+ A entrada `@softize/opus/ui/react` é um arquivo `.tsx`, e o prebundle do Vite só considera entradas
89
+ `.js` e `.ts`. Sem ajuste, o dev server avisa `Cannot optimize dependency` e serve os arquivos do
90
+ Opus um a um na primeira carga. Há duas saídas, cada uma com um custo conhecido:
40
91
 
41
92
  - `optimizeDeps.include: ['@softize/opus/ui/react']` com `extensions: ['.tsx']` pré-empacota o
42
- Opus, mas pode duplicar `@tanstack/react-query` quando o app também o importa direto (dois
43
- `QueryClient`, hooks fora do provider).
44
- - `optimizeDeps.exclude: ['@softize/opus/ui/react']` evita a duplicação e infla a carga fria.
93
+ Opus, mas pode duplicar `@tanstack/react-query` quando o app também o importa diretamente. O
94
+ resultado são dois `QueryClient` e hooks que não encontram o provider.
95
+ - `optimizeDeps.exclude: ['@softize/opus/ui/react']` evita a duplicação e deixa a primeira carga
96
+ mais lenta.
45
97
 
46
- Escolha pelo sintoma que aparece no seu app e registre a decisão no `vite.config.ts`. A saída
47
- definitiva (entrada `.ts` no pacote) está no radar do Opus.
98
+ Escolha pelo sintoma que aparece no seu app e registre a decisão no `vite.config.ts`. Uma entrada
99
+ `.ts` no pacote, que resolveria os dois custos, ainda não existe.
48
100
 
49
- ## Origem e anti-drift
101
+ ## Origem dos componentes
50
102
 
51
- > Cada componente declara de onde veio — e a regra de sincronização vem junto.
103
+ Cada componente declara de onde veio, e essa origem define como ele acompanha mudanças:
52
104
 
53
- - **Origem shadcn** — port curado do shadcn (Radix + cmdk). Toda divergência do upstream é
54
- DECLARADA no componente ou no tema — o que diverge sem declaração é drift e deve ser
55
- sincronizado (skill `build-opus-ui`).
56
- - **Nativo do Opus** — nasceu aqui (os patterns de action). Não tem upstream para acompanhar; a
57
- referência é esta doc.
105
+ - **Origem shadcn:** adaptação do shadcn (Radix e cmdk). Toda divergência do original é declarada
106
+ no componente ou no tema. Uma divergência sem declaração deve ser sincronizada; a skill
107
+ `$build-opus-ui` orienta esse trabalho.
108
+ - **Nativo do Opus:** criado no próprio Opus, como os componentes de action. Não há versão externa a
109
+ acompanhar; a referência é esta documentação.
@@ -4,74 +4,83 @@ title: Atualizar o Opus
4
4
 
5
5
  # Atualizar o Opus
6
6
 
7
- Use este fluxo para atualizar o Opus por uma ou várias versões. O changelog explica as mudanças e os
8
- gates apontam as adaptações necessárias no projeto.
7
+ Use este fluxo para atualizar o Opus por uma ou várias versões. O [changelog](/changelog) explica
8
+ as mudanças, e os gates apontam as adaptações necessárias no projeto. Em projetos que usam as
9
+ skills do Opus, a skill `$upgrade-opus` conduz as mesmas etapas: lê o changelog, atualiza a
10
+ dependência, materializa os artefatos e aplica as migrações.
9
11
 
10
- ## 1. Identificar a versão disponível
12
+ ## 1. Atualizar a dependência
11
13
 
12
- O **Maestro** compara o pin (`opus.json`) com a versão instalada e com a fonte a cada
13
- sessão — o alerta no rodapé diz a distância (`v18.0.1 → v18.1.0`) e o **Atualizar** faz
14
- a parte mecânica: bump da dependência, re-materialização da camada de IA (skills,
15
- hooks, agentes) e re-sync do bloco gerenciado do CLAUDE.md.
16
-
17
- Fora do Maestro, faça essa parte à mão:
14
+ A versão adotada pelo projeto fica no `package.json` e no `opus.json` de cada app. Compare-a com a
15
+ versão publicada e atualize:
18
16
 
19
17
  ```bash
20
18
  pnpm outdated @softize/opus # distância até a versão publicada
21
- pnpm -r up @softize/opus@<versão> # em todo workspace que declara o Opus
22
- pnpm run setup # opus setup && base setup: re-materializa base.json e skills
19
+ pnpm -r up @softize/opus@<versão> # em todo pacote do workspace que declara o Opus
20
+ pnpm run setup # opus setup && base setup: materializa base.json e skills
23
21
  ```
24
22
 
25
- Rode `setup` no diretório de cada app que tem `opus.json`; num monorepo criado por
23
+ Rode `setup` no diretório de cada app que tem `opus.json`. Num monorepo criado por
26
24
  `opus create --monorepo`, a raiz não tem esse script, e `pnpm -r run setup` alcança todos os apps.
27
- O postinstall do pacote tenta materializar sozinho, mas é best-effort; se a camada materializada
28
- ainda corresponder à versão anterior, o `opus check` aborta com
25
+ O postinstall do pacote tenta materializar os artefatos sozinho, mas não garante o resultado. Se a
26
+ camada materializada ainda corresponder à versão anterior, o `opus check` para com
29
27
  `base.json: Opus aplicado X, instalado Y`.
30
28
 
29
+ Interfaces de desenvolvimento podem mostrar a diferença entre a versão aplicada na
30
+ sua cópia de trabalho e a adotada pela branch principal. Esse aviso não atualiza o pacote; a
31
+ atualização continua sendo este fluxo.
32
+
31
33
  ## 2. Ler as mudanças acumuladas
32
34
 
33
- O changelog **viaja no pacote** — depois do bump, está em
34
- `node_modules/@softize/opus/CHANGELOG.md`. Uma entrada por versão; quando quebra,
35
- a seção **Breaking** diz a migração (o que renomeou, o que fazer no seu código).
36
- A esteira de release **recusa** publicar versão sem entrada — o arquivo não defasa.
35
+ O changelog também acompanha o pacote: depois da atualização, ele está em
36
+ `node_modules/@softize/opus/CHANGELOG.md`. Cada versão tem uma entrada, e a publicação é recusada
37
+ quando a entrada falta. Quando uma versão quebra compatibilidade, a seção **Breaking** descreve o
38
+ que mudou e como migrar o seu código.
37
39
 
38
- - **minor e patch** (18.0 → 18.1): leia todas as entradas intermediárias. Minors podem pedir ação,
39
- como a troca de `PageHeader` por `PageIntro` no `PageShell` (17.2.0) ou o layout desktop fixo (18.1.0).
40
- - **major** (17.x → 18.0): além disso, aplique as seções Breaking de cada versão pulada, na ordem.
40
+ - **Minor e patch** (18.0 → 18.5): leia todas as entradas intermediárias, porque elas também podem
41
+ mudar comportamento observável. Na 18.1.1, por exemplo, uma action que não é `public` passou a
42
+ exigir usuário antes de executar os `loads`.
43
+ - **Major** (18.x → 19.0): além disso, aplique as seções Breaking de cada versão pulada, na ordem.
44
+ Na 19.0.0, por exemplo, os headers de `Card`, `Dialog` e `Drawer` passaram a aceitar somente os
45
+ slots da própria família.
41
46
 
42
47
  ## 3. Executar as verificações
43
48
 
44
- O Opus é publicado como código-fonte TypeScript: o que quebrou aparece com arquivo e linha.
45
- Rode, na ordem:
49
+ O Opus é publicado como código-fonte TypeScript, então uma incompatibilidade aparece com arquivo e
50
+ linha. Rode, na ordem:
46
51
 
47
52
  ```bash
48
- pnpm typecheck # a API nova cobra os tipos
49
- pnpm exec opus check # convenções (regras novas inclusas)
50
- pnpm exec opus copy --check # inventário de copy em dia
51
- pnpm exec base copy check # política de copy da base
53
+ pnpm typecheck # a API nova confere os tipos
54
+ pnpm exec opus check # convenções, inclusive regras novas
55
+ pnpm exec opus copy --check # inventário de copy atualizado
56
+ pnpm exec base copy check # política de copy da Base
52
57
  pnpm test # comportamento
53
- pnpm manifest:check # a spec projetada ficou fresca?
58
+ pnpm manifest:check # o manifest projetado continua atualizado
54
59
  ```
55
60
 
56
- Num monorepo, `typecheck` e `test` existem na raiz, mas `manifest:check` é script de cada app:
57
- rode-o no diretório do app ou com `pnpm -r run manifest:check`. Nenhum desses comandos migra o
61
+ Num monorepo, `typecheck` e `test` existem na raiz, mas `manifest:check` é um script de cada app:
62
+ rode-o no diretório do app ou com `pnpm -r run manifest:check`. Nenhum desses comandos altera o
58
63
  banco. Se o projeto tiver testes de integração, eles usam o banco que o próprio projeto configurar.
59
64
 
60
65
  ## 4. Migrar o banco, se houver
61
66
 
62
67
  Se o `opus.config.ts` declara `database`, confira o schema e aplique a migração como uma etapa
63
- separada, contra o banco do ambiente que você pretende alterar:
68
+ separada:
64
69
 
65
70
  ```bash
66
71
  pnpm exec opus db check # compara entidades e banco, sem escrever
67
72
  pnpm exec opus db migrate # aplica o schema idempotente; escreve no banco
68
73
  ```
69
74
 
70
- Com todas as verificações verdes, revise a atualização como qualquer outra mudança de código antes
71
- de entregá-la.
75
+ Os comandos `opus db` carregam o `.env` que está ao lado do `opus.config.ts` antes de abrir a
76
+ conexão, e uma variável já definida no ambiente vence o arquivo. Antes de migrar, confirme para
77
+ qual banco a factory `database` vai apontar. Para migrar outro ambiente, informe a variável no
78
+ próprio comando, como `DATABASE_URL=… pnpm exec opus db migrate` quando a factory lê essa variável.
79
+
80
+ Com todas as verificações aprovadas, revise a atualização como qualquer outra mudança de código
81
+ antes de entregá-la.
72
82
 
73
83
  ## Pulou muitas versões?
74
84
 
75
- O ritual não muda, só o volume: leia todas as entradas acumuladas, não só as seções Breaking
76
- (o changelog é uma lista, não um diff), aplique na ordem e deixe os gates validarem o conjunto.
77
- Não há caminho especial — é o mesmo laço, com mais iterações.
85
+ O fluxo é o mesmo, com mais entradas para ler. Leia todas as entradas acumuladas, não só as seções
86
+ Breaking, aplique as migrações na ordem das versões e deixe os gates validarem o conjunto.
@@ -11,6 +11,11 @@
11
11
  * `mountDocs`. Importando o MESMO CSS do app, a doc herda identidade e utilitários de
12
12
  * graça. É dev-only (apply: 'serve') — nada vai pro bundle de produção.
13
13
  *
14
+ * O CSS entra por um entry CSS virtual que importa o do app e acrescenta um `@source` para
15
+ * as páginas `.md` da doc do Opus. Os exemplos ```tsx preview usam classes que só aparecem
16
+ * nesses `.md`, e o Tailwind do app não varre o pacote instalado. O `@source` fica só nesse
17
+ * entry: o CSS de produção do app não muda.
18
+ *
14
19
  * Uso (vite.config.ts do app):
15
20
  * import { opusDocs } from '@softize/opus/ui/docs/plugin'
16
21
  * export default defineConfig({ plugins: [react(), tailwindcss(), opusDocs()] })
@@ -36,6 +41,29 @@ const VIRTUAL_ID = 'virtual:opus-docs-entry'
36
41
  // O `/@id/` é como o vite serve módulos por id; o `\0` (convenção de virtual) vira `__x00__` na URL.
37
42
  const RESOLVED_ID = '\0' + VIRTUAL_ID
38
43
  const ENTRY_URL = '/@id/__x00__' + VIRTUAL_ID
44
+ // A extensão `.css` faz o Vite e o @tailwindcss/vite tratarem o módulo virtual como folha de estilo.
45
+ const VIRTUAL_CSS_ID = 'virtual:opus-docs-entry.css'
46
+ const RESOLVED_CSS_ID = '\0' + VIRTUAL_CSS_ID
47
+
48
+ /**
49
+ * Pasta das páginas `.md` da doc do Opus, ao lado deste arquivo. O Node carrega o plugin pelo
50
+ * caminho real (resolve o symlink do pnpm/workspace), então `import.meta.url` já aponta para
51
+ * onde o scanner do Tailwind encontra os arquivos.
52
+ *
53
+ * Sem builtins do Node: o arquivo mora em `ui/`, e o gate de fronteira proíbe `node:*` ali.
54
+ * Sem `new URL('./content', import.meta.url)`: o Vite reescreve esse padrão como asset.
55
+ */
56
+ export function opusDocsContentDir(moduleUrl: string = import.meta.url): string {
57
+ const folder = moduleUrl.slice(0, moduleUrl.lastIndexOf('/'))
58
+ const path = decodeURIComponent(new URL(folder).pathname)
59
+ // No Windows o pathname vem como `/C:/…`; o Tailwind espera `C:/…`.
60
+ return (/^\/[A-Za-z]:\//.test(path) ? path.slice(1) : path) + '/content'
61
+ }
62
+
63
+ /** O CSS do entry virtual: o do app (tema + tailwind) + a varredura dos `.md` da doc. */
64
+ export function opusDocsEntryCss(appCss: string, contentDir: string = opusDocsContentDir()): string {
65
+ return [`@import ${JSON.stringify(appCss)};`, `@source ${JSON.stringify(contentDir + '/*.md')};`].join('\n')
66
+ }
39
67
 
40
68
  export function opusDocs(options: opusDocsOptions = {}): Plugin {
41
69
  const basePath = options.basePath ?? '/__docs'
@@ -48,12 +76,18 @@ export function opusDocs(options: opusDocsOptions = {}): Plugin {
48
76
  apply: 'serve',
49
77
  resolveId(id) {
50
78
  if (id === VIRTUAL_ID) return RESOLVED_ID
79
+ if (id === VIRTUAL_CSS_ID) return RESOLVED_CSS_ID
51
80
  return undefined
52
81
  },
53
- load(id) {
82
+ async load(id) {
83
+ if (id === RESOLVED_CSS_ID) {
84
+ // O `@import` do entry virtual não tem pasta de referência: vai com o caminho resolvido.
85
+ const resolved = await this.resolve(css)
86
+ return opusDocsEntryCss(resolved?.id ?? css)
87
+ }
54
88
  if (id !== RESOLVED_ID) return undefined
55
89
  // Sempre importa o CSS do app (tema + tailwind) antes de subir a doc.
56
- const head = `import ${JSON.stringify(css)}`
90
+ const head = `import ${JSON.stringify(VIRTUAL_CSS_ID)}`
57
91
  if (source === undefined || source === '') {
58
92
  // Sem `source`: a doc do próprio Opus (DOC_SECTIONS), como sempre.
59
93
  return [head, `import { mountDocs } from '@softize/opus/ui/docs/standalone'`, `mountDocs({ basePath: ${JSON.stringify(basePath)} })`].join('\n')
@@ -37,6 +37,7 @@ import executionsMd from "./content/executions.md?raw";
37
37
  import schedulerMd from "./content/scheduler.md?raw";
38
38
  import actionsMd from "./content/actions.md?raw";
39
39
  import dataMd from "./content/data.md?raw";
40
+ import dataProductsMd from "./content/data-products.md?raw";
40
41
  import storageMd from "./content/storage.md?raw";
41
42
  import aiMd from "./content/ai.md?raw";
42
43
  import mcpMd from "./content/mcp.md?raw";
@@ -276,6 +277,11 @@ export const PROTOCOL_SECTIONS: DocSection[] = [
276
277
  render: doc(runtimeMd),
277
278
  },
278
279
  { slug: "data", title: "Camada de dados", render: doc(dataMd) },
280
+ {
281
+ slug: "data-products",
282
+ title: "Produtos de Dados",
283
+ render: doc(dataProductsMd),
284
+ },
279
285
  {
280
286
  slug: "testing",
281
287
  title: "Testes de action",
@@ -83,11 +83,19 @@ export function OpusProvider({
83
83
  return <OpusContext.Provider value={value}>{children}</OpusContext.Provider>
84
84
  }
85
85
 
86
- function useClient(): ClientAdapter {
86
+ /**
87
+ * O adapter do provider. `hook` nomeia quem chamou, para a mensagem apontar o hook que a
88
+ * pessoa vê no stack — muitas vezes chamado por dentro de ActionForm, ActionList ou
89
+ * ActionView, e não diretamente pela tela.
90
+ */
91
+ function useClient(hook: string): ClientAdapter {
87
92
  const ctx = useContext(OpusContext)
88
93
  if (ctx === null) {
89
94
  throw new Error(
90
- 'useAction/useLookupAction precisam estar dentro de <OpusProvider>.',
95
+ `${hook} foi chamado fora de um <OpusProvider> e não tem como executar a action. ` +
96
+ 'Os hooks de action e os componentes que os usam, como ActionForm, ActionList e ActionView, ' +
97
+ 'precisam de um <OpusProvider client={...}> acima deles na árvore. Envolva a aplicação no provider; ' +
98
+ 'em testes e previews, envolva o componente renderizado.',
91
99
  )
92
100
  }
93
101
  return ctx.client
@@ -113,7 +121,7 @@ export interface UseActionResult<TInput, TData> extends ActionState<TData> {
113
121
  export function useAction<TInput = unknown, TData = unknown>(
114
122
  action: ActionDef,
115
123
  ): UseActionResult<TInput, TData> {
116
- const client = useClient()
124
+ const client = useClient('useAction')
117
125
  const [state, setState] = useState<ActionState<TData>>(() => idleAction<TData>())
118
126
 
119
127
  const run = useCallback(
@@ -173,7 +181,7 @@ export interface UseLookupActionResult<TInput, TItem>
173
181
  export function useLookupAction<TInput = unknown, TItem = unknown>(
174
182
  action: ActionDef,
175
183
  ): UseLookupActionResult<TInput, TItem> {
176
- const client = useClient()
184
+ const client = useClient('useLookupAction')
177
185
  const [state, setState] = useState<ListState<TItem>>(() => idleLookup<TItem>())
178
186
 
179
187
  const run = useCallback(
@@ -257,7 +265,7 @@ export function useListAction<TItem = unknown, TInput extends Record<string, unk
257
265
  action: { name: string; kind: 'list' },
258
266
  input?: TInput,
259
267
  ): UseListActionResult<TItem> {
260
- const client = useClient()
268
+ const client = useClient('useListAction')
261
269
 
262
270
  const query = useQuery<Paginated<TItem>, ActionError>({
263
271
  queryKey: [action.name, input ?? {}],
@@ -358,7 +366,7 @@ export function useFormAction<
358
366
  action: FormContract<TInput, TData>,
359
367
  options: UseFormActionOptions<TInput, TData> = {},
360
368
  ): UseFormActionResult<TInput, TData> {
361
- const client = useClient()
369
+ const client = useClient('useFormAction')
362
370
  const queryClient = useQueryClient()
363
371
 
364
372
  const resolver = useMemo(() => {
@@ -484,7 +492,7 @@ export function useViewAction<TInput = unknown, TData = unknown>(
484
492
  action: { name: string; kind: 'view' },
485
493
  input: TInput,
486
494
  ): UseViewActionResult<TData> {
487
- const client = useClient()
495
+ const client = useClient('useViewAction')
488
496
 
489
497
  // TanStack Query serializa queryKey → dedupe automático mesmo com input
490
498
  // inline (referência nova a cada render). Sem isso entraria em loop.
@@ -551,7 +559,7 @@ export function useTriggerAction<TInput = unknown, TData = unknown>(
551
559
  action: ActionContract<TInput, TData>,
552
560
  options: UseTriggerActionOptions<TInput, TData> = {},
553
561
  ): UseTriggerActionResult<TInput, TData> {
554
- const client = useClient()
562
+ const client = useClient('useTriggerAction')
555
563
  const queryClient = useQueryClient()
556
564
  const [state, setState] = useState<ActionState<TData>>(() =>
557
565
  idleAction<TData>(),