@softize/opus 18.1.0 → 18.2.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 (102) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/PROMOTED.md +4 -5
  3. package/README.md +5 -4
  4. package/bin/cli.mjs +4 -0
  5. package/docs/adr/0004-page-content-state-is-composed.md +3 -0
  6. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +6 -2
  7. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +6 -1
  8. package/docs/adr/0014-structural-headers-do-not-carry-description.md +2 -2
  9. package/docs/adr/0015-action-size-follows-interaction-density.md +4 -3
  10. package/docs/adr/{0012-modal-header-only-names-the-surface.md → 0018-modal-header-only-names-the-surface.md} +4 -1
  11. package/docs/adr/{0016-productive-surfaces-use-compact-density.md → 0019-productive-surfaces-use-compact-density.md} +4 -1
  12. package/docs/code-style.md +2 -2
  13. package/docs/consumer-upgrade-propagation.md +1 -1
  14. package/docs/data-products.md +5 -3
  15. package/docs/protocol.md +6 -6
  16. package/docs/releasing.md +28 -4
  17. package/package.json +1 -1
  18. package/registry/skills/build-opus-ui/references/ui-patterns.md +11 -1
  19. package/src/auth/drivers/jwt.ts +2 -1
  20. package/src/core/presentation.ts +143 -58
  21. package/src/core/runtime.ts +25 -4
  22. package/src/core/types.ts +4 -4
  23. package/src/mcp/index.ts +9 -0
  24. package/src/ui/components/patterns/content-header.tsx +1 -1
  25. package/src/ui/components/patterns/form.tsx +1 -1
  26. package/src/ui/components/patterns/presentation.tsx +199 -60
  27. package/src/ui/components/patterns/sidebar.tsx +1 -1
  28. package/src/ui/components/patterns/split.tsx +5 -2
  29. package/src/ui/components/patterns/surface-assistant.tsx +74 -0
  30. package/src/ui/components/primitives/card.tsx +1 -1
  31. package/src/ui/components/primitives/detail.tsx +7 -7
  32. package/src/ui/components/primitives/radio-group.tsx +1 -1
  33. package/src/ui/components/primitives/select.tsx +1 -1
  34. package/src/ui/docs/content/action-form-dialog.md +11 -4
  35. package/src/ui/docs/content/action-form.md +7 -16
  36. package/src/ui/docs/content/action-list-dialog.md +4 -6
  37. package/src/ui/docs/content/action-list.md +46 -4
  38. package/src/ui/docs/content/action-trigger.md +9 -5
  39. package/src/ui/docs/content/action-view.md +12 -8
  40. package/src/ui/docs/content/actions.md +36 -13
  41. package/src/ui/docs/content/ai.md +26 -7
  42. package/src/ui/docs/content/alert.md +4 -3
  43. package/src/ui/docs/content/aspect-ratio.md +2 -2
  44. package/src/ui/docs/content/auth.md +25 -10
  45. package/src/ui/docs/content/avatar.md +1 -1
  46. package/src/ui/docs/content/badge.md +2 -2
  47. package/src/ui/docs/content/breadcrumb.md +3 -2
  48. package/src/ui/docs/content/button.md +31 -7
  49. package/src/ui/docs/content/calendar.md +1 -1
  50. package/src/ui/docs/content/card.md +1 -1
  51. package/src/ui/docs/content/carousel.md +14 -3
  52. package/src/ui/docs/content/chat.md +1 -1
  53. package/src/ui/docs/content/cli.md +13 -7
  54. package/src/ui/docs/content/command.md +34 -2
  55. package/src/ui/docs/content/composer.md +1 -1
  56. package/src/ui/docs/content/content.md +5 -4
  57. package/src/ui/docs/content/customization.md +1 -1
  58. package/src/ui/docs/content/cycle.md +7 -5
  59. package/src/ui/docs/content/data-state.md +6 -5
  60. package/src/ui/docs/content/data.md +3 -3
  61. package/src/ui/docs/content/detail.md +3 -2
  62. package/src/ui/docs/content/dialog.md +2 -2
  63. package/src/ui/docs/content/dictionary-value.md +1 -1
  64. package/src/ui/docs/content/dock.md +23 -2
  65. package/src/ui/docs/content/dot.md +0 -2
  66. package/src/ui/docs/content/drawer.md +1 -1
  67. package/src/ui/docs/content/empty.md +1 -4
  68. package/src/ui/docs/content/events.md +1 -1
  69. package/src/ui/docs/content/field.md +20 -11
  70. package/src/ui/docs/content/getting-started.md +4 -2
  71. package/src/ui/docs/content/icon-picker.md +2 -2
  72. package/src/ui/docs/content/input-otp.md +2 -0
  73. package/src/ui/docs/content/input.md +2 -3
  74. package/src/ui/docs/content/item.md +5 -4
  75. package/src/ui/docs/content/kbd.md +2 -1
  76. package/src/ui/docs/content/mcp.md +10 -4
  77. package/src/ui/docs/content/menu.md +27 -0
  78. package/src/ui/docs/content/page.md +19 -5
  79. package/src/ui/docs/content/pagination.md +9 -2
  80. package/src/ui/docs/content/popover.md +2 -2
  81. package/src/ui/docs/content/presentation.md +85 -58
  82. package/src/ui/docs/content/progress.md +2 -6
  83. package/src/ui/docs/content/runtime.md +8 -5
  84. package/src/ui/docs/content/scheduler.md +1 -1
  85. package/src/ui/docs/content/select.md +13 -8
  86. package/src/ui/docs/content/sidebar.md +3 -2
  87. package/src/ui/docs/content/skeleton.md +1 -1
  88. package/src/ui/docs/content/slider.md +4 -4
  89. package/src/ui/docs/content/spinner.md +3 -3
  90. package/src/ui/docs/content/split.md +80 -19
  91. package/src/ui/docs/content/tabs.md +6 -6
  92. package/src/ui/docs/content/testing.md +4 -2
  93. package/src/ui/docs/content/toast.md +3 -5
  94. package/src/ui/docs/content/toggle.md +37 -0
  95. package/src/ui/docs/content/tooltip.md +4 -3
  96. package/src/ui/docs/content/truncate.md +3 -2
  97. package/src/ui/docs/content/ui.md +3 -1
  98. package/src/ui/docs/content/upgrading.md +43 -13
  99. package/src/ui/docs/doc-client.tsx +1 -1
  100. package/src/ui/docs/registry.tsx +30 -5
  101. package/src/ui/meta.ts +5 -5
  102. package/src/ui/react.tsx +5 -0
