@softize/opus 19.0.0 → 20.1.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 (148) hide show
  1. package/CHANGELOG.md +239 -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/0020-icon-tiles-are-explicitly-composed.md +8 -0
  7. package/docs/adr/0021-content-carries-structure-and-semantic-element.md +7 -0
  8. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +19 -1
  9. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  10. package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +134 -0
  11. package/docs/data-products.md +15 -9
  12. package/docs/protocol.md +3 -4
  13. package/docs/releasing.md +2 -2
  14. package/package.json +2 -2
  15. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  16. package/registry/templates/app/package.json +1 -1
  17. package/src/core/runtime.ts +96 -8
  18. package/src/core/types.ts +5 -2
  19. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  20. package/src/ui/components/patterns/content-header.tsx +109 -18
  21. package/src/ui/components/patterns/fill-region.ts +150 -0
  22. package/src/ui/components/patterns/list.tsx +91 -92
  23. package/src/ui/components/patterns/page.tsx +20 -4
  24. package/src/ui/components/patterns/presentation.tsx +52 -37
  25. package/src/ui/components/patterns/split.tsx +44 -4
  26. package/src/ui/components/patterns/surface-anatomy.ts +35 -8
  27. package/src/ui/components/patterns/surface-header.tsx +58 -25
  28. package/src/ui/components/patterns/trigger.tsx +12 -2
  29. package/src/ui/components/primitives/alert.tsx +9 -1
  30. package/src/ui/components/primitives/avatar.tsx +7 -3
  31. package/src/ui/components/primitives/card.tsx +1 -1
  32. package/src/ui/components/primitives/carousel.tsx +34 -5
  33. package/src/ui/components/primitives/checkbox.tsx +11 -3
  34. package/src/ui/components/primitives/command.tsx +2 -1
  35. package/src/ui/components/primitives/copyable.tsx +120 -10
  36. package/src/ui/components/primitives/dialog.tsx +42 -11
  37. package/src/ui/components/primitives/drawer.tsx +10 -2
  38. package/src/ui/components/primitives/empty.tsx +1 -1
  39. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  40. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  41. package/src/ui/components/primitives/input-group.tsx +9 -2
  42. package/src/ui/components/primitives/input-otp.tsx +1 -1
  43. package/src/ui/components/primitives/kbd.tsx +1 -1
  44. package/src/ui/components/primitives/label.tsx +3 -0
  45. package/src/ui/components/primitives/menu.tsx +5 -0
  46. package/src/ui/components/primitives/popover.tsx +1 -1
  47. package/src/ui/components/primitives/progress.tsx +9 -1
  48. package/src/ui/components/primitives/slider.tsx +62 -11
  49. package/src/ui/components/primitives/truncate.tsx +10 -1
  50. package/src/ui/docs/content/accordion.md +74 -27
  51. package/src/ui/docs/content/action-form-card.md +24 -15
  52. package/src/ui/docs/content/action-form-dialog.md +17 -7
  53. package/src/ui/docs/content/action-form.md +92 -77
  54. package/src/ui/docs/content/action-list-dialog.md +75 -38
  55. package/src/ui/docs/content/action-list.md +141 -58
  56. package/src/ui/docs/content/action-trigger.md +27 -10
  57. package/src/ui/docs/content/action-view.md +14 -14
  58. package/src/ui/docs/content/actions.md +161 -66
  59. package/src/ui/docs/content/ai.md +43 -44
  60. package/src/ui/docs/content/alert.md +83 -29
  61. package/src/ui/docs/content/ask.md +51 -26
  62. package/src/ui/docs/content/aspect-ratio.md +22 -24
  63. package/src/ui/docs/content/audit.md +23 -19
  64. package/src/ui/docs/content/auth.md +38 -19
  65. package/src/ui/docs/content/avatar.md +54 -5
  66. package/src/ui/docs/content/badge.md +29 -9
  67. package/src/ui/docs/content/breadcrumb.md +21 -8
  68. package/src/ui/docs/content/button.md +73 -43
  69. package/src/ui/docs/content/calendar.md +134 -26
  70. package/src/ui/docs/content/card.md +41 -15
  71. package/src/ui/docs/content/carousel.md +37 -6
  72. package/src/ui/docs/content/chat.md +118 -37
  73. package/src/ui/docs/content/checkbox.md +73 -7
  74. package/src/ui/docs/content/cli.md +94 -55
  75. package/src/ui/docs/content/collapsible.md +43 -19
  76. package/src/ui/docs/content/command.md +30 -8
  77. package/src/ui/docs/content/composer.md +23 -8
  78. package/src/ui/docs/content/content.md +119 -27
  79. package/src/ui/docs/content/copyable.md +39 -8
  80. package/src/ui/docs/content/customization.md +55 -46
  81. package/src/ui/docs/content/cycle.md +33 -18
  82. package/src/ui/docs/content/data-products.md +115 -0
  83. package/src/ui/docs/content/data-state.md +20 -6
  84. package/src/ui/docs/content/data.md +20 -19
  85. package/src/ui/docs/content/detail.md +12 -5
  86. package/src/ui/docs/content/dialog.md +107 -24
  87. package/src/ui/docs/content/dictionary-value.md +4 -4
  88. package/src/ui/docs/content/dock.md +54 -19
  89. package/src/ui/docs/content/dot.md +35 -5
  90. package/src/ui/docs/content/drawer.md +63 -19
  91. package/src/ui/docs/content/empty-value.md +20 -6
  92. package/src/ui/docs/content/empty.md +24 -3
  93. package/src/ui/docs/content/events.md +76 -16
  94. package/src/ui/docs/content/executions.md +9 -9
  95. package/src/ui/docs/content/field.md +21 -6
  96. package/src/ui/docs/content/getting-started.md +44 -34
  97. package/src/ui/docs/content/icon-picker.md +19 -13
  98. package/src/ui/docs/content/icon-tile.md +38 -15
  99. package/src/ui/docs/content/input-otp.md +58 -7
  100. package/src/ui/docs/content/input.md +22 -3
  101. package/src/ui/docs/content/item.md +53 -6
  102. package/src/ui/docs/content/kbd.md +5 -1
  103. package/src/ui/docs/content/label.md +13 -4
  104. package/src/ui/docs/content/log.md +5 -5
  105. package/src/ui/docs/content/markdown.md +5 -4
  106. package/src/ui/docs/content/mcp.md +49 -23
  107. package/src/ui/docs/content/menu.md +95 -10
  108. package/src/ui/docs/content/metric-card.md +12 -4
  109. package/src/ui/docs/content/observability.md +10 -9
  110. package/src/ui/docs/content/page.md +118 -12
  111. package/src/ui/docs/content/pagination.md +13 -5
  112. package/src/ui/docs/content/pane.md +55 -5
  113. package/src/ui/docs/content/popover.md +42 -5
  114. package/src/ui/docs/content/presentation.md +91 -47
  115. package/src/ui/docs/content/progress.md +27 -14
  116. package/src/ui/docs/content/queue.md +39 -21
  117. package/src/ui/docs/content/radio-group.md +26 -4
  118. package/src/ui/docs/content/router.md +29 -10
  119. package/src/ui/docs/content/runtime.md +90 -54
  120. package/src/ui/docs/content/scheduler.md +53 -21
  121. package/src/ui/docs/content/scroll-area.md +42 -35
  122. package/src/ui/docs/content/select.md +78 -33
  123. package/src/ui/docs/content/semantic-context.md +21 -2
  124. package/src/ui/docs/content/separator.md +15 -9
  125. package/src/ui/docs/content/sidebar.md +3 -2
  126. package/src/ui/docs/content/skeleton.md +6 -1
  127. package/src/ui/docs/content/slider.md +37 -9
  128. package/src/ui/docs/content/spinner.md +4 -3
  129. package/src/ui/docs/content/split.md +19 -0
  130. package/src/ui/docs/content/storage.md +8 -9
  131. package/src/ui/docs/content/switch.md +10 -7
  132. package/src/ui/docs/content/table.md +46 -8
  133. package/src/ui/docs/content/tabs.md +14 -4
  134. package/src/ui/docs/content/testing.md +76 -31
  135. package/src/ui/docs/content/textarea.md +5 -4
  136. package/src/ui/docs/content/toast.md +48 -7
  137. package/src/ui/docs/content/toggle.md +3 -3
  138. package/src/ui/docs/content/tokens.md +15 -26
  139. package/src/ui/docs/content/tooltip.md +18 -2
  140. package/src/ui/docs/content/truncate.md +36 -6
  141. package/src/ui/docs/content/ui.md +74 -22
  142. package/src/ui/docs/content/upgrading.md +45 -36
  143. package/src/ui/docs/plugin.ts +36 -2
  144. package/src/ui/docs/registry.tsx +6 -0
  145. package/src/ui/drivers/react.tsx +16 -8
  146. package/src/ui/meta.ts +32 -32
  147. package/src/ui/react.tsx +1 -0
  148. package/src/vite/design.ts +16 -4
