@softize/opus 18.5.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 (151) hide show
  1. package/CHANGELOG.md +275 -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 +298 -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 +16 -12
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/heading-level.ts +16 -0
  20. package/src/ui/components/patterns/list.tsx +103 -73
  21. package/src/ui/components/patterns/page.tsx +20 -4
  22. package/src/ui/components/patterns/pane.tsx +294 -0
  23. package/src/ui/components/patterns/presentation.tsx +81 -54
  24. package/src/ui/components/patterns/sidebar.tsx +13 -72
  25. package/src/ui/components/patterns/split.tsx +51 -38
  26. package/src/ui/components/patterns/surface-anatomy.ts +246 -0
  27. package/src/ui/components/patterns/surface-header.tsx +51 -6
  28. package/src/ui/components/patterns/surface-media.tsx +69 -0
  29. package/src/ui/components/patterns/trigger.tsx +12 -2
  30. package/src/ui/components/primitives/alert.tsx +9 -1
  31. package/src/ui/components/primitives/avatar.tsx +7 -3
  32. package/src/ui/components/primitives/card.tsx +91 -29
  33. package/src/ui/components/primitives/carousel.tsx +34 -5
  34. package/src/ui/components/primitives/checkbox.tsx +11 -3
  35. package/src/ui/components/primitives/command.tsx +14 -5
  36. package/src/ui/components/primitives/copyable.tsx +120 -10
  37. package/src/ui/components/primitives/detail.tsx +10 -13
  38. package/src/ui/components/primitives/dialog.tsx +179 -45
  39. package/src/ui/components/primitives/drawer.tsx +131 -24
  40. package/src/ui/components/primitives/empty.tsx +1 -1
  41. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  42. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  43. package/src/ui/components/primitives/input-group.tsx +9 -2
  44. package/src/ui/components/primitives/input-otp.tsx +1 -1
  45. package/src/ui/components/primitives/kbd.tsx +1 -1
  46. package/src/ui/components/primitives/label.tsx +3 -0
  47. package/src/ui/components/primitives/menu.tsx +5 -0
  48. package/src/ui/components/primitives/metric-card.tsx +98 -39
  49. package/src/ui/components/primitives/popover.tsx +1 -1
  50. package/src/ui/components/primitives/progress.tsx +9 -1
  51. package/src/ui/components/primitives/slider.tsx +62 -11
  52. package/src/ui/components/primitives/truncate.tsx +10 -1
  53. package/src/ui/docs/content/accordion.md +74 -27
  54. package/src/ui/docs/content/action-form-card.md +24 -15
  55. package/src/ui/docs/content/action-form-dialog.md +17 -7
  56. package/src/ui/docs/content/action-form.md +92 -77
  57. package/src/ui/docs/content/action-list-dialog.md +75 -38
  58. package/src/ui/docs/content/action-list.md +134 -52
  59. package/src/ui/docs/content/action-trigger.md +27 -10
  60. package/src/ui/docs/content/action-view.md +14 -14
  61. package/src/ui/docs/content/actions.md +161 -66
  62. package/src/ui/docs/content/ai.md +43 -44
  63. package/src/ui/docs/content/alert.md +83 -29
  64. package/src/ui/docs/content/ask.md +51 -26
  65. package/src/ui/docs/content/aspect-ratio.md +22 -24
  66. package/src/ui/docs/content/audit.md +23 -19
  67. package/src/ui/docs/content/auth.md +38 -19
  68. package/src/ui/docs/content/avatar.md +54 -5
  69. package/src/ui/docs/content/badge.md +29 -9
  70. package/src/ui/docs/content/breadcrumb.md +21 -8
  71. package/src/ui/docs/content/button.md +73 -43
  72. package/src/ui/docs/content/calendar.md +134 -26
  73. package/src/ui/docs/content/card.md +252 -35
  74. package/src/ui/docs/content/carousel.md +37 -6
  75. package/src/ui/docs/content/chat.md +118 -37
  76. package/src/ui/docs/content/checkbox.md +73 -7
  77. package/src/ui/docs/content/cli.md +94 -55
  78. package/src/ui/docs/content/collapsible.md +43 -19
  79. package/src/ui/docs/content/command.md +30 -8
  80. package/src/ui/docs/content/composer.md +23 -8
  81. package/src/ui/docs/content/content.md +37 -7
  82. package/src/ui/docs/content/copyable.md +39 -8
  83. package/src/ui/docs/content/customization.md +55 -46
  84. package/src/ui/docs/content/cycle.md +33 -18
  85. package/src/ui/docs/content/data-products.md +115 -0
  86. package/src/ui/docs/content/data-state.md +20 -6
  87. package/src/ui/docs/content/data.md +20 -19
  88. package/src/ui/docs/content/detail.md +12 -5
  89. package/src/ui/docs/content/dialog.md +219 -28
  90. package/src/ui/docs/content/dictionary-value.md +4 -4
  91. package/src/ui/docs/content/dock.md +54 -19
  92. package/src/ui/docs/content/dot.md +35 -5
  93. package/src/ui/docs/content/drawer.md +193 -22
  94. package/src/ui/docs/content/empty-value.md +20 -6
  95. package/src/ui/docs/content/empty.md +24 -3
  96. package/src/ui/docs/content/events.md +76 -16
  97. package/src/ui/docs/content/executions.md +9 -9
  98. package/src/ui/docs/content/field.md +21 -6
  99. package/src/ui/docs/content/getting-started.md +44 -34
  100. package/src/ui/docs/content/icon-picker.md +19 -13
  101. package/src/ui/docs/content/icon-tile.md +28 -5
  102. package/src/ui/docs/content/input-otp.md +58 -7
  103. package/src/ui/docs/content/input.md +22 -3
  104. package/src/ui/docs/content/item.md +53 -6
  105. package/src/ui/docs/content/kbd.md +5 -1
  106. package/src/ui/docs/content/label.md +13 -4
  107. package/src/ui/docs/content/log.md +5 -5
  108. package/src/ui/docs/content/markdown.md +5 -4
  109. package/src/ui/docs/content/mcp.md +49 -23
  110. package/src/ui/docs/content/menu.md +95 -10
  111. package/src/ui/docs/content/metric-card.md +68 -21
  112. package/src/ui/docs/content/observability.md +10 -9
  113. package/src/ui/docs/content/page.md +125 -11
  114. package/src/ui/docs/content/pagination.md +13 -5
  115. package/src/ui/docs/content/pane.md +300 -0
  116. package/src/ui/docs/content/popover.md +42 -5
  117. package/src/ui/docs/content/presentation.md +91 -47
  118. package/src/ui/docs/content/progress.md +27 -14
  119. package/src/ui/docs/content/queue.md +39 -21
  120. package/src/ui/docs/content/radio-group.md +26 -4
  121. package/src/ui/docs/content/router.md +29 -10
  122. package/src/ui/docs/content/runtime.md +90 -54
  123. package/src/ui/docs/content/scheduler.md +53 -21
  124. package/src/ui/docs/content/scroll-area.md +42 -35
  125. package/src/ui/docs/content/select.md +78 -33
  126. package/src/ui/docs/content/semantic-context.md +21 -2
  127. package/src/ui/docs/content/separator.md +15 -9
  128. package/src/ui/docs/content/sidebar.md +25 -45
  129. package/src/ui/docs/content/skeleton.md +13 -4
  130. package/src/ui/docs/content/slider.md +37 -9
  131. package/src/ui/docs/content/spinner.md +4 -3
  132. package/src/ui/docs/content/split.md +34 -20
  133. package/src/ui/docs/content/storage.md +8 -9
  134. package/src/ui/docs/content/switch.md +10 -7
  135. package/src/ui/docs/content/table.md +46 -8
  136. package/src/ui/docs/content/tabs.md +14 -4
  137. package/src/ui/docs/content/testing.md +76 -31
  138. package/src/ui/docs/content/textarea.md +5 -4
  139. package/src/ui/docs/content/toast.md +48 -7
  140. package/src/ui/docs/content/toggle.md +3 -3
  141. package/src/ui/docs/content/tokens.md +15 -26
  142. package/src/ui/docs/content/tooltip.md +18 -2
  143. package/src/ui/docs/content/truncate.md +36 -6
  144. package/src/ui/docs/content/ui.md +74 -22
  145. package/src/ui/docs/content/upgrading.md +45 -36
  146. package/src/ui/docs/plugin.ts +36 -2
  147. package/src/ui/docs/registry.tsx +14 -2
  148. package/src/ui/drivers/react.tsx +16 -8
  149. package/src/ui/meta.ts +38 -32
  150. package/src/ui/react.tsx +14 -0
  151. package/src/vite/design.ts +16 -4