@@ -22,13 +22,13 @@ salvo em listas, cards e árvores.
22
22
 
23
23
  ```tsx preview col md
24
24
  const [icon, setIcon] = useState('store')
25
- const Icon = iconPickerIcons[icon] ?? FileText
25
+ const SelectedIcon = iconPickerIcons[icon] ?? FileText
26
26
 
27
27
  render(
28
28
  <div className="flex items-center gap-3">
29
29
  <IconPicker value={icon} onChange={setIcon} className="w-56" />
30
30
  <span className="flex items-center gap-2 rounded-md border px-3 py-2 text-sm">
31
- <Icon className="size-4 text-muted-foreground" />
31
+ <SelectedIcon className="size-4 text-muted-foreground" />
32
32
  Painel comercial
33
33
  </span>
34
34
  </div>,
@@ -71,6 +71,8 @@ render(
71
71
  | `maxLength` | `number` | | Quantidade de posições do código. Defina um `InputOTPSlot` para cada posição. |
72
72
  | `value` | `string` | | Código no modo controlado. Use com `onChange`. |
73
73
  | `onChange` | `(value: string) => void` | | Chamado a cada entrada com o código acumulado. |
74
+ | `onComplete` | `(value: string) => void` | | Chamado quando o código atinge `maxLength`. |
75
+ | `pattern` | `string` | | Expressão regular que o código precisa satisfazer; entradas que não casam são descartadas. Sem `pattern`, qualquer caractere é aceito, inclusive letras. Para somente dígitos, use `'^\\d+$'`. |
74
76
  | `disabled` | `boolean` | `false` | Desabilita a digitação em todo o conjunto. |
75
77
 
76
78
  ## Propriedades de InputOTPSlot
@@ -21,7 +21,6 @@ como calendário em uma data; o `Label` continua responsável pelo nome acessív
21
21
 
22
22
  Quando o campo precisar combinar vários adornos, prefixos ou botões, use `InputGroup`.
23
23
 
24
-
25
24
  ## Ação no fim do campo (trailing)
26
25
 
27
26
  `trailing` posiciona no fim do campo uma ação relacionada ao valor, como limpar ou mostrar uma
@@ -37,7 +36,7 @@ function Demo() {
37
36
  onChange={(e) => setQ(e.target.value)}
38
37
  trailing={
39
38
  q ? (
40
- <Button variant="ghost" size="icon-xs" onClick={() => setQ('')}>
39
+ <Button variant="ghost" size="icon-xs" aria-label="Limpar busca" onClick={() => setQ('')}>
41
40
  <X />
42
41
  </Button>
43
42
  ) : undefined
@@ -167,7 +166,7 @@ Com `InputGroupTextarea`, um addon em `block-end` forma uma região de ações a
167
166
  | Propriedade | Tipo | Padrão | Descrição |
168
167
  |---|---|---|---|
169
168
  | `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | O subconjunto da escala única que cabe num campo (1.5 · 2 · 1.5 · 1.75rem); os `icon-*` são quadrados. |
170
- | `context` | `'neutral' \| 'primary' \| 'danger'` | `'primary'` | Contexto semântico herdado de Button. |
169
+ | `context` | `'neutral' \| 'primary' \| 'danger'` | `'neutral'` | Contexto semântico herdado de Button. Com `variant` `solid` ou `subtle` sem contexto declarado, o padrão passa a `'primary'`. |
171
170
  | `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'ghost'` | Tratamento visual; use `solid` quando a ação precisar de ênfase. |
172
171
 
173
172
  ### Propriedades de InputGroupInput
@@ -4,7 +4,8 @@ A composição completa usa `ItemMedia` à esquerda, `ItemHeader` (`ItemTitle` +
4
4
  `ItemDescription`) no meio e `ItemActions` à direita. Ícone e imagem mantêm uma moldura quadrada
5
5
  alinhada ao topo, mesmo quando a descrição ocupa mais linhas. `variant="outline"` desenha a borda.
6
6
  O tamanho padrão usa corpo produtivo, `0.75rem` de padding e `0.75rem` entre regiões. `size="sm"`
7
- mantém o alinhamento horizontal e reduz o padding vertical para `0.5rem` em listas densas.
7
+ mantém o padding horizontal, reduz o padding vertical para `0.5rem` e o espaço entre regiões para
8
+ `0.625rem` em listas densas.
8
9
 
9
10
  ```tsx preview col
10
11
  <Item variant="outline">
@@ -61,7 +62,7 @@ mantém o alinhamento horizontal e reduz o padding vertical para `0.5rem` em lis
61
62
 
62
63
  ## Clicável e compacto
63
64
 
64
- asChild funde o Item em um <a> — a linha inteira vira alvo (hover no fundo). size=sm aperta o respiro; ItemMedia variant=image ancora um avatar.
65
+ `asChild` funde o Item em um `<a>` — a linha inteira vira alvo (hover no fundo). `size="sm"` aperta o respiro; `ItemMedia` na variante padrão recebe um `Avatar`, que já traz a própria moldura.
65
66
 
66
67
  ```tsx preview col