@@ -1,6 +1,8 @@
1
1
  ## Com campo de texto
2
2
 
3
- Associe `htmlFor` ao `id` do controle para que clicar no rótulo mova o foco para o campo.
3
+ Use `Label` para nomear um controle de formulário. Associe `htmlFor` ao `id` do controle: o rótulo
4
+ passa a ser o nome acessível do campo, e clicar nele move o foco para o controle. Em formulários
5
+ com ajuda e erro, [`Field`](/ui/field) organiza o rótulo junto dos demais elementos.
4
6
 
5
7
  ```tsx preview col md
6
8
  <div className="grid gap-2">
@@ -23,8 +25,11 @@ também alterna a seleção.
23
25
 
24
26
  ## Par desabilitado
25
27
 
26
- Quando o controle está desabilitado, o rótulo associado acompanha o tratamento visual sem exigir
27
- uma classe adicional.
28
+ O rótulo esmaece junto com um controle desabilitado quando é escrito logo depois dele, no mesmo
29
+ contêiner, como nos pares com `Checkbox`, `Switch` e `RadioGroupItem`. Nessa posição, o estilo lê o
30
+ estado do controle vizinho. Um rótulo escrito antes do controle, como acima de um `Input`, não
31
+ acompanha o estado dessa forma; nesse caso, coloque o par em um [`Field`](/ui/field) com
32
+ `data-disabled="true"`. `Label` e `FieldLabel` esmaecem com ele.
28
33
 
29
34
  ```tsx preview