@@ -4,45 +4,71 @@ title: MCP
4
4
 
5
5
  # MCP
6
6
 
7
- Use o servidor MCP para disponibilizar actions `ai:enabled` a agentes que executam fora da
8
- aplicação. A chamada continua passando por `runtime.execute`, com a mesma validação, autorização e
9
- auditoria; o MCP fornece apenas o transporte.
7
+ Use o servidor MCP do runtime para disponibilizar actions do app a agentes que executam fora da
8
+ aplicação, como o Claude Desktop ou o agente de um parceiro. A chamada passa por `runtime.execute`,
9
+ com a mesma validação, autorização e auditoria de qualquer outra; o MCP fornece apenas o transporte.
10
+
11
+ Este servidor é diferente de `opus mcp`, que atende o agente que trabalha no código do projeto
12
+ durante o desenvolvimento e não executa actions. Veja [CLI opus](/docs/cli).
10
13
 
11
14
  ## Montar
12
15
 
16
+ `createOpusMcpServer` recebe o runtime e devolve um servidor MCP; o app escolhe o transporte e o
17
+ conecta. `resolveContext` recebe os dados da requisição MCP e devolve o contexto do runtime, com
18
+ usuário, tenant e `can`. No transporte HTTP do SDK MCP, os headers da requisição chegam em
19
+ `requestInfo`, e o adapter de autenticação do app pode lê-los:
20
+
13
21
  ```ts
14
22
  import { createOpusMcpServer } from '@softize/opus/mcp'
15
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
23
+ import { jwtAuth } from '@softize/opus/auth/jwt'
24
+
25
+ const auth = jwtAuth({
26
+ secret: process.env.JWT_SECRET!,
27
+ mapUser: (payload) => ({ id: String(payload.sub) }),
28
+ })
29
+
30
+ type McpRequestExtra = {
31
+ requestInfo?: { headers: Record<string, string | string[] | undefined> }
32
+ }
16
33
 
17
34
  const server = createOpusMcpServer(runtime, {
18
35
  name: 'meu-app',
19
- // Valida a identidade da requisição (IdP) → contexto do runtime. Sem isso, roda anônimo
20
- // (sem usuário, só actions `public` rodam). Com isso, o agente age COMO O USUÁRIO.
21
- resolveContext: async (extra) => auth.resolveFromMcp(extra),
36
+ resolveContext: async (extra) => {
37
+ const request = (extra as McpRequestExtra).requestInfo ?? { headers: {} }
38
+ const { user, tenantId, can } = await auth.resolveContext(request)
39
+ return { user, tenantId: tenantId ?? null, can }
40
+ },
22
41
  })
23
42
 
24
- await server.connect(new StdioServerTransport()) // local; HTTP/SSE para remoto
43
+ await server.connect(transport) // StreamableHTTPServerTransport, por exemplo
25
44
  ```
26
45
 