67
68
  <Item asChild size="sm" variant="outline">
@@ -99,7 +100,7 @@ Use `ItemBody` quando a linha também apresenta um valor ou controle que não pe
99
100
  </Item>
100
101
  ```
101
102
 
102
- `ItemContent` permanece exportado somente para compatibilidade durante a versão 12. Código novo
103
+ `ItemContent` permanece exportado apenas por compatibilidade e está obsoleto. Código novo
103
104
  usa `ItemHeader` ou `ItemBody` conforme o papel do conteúdo.
104
105
 
105
106
  ## Propriedades de Item
@@ -124,4 +125,4 @@ usa `ItemHeader` ou `ItemBody` conforme o papel do conteúdo.
124
125
 
125
126
  `ItemHeader`, `ItemTitle`, `ItemDescription`, `ItemBody`, `ItemActions`, `ItemFooter` e
126
127
  `ItemSeparator` aceitam as props nativas dos respectivos elementos e não adicionam propriedades
127
- próprias. `ItemContent` preserva esse mesmo contrato somente durante a migração da versão 12.
128
+ próprias. O `ItemContent` obsoleto preserva esse mesmo contrato por compatibilidade.
@@ -27,7 +27,7 @@ reconhecível que o nome da tecla.
27
27
  ```tsx preview col-start
28
28
  <KbdGroup>
29
29
  <Kbd>
30
- <Command />
30
+ <CommandIcon />
31
31
  </Kbd>
32
32
  <Kbd>
33
33
  <CornerDownLeft />
@@ -49,6 +49,7 @@ ação antes de informar seu atalho.
49
49
  Buscar workspace
50
50
  <KbdGroup>
51
51
  <Kbd>⌘</Kbd>
52
+ <span>+</span>
52
53
  <Kbd>K</Kbd>
53
54
  </KbdGroup>
54
55
  </TooltipContent>
@@ -17,16 +17,22 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
17
17
  const server = createOpusMcpServer(runtime, {
18
18
  name: 'meu-app',
19
19
  // Valida a identidade da requisição (IdP) → contexto do runtime. Sem isso, roda anônimo
20
- // (o `can` nega → só actions públicas). Com isso, o agente age COMO O USUÁRIO.
20
+ // (sem usuário, só actions `public` rodam). Com isso, o agente age COMO O USUÁRIO.
21
21
  resolveContext: async (extra) => auth.resolveFromMcp(extra),
22
22
  })
23
23
 
24
24
  await server.connect(new StdioServerTransport()) // local; HTTP/SSE para remoto
25
25
  ```
26
26
 
27
- `ListTools` devolve as actions `ai:enabled` (nome + descrição + JSON Schema do input);
28
- `CallTool` executa a action pelo `runtime.execute` validação, auth (`ctx.can`) e audit,
29
- tudo igual a uma chamada normal. Read-only? Marque actions de leitura com `ai:enabled`.
27
+ 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.
30
36
 
31
37
  ## Escolher entre integração interna e MCP
32
38
 
@@ -104,6 +104,14 @@ MenuSub aninha um nível; inset alinha itens sem ícone com os que têm.
104
104
  </Menu>
105
105
  ```
106
106
 
107
+ ## Propriedades de Menu
108
+
109
+ | Propriedade | Tipo | Padrão | Descrição |
110
+ |---|---|---|---|
111
+ | `open` | `boolean` | | Estado do painel no modo controlado, como no menu de contexto. |
112
+ | `onOpenChange` | `(open: boolean) => void` | | Chamado quando o painel abre ou fecha. |
113
+ | `defaultOpen` | `boolean` | `false` | Estado inicial no modo não controlado. |
114
+
107
115
  ## Propriedades de MenuItem
108
116
 
109
117
  | Propriedade | Tipo | Padrão | Descrição |
@@ -137,3 +145,22 @@ próprias além das de DOM.
137
145
  |---|---|---|---|
138
146
  | `value` | `string` | | Valor selecionado no grupo. |
139
147
  | `onValueChange` | `(value: string) => void` | | Chamado quando a pessoa seleciona outro `MenuRadioItem`. |
148
+
149
+ ## Propriedades de MenuRadioItem
150
+
151
+ | Propriedade | Tipo | Padrão | Descrição |
152
+ |---|---|---|---|
153
+ | `value` | `string` | | Valor que o item entrega ao `MenuRadioGroup` quando é selecionado. |
154
+ | `disabled` | `boolean` | `false` | Impede a seleção do item. |
155
+
156
+ ## Propriedades de MenuLabel
157
+
158
+ | Propriedade | Tipo | Padrão | Descrição |
159
+ |---|---|---|---|
160
+ | `inset` | `boolean` | | Alinha o rótulo com itens que possuem ícone. |
161
+
162
+ ## Propriedades de MenuSubTrigger
163
+
164
+ | Propriedade | Tipo | Padrão | Descrição |
165
+ |---|---|---|---|
166
+ | `inset` | `boolean` | | Alinha o gatilho do submenu sem ícone com os itens que possuem ícone. |
@@ -92,7 +92,17 @@ Na composição explícita, combine somente as regiões necessárias. Este exemp
92
92
  uma ação global e o intro para o título:
93
93
 