30
35
  <div className="flex items-center gap-2">
@@ -38,4 +43,8 @@ uma classe adicional.
38
43
  | Propriedade | Tipo | Padrão | Descrição |
39
44
  |---|---|---|---|
40
45
  | `htmlFor` | `string` | | O `id` do controle rotulado; clicar no rótulo foca o controle. |
41
- | `className` | `string` | | Compõe sobre o estilo padrão; o rótulo esmaece com `peer-disabled` e dentro de `group[data-disabled]`. |
46
+ | `className` | `string` | | Classes somadas ao estilo padrão. |
47
+
48
+ `Label` aceita as props nativas de `<label>`. Ele esmaece quando vem logo depois de um controle
49
+ desabilitado, dentro de um `Field` com `data-disabled="true"` ou quando um ancestral com a classe
50
+ `group` declara `data-disabled="true"`.
@@ -31,8 +31,8 @@ import { pinoLogger } from '@softize/opus/log/pino'
31
31
  const logger = pinoLogger({ logger: pino({ level: 'info' }) })
32
32
  ```
33
33
 
34
- O `pino` é seu (nível, transport, redact) — o driver só faz a ponte. Peer opcional: quem
35
- não usa o driver não instala o `pino`.
34
+ O app configura o `pino` (nível, transport e redact), e o driver apenas o conecta ao runtime. `pino`
35
+ é dependência opcional: só precisa ser instalado por quem usa o driver.
36
36
 
37
37
  ## No runtime
38
38
 
@@ -42,7 +42,7 @@ const runtime = createRuntime({
42
42
  logger,
43
43
  })
44
44
 
45
- // No handler: ctx.log já vem com o requestId/action injetados (via child()).
45
+ // No handler: ctx.log já traz o request ID e a action, adicionados com child().
46
46
  handler: async (ctx, input) => {
47
47
  ctx.log.info('emitindo nota', { valor: input.valor })
48
48
  return repo.create(input)
@@ -51,5 +51,5 @@ handler: async (ctx, input) => {
51
51
 
52
52
  ## Limites atuais
53
53
 
54
- Superfície mínima de níveis + `child`. Sem sampling nem transports próprios — isso mora no
55
- `pino` que você passa. Drivers hoje: `pino`; outros entram por reincidência.
54
+ O contrato cobre os níveis e `child`. Amostragem e transports ficam no `pino` que o app configura.
55
+ O único driver disponível é o `pino`.
@@ -1,7 +1,8 @@
1
1
  ## Fonte e resultado
2
2
 
3
- Passe o texto em `content`. O exemplo mostra primeiro o resultado renderizado e, depois, o Markdown
4
- que o produziu.
3
+ Use `Markdown` para exibir texto formatado que chega como Markdown, como a resposta de um agente
4
+ no [`Chat`](/ui/chat) ou uma descrição escrita por uma pessoa. Passe o texto em `content`. O
5
+ exemplo mostra primeiro o resultado renderizado e, depois, o Markdown que o produziu.
5
6
 
6
7
  ```tsx preview col 2xl