27
- Só actions marcadas com `ai: { enabled: true }` ficam disponíveis, tanto para listar quanto para
28
- executar. `ListTools` devolve cada uma com `name` (identificador técnico), `title` (a `label`
29
- legível, quando existe), descrição, JSON Schema do input e, em `_meta`, os Produtos de Dados
30
- relacionados: `com.softize.opus/data-products` (identificadores) e
31
- `com.softize.opus/data-product-labels` (nome legível por identificador). `CallTool` recusa, com
32
- erro, qualquer nome fora dessa lista; os demais seguem pelo `runtime.execute` — validação,
33
- autorização e audit, tudo igual a uma chamada normal. Read-only? Marque só actions de leitura
34
- com `ai:enabled`: a marcação decide o que o cliente MCP pode executar, e a autorização do
35
- contexto continua valendo por cima.
46
+ Sem `resolveContext`, a chamada é anônima: não há usuário, `can` nega tudo e só actions `public`
47
+ executam. Com ele, o agente age como o usuário identificado. No transporte stdio não há headers;
48
+ a identidade precisa vir do próprio processo, como um token de serviço lido do ambiente.
49
+
50
+ ## O que fica disponível
51
+
52
+ Só actions expostas à IA ficam disponíveis, tanto para listar quanto para executar: as que declaram
53
+ `ai: true` ou um objeto `ai` sem `enabled: false`. `ListTools` devolve cada uma com `name` (o
54
+ identificador técnico), `title` (a `label` legível, quando existe), descrição e JSON Schema da
55
+ entrada. Em `_meta`, cada tool traz os
56
+ [Produtos de Dados](/docs/data-products) relacionados: `com.softize.opus/data-products` lista os
57
+ identificadores e `com.softize.opus/data-product-labels`, o nome legível de cada um.
58
+
59
+ `CallTool` recusa com erro qualquer nome fora dessa lista; os demais seguem por `runtime.execute`.
60
+ A exposição declarada em `ai` decide o que o cliente MCP pode executar, e a autorização do contexto
61
+ continua valendo sobre ela. Para um acesso somente de leitura, exponha apenas actions de leitura.
36
62
 
37
63
  ## Escolher entre integração interna e MCP
38
64
 
39
65
  - **Agente dentro da aplicação:** use `runtime.aiFor(base).run()` para chamar as actions no mesmo
40
- processo.
41
- - **Agente em outro serviço:** use MCP para oferecer a mesma coleção de ferramentas por uma
42
- interface interoperável.
66
+ processo. Veja [IA generativa](/docs/ai).
67
+ - **Agente em outro serviço:** use o servidor MCP para oferecer a mesma coleção de ferramentas por
68
+ uma interface interoperável.
43
69
 
44
70
  ## Limites atuais
45
71
 
46
- O consumidor escolhe e monta o transporte, como stdio ou HTTP. A confirmação de actions destrutivas
47
- ainda não faz parte desta integração; até que esse fluxo exista, exponha somente operações de
48
- leitura.
72
+ O servidor MCP não pede confirmação: uma action com `ai.destructive` ou `ai.requiresConfirmation`
73
+ executa como qualquer outra ferramenta anunciada. Como a mesma exposição em `ai` alimenta o agente
74
+ do app e o MCP, ao montar o servidor MCP exponha à IA somente operações de leitura.
@@ -17,13 +17,58 @@ direita; separe ações perigosas e declare `context="danger"` nelas.
17
17
  </Menu>
18
18
  ```
19
19
 
20
+ `MenuShortcut` só exibe o atalho. Registre a tecla no aplicativo; o menu não a escuta.
21
+
22
+ ## Executar a ação do item
23
+
24
+ `onSelect` recebe a escolha feita por clique ou teclado. Depois dele, o menu fecha. Para manter o
25
+ painel aberto, como em uma alternância que a pessoa pode repetir, chame `event.preventDefault()` no
26
+ handler. `disabled` mantém o item visível, mas impede a seleção.
27
+
28
+ ```tsx preview
29
+ const [pinned, setPinned] = useState(false)
30
+ const [last, setLast] = useState('Nenhuma ação ainda.')
31
+
32
+ render(
33
+ <div className="flex items-center gap-4">
34
+ <Menu>
35
+ <MenuTrigger asChild>
36
+ <Button variant="outline">Sessão <ChevronDown /></Button>
37
+ </MenuTrigger>
38
+ <MenuContent align="start">
39
+ <MenuItem onSelect={() => setLast('Link copiado.')}><Link /> Copiar link</MenuItem>
40
+ <MenuItem
41
+ onSelect={(event) => {
42
+ event.preventDefault()
43
+ setPinned((current) => !current)
44
+ }}
45
+ >
46
+ {pinned ? <PinOff /> : <Pin />} {pinned ? 'Desafixar' : 'Fixar'}
47
+ </MenuItem>
48
+ <MenuItem disabled><Share2 /> Compartilhar</MenuItem>
49
+ </MenuContent>
50
+ </Menu>
51
+ <p className="text-sm text-muted-foreground">{last}</p>
52
+ </div>,
53
+ )
54
+ ```
55
+
56
+ Para navegar em vez de executar, use `asChild` com um link: o item passa a renderizar o `<a>`,
57
+ com o estilo e a navegação por teclado do menu.
58
+
59
+ ```tsx
60
+ <MenuItem asChild>
61
+ <a href="/sessoes/42">Abrir sessão</a>
62
+ </MenuItem>
63
+ ```
64
+
20
65
  ## Abrir pelo menu de contexto
21
66
 
22
67
  Para abrir pelo clique direito, controle o estado do menu e use a posição do ponteiro como âncora.
23
- Esse padrão substitui o antigo `ContextMenu` sem introduzir outra família de componentes.
68
+ Não há componente separado para menu de contexto; use `Menu` controlado.
24
69
 
25
70
  ```tsx preview
26
- const [pos, setPos] = useState(null)
71
+ const [pos, setPos] = useState<{ x: number; y: number } | null>(null)
27
72
 