94
94
  ```tsx preview col
95
- <PageShell navigation={<Breadcrumb>...</Breadcrumb>}>
95
+ <PageShell
96
+ navigation={
97
+ <Breadcrumb>
98
+ <BreadcrumbList>
99
+ <BreadcrumbItem>Vendas</BreadcrumbItem>
100
+ <BreadcrumbSeparator />
101
+ <BreadcrumbPage>Clientes</BreadcrumbPage>
102
+ </BreadcrumbList>
103
+ </Breadcrumb>
104
+ }
105
+ >
96
106
  <Page>
97
107
  <PageHeader>
98
108
  <PageActions>
@@ -172,7 +182,7 @@ e, sem `title`, valem `errorMessage` e `emptyMessage`.
172
182
  status="empty"
173
183
  title="Nenhum relatório"
174
184
  description="Crie o primeiro relatório para começar."
175
- action={<Button>Novo relatório</Button>}
185
+ action={<Button>Criar relatório</Button>}
176
186
  />
177
187
  </Page>
178
188
  ```
@@ -204,7 +214,7 @@ estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert
204
214
 
205
215
  | Propriedade | Tipo | Descrição |
206
216
  | ----------- | ----------------------------- | --------------------------------------------------------------------- |
207
- | `children` | `ReactNode` | Título introdutório e, quando necessário, ações ligadas à introdução. |
217
+ | `children` | `ReactNode` | `PageBack` ou `PageNavigation`, título introdutório e, quando necessário, ações ligadas à introdução. |
208
218
  | `className` | `string` | Classes adicionais da região introdutória. |
209
219
  | demais | Atributos de `HTMLDivElement` | Atributos nativos repassados à região introdutória. |
210
220
 
@@ -212,15 +222,19 @@ estado na própria seção com `DataState`, `ActionView`, `ActionList` ou `Alert
212
222
 
213
223
  | Propriedade | Tipo | Descrição |
214
224
  | ----------- | ----------- | ---------------------------------------------------------------------------------------- |
215
- | `className` | `string` | Classes adicionais da região externa do cabeçalho. |
225
+ | `className` | `string` | Classes adicionais da região externa do cabeçalho, fora de `PageShell`. |
216
226
  | `children` | `ReactNode` | `PageBack` ou `PageNavigation`, `PageActions` e, fora do shell, um `PageTitle` opcional. |
227
+ | demais | Atributos de `HTMLDivElement` | Atributos nativos repassados à região externa do cabeçalho, fora de `PageShell`. |
228
+
229
+ Dentro de `PageShell`, `PageHeader` não renderiza uma região própria: devolve somente os filhos,
230
+ que o shell projeta na barra, e ignora `className` e os demais atributos.
217
231
 
218
232
  ## Propriedades de PageBack
219
233
 
220
234
  | Propriedade | Tipo | Padrão | Descrição |
221
235
  | ------------ | -------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------ |
222
236
  | `href` | `string` | obrigatório | Destino explícito da página pai. Sem ele o retorno não é tabulável nem tem nome acessível. |
223
- | `children` | `ReactNode` | | Nome do destino pai usado no rótulo acessível e no tooltip. |
237
+ | `children` | `ReactNode` | obrigatório | Nome do destino pai usado no rótulo acessível e no tooltip. |
224
238
  | `aria-label` | `string` | `Voltar para {children}` | Nome acessível; informe-o quando `children` não for texto simples. |
225
239
  | `onClick` | `MouseEventHandler<HTMLAnchorElement>` | | Integração opcional com o roteador do consumidor, junto do `href`, nunca no lugar dele. |
226
240
  | `className` | `string` | | Classes adicionais do link renderizado como botão `ghost`. |
@@ -131,7 +131,7 @@ render(
131
131
  | `page` | `number` | | Número usado como conteúdo, quando `children` não é informado, e no nome acessível “Página N”. |
132
132
  | `isActive` | `boolean` | `false` | Marca a página atual com `aria-current="page"` e tratamento `outline`. |
133
133
  | `href` | `string` | | Renderiza um `<a>`. Sem `href`, o componente usa `<button>` e aceita `onClick` e `disabled`. |
134
- | `size` | `ButtonProps['size']` | `'default'` | A escala única, via `Button`. A largura mínima cresce para acomodar números longos; os `icon-*` são quadrados. |
134
+ | `size` | `ButtonProps['size']` | `'default'` | A escala única, via `Button`. Os `icon-*` são quadrados. Nos tamanhos textuais, a altura fica fixa em 2.25rem com largura mínima quadrada que cresce para números longos, então `size="sm"` não reduz a altura; para uma barra mais baixa, use um `icon-*` ou ajuste por `className` (ex.: `h-7 min-w-7 text-xs`). |
135
135
 
136
136
  ## Propriedades de PaginationPrevious e PaginationNext
137
137
 
@@ -139,5 +139,12 @@ render(
139
139
  |---|---|---|---|
140
140
  | `label` | `string` | `'Página anterior'` ou `'Próxima página'` | Nome acessível e texto visível da ação. |
141
141
  | `iconOnly` | `boolean` | `false` | Exibe somente a seta; `label` continua disponível para leitura assistiva. |
142
- | `size` | `ButtonProps['size']` | `'default'`; `'icon'` com `iconOnly` | A escala única; `icon-sm` para o rodapé denso. |
142
+ | `size` | `ButtonProps['size']` | `'default'`; `'icon'` com `iconOnly` | A escala única; `icon-sm` para o rodapé denso. Sem `iconOnly`, a altura permanece 2.25rem em qualquer tamanho textual, como em `PaginationLink`. |
143
143
  | `iconClassName` | `string` | | Classes aplicadas ao ícone da seta, quando o glifo da escala não servir. |
144
+
145
+ ## Propriedades de PaginationEllipsis
146
+
147
+ | Propriedade | Tipo | Padrão | Descrição |
148
+ |---|---|---|---|
149
+ | `size` | `'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | `'icon'` | Quadrado da escala única; use o mesmo tamanho das setas ao lado. |
150
+ | `className` | `string` | | Classes adicionais aplicadas à elipse. |
@@ -29,13 +29,13 @@ detalhes.
29
29
 