7
8
  const sample = [
@@ -38,5 +39,5 @@ render(<Markdown content={sample} />)
38
39
 
39
40
  | Propriedade | Tipo | Padrão | Descrição |
40
41
  |---|---|---|---|
41
- | `content` | `string` | | O markdown cru (CommonMark + tabela GFM, via markdown-it). HTML no fonte é **escapado**, não interpretado — por isso serve para texto vindo de gente ou de modelo. |
42
- | `className` | `string` | | Classes do wrapper. |
42
+ | `content` | `string` | obrigatório | O texto em Markdown (CommonMark e tabelas do GFM, via markdown-it). HTML no texto é escapado, não interpretado, por isso o componente serve para texto escrito por pessoas ou gerado por modelos. Endereços soltos, como `https://…`, viram links. |
43
+ | `className` | `string` | | Classes do contêiner. |
@@ -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
@@ -101,6 +101,11 @@ render(
101
101
  `active`, `activationLabel` e `onActivate` formam uma capacidade única: informe as três propriedades
102
102
  para tornar o card ativável. O consumidor continua responsável pelo estado e pelo recorte aplicado.
103
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
+
104
109
  ## Carregamento
105
110
 
106
111
  `loading` preserva a geometria do card com placeholders e marca a superfície como ocupada para
@@ -125,7 +130,10 @@ tecnologias assistivas. O contêiner da coleção continua responsável pela men
125
130
  | `action` | `ReactNode` | | Ação relacionada diretamente à medida. |
126
131
  | `active` | `boolean` | | Indica se a medida controla o recorte atualmente aplicado. Exige `activationLabel` e `onActivate`. |
127
132
  | `activationLabel` | `string` | | Nome acessível da ativação. Exige `active` e `onActivate`. |
128
- | `onActivate` | `() => void` | | Ativa a medida como recorte ou seleção. Exige `active` e `activationLabel`. |
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`.
129
137
 
130
138
  ## Propriedades de MetricCardSecondaryMetric
131
139
 
@@ -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,8 @@ 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.
19
18
 
20
19
  A `Page` é contínua por definição: separar o chrome do conteúdo é trabalho do shell, e não há
21
20
  divisores entre regiões para declarar. O `layout` da `Page` não é a anatomia de superfície de
@@ -51,7 +50,8 @@ render(
51
50
 
52
51
  Use `PageBack` em uma subpágina simples. O destino é explícito para continuar correto após refresh ou
53
52
  acesso por link direto; não derive esse retorno do histórico do navegador. No header padrão, o
54
- 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.
55
55
 
56
56
  ```tsx preview col
57
57
  <Page>
@@ -64,8 +64,30 @@ controle aparece antes do título como ícone com nome acessível e tooltip deri
64
64
  ```
65
65
 
66
66
  Não combine `PageBack` com breadcrumb. Use o retorno para um único pai conhecido. Quando houver
67
- mais de um ancestral relevante, envolva o `Breadcrumb` em `PageNavigation`; ele ocupa a mesma
68
- 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
+ ```
69
91
 
70
92
  ## Barra persistente do shell
71
93
 
@@ -149,8 +171,87 @@ render(
149
171
 
150
172
  Fora de `PageShell`, use `PageIntro` quando o conteúdo precisar de uma introdução separada do
151
173
  chrome. Dentro do shell, ele pode ser combinado com `PageHeader`: o primeiro permanece no conteúdo
152
- e o segundo alimenta a barra. Em uma página de listagem, `Content` pode fornecer o título e as ações
153
- 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
+ ```
154
255
 
155
256
  ## Estados integrais
156
257
 
@@ -195,7 +296,8 @@ e, sem `title`, valem `errorMessage` e `emptyMessage`.
195
296
  ```
196
297
 
197
298
  Não use `PageState` para uma falha parcial. Se outra parte da página continua utilizável, mantenha o
198
- 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).
199
301
 
200
302
  ## Propriedades de Page
201
303
 
@@ -203,9 +305,10 @@ estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert
203
305
  | ----------- | ----------- | ----------- | ----------------------------------------------------------------------------------------------- |
204
306
  | `title` | `ReactNode` | | O h1 da página. |
205
307
  | `actions` | `ReactNode` | | Ações contextuais no extremo oposto do cabeçalho. |
206
- | `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. |
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`. |
207
309
  | `className` | `string` | `max-w-7xl` | Classes do container para substituir o teto padrão de `80rem`. |
208
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. |
209
312
 
210
313
  ## Propriedades de PageShell
211
314
 
@@ -242,7 +345,7 @@ que o shell projeta na barra, e ignora `className` e os demais atributos.
242
345
  | Propriedade | Tipo | Padrão | Descrição |
243
346
  | ------------ | -------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------ |
244
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. |
245
- | `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`). |
246
349
  | `aria-label` | `string` | `Voltar para {children}` | Nome acessível; informe-o quando `children` não for texto simples. |
247
350
  | `onClick` | `MouseEventHandler<HTMLAnchorElement>` | | Integração opcional com o roteador do consumidor, junto do `href`, nunca no lugar dele. |
248
351
  | `className` | `string` | | Classes adicionais do link renderizado como botão `ghost`. |
@@ -259,7 +362,7 @@ que o shell projeta na barra, e ignora `className` e os demais atributos.
259
362
 
260
363
  | Propriedade | Tipo | Descrição |
261
364
  | ----------- | --------------------------------- | ----------------------------------------------- |
262
- | `children` | `ReactNode` | Título principal `h1`; ampliado em `PageIntro`. |
365
+ | `children` | `ReactNode` | Título principal `h1`, com a mesma escala em `PageHeader` e `PageIntro`. |
263
366
  | `className` | `string` | Classes adicionais do título. |
264
367
  | demais | Atributos de `HTMLHeadingElement` | Atributos nativos repassados ao heading. |
265
368
 
@@ -287,6 +390,9 @@ que o shell projeta na barra, e ignora `className` e os demais atributos.
287
390
  | `className` | `string` | Classes adicionais da região principal. |
288
391
  | demais | Atributos de `HTMLDivElement` | Atributos nativos repassados ao container. |
289
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
+
290
396
  ## Propriedades de PageActionsTarget
291
397
 
292
398
  | Propriedade | Tipo | Descrição |