28
73
  render(
29
74
  <div>
@@ -34,7 +79,7 @@ render(
34
79
  }}
35
80
  className="flex h-28 items-center justify-center rounded-md border border-dashed text-sm text-muted-foreground select-none"
36
81
  >
37
- clique direito aqui
82
+ Clique com o botão direito aqui
38
83
  </div>
39
84
  <Menu open={pos !== null} onOpenChange={(open) => { if (!open) setPos(null) }}>
40
85
  <MenuTrigger asChild>
@@ -83,7 +128,8 @@ render(
83
128
 
84
129
  ## Submenu e inset
85
130
 
86
- MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
131
+ `MenuSub` aninha um nível: `MenuSubTrigger` abre o painel `MenuSubContent` ao lado do item. `inset`
132
+ alinha itens sem ícone com os que têm.
87
133
 
88
134
  ```tsx preview
89
135
  <Menu>
@@ -93,7 +139,7 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
93
139
  <MenuContent align="start">
94
140
  <MenuItem><ExternalLink /> Abrir preview</MenuItem>
95
141
  <MenuSub>
96
- <MenuSubTrigger><ArrowDownAZ /> Mover para</MenuSubTrigger>
142
+ <MenuSubTrigger><FolderInput /> Mover para</MenuSubTrigger>
97
143
  <MenuSubContent>
98
144
  <MenuItem>Empresa X</MenuItem>
99
145
  <MenuItem>Softize</MenuItem>
@@ -104,6 +150,10 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
104
150
  </Menu>
105
151
  ```
106
152
 
153
+ `MenuContent` já renderiza o painel em um portal, fora do fluxo da página. `MenuPortal` continua
154
+ exportado, mas está depreciado e sai em uma próxima versão maior: envolver o conteúdo com ele não
155
+ muda onde o painel aparece. Código novo usa só `MenuContent`.
156
+
107
157
  ## Propriedades de Menu
108
158
 
109
159
  | Propriedade | Tipo | Padrão | Descrição |
@@ -112,10 +162,26 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
112
162
  | `onOpenChange` | `(open: boolean) => void` | | Chamado quando o painel abre ou fecha. |
113
163
  | `defaultOpen` | `boolean` | `false` | Estado inicial no modo não controlado. |
114
164
 
165
+ ## Propriedades de MenuTrigger
166
+
167
+ | Propriedade | Tipo | Padrão | Descrição |
168
+ |---|---|---|---|
169
+ | `asChild` | `boolean` | `false` | Usa o filho, como um `Button`, como gatilho em vez de renderizar outro `<button>`. |
170
+
171
+ ## Propriedades de MenuContent
172
+
173
+ | Propriedade | Tipo | Padrão | Descrição |
174
+ |---|---|---|---|
175
+ | `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alinhamento do painel em relação ao gatilho. |
176
+ | `sideOffset` | `number` | `4` | Distância entre o gatilho e o painel. |
177
+
115
178
  ## Propriedades de MenuItem
116
179
 
117
180
  | Propriedade | Tipo | Padrão | Descrição |
118
181
  |---|---|---|---|
182
+ | `onSelect` | `(event: Event) => void` | | Chamado quando a pessoa escolhe o item por clique ou teclado. `event.preventDefault()` mantém o menu aberto. |
183
+ | `disabled` | `boolean` | `false` | Mantém o item visível e impede a seleção. |
184
+ | `asChild` | `boolean` | `false` | Renderiza o filho, como um `<a>`, com o comportamento de item de menu. |
119
185
  | `context` | `'neutral' \| 'danger'` | `'neutral'` | `danger` sinaliza uma ação com consequência perigosa. |
120
186
  | `inset` | `boolean` | | Alinha um item sem ícone com os itens que possuem ícone. |
121
187
 
@@ -125,12 +191,11 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
125
191
  `MenuLabel` para nomear o grupo e `MenuSeparator` para separá-lo do próximo. Não tem props
126
192
  próprias além das de DOM.
127
193
 
128
- ## Propriedades de MenuContent
194
+ ## Propriedades de MenuLabel
129
195
 
130
196
  | Propriedade | Tipo | Padrão | Descrição |
131
197
  |---|---|---|---|
132
- | `align` | `'start' \| 'center' \| 'end'` | `'center'` | Alinhamento do painel em relação ao gatilho. |
133
- | `sideOffset` | `number` | `4` | Distância entre o gatilho e o painel. |
198
+ | `inset` | `boolean` | | Alinha o rótulo com itens que possuem ícone. |
134
199
 
135
200
  ## Propriedades de MenuCheckboxItem
136
201
 
@@ -153,14 +218,34 @@ próprias além das de DOM.
153
218
  | `value` | `string` | | Valor que o item entrega ao `MenuRadioGroup` quando é selecionado. |
154
219
  | `disabled` | `boolean` | `false` | Impede a seleção do item. |
155
220
 
156
- ## Propriedades de MenuLabel
221
+ ## Propriedades de MenuSeparator
222
+
223
+ `MenuSeparator` desenha a linha entre grupos de itens. Não tem props próprias; aceita as de um
224
+ elemento `div`, como `className`.
225
+
226
+ ## Propriedades de MenuShortcut
227
+
228
+ `MenuShortcut` exibe o atalho no extremo direito do item. Não tem props próprias; aceita as de um
229
+ elemento `span`, como `className`.
230
+
231
+ ## Propriedades de MenuSub
157
232
 
158
233
  | Propriedade | Tipo | Padrão | Descrição |
159
234
  |---|---|---|---|
160
- | `inset` | `boolean` | | Alinha o rótulo com itens que possuem ícone. |
235
+ | `open` | `boolean` | | Estado do submenu no modo controlado. |
236
+ | `onOpenChange` | `(open: boolean) => void` | | Chamado quando o submenu abre ou fecha. |
237
+ | `defaultOpen` | `boolean` | `false` | Estado inicial no modo não controlado. |
161
238
 
162
239
  ## Propriedades de MenuSubTrigger
163
240
 
164
241
  | Propriedade | Tipo | Padrão | Descrição |
165
242
  |---|---|---|---|
166
243
  | `inset` | `boolean` | | Alinha o gatilho do submenu sem ícone com os itens que possuem ícone. |
244
+ | `disabled` | `boolean` | `false` | Impede que o submenu abra. |
245
+
246
+ ## Propriedades de MenuSubContent
247
+
248
+ | Propriedade | Tipo | Padrão | Descrição |
249
+ |---|---|---|---|
250
+ | `sideOffset` | `number` | `0` | Distância entre o item que abre o submenu e o painel. |
251
+ | `alignOffset` | `number` | `0` | Deslocamento do painel ao longo do item que o abre. |
@@ -1,6 +1,6 @@
1
- MetricCard apresenta uma medida resumida com hierarquia e espaçamento consistentes. Passe o
2
- valor já calculado e formatado; busca de dados, recorte e regras de negócio permanecem no
3
- consumidor.
1
+ Use `MetricCard` para apresentar uma medida resumida, como um total ou uma contagem, com rótulo,
2
+ valor e contexto em hierarquia consistente. Passe o valor já calculado e formatado; busca de dados,
3
+ recorte e regras de negócio permanecem no consumidor.
4
4
 
5
5
  ```tsx preview col
6
6
  <MetricCard
@@ -45,7 +45,9 @@ apresente dois cards independentes.
45
45
  labelAction={
46
46
  <Tooltip>
47
47
  <TooltipTrigger asChild>
48
- <Button size="icon-xs" variant="ghost" aria-label="Sobre Agendados"><Info /></Button>
48
+ <Button size="icon-xs" variant="ghost" aria-label="Sobre Agendados">
49
+ <Info />
50
+ </Button>
49
51
  </TooltipTrigger>
50
52
  <TooltipContent>Eventos atualmente agendados.</TooltipContent>
51
53
  </Tooltip>
@@ -56,7 +58,9 @@ apresente dois cards independentes.
56
58
  labelAction: (
57
59
  <Tooltip>
58
60
  <TooltipTrigger asChild>
59
- <Button size="icon-xs" variant="ghost" aria-label="Sobre o período"><Info /></Button>
61
+ <Button size="icon-xs" variant="ghost" aria-label="Sobre o período">
62
+ <Info />
63
+ </Button>
60
64
  </TooltipTrigger>
61
65
  <TooltipContent>Agendamentos registrados dentro do período.</TooltipContent>
62
66
  </Tooltip>
@@ -65,6 +69,43 @@ apresente dois cards independentes.
65
69
  />
66
70
  ```
67
71
 
72
+ ## Como filtro ou seleção
73
+
74
+ Use a ativação controlada quando escolher uma métrica também precisar aplicar um recorte. O card
75
+ expõe o estado por `aria-pressed` e preserva controles complementares, como o tooltip do rótulo,
76
+ sem disparar a ativação.
77
+
78
+ ```tsx preview col
79
+ const [metric, setMetric] = useState('scheduled')
80
+
81
+ render(
82
+ <div className="grid grid-cols-2 gap-3">
83
+ <MetricCard
84
+ label="Agendados"
85
+ value="18"
86
+ active={metric === 'scheduled'}
87
+ activationLabel="Filtrar por Agendados"
88
+ onActivate={() => setMetric('scheduled')}
89
+ />
90
+ <MetricCard
91
+ label="Efetivados"
92
+ value="11"
93
+ active={metric === 'completed'}
94
+ activationLabel="Filtrar por Efetivados"
95
+ onActivate={() => setMetric('completed')}
96
+ />
97
+ </div>,
98
+ )
99
+ ```
100
+
101
+ `active`, `activationLabel` e `onActivate` formam uma capacidade única: informe as três propriedades
102
+ para tornar o card ativável. O consumidor continua responsável pelo estado e pelo recorte aplicado.
103
+
104
+ O card ativo recebe a borda na cor primária. Ao passar o ponteiro, a borda se destaca, e o foco de
105
+ teclado desenha um anel por dentro da borda. Com ativação, use `onActivate` em vez de `onClick`: o
106
+ tipo recusa `onClick`, porque um clique genérico no card também dispararia ao usar `labelAction` ou
107
+ `action`. Enquanto `loading` estiver ativo, a ativação fica desabilitada.
108
+
68
109
  ## Carregamento
69
110
 
70
111
  `loading` preserva a geometria do card com placeholders e marca a superfície como ocupada para
@@ -76,22 +117,28 @@ tecnologias assistivas. O contêiner da coleção continua responsável pela men
76
117
 
77
118
  ## Propriedades de MetricCard
78
119
 
79
- | Propriedade | Tipo | Padrão | Descrição |
80
- |---|---|---|---|
81
- | `label` | `ReactNode` | | O nome da medida. |
82
- | `value` | `ReactNode` | | O valor já formatado pelo consumidor. |
83
- | `loading` | `boolean` | `false` | Substitui rótulo e valor por skeletons e marca `aria-busy`. |
84
- | `labelAction` | `ReactNode` | | Controle ao lado do rótulo, como uma explicação em tooltip. |
85
- | `description` | `ReactNode` | | Contexto que explica recorte, proporção ou significado do valor. |
86
- | `secondaryMetric` | `MetricCardSecondaryMetric` | | Medida complementar exibida como uma lâmina posterior sob o card principal. |
87
- | `icon` | `ReactNode` | | Ícone decorativo que identifica a natureza da medida. |
88
- | `context` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Contexto semântico do ícone; não altera a superfície do card. |
89
- | `action` | `ReactNode` | | Ação relacionada diretamente à medida. |
120
+ | Propriedade | Tipo | Padrão | Descrição |
121
+ | ----------------- | ----------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------- |
122
+ | `label` | `ReactNode` | | O nome da medida. |
123
+ | `value` | `ReactNode` | | O valor já formatado pelo consumidor. |
124
+ | `loading` | `boolean` | `false` | Substitui rótulo e valor por skeletons e marca `aria-busy`. |
125
+ | `labelAction` | `ReactNode` | | Controle ao lado do rótulo, como uma explicação em tooltip. |
126
+ | `description` | `ReactNode` | | Contexto que explica recorte, proporção ou significado do valor. |
127
+ | `secondaryMetric` | `MetricCardSecondaryMetric` | | Medida complementar exibida como uma lâmina posterior sob o card principal. |
128
+ | `icon` | `ReactNode` | | Ícone decorativo que identifica a natureza da medida. |
129
+ | `context` | `'neutral' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'neutral'` | Contexto semântico do ícone; não altera a superfície do card. |
130
+ | `action` | `ReactNode` | | Ação relacionada diretamente à medida. |
131
+ | `active` | `boolean` | | Indica se a medida controla o recorte atualmente aplicado. Exige `activationLabel` e `onActivate`. |
132
+ | `activationLabel` | `string` | | Nome acessível da ativação. Exige `active` e `onActivate`. |
133
+ | `onActivate` | `() => void` | | Ativa a medida como recorte ou seleção. Exige `active` e `activationLabel`; não combina com `onClick`. Desabilitada durante `loading`. |
134
+ | `className` | `string` | | Ajusta o card, ou o contêiner quando há ativação ou medida complementar. |
135
+
136
+ As demais propriedades são as nativas de `div`.
90
137
 
91
138
  ## Propriedades de MetricCardSecondaryMetric
92
139
 
93
- | Propriedade | Tipo | Padrão | Descrição |
94
- |---|---|---|---|
95
- | `label` | `ReactNode` | | O nome da medida complementar. |
96
- | `value` | `ReactNode` | | O valor complementar já formatado pelo consumidor. |
97
- | `labelAction` | `ReactNode` | | Controle próprio ao lado do rótulo, como uma explicação em tooltip. |
140
+ | Propriedade | Tipo | Padrão | Descrição |
141
+ | ------------- | ----------- | ------ | ------------------------------------------------------------------- |
142
+ | `label` | `ReactNode` | | O nome da medida complementar. |
143
+ | `value` | `ReactNode` | | O valor complementar já formatado pelo consumidor. |
144
+ | `labelAction` | `ReactNode` | | Controle próprio ao lado do rótulo, como uma explicação em tooltip. |
@@ -37,20 +37,21 @@ global. Um `Tracer` também pode ser injetado para testes ou providers não glob
37
37
 
38
38
  ## Semântica
39
39
 
40
- - nomes de span: `opus.action <action>` e `opus.reaction <reaction>`;
41
- - parent vem de `ContextBase.trace` ou do evento recebido;
42
- - actions usam `ActionResult.ok` para status; reactions usam resolve/reject;
43
- - somente atributos allowlisted de baixa cardinalidade são anexados;
44
- - input, output, actor, tenant, request ID, error code/message e baggage não entram no span;
40
+ - Os spans se chamam `opus.action <action>` e `opus.reaction <reaction>`.
41
+ - O span pai vem de `ContextBase.trace` ou do evento recebido.
42
+ - Actions usam `ActionResult.ok` para o status; reactions usam a resolução ou a rejeição da Promise.
43
+ - Somente atributos de baixa cardinalidade, de uma lista fixa, são anexados.
44
+ - Input, output, ator, tenant, request ID, código e mensagem de erro e baggage não entram no span.
45
45
  - `TraceContext` devolve apenas `traceId`, `spanId`, `traceFlags` e `traceState`.
46
46
 
47
47
  ## Fronteira HTTP W3C
48
48
 
49
- Os drivers Fastify e Node oferecem propagação explícita, desativada por default:
49
+ Os drivers Fastify e Node oferecem propagação explícita, desativada por padrão:
50
50
 
51
51
  ```ts
52
- fastifyServer({ app, traceContext: 'w3c' })
53
- nodeServer({ traceContext: 'w3c' })
52
+ const server = fastifyServer({ app, traceContext: 'w3c' })
53
+ // nodeServer devolve { adapter, handler, hasRoute }; o runtime recebe `adapter`.
54
+ const node = nodeServer({ traceContext: 'w3c' })
54
55
  ```
55
56
 
56
57
  O opt-in extrai um `traceparent` v00 válido (e `tracestate` limitado) para
@@ -65,7 +66,7 @@ O driver OpenTelemetry não instrumenta HTTP automaticamente e não oferece back
65
66
  Use o opt-in dos drivers Fastify/Node acima ou forneça `ContextBase.trace` na instrumentação
66
67
  de outro transporte. A fronteira implementa somente `traceparent` v00 e uma validação
67
68
  defensiva limitada de `tracestate`; não é um propagator OTel genérico.
68
- Health do exporter/Collector só é reportado quando o app injeta `healthCheck`; o default
69
+ A saúde do exporter e do Collector só é reportada quando o app injeta `healthCheck`; o padrão
69
70
  confirma apenas que a API do driver está configurada.
70
71
  Sem um provider que produza spans válidos, o driver falha antes do callback e o runtime aplica
71
72
  a degradação lenient — executa a action uma vez, sem trace — em vez de propagar IDs inválidos.
@@ -13,9 +13,15 @@ A forma curta é o padrão para páginas comuns. Fora de `PageShell`, ela cria `
13
13
  `PageBody`. Dentro dele, cria `PageHeader` para as ações, `PageIntro` para o título e `PageBody`.
14
14
  A forma explícita exige exatamente um heading principal. Use `PageTitle` no header ou intro,
15
15
  `Content level={1}` no body, ou deixe um `PageState` ativo assumir esse papel. Assim, header e
16
- introdução podem ser omitidos quando o próprio conteúdo nomeia a página.
17
- Contadores e outros indicadores pertencem ao
18
- conteúdo que os explica.
16
+ introdução podem ser omitidos quando o próprio conteúdo nomeia a página. Contadores e outros
17
+ indicadores pertencem ao conteúdo que os explica.
18
+
19
+ A `Page` é contínua por definição: separar o chrome do conteúdo é trabalho do shell, e não há
20
+ divisores entre regiões para declarar. O `layout` da `Page` não é a anatomia de superfície de
21
+ [`Card`](/ui/card), [`Dialog`](/ui/dialog), [`Drawer`](/ui/drawer) e [`Pane`](/ui/pane) — aqui ele
22
+ decide a ocupação de altura. `flow`, o padrão, deixa a página crescer e a rolagem acontecer na
23
+ janela; `fill` limita a página à altura do shell para que uma região interna, como uma tabela longa
24
+ ou um split, assuma a rolagem.
19
25
 
20
26
  `PageShell` não inclui sidebar nem inventa breadcrumb. Ele ocupa o painel principal já delimitado e
21
27
  mantém a barra com `3.75rem`, mesmo quando o conteúdo muda de estado. Tabs ficam reservados a recortes
@@ -44,7 +50,8 @@ render(
44
50
 
45
51
  Use `PageBack` em uma subpágina simples. O destino é explícito para continuar correto após refresh ou
46
52
  acesso por link direto; não derive esse retorno do histórico do navegador. No header padrão, o
47
- controle aparece antes do título como ícone com nome acessível e tooltip derivados do destino.
53
+ controle aparece antes do título como ícone; o nome acessível e a dica nativa do navegador (atributo
54
+ `title`) derivam do destino.
48
55
 
49
56
  ```tsx preview col
50
57
  <Page>
@@ -57,8 +64,30 @@ controle aparece antes do título como ícone com nome acessível e tooltip deri
57
64
  ```
58
65
 
59
66
  Não combine `PageBack` com breadcrumb. Use o retorno para um único pai conhecido. Quando houver
60
- mais de um ancestral relevante, envolva o `Breadcrumb` em `PageNavigation`; ele ocupa a mesma
61
- posição introdutória sem transformar a trilha em ação.
67
+ mais de um ancestral relevante, envolva o [`Breadcrumb`](/ui/breadcrumb) em `PageNavigation`; ele
68
+ ocupa a mesma posição introdutória sem transformar a trilha em ação.
69
+
70
+ ```tsx preview col
71
+ <Page>
72
+ <PageHeader>
73
+ <PageNavigation>
74
+ <Breadcrumb>
75
+ <BreadcrumbList>
76
+ <BreadcrumbItem>Vendas</BreadcrumbItem>
77
+ <BreadcrumbSeparator />
78
+ <BreadcrumbItem>Clientes</BreadcrumbItem>
79
+ <BreadcrumbSeparator />
80
+ <BreadcrumbPage>Carla Souza</BreadcrumbPage>
81
+ </BreadcrumbList>
82
+ </Breadcrumb>
83
+ </PageNavigation>
84
+ </PageHeader>
85
+ <PageIntro>
86
+ <PageTitle>Carla Souza</PageTitle>
87
+ </PageIntro>
88
+ <PageBody>Dados do cliente.</PageBody>
89
+ </Page>
90
+ ```
62
91
 
63
92
  ## Barra persistente do shell
64
93
 
@@ -142,8 +171,87 @@ render(
142
171
 
143
172
  Fora de `PageShell`, use `PageIntro` quando o conteúdo precisar de uma introdução separada do
144
173
  chrome. Dentro do shell, ele pode ser combinado com `PageHeader`: o primeiro permanece no conteúdo
145
- e o segundo alimenta a barra. Em uma página de listagem, `Content` pode fornecer o título e as ações
146
- da coleção, deixando `Page` apenas com `PageBody`.
174
+ e o segundo alimenta a barra. Em uma página de listagem, [`Content`](/ui/content) pode fornecer o
175
+ título e as ações da coleção, deixando `Page` apenas com `PageBody`.
176
+
177
+ ## Ações de etapa no rodapé
178
+
179
+ Use `PageFooter` quando a página for uma etapa de um fluxo e a decisão precisar ficar ao alcance ao
180
+ fim do conteúdo, como confirmar uma importação. O rodapé fica depois do body, com a mesma altura da
181
+ barra e as ações alinhadas no fim da linha.
182
+
183
+ ```tsx preview col
184
+ <Page>
185
+ <PageHeader>
186
+ <PageTitle>Importar clientes</PageTitle>
187
+ </PageHeader>
188
+ <PageBody>Revise os 128 registros antes de confirmar.</PageBody>
189
+ <PageFooter>
190
+ <ButtonGroup mode="spaced">
191
+ <Button variant="ghost">Cancelar</Button>
192
+ <Button>Importar</Button>
193
+ </ButtonGroup>
194
+ </PageFooter>
195
+ </Page>
196
+ ```
197
+
198
+ ## Ocupar a altura disponível
199
+
200
+ Use `layout="fill"` quando uma região interna, como uma tabela longa ou um split, precisar rolar
201
+ dentro da página em vez de a página inteira rolar. A página passa a ocupar exatamente a altura do
202
+ contêiner. Para uma região que você mesmo compõe, dê `min-h-0 flex-1` à que assume a folga e a
203
+ rolagem, como o `PageBody` do exemplo.
204
+
205
+ ```tsx preview col
206
+ render(
207
+ <div className="h-72 overflow-hidden rounded-lg border border-border">
208
+ <Page layout="fill">
209
+ <PageHeader>
210
+ <PageTitle>Ordens de serviço</PageTitle>
211
+ </PageHeader>
212
+ <PageBody className="min-h-0 flex-1 space-y-2 overflow-y-auto">
213
+ {['OS-1842', 'OS-1839', 'OS-1835', 'OS-1830', 'OS-1827', 'OS-1821', 'OS-1818'].map((id) => (
214
+ <div key={id} className="rounded-md border border-border p-3 text-sm">
215
+ {id}
216
+ </div>
217
+ ))}
218
+ </PageBody>
219
+ </Page>
220
+ </div>,
221
+ );
222
+ ```
223
+
224
+ Uma [`ActionList`](/ui/action-list) não precisa dessas classes. Filha de `PageBody`, ou de um
225
+ [`Content`](/ui/content) dentro dele, ela recebe a altura da página e assume `layout="fill"`
226
+ sozinha: `PageBody`, `Content` e `ContentBody` passam a repassar a altura até ela, o cabeçalho da
227
+ tabela fica fixo e só os resultados rolam. As demais seções do `PageBody` mantêm a altura do
228
+ próprio conteúdo, e uma página `fill` sem lista não muda.
229
+
230
+ ## Levar as ações para outra barra
231
+
232
+ `PageShell` já projeta as ações na barra persistente. Use `PageActionsTarget` somente quando um
233
+ hospedeiro próprio tiver outra barra para elas: as `PageActions` da árvore interna passam a ser
234
+ renderizadas no elemento indicado em `target`. Enquanto `target` for `null`, fora de um shell, as
235
+ ações continuam no cabeçalho da página.
236
+
237
+ ```tsx preview col
238
+ function Example() {
239
+ const [target, setTarget] = useState<HTMLElement | null>(null)
240
+
241
+ return (
242
+ <div className="space-y-3">
243
+ <div ref={setTarget} className="flex justify-end rounded-lg border border-border p-2" />
244
+ <PageActionsTarget target={target}>
245
+ <Page title="Clientes" actions={<Button variant="outline">Exportar</Button>}>
246
+ Conteúdo da listagem.
247
+ </Page>
248
+ </PageActionsTarget>
249
+ </div>
250
+ )
251
+ }
252
+
253
+ render(<Example />)
254
+ ```
147
255
 
148
256
  ## Estados integrais
149
257
 
@@ -188,7 +296,8 @@ e, sem `title`, valem `errorMessage` e `emptyMessage`.
188
296
  ```
189
297
 
190
298
  Não use `PageState` para uma falha parcial. Se outra parte da página continua utilizável, mantenha o
191
- estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert`.
299
+ estado na própria seção com [`DataState`](/ui/data-state), [`ActionView`](/ui/action-view),
300
+ [`ActionList`](/ui/action-list) ou [`Alert`](/ui/alert).
192
301
 
193
302
  ## Propriedades de Page
194
303
 
@@ -196,8 +305,10 @@ estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert
196
305
  | ----------- | ----------- | ----------- | ----------------------------------------------------------------------------------------------- |
197
306
  | `title` | `ReactNode` | | O h1 da página. |
198
307
  | `actions` | `ReactNode` | | Ações contextuais no extremo oposto do cabeçalho. |
308
+ | `layout` | `'flow' \| 'fill'` | `'flow'` | Ocupação de altura, não anatomia: `fill` limita a página ao shell para que uma região interna role e leva essa altura até uma `ActionList` composta em `PageBody`. |
199
309
  | `className` | `string` | `max-w-7xl` | Classes do container para substituir o teto padrão de `80rem`. |
200
310
  | `children` | `ReactNode` | | O body da página — espaçamento e diagramação são seus. |
311
+ | demais | Atributos de `HTMLElement` | | Atributos nativos repassados ao elemento `main`, como `id` ou `aria-label`. `className` vai para o container interno. |
201
312
 
202
313
  ## Propriedades de PageShell
203
314
 
@@ -234,7 +345,7 @@ que o shell projeta na barra, e ignora `className` e os demais atributos.
234
345
  | Propriedade | Tipo | Padrão | Descrição |
235
346
  | ------------ | -------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------ |
236
347
  | `href` | `string` | obrigatório | Destino explícito da página pai. Sem ele o retorno não é tabulável nem tem nome acessível. |
237
- | `children` | `ReactNode` | obrigatório | Nome do destino pai usado no rótulo acessível e no tooltip. |
348
+ | `children` | `ReactNode` | obrigatório | Nome do destino pai usado no rótulo acessível e na dica nativa (`title`). |
238
349
  | `aria-label` | `string` | `Voltar para {children}` | Nome acessível; informe-o quando `children` não for texto simples. |
239
350
  | `onClick` | `MouseEventHandler<HTMLAnchorElement>` | | Integração opcional com o roteador do consumidor, junto do `href`, nunca no lugar dele. |
240
351
  | `className` | `string` | | Classes adicionais do link renderizado como botão `ghost`. |
@@ -251,7 +362,7 @@ que o shell projeta na barra, e ignora `className` e os demais atributos.
251
362
 
252
363
  | Propriedade | Tipo | Descrição |
253
364
  | ----------- | --------------------------------- | ----------------------------------------------- |
254
- | `children` | `ReactNode` | Título principal `h1`; ampliado em `PageIntro`. |
365
+ | `children` | `ReactNode` | Título principal `h1`, com a mesma escala em `PageHeader` e `PageIntro`. |
255
366
  | `className` | `string` | Classes adicionais do título. |
256
367
  | demais | Atributos de `HTMLHeadingElement` | Atributos nativos repassados ao heading. |
257
368
 
@@ -279,6 +390,9 @@ que o shell projeta na barra, e ignora `className` e os demais atributos.
279
390
  | `className` | `string` | Classes adicionais da região principal. |
280
391
  | demais | Atributos de `HTMLDivElement` | Atributos nativos repassados ao container. |
281
392
 
393
+ Em uma `Page` com `layout="fill"`, o `PageBody` vira coluna flexível sozinho quando contém uma `ActionList` que
394
+ ocupa a altura.
395
+
282
396
  ## Propriedades de PageActionsTarget
283
397
 
284
398
  | Propriedade | Tipo | Descrição |