30
30
  ```tsx preview
31
31
  <Popover>
32
- <PopoverTrigger asChild><Button variant="ghost">start</Button></PopoverTrigger>
32
+ <PopoverTrigger asChild><Button variant="ghost">Início</Button></PopoverTrigger>
33
33
  <PopoverContent align="start" className="w-56">
34
34
  <PopoverDescription>Alinhado à borda esquerda do gatilho.</PopoverDescription>
35
35
  </PopoverContent>
36
36
  </Popover>
37
37
  <Popover>
38
- <PopoverTrigger asChild><Button variant="ghost">end</Button></PopoverTrigger>
38
+ <PopoverTrigger asChild><Button variant="ghost">Fim</Button></PopoverTrigger>
39
39
  <PopoverContent align="end" className="w-56">
40
40
  <PopoverDescription>Alinhado à borda direita do gatilho.</PopoverDescription>
41
41
  </PopoverContent>
@@ -9,8 +9,10 @@ uma etapa de navegação. Formulários modais dimensionam o footer pelo conteúd
9
9
  `ghost` e a ação principal sólida.
10
10
  Em Page hospedada por `PageShell`, a barra concentra navegação e ações globais. Uma Presentation
11
11
  de listagem materializa título e comandos de header no `Content` que envolve o `ActionList`; assim
12
- a criação permanece próxima da coleção. Esses comandos usam `default`. Nas demais Presentations, o
13
- título abre o conteúdo em `PageIntro` e comandos da superfície usam `default`.
12
+ a criação permanece próxima da coleção. Esses comandos usam o tamanho `default`: o que abre outra
13
+ Presentation é um `Button` na variante padrão (`solid`), e uma action `simple` vira um
14
+ `ActionTrigger` na variante `ghost`. Nas demais Presentations, o título abre o conteúdo em
15
+ `PageIntro` e os comandos da superfície seguem a mesma regra.
14
16
 
15
17
  Use esse padrão quando uma lista puder abrir um detalhe lateral, quando a mesma edição precisar
16
18
  funcionar em modal e em rota própria ou quando um fluxo começar compacto e crescer sem ganhar uma
@@ -22,25 +24,29 @@ compõe `PageBody` e, quando necessário, `PageFooter`. Para uma action `list`,
22
24
  `Content + ActionList`; o header da coleção recebe o título e os comandos declarados no placement
23
25
  `header`. Dialog e Drawer continuam usando o header da própria superfície.
24
26
  Os comandos da coleção ficam no extremo oposto ao título e permanecem textuais, mesmo quando a
25
- action declara um ícone. A criação usa `Criar recurso`, sem ícone, em um botão `default`.
27
+ action declara um ícone. A criação usa `Criar recurso`, sem ícone, em um `Button` na variante
28
+ padrão (`solid`) e no tamanho `default`.
26
29
  Não envolva a Presentation em um card para simular a página; valide proporção, rolagem e ações no
27
30
  shell que efetivamente hospeda a rota.
28
31
 
29
- ```tsx live
30
- const workspaceUpdate = defineContract({
31
- name: "workspace.update",
32
- kind: "form",
33
- label: "Editar workspace",
34
- input: z.object({ name: z.string() }),
35
- output: z.object({ id: z.string() }),
36
- fields: { name: { label: "Nome" } },
37
- });
32
+ O exemplo declara a Presentation sobre um contrato de formulário e alterna a superfície. O
33
+ `DocBrowserActionProvider` simula o cliente de actions; na aplicação, esse papel é do provider real.
38
34
 
35
+ Quando o recurso aceita assistência contextual, declare somente o gatilho serializável em
36
+ `assistant`. O renderer coloca esse gatilho no cabeçalho e abre o painel à direita do body da
37
+ própria Page, Dialog ou Drawer. Cabeçalho e rodapé continuam ocupando toda a largura da superfície;
38
+ enquanto o painel está aberto, o gatilho sai do cabeçalho e o próprio painel assume sua identidade
39
+ visual. O dado vivo do recurso não entra na definição: a página que o carregou fornece
40
+ `assistant.render`, captura identidade, nome e demais dados autorizados e monta o chat. Assim,
41
+ contexto de conversa não vira autorização nem dado persistido no manifest.
42
+
43
+ ```tsx live
39
44
  const workspacePresentation = definePresentation({
40
45
  schemaVersion: 1,
41
- id: "workspace.update",
42
- title: "Editar workspace",
43
- body: { action: workspaceUpdate.name },
46
+ id: "workspace.create",
47
+ title: "Criar workspace",
48
+ assistant: { triggerLabel: "Conversar sobre este workspace" },
49
+ body: { action: docWorkspaceCreate.name },
44
50
  });
45
51
 
46
52
  function Example() {
@@ -55,36 +61,54 @@ function Example() {
55
61
  );
56
62
 
57
63
  return (
58
- <div className="space-y-4">
59
- <ButtonGroup mode="spaced">
60
- {["page", "dialog", "drawer"].map((value) => (
61
- <Button
62
- key={value}
63
- size="sm"
64
- variant={invocation.surface === value ? "solid" : "outline"}
65
- onClick={() => {
66
- setInvocation({ ...invocation, surface: value });
67
- setOpen(true);
68
- }}
69
- >
70
- {value}
71
- </Button>
72
- ))}
73
- </ButtonGroup>
74
-
75
- <Presentation
76
- definition={workspacePresentation}
77
- definitions={[workspacePresentation]}
78
- actions={{ [workspaceUpdate.name]: workspaceUpdate }}
79
- invocation={invocation}
80
- open={invocation.surface === "page" || open}
81
- onOpenChange={setOpen}
82
- onInvocationChange={(next) => {
83
- if (next) setInvocation(next);
84
- else setOpen(false);
85
- }}
86
- />
87
- </div>
64
+ <DocBrowserActionProvider>
65
+ <div className="space-y-4">
66
+ <ButtonGroup mode="spaced">
67
+ {["page", "dialog", "drawer"].map((value) => (
68
+ <Button
69
+ key={value}
70
+ size="sm"
71
+ variant={invocation.surface === value ? "solid" : "outline"}
72
+ onClick={() => {
73
+ setInvocation({ ...invocation, surface: value });
74
+ setOpen(true);
75
+ }}
76
+ >
77
+ {value}
78
+ </Button>
79
+ ))}
80
+ </ButtonGroup>
81
+
82
+ <Presentation
83
+ definition={workspacePresentation}
84
+ definitions={[workspacePresentation]}
85
+ actions={{ [docWorkspaceCreate.name]: docWorkspaceCreate }}
86
+ invocation={invocation}
87
+ open={invocation.surface === "page" || open}
88
+ onOpenChange={setOpen}
89
+ onInvocationChange={(next) => {
90
+ if (next) setInvocation(next);
91
+ else setOpen(false);
92
+ }}
93
+ assistant={{
94
+ render: ({ close }) => (
95
+ <div className="flex h-full flex-col p-3">
96
+ <p className="text-sm">
97
+ Conversa vinculada ao workspace carregado pela página.
98
+ </p>
99
+ <Button
100
+ className="mt-3 self-start"
101
+ variant="ghost"
102
+ onClick={close}
103
+ >
104
+ Fechar
105
+ </Button>
106
+ </div>
107
+ ),
108
+ }}
109
+ />
110
+ </div>
111
+ </DocBrowserActionProvider>
88
112
  );
89
113
  }
90
114
 
@@ -165,19 +189,22 @@ no manifest e concentre a inspeção na Lens em vez de criar um launcher flutuan
165
189
 
166
190
  ## Propriedades de Presentation
167
191
 
168
- | Propriedade | Tipo | Padrão | Descrição |
169
- | -------------------- | ------------------------------------------------ | ------ | ------------------------------------------------------ |
170
- | `definition` | `PresentationDefinition` | | Artefato estático que descreve o recurso. |
171
- | `definitions` | `PresentationDefinition[]` | | Registry alcançável por navegação, abertura e retorno. |
172
- | `actions` | `PresentationActionRegistry` | | Contratos compartilháveis referenciados pelo artefato. |
173
- | `invocation` | `PresentationInvocation` | | Surface, input e pilha da exibição atual. |
174
- | `bindingContext` | `PresentationBindingContext` | | Rota, item, seleção, sessão e resultado disponíveis. |
175
- | `onInvocationChange` | `(next: PresentationInvocation \| null) => void` | | Recebe navegação, retorno e fechamento. |
176
- | `onRefresh` | `(action: string \| null) => void` | | Recebe invalidações declaradas após sucesso. |
177
- | `open` | `boolean` | `true` | Estado controlado de Dialog ou Drawer. |
178
- | `onOpenChange` | `(open: boolean) => void` | | Notifica abertura e fechamento da superfície modal. |
179
- | `className` | `string` | | Classes adicionais da superfície. |
180
- | `bodyClassName` | `string` | | Classes adicionais do body, aplicadas uma única vez. |
192
+ | Propriedade | Tipo | Padrão | Descrição |
193
+ | -------------------- | ------------------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- |
194
+ | `definition` | `PresentationDefinition` | | Artefato estático que descreve o recurso. |
195
+ | `definitions` | `PresentationDefinition[]` | | Registry alcançável por navegação, abertura e retorno. |
196
+ | `actions` | `PresentationActionRegistry` | | Contratos compartilháveis referenciados pelo artefato. |
197
+ | `invocation` | `PresentationInvocation` | | Surface, input e pilha da exibição atual. |
198
+ | `bindingContext` | `PresentationBindingContext` | | Rota, item, seleção, sessão e resultado disponíveis. |
199
+ | `assistant` | `PresentationAssistantRuntime` | | Painel contextual fornecido pela superfície hospedeira. |
200
+ | `onInvocationChange` | `(next: PresentationInvocation \| null) => void` | | Recebe navegação, retorno e fechamento. |
201
+ | `onRefresh` | `(action: string \| null) => void` | | Recebe invalidações declaradas após sucesso. |
202
+ | `listState` | `ActionListState` | | Recorte controlado da action `list` do body, quando a aplicação o sincroniza com a URL. |
203
+ | `onListStateChange` | `(state: ActionListState) => void` | | Recebe cada mudança de recorte ou exibição da lista do body, como busca, filtros, período e página. |
204
+ | `open` | `boolean` | `true` | Estado controlado de Dialog ou Drawer. |
205
+ | `onOpenChange` | `(open: boolean) => void` | | Notifica abertura e fechamento da superfície modal. |
206
+ | `className` | `string` | | Classes adicionais da superfície. |
207
+ | `bodyClassName` | `string` | | Classes adicionais do body, aplicadas uma única vez. |
181
208
 
182
209
  ## Propriedades de PresentationInspector
183
210
 
@@ -29,13 +29,9 @@ render(
29
29
  <span className="text-muted-foreground">{step}%</span>
30
30
  </div>
31
31
  <Progress value={step} />
32
- <button
33
- type="button"
34
- onClick={advance}
35
- className="rounded-md border px-3 py-1.5 text-sm hover:bg-muted"
36
- >
32
+ <Button variant="outline" size="sm" onClick={advance}>
37
33
  Avançar etapa
38
- </button>
34
+ </Button>
39
35
  </div>,
40
36
  )
41
37
  ```
@@ -38,16 +38,17 @@ await runtime.start()
38
38
 
39
39
  | Capacidade | Fornece | Driver pronto |
40
40
  |---|---|---|
41
- | `server` | monta as actions (HTTP) | `fastifyServer` — `@softize/opus/server/fastify` |
41
+ | `server` | monta as actions (HTTP) | `fastifyServer` — `@softize/opus/server/fastify` · `nodeServer` — `…/server/node` |
42
42
  | `data` | `ctx.db` + drift-check + CRUD | `kyselyData`, `kyselyRepo`, `crudActions` — `@softize/opus/data/kysely` |
43
43
  | `auth` | `user`/`tenantId`/`can` do contexto | `jwtAuth` — `…/auth/jwt` · `betterAuthSession` — `…/auth/better-auth` |
44
44
  | `audit` | trilha por execução de action | `pgAudit` — `…/audit/pg` · `consoleAudit` — `…/audit/console` |
45
- | `log` | `ctx.log` estruturado | `pinoLogger` — `@softize/opus/log/pino` |
46
- | `observability` | span ativo + propagação de trace | porta no core; driver opt-in |
45
+ | `logger` | `ctx.log` estruturado | `pinoLogger` — `@softize/opus/log/pino` |
46
+ | `observability` | span ativo + propagação de trace | `openTelemetryObservability` `@softize/opus/observability/opentelemetry` |
47
47
  | `eventBus` | `ctx.emit` + **reactions** | `mittEvents` — `@softize/opus/events/mitt` |
48
48
  | `queue` | jobs em background | `bullmqQueue` — `@softize/opus/queue/bullmq` |
49
49
  | `scheduler` | **schedules** (cron/intervalo) | `nodeCronScheduler` — `@softize/opus/scheduler/node-cron` |
50
50
  | `storage` | `ctx.storage` (arquivos) | `fsStorage` — `…/storage/fs` · `s3Storage` — `…/storage/s3` |
51
+ | `cache` | `ctx.cache` (leitura, experimental) | `memoryCache` — `@softize/opus/cache/memory` |
51
52
  | `ai` | `ctx.ai` (complete/extract) | `anthropicAi` — `@softize/opus/ai/anthropic` |
52
53
  | `client` | chamar actions de fora (stubs) | `fetchClient` — `@softize/opus/client/fetch` |
53
54
 
@@ -61,7 +62,7 @@ Fora do runtime, mas parte do protocolo: o harness de teste (`runAction`/`testCo
61
62
  > e o handler decide como degradar.
62
63
 
63
64
  `user`/`tenantId`/`can` (auth) · `db` (data) · `log` (logger) · `emit` (eventBus) ·
64
- `storage` (storage) · `ai` (ai) · `provenance` (quem disparou: http, schedule,
65
+ `storage` (storage) · `cache` (cache) · `ai` (ai) · `provenance` (quem disparou: http, schedule,
65
66
  reaction…) · `trace` (quando configurado) · `meta`.
66
67
 
67
68
  O core não depende de OpenTelemetry. O `ObservabilityAdapter` envolve actions e reactions
@@ -85,7 +86,9 @@ representa span de erro. Reactions usam `resultKind: 'void'` e rejeitam em falha
85
86
  > tempo dispara schedule (que executa uma action, com provenance própria).
86
87
 
87
88
  Declarados como as actions e registrados no mesmo `runtime.register` — o manifest
88
- projeta os três (o Maestro mostra o wiring em Visão geral).
89
+ projeta os três. `opus introspect` lista reactions, schedules e o wiring entre eles, mas por
90
+ enquanto só reconhece actions declaradas com `defineAction`: as do split `defineContract` +
91
+ `bindAction` ficam de fora do modelo que ele monta.
89
92
 
90
93
  ## Referência profunda
91
94
 
@@ -19,7 +19,7 @@ interface ScheduleDef {
19
19
  action: string // action do registry, executada quando dispara
20
20
  cron?: string // '0 9 * * *' (9h todo dia)
21
21
  every?: string // '1h', '30m', '15s' — atalho de intervalo
22
- timezone?: string // IANA, ex.: 'America/Sao_Paulo' (default UTC)
22
+ timezone?: string // IANA, ex.: 'America/Sao_Paulo' (sem ele: hora local do processo)
23
23
  input?: unknown | (() => unknown | Promise<unknown>) // estático ou dinâmico
24
24
  enabled?: boolean
25
25
  }
@@ -26,8 +26,9 @@ render(
26
26
 
27
27
  ## Lista pesquisável
28
28
 
29
- Use `searchable` quando a quantidade ou os rótulos dificultarem encontrar uma opção. O próprio
30
- campo passa a filtrar `label`, `hint` e `value` com a busca do cmdk.
29
+ Use `searchable` quando a quantidade ou os rótulos dificultarem encontrar uma opção. O gatilho
30
+ continua exibindo a escolha; a busca aparece em uma linha no topo da lista e filtra `label`, `hint`
31
+ e `value`.
31
32
 
32
33
  ```tsx preview col md
33
34
  const [issue, setIssue] = useState('')
@@ -40,7 +41,7 @@ render(
40
41
  id="issue"
41
42
  value={issue}
42
43
  onChange={setIssue}
43
- placeholder="Buscar item…"
44
+ placeholder="Selecione a issue"
44
45
  options={[
45
46
  { value: '412', label: 'Ajustar microcopy do handoff', hint: 'SOF-412' },
46
47
  { value: '418', label: 'Preview da sessão cai após deploy', hint: 'SOF-418' },
@@ -323,7 +324,7 @@ render(
323
324
  { value: 'gra-7', label: 'Filtro por filial', hint: 'GRA-7' },
324
325
  ]}
325
326
  trailing={
326
- <Button variant="ghost" size="icon-xs">
327
+ <Button variant="ghost" size="icon-xs" aria-label="Abrir tarefa">
327
328
  <FileText />
328
329
  </Button>
329
330
  }
@@ -338,12 +339,12 @@ render(
338
339
  | `options` | `SelectOption[]` | | As opções: `{ value, label, hint?, content?, triggerLabel?, group?, disabled? }`. |
339
340
  | `value` | `string \| string[]` | | O selecionado: string no single, string[] no multiple. |
340
341
  | `onChange` | `(value: string) => void \| (value: string[]) => void` | | Chamado ao escolher (e ao remover chip, no multiple) — a assinatura segue o modo. |
341
- | `native` | `boolean` | `false` | Renderiza o `<select>` do sistema. Exclui busca, multi e ghost (o browser é quem desenha a lista). |
342
- | `searchable` | `boolean` | `false` | O campo vira busca: filtra a lista enquanto digita. |
342
+ | `native` | `boolean` | `false` | Renderiza o `<select>` do sistema. Exclui busca, seleção múltipla e variantes visuais (o browser é quem desenha a lista). |
343
+ | `searchable` | `boolean` | `false` | Acrescenta uma linha de busca no topo da lista, que filtra as opções enquanto a pessoa digita. O gatilho continua exibindo a escolha. |
343
344
  | `multiple` | `boolean` | `false` | Chips removíveis, lista que permanece aberta e Selecionar tudo. |
344
- | `variant` | `'default' \| 'ghost'` | `'default'` | `ghost` = sem moldura, para barra do composer. |
345
+ | `variant` | `'default' \| 'outline' \| 'ghost'` | `'default'` | `outline` = compacto com borda; `ghost` = compacto sem borda nem fundo, para barra do composer. |
345
346
  | `placeholder` | `string` | `'Selecione…'` | Texto do campo vazio. |
346
- | `searchPlaceholder` | `string` | | Placeholder enquanto busca; cai para o `placeholder` se ausente. |
347
+ | `searchPlaceholder` | `string` | `'Buscar…'` | Placeholder da linha de busca dentro da lista. |
347
348
  | `emptyText` | `string` | `'Nada encontrado.'` | Mensagem quando a busca não acha nada. |
348
349
  | `onSearch` | `(query: string) => void` | | Busca server-side (debounced, ao abrir e ao digitar): desliga o filtro do cmdk — o pai atualiza `options`. |
349
350
  | `loading` | `boolean` | | Mostra Buscando… enquanto o fetch corre (use com `onSearch`). |
@@ -351,6 +352,10 @@ render(
351
352
  | `icon` | `React.ReactNode` | | Ícone leading DENTRO do controle (decorativo) — herda `size-4` e o tom muted. |
352
353
  | `trailing` | `React.ReactNode` | | Ação custom no FIM do controle (antes do chevron) — o clique não abre a lista. |
353
354
  | `size` | `'default' \| 'sm'` | `'default'` | Altura: default (h-9, a do Input e do Button) ou sm (h-8) para toolbar densa. |
355
+ | `shape` | `'default' \| 'pill'` | `'default'` | Geometria do controle; `pill` arredonda as extremidades e preserva a variante visual. |
354
356
  | `disabled` | `boolean` | `false` | Esmaece e trava o controle. |
355
357
  | `id` | `string` | | Vai para o campo — para parear com o `htmlFor` do Label. |
358
+ | `aria-label` | `string` | | Nome acessível quando não há `Label` associado. |
359
+ | `aria-invalid` | `boolean` | | Comunica e apresenta o estado inválido. |
360
+ | `aria-describedby` | `string` | | Liga o controle a uma ajuda ou mensagem de erro. |
356
361
  | `className` | `string` | | Classes da raiz do controle, incluindo campo, ícones e ações, em todos os modos. |
@@ -63,7 +63,7 @@ uma sidebar fixa, redimensionável ou recolhida.
63
63
  | `PaneFooter` | Mantém ações persistentes no rodapé. |
64
64
  | `SidebarNav` | Apresenta e controla os destinos de navegação. |
65
65
 
66
- `PaneContent` permanece como alias temporário de `PaneBody` durante a versão 12. Código novo usa
66
+ `PaneContent` continua exportado como alias depreciado de `PaneBody`. Código novo usa
67
67
  `PaneBody`.
68
68
 
69
69
  ## Escolher a navegação
@@ -138,7 +138,8 @@ páginas são folhas. Um grupo começa aberto e volta a abrir quando contém a p
138
138
 
139
139
  `collapsed` pertence à `Sidebar`. Nesse estado, `SidebarItem` e `SidebarNav` mantêm
140
140
  somente os ícones e expõem os rótulos em tooltips. Por isso, todo destino que aparece no modo
141
- recolhido precisa de um ícone reconhecível e de um `label` completo.
141
+ recolhido precisa de um ícone reconhecível e de um `label` completo. `SidebarNav` já monta o
142
+ `TooltipProvider`; um `SidebarItem` recolhido fora dele depende do provider na raiz do aplicativo.
142
143
 
143
144
  O slot de ícone do `SidebarItem` ocupa `1rem` nos dois estados e normaliza SVGs para essa medida.
144
145
  O consumidor escolhe o símbolo e sua cor sem precisar repetir largura ou altura em ícones SVG.
@@ -15,7 +15,7 @@ avatares e barras com dimensões próximas às linhas de texto esperadas.
15
15
 
16
16
  ## Card em carregamento
17
17
 
18
- O esqueleto reproduz o layout final do card — título, descrição, conteúdo e ações — para tela não pular quando os dados chegarem.
18
+ O esqueleto reproduz o layout final do card — título, descrição, conteúdo e ações — para a tela não pular quando os dados chegarem.
19
19
 
20
20
  ```tsx preview col
21
21
  <Card>