@softize/opus 19.0.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +5 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +15 -3
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/list.tsx +89 -78
  20. package/src/ui/components/patterns/page.tsx +20 -4
  21. package/src/ui/components/patterns/presentation.tsx +52 -37
  22. package/src/ui/components/patterns/split.tsx +44 -4
  23. package/src/ui/components/patterns/surface-anatomy.ts +35 -8
  24. package/src/ui/components/patterns/trigger.tsx +12 -2
  25. package/src/ui/components/primitives/alert.tsx +9 -1
  26. package/src/ui/components/primitives/avatar.tsx +7 -3
  27. package/src/ui/components/primitives/carousel.tsx +34 -5
  28. package/src/ui/components/primitives/checkbox.tsx +11 -3
  29. package/src/ui/components/primitives/command.tsx +2 -1
  30. package/src/ui/components/primitives/copyable.tsx +120 -10
  31. package/src/ui/components/primitives/dialog.tsx +41 -10
  32. package/src/ui/components/primitives/drawer.tsx +9 -1
  33. package/src/ui/components/primitives/empty.tsx +1 -1
  34. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  35. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  36. package/src/ui/components/primitives/input-group.tsx +9 -2
  37. package/src/ui/components/primitives/input-otp.tsx +1 -1
  38. package/src/ui/components/primitives/kbd.tsx +1 -1
  39. package/src/ui/components/primitives/label.tsx +3 -0
  40. package/src/ui/components/primitives/menu.tsx +5 -0
  41. package/src/ui/components/primitives/popover.tsx +1 -1
  42. package/src/ui/components/primitives/progress.tsx +9 -1
  43. package/src/ui/components/primitives/slider.tsx +62 -11
  44. package/src/ui/components/primitives/truncate.tsx +10 -1
  45. package/src/ui/docs/content/accordion.md +74 -27
  46. package/src/ui/docs/content/action-form-card.md +24 -15
  47. package/src/ui/docs/content/action-form-dialog.md +17 -7
  48. package/src/ui/docs/content/action-form.md +92 -77
  49. package/src/ui/docs/content/action-list-dialog.md +75 -38
  50. package/src/ui/docs/content/action-list.md +134 -52
  51. package/src/ui/docs/content/action-trigger.md +27 -10
  52. package/src/ui/docs/content/action-view.md +14 -14
  53. package/src/ui/docs/content/actions.md +161 -66
  54. package/src/ui/docs/content/ai.md +43 -44
  55. package/src/ui/docs/content/alert.md +83 -29
  56. package/src/ui/docs/content/ask.md +51 -26
  57. package/src/ui/docs/content/aspect-ratio.md +22 -24
  58. package/src/ui/docs/content/audit.md +23 -19
  59. package/src/ui/docs/content/auth.md +38 -19
  60. package/src/ui/docs/content/avatar.md +54 -5
  61. package/src/ui/docs/content/badge.md +29 -9
  62. package/src/ui/docs/content/breadcrumb.md +21 -8
  63. package/src/ui/docs/content/button.md +73 -43
  64. package/src/ui/docs/content/calendar.md +134 -26
  65. package/src/ui/docs/content/card.md +41 -15
  66. package/src/ui/docs/content/carousel.md +37 -6
  67. package/src/ui/docs/content/chat.md +118 -37
  68. package/src/ui/docs/content/checkbox.md +73 -7
  69. package/src/ui/docs/content/cli.md +94 -55
  70. package/src/ui/docs/content/collapsible.md +43 -19
  71. package/src/ui/docs/content/command.md +30 -8
  72. package/src/ui/docs/content/composer.md +23 -8
  73. package/src/ui/docs/content/content.md +31 -7
  74. package/src/ui/docs/content/copyable.md +39 -8
  75. package/src/ui/docs/content/customization.md +55 -46
  76. package/src/ui/docs/content/cycle.md +33 -18
  77. package/src/ui/docs/content/data-products.md +115 -0
  78. package/src/ui/docs/content/data-state.md +20 -6
  79. package/src/ui/docs/content/data.md +20 -19
  80. package/src/ui/docs/content/detail.md +12 -5
  81. package/src/ui/docs/content/dialog.md +107 -24
  82. package/src/ui/docs/content/dictionary-value.md +4 -4
  83. package/src/ui/docs/content/dock.md +54 -19
  84. package/src/ui/docs/content/dot.md +35 -5
  85. package/src/ui/docs/content/drawer.md +63 -19
  86. package/src/ui/docs/content/empty-value.md +20 -6
  87. package/src/ui/docs/content/empty.md +24 -3
  88. package/src/ui/docs/content/events.md +76 -16
  89. package/src/ui/docs/content/executions.md +9 -9
  90. package/src/ui/docs/content/field.md +21 -6
  91. package/src/ui/docs/content/getting-started.md +44 -34
  92. package/src/ui/docs/content/icon-picker.md +19 -13
  93. package/src/ui/docs/content/icon-tile.md +28 -5
  94. package/src/ui/docs/content/input-otp.md +58 -7
  95. package/src/ui/docs/content/input.md +22 -3
  96. package/src/ui/docs/content/item.md +53 -6
  97. package/src/ui/docs/content/kbd.md +5 -1
  98. package/src/ui/docs/content/label.md +13 -4
  99. package/src/ui/docs/content/log.md +5 -5
  100. package/src/ui/docs/content/markdown.md +5 -4
  101. package/src/ui/docs/content/mcp.md +49 -23
  102. package/src/ui/docs/content/menu.md +95 -10
  103. package/src/ui/docs/content/metric-card.md +12 -4
  104. package/src/ui/docs/content/observability.md +10 -9
  105. package/src/ui/docs/content/page.md +118 -12
  106. package/src/ui/docs/content/pagination.md +13 -5
  107. package/src/ui/docs/content/pane.md +55 -5
  108. package/src/ui/docs/content/popover.md +42 -5
  109. package/src/ui/docs/content/presentation.md +91 -47
  110. package/src/ui/docs/content/progress.md +27 -14
  111. package/src/ui/docs/content/queue.md +39 -21
  112. package/src/ui/docs/content/radio-group.md +26 -4
  113. package/src/ui/docs/content/router.md +29 -10
  114. package/src/ui/docs/content/runtime.md +90 -54
  115. package/src/ui/docs/content/scheduler.md +53 -21
  116. package/src/ui/docs/content/scroll-area.md +42 -35
  117. package/src/ui/docs/content/select.md +78 -33
  118. package/src/ui/docs/content/semantic-context.md +21 -2
  119. package/src/ui/docs/content/separator.md +15 -9
  120. package/src/ui/docs/content/sidebar.md +3 -2
  121. package/src/ui/docs/content/skeleton.md +6 -1
  122. package/src/ui/docs/content/slider.md +37 -9
  123. package/src/ui/docs/content/spinner.md +4 -3
  124. package/src/ui/docs/content/split.md +19 -0
  125. package/src/ui/docs/content/storage.md +8 -9
  126. package/src/ui/docs/content/switch.md +10 -7
  127. package/src/ui/docs/content/table.md +46 -8
  128. package/src/ui/docs/content/tabs.md +14 -4
  129. package/src/ui/docs/content/testing.md +76 -31
  130. package/src/ui/docs/content/textarea.md +5 -4
  131. package/src/ui/docs/content/toast.md +48 -7
  132. package/src/ui/docs/content/toggle.md +3 -3
  133. package/src/ui/docs/content/tokens.md +15 -26
  134. package/src/ui/docs/content/tooltip.md +18 -2
  135. package/src/ui/docs/content/truncate.md +36 -6
  136. package/src/ui/docs/content/ui.md +74 -22
  137. package/src/ui/docs/content/upgrading.md +45 -36
  138. package/src/ui/docs/plugin.ts +36 -2
  139. package/src/ui/docs/registry.tsx +6 -0
  140. package/src/ui/drivers/react.tsx +16 -8
  141. package/src/ui/meta.ts +31 -31
  142. package/src/vite/design.ts +16 -4
@@ -1,9 +1,9 @@
1
- Use `IconPicker` para selecionar um ícone por seu nome em kebab-case. O consumidor renderiza o
2
- valor com a mesma paleta, por exemplo `iconPickerIcons[name] ?? FallbackIcon`. Selecionar novamente o
3
- ícone atual limpa o valor.
4
-
5
1
  ## Escolher na paleta padrão
6
2
 
3
+ Use `IconPicker` quando a pessoa puder escolher o ícone que identifica um registro, como um
4
+ relatório ou um painel. A lista abre em um popover com busca, e o valor guardado é o nome do ícone em
5
+ kebab-case, como `chart-line`. Selecionar novamente o ícone atual limpa o valor.
6
+
7
7
  ```tsx preview col md
8
8
  const [icon, setIcon] = useState('chart-line')
9
9
 
@@ -15,10 +15,11 @@ render(
15
15
  )
16
16
  ```
17
17
 
18
- ## Renderizando o escolhido
18
+ ## Apresentar o ícone escolhido
19
19
 
20
- A paleta padrão é exportada como `iconPickerIcons`. Use a mesma coleção para apresentar o ícone
21
- salvo em listas, cards e árvores.
20
+ O valor é somente um nome: quem apresenta o registro renderiza o ícone com a mesma paleta. A paleta
21
+ padrão é exportada como `iconPickerIcons`; use-a em listas, cards e árvores, com um ícone de reserva
22
+ para nomes que não existam mais, como em `iconPickerIcons[name] ?? FileText`.
22
23
 
23
24
  ```tsx preview col md
24
25
  const [icon, setIcon] = useState('store')
@@ -27,10 +28,14 @@ const SelectedIcon = iconPickerIcons[icon] ?? FileText
27
28
  render(
28
29
  <div className="flex items-center gap-3">
29
30
  <IconPicker value={icon} onChange={setIcon} className="w-56" />
30
- <span className="flex items-center gap-2 rounded-md border px-3 py-2 text-sm">
31
- <SelectedIcon className="size-4 text-muted-foreground" />
32
- Painel comercial
33
- </span>
31
+ <Item variant="outline" size="sm">
32
+ <ItemMedia variant="icon">
33
+ <SelectedIcon />
34
+ </ItemMedia>
35
+ <ItemHeader>
36
+ <ItemTitle>Painel comercial</ItemTitle>
37
+ </ItemHeader>
38
+ </Item>
34
39
  </div>,
35
40
  )
36
41
  ```
@@ -62,5 +67,6 @@ render(
62
67
  | `onChange` | `(name: string) => void` | | Recebe o nome escolhido. |
63
68
  | `icons` | `Record<string, LucideIcon>` | `iconPickerIcons` | Paleta nome → componente. |
64
69
  | `placeholder` | `string` | `'Selecione um ícone…'` | Texto do gatilho sem seleção. |
65
- | `disabled` | `boolean` | | Desabilita o gatilho. |
66
- | `id`, `aria-invalid`, `aria-describedby` | | | Integração com `Field`/`ActionForm`: o gatilho recebe a identidade e o estado de erro do campo. |
70
+ | `disabled` | `boolean` | `false` | Desabilita o gatilho. |
71
+ | `className` | `string` | | Classes do gatilho, em geral para ajustar a largura. Sem elas, o gatilho ocupa toda a largura disponível. |
72
+ | `id`, `aria-invalid`, `aria-describedby` | `string`, `boolean`, `string` | | Integração com `Field` e `ActionForm`: o gatilho recebe a identidade do campo, o estado inválido e a mensagem associada. Com `aria-invalid`, a moldura assume o contexto de erro, como no `Input`. |
@@ -1,8 +1,8 @@
1
1
  ## Moldura decorativa para ícone
2
2
 
3
3
  Use `IconTile` quando um ícone precisar de uma pequena superfície para ganhar hierarquia em uma
4
- legenda, item ou resumo. O componente não é interativo; para uma ação, use `Button` com tamanho de
5
- ícone e um nome acessível.
4
+ legenda, item ou resumo. O componente não é interativo; para uma ação, use [`Button`](/ui/button)
5
+ com tamanho de ícone e um nome acessível.
6
6
 
7
7
  ```tsx preview row
8
8
  <IconTile size="xs"><UserRound /></IconTile>
@@ -16,7 +16,7 @@ uma classe `size-*` preserva a própria medida.
16
16
  ## Em uma legenda
17
17
 
18
18
  Slots de ícone não criam a moldura automaticamente. Componha `IconTile` somente quando ela fizer
19
- sentido para a superfície.
19
+ sentido para a superfície, como na legenda de um [`Content`](/ui/content) emoldurado.
20
20
 
21
21
  ```tsx preview col md
22
22
  <Content
@@ -32,11 +32,16 @@ sentido para a superfície.
32
32
  </span>
33
33
  }
34
34
  >
35
- <FieldGroup />
35
+ <FieldGroup>
36
+ <Field>
37
+ <FieldLabel htmlFor="icon-tile-plate">Placa</FieldLabel>
38
+ <Input id="icon-tile-plate" />
39
+ </Field>
40
+ </FieldGroup>
36
41
  </Content>
37
42
  ```
38
43
 
39
- ## Propriedades
44
+ ## Propriedades de IconTile
40
45
 
41
46
  | Propriedade | Tipo | Padrão | Descrição |
42
47
  | ----------- | ------------------------------------------------------------------------ | ----------- | ------------------------------------------------------------- |
@@ -45,3 +50,21 @@ sentido para a superfície.
45
50
  | `variant` | `'subtle' \| 'outline'` | `'subtle'` | Usa preenchimento tonal ou acrescenta borda. |
46
51
 
47
52
  As demais propriedades são as propriedades nativas de `span`.
53
+
54
+ ## Função iconTileVariants
55
+
56
+ `iconTileVariants({ context, variant, size })` retorna as classes da moldura para um elemento que
57
+ não é renderizado pelo `IconTile`. Os padrões são os mesmos do componente: `neutral`, `subtle` e
58
+ `default`.
59
+
60
+ ```ts
61
+ function iconTileVariants(options?: {
62
+ context?: IconTileContext | null
63
+ variant?: IconTileVariant | null
64
+ size?: IconTileSize | null
65
+ }): string
66
+ ```
67
+
68
+ `resolveIconTileStyle` continua exportado, mas está depreciado e sai em uma próxima versão maior. Ele
69
+ só preenche os padrões acima, que `IconTile` e `iconTileVariants` já aplicam. Código novo usa um dos
70
+ dois.
@@ -1,10 +1,15 @@
1
1
  ## Código em uma sequência
2
2
 
3
- `maxLength` define o comprimento do código. Cada `InputOTPSlot` recebe o índice de uma posição e
4
- `InputOTPGroup` mantém a sequência agrupada.
3
+ Use `InputOTP` quando a pessoa precisar digitar um código curto recebido por outro canal, como a
4
+ confirmação de acesso enviada por e-mail ou por um aplicativo autenticador. Cada caractere ocupa uma
5
+ posição própria, e o foco avança sozinho durante a digitação ou ao colar o código inteiro.
6
+
7
+ `maxLength` define o comprimento. Cada `InputOTPSlot` recebe o índice de uma posição, e
8
+ `InputOTPGroup` reúne as posições em um bloco. `pattern` restringe os caracteres aceitos; no
9
+ exemplo, somente dígitos.
5
10
 
6
11
  ```tsx preview
7
- <InputOTP maxLength={6}>
12
+ <InputOTP maxLength={6} pattern="^\d+$">
8
13
  <InputOTPGroup>
9
14
  <InputOTPSlot index={0} />
10
15
  <InputOTPSlot index={1} />
@@ -40,14 +45,14 @@ Use `InputOTPSeparator` entre grupos para dividir visualmente o código. Os índ
40
45
  ## Controlado
41
46
 
42
47
  Use `value` e `onChange` quando o estado externo precisar validar o comprimento antes de liberar a
43
- próxima ação.
48
+ próxima ação. `onComplete` é chamado quando todas as posições estão preenchidas.
44
49
 
45
50
  ```tsx preview col-start
46
51
  const [code, setCode] = useState('')
47
52
 
48
53
  render(
49
54
  <>
50
- <InputOTP maxLength={6} value={code} onChange={setCode}>
55
+ <InputOTP maxLength={6} pattern="^\d+$" value={code} onChange={setCode}>
51
56
  <InputOTPGroup>
52
57
  <InputOTPSlot index={0} />
53
58
  <InputOTPSlot index={1} />
@@ -64,6 +69,41 @@ render(
64
69
  )
65
70
  ```
66
71
 
72
+ ## Código recusado
73
+
74
+ Quando o código for recusado, declare `aria-invalid` no `InputOTP`, que o repassa ao campo que
75
+ recebe a digitação, para que leitores de tela anunciem o erro. Declare-o também em cada
76
+ `InputOTPSlot` para aplicar a borda de erro e explique o problema em um texto associado por
77
+ `aria-describedby`. `disabled` no `InputOTP` bloqueia a digitação e
78
+ esmaece o conjunto, por exemplo enquanto o código é conferido.
79
+
80
+ ```tsx preview col-start
81
+ const [code, setCode] = useState('1234')
82
+
83
+ render(
84
+ <>
85
+ <InputOTP
86
+ maxLength={4}
87
+ pattern="^\d+$"
88
+ value={code}
89
+ onChange={setCode}
90
+ aria-invalid
91
+ aria-describedby="otp-error"
92
+ >
93
+ <InputOTPGroup>
94
+ <InputOTPSlot index={0} aria-invalid />
95
+ <InputOTPSlot index={1} aria-invalid />
96
+ <InputOTPSlot index={2} aria-invalid />
97
+ <InputOTPSlot index={3} aria-invalid />
98
+ </InputOTPGroup>
99
+ </InputOTP>
100
+ <p id="otp-error" className="text-sm text-context-danger-emphasis">
101
+ Código expirado. Peça um novo código para continuar.
102
+ </p>
103
+ </>,
104
+ )
105
+ ```
106
+
67
107
  ## Propriedades de InputOTP
68
108
 
69
109
  | Propriedade | Tipo | Padrão | Descrição |
@@ -72,11 +112,22 @@ render(
72
112
  | `value` | `string` | | Código no modo controlado. Use com `onChange`. |
73
113
  | `onChange` | `(value: string) => void` | | Chamado a cada entrada com o código acumulado. |
74
114
  | `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+$'`. |
76
- | `disabled` | `boolean` | `false` | Desabilita a digitação em todo o conjunto. |
115
+ | `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 `pattern="^\d+$"` em JSX; em uma string JavaScript, a barra é escapada: `'^\\d+$'`. |
116
+ | `disabled` | `boolean` | `false` | Desabilita a digitação e esmaece todo o conjunto. |
117
+ | `containerClassName` | `string` | | Classes do contêiner que alinha grupos e separadores. `className` vai para o campo de texto oculto que recebe a digitação. |
77
118
 
78
119
  ## Propriedades de InputOTPSlot
79
120
 
80
121
  | Propriedade | Tipo | Padrão | Descrição |
81
122
  |---|---|---|---|
82
123
  | `index` | `number` | | Posição do caractere, começando em zero. |
124
+ | `aria-invalid` | `boolean` | | Aplica a borda de erro à posição. |
125
+
126
+ ## Propriedades de InputOTPGroup
127
+
128
+ `InputOTPGroup` não tem propriedades próprias e aceita as props nativas de `<div>`.
129
+
130
+ ## Propriedades de InputOTPSeparator
131
+
132
+ `InputOTPSeparator` desenha um traço com `role="separator"` entre grupos. Não tem propriedades
133
+ próprias e aceita as props nativas de `<div>`.
@@ -99,8 +99,8 @@ Use `InputGroup` quando o campo precisar combinar vários adornos, prefixos, suf
99
99
 
100
100
  ### Com ícone e ação
101
101
 
102
- `InputGroupAddon` ancora os adornos nas extremidades. O clique em uma área inerte do addon transfere
103
- o foco para o campo.
102
+ `InputGroupAddon` ancora os adornos nas extremidades. Um clique em uma área inerte do addon, fora
103
+ dos botões, transfere o foco para o campo, seja `InputGroupInput` ou `InputGroupTextarea`.
104
104
 
105
105
  ```tsx preview col
106
106
  <InputGroup>
@@ -149,11 +149,30 @@ Com `InputGroupTextarea`, um addon em `block-end` forma uma região de ações a
149
149
  </InputGroup>
150
150
  ```
151
151
 
152
+ ### Estados do grupo
153
+
154
+ `aria-invalid` no controle interno aplica o contexto de erro à moldura inteira. Para desabilitar,
155
+ basta declarar `disabled` no controle: o campo esmaece pelo próprio estado e os addons acompanham.
156
+ Um `InputGroupButton` desabilitado não apaga o grupo. Quando os addons precisarem esmaecer com o
157
+ controle ativo, declare `data-disabled="true"` no `InputGroup`.
158
+
159
+ ```tsx preview col
160
+ <InputGroup>
161
+ <InputGroupAddon><AtSign /></InputGroupAddon>
162
+ <InputGroupInput aria-invalid defaultValue="joao@" placeholder="E-mail do responsável" />
163
+ </InputGroup>
164
+ <InputGroup>
165
+ <InputGroupAddon><AtSign /></InputGroupAddon>
166
+ <InputGroupInput disabled defaultValue="joao@softize.com.br" />
167
+ </InputGroup>
168
+ ```
169
+
152
170
  ### Propriedades de InputGroup
153
171
 
154
172
  | Propriedade | Tipo | Padrão | Descrição |
155
173
  |---|---|---|---|
156
174
  | `shape` | `'default' \| 'pill'` | `'default'` | Geometria da moldura compartilhada. |
175
+ | `data-disabled` | `'true' \| 'false'` | | Com `true`, esmaece os addons mesmo com o controle ativo. Com o controle desabilitado, os addons já esmaecem sem ele. |
157
176
 
158
177
  ### Propriedades de InputGroupAddon
159
178
 
@@ -165,7 +184,7 @@ Com `InputGroupTextarea`, um addon em `block-end` forma uma região de ações a
165
184
 
166
185
  | Propriedade | Tipo | Padrão | Descrição |
167
186
  |---|---|---|---|
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. |
187
+ | `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | O subconjunto da escala única que cabe em um campo (1.5 · 2 · 1.5 · 1.75rem); os `icon-*` são quadrados. |
169
188
  | `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'`. |
170
189
  | `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'ghost'` | Tratamento visual; use `solid` quando a ação precisar de ênfase. |
171
190
 
@@ -1,7 +1,13 @@
1
1
  ## Linha composta
2
2
 
3
+ Use `Item` para uma linha de lista que identifica algo — um repositório, uma pessoa, uma
4
+ configuração — e oferece o que fazer com ele. Para dados em colunas comparáveis, use
5
+ [`Table`](/ui/table); para uma coleção carregada por uma action `kind: 'list'`, use
6
+ [`ActionList`](/ui/action-list).
7
+
3
8
  A composição completa usa `ItemMedia` à esquerda, `ItemHeader` (`ItemTitle` +
4
- `ItemDescription`) no meio e `ItemActions` à direita. Ícone e imagem mantêm uma moldura quadrada
9
+ `ItemDescription`) no meio e `ItemActions` à direita. A descrição mostra no máximo duas linhas e
10
+ termina em reticências quando passa disso. Ícone e imagem mantêm uma moldura quadrada
5
11
  alinhada ao topo, mesmo quando a descrição ocupa mais linhas. `variant="outline"` desenha a borda.
6
12
  O tamanho padrão usa corpo produtivo, `0.75rem` de padding e `0.75rem` entre regiões. `size="sm"`
7
13
  mantém o padding horizontal, reduz o padding vertical para `0.5rem` e o espaço entre regiões para
@@ -28,7 +34,10 @@ mantém o padding horizontal, reduz o padding vertical para `0.5rem` e o espaço
28
34
 
29
35
  ## Lista emoldurada
30
36
 
31
- `ItemGroup variant="framed"` aplica a superfície canônica. Os divisores continuam explícitos com `ItemSeparator`, tanto no modo `framed` quanto no `plain` — o padrão para listas de repositórios, agentes ou sessões.
37
+ `ItemGroup variant="framed"` aplica a superfície canônica. Os divisores continuam explícitos com
38
+ `ItemSeparator`, tanto no modo `framed` quanto no `plain` — o padrão para listas de repositórios,
39
+ agentes ou sessões. O grupo é anunciado como lista, e cada `Item` dentro dele, como um item da
40
+ lista.
32
41
 
33
42
  ```tsx preview col
34
43
  <ItemGroup variant="framed">
@@ -38,7 +47,7 @@ mantém o padding horizontal, reduz o padding vertical para `0.5rem` e o espaço
38
47
  </ItemMedia>
39
48
  <ItemHeader>
40
49
  <ItemTitle>empresa-x-web</ItemTitle>
41
- <ItemDescription>Front do workspace Empresa X.</ItemDescription>
50
+ <ItemDescription>Frontend do workspace Empresa X.</ItemDescription>
42
51
  </ItemHeader>
43
52
  <ItemActions>
44
53
  <Badge context="success">Sincronizado</Badge>
@@ -62,7 +71,9 @@ mantém o padding horizontal, reduz o padding vertical para `0.5rem` e o espaço
62
71
 
63
72
  ## Clicável e compacto
64
73
 
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.
74
+ `asChild` funde o Item em um `<a>`: a linha inteira vira alvo e o fundo muda ao passar o ponteiro.
75
+ `size="sm"` aperta o respiro; `ItemMedia` na variante padrão recebe um [`Avatar`](/ui/avatar), que
76
+ já traz a própria moldura.
66
77
 
67
78
  ```tsx preview col
68
79
  <Item asChild size="sm" variant="outline">
@@ -103,6 +114,36 @@ Use `ItemBody` quando a linha também apresenta um valor ou controle que não pe
103
114
  `ItemContent` permanece exportado apenas por compatibilidade e está obsoleto. Código novo
104
115
  usa `ItemHeader` ou `ItemBody` conforme o papel do conteúdo.
105
116
 
117
+ ## Rodapé
118
+
119
+ `ItemFooter` ocupa uma linha inteira abaixo das demais regiões, com o conteúdo distribuído entre as
120
+ pontas. Use-o para metadados ou estado que complementam a linha.
121
+
122
+ ```tsx preview col
123
+ <Item variant="outline">
124
+ <ItemMedia variant="icon">
125
+ <FolderGit2 />
126
+ </ItemMedia>
127
+ <ItemHeader>
128
+ <ItemTitle>empresa-x-api</ItemTitle>
129
+ <ItemDescription>Repositório do backend do workspace Empresa X.</ItemDescription>
130
+ </ItemHeader>
131
+ <ItemActions>
132
+ <Button size="sm" variant="outline">Abrir</Button>
133
+ </ItemActions>
134
+ <ItemFooter>
135
+ <span className="text-xs text-muted-foreground">Atualizado há 2 horas</span>
136
+ <Badge context="success">Sincronizado</Badge>
137
+ </ItemFooter>
138
+ </Item>
139
+ ```
140
+
141
+ ## Composições que lançam erro
142
+
143
+ As partes conferem o próprio lugar ao renderizar. `ItemMedia`, `ItemHeader`, `ItemBody`,
144
+ `ItemActions` e `ItemFooter` lançam erro fora de um `Item`; `ItemTitle` e `ItemDescription` lançam
145
+ erro fora de um `ItemHeader` ou do `ItemContent` obsoleto. A mensagem nomeia o pai esperado.
146
+
106
147
  ## Propriedades de Item
107
148
 
108
149
  | Propriedade | Tipo | Padrão | Descrição |
@@ -117,12 +158,18 @@ usa `ItemHeader` ou `ItemBody` conforme o papel do conteúdo.
117
158
  |---|---|---|---|
118
159
  | `variant` | `'plain' \| 'framed'` | `'plain'` | `framed` aplica moldura e superfície; os divisores permanecem explícitos nos dois modos. |
119
160
 
161
+ O grupo renderiza um `div` com `role="list"` e aceita as props nativas desse elemento.
162
+
120
163
  ## Propriedades de ItemMedia
121
164
 
122
165
  | Propriedade | Tipo | Padrão | Descrição |
123
166
  |---|---|---|---|
124
167
  | `variant` | `'default' \| 'icon' \| 'image'` | `'default'` | Define mídia livre ou uma moldura quadrada para ícone ou imagem. |
125
168
 
169
+ ## Propriedades das demais partes
170
+
126
171
  `ItemHeader`, `ItemTitle`, `ItemDescription`, `ItemBody`, `ItemActions`, `ItemFooter` e
127
- `ItemSeparator` aceitam as props nativas dos respectivos elementos e não adicionam propriedades
128
- próprias. O `ItemContent` obsoleto preserva esse mesmo contrato por compatibilidade.
172
+ `ItemSeparator` não têm props próprias. Cada um aceita as props nativas do elemento que renderiza:
173
+ `div`, exceto `ItemDescription`, que renderiza `p`, e `ItemSeparator`, que aceita as props de
174
+ [`Separator`](/ui/separator). O `ItemContent` obsoleto preserva esse mesmo contrato por
175
+ compatibilidade.
@@ -1,7 +1,7 @@
1
1
  ## Uma tecla
2
2
 
3
3
  Use um `Kbd` para representar cada tecla. O componente apresenta o atalho; a captura do teclado
4
- continua no handler do aplicativo.
4
+ continua no handler do aplicativo. Em um item de [`Menu`](/ui/menu), o atalho usa `MenuShortcut`.
5
5
 
6
6
  ```tsx preview
7
7
  <Kbd>Esc</Kbd>
@@ -29,6 +29,7 @@ reconhecível que o nome da tecla.
29
29
  <Kbd>
30
30
  <CommandIcon />
31
31
  </Kbd>
32
+ <span className="text-xs text-muted-foreground">+</span>
32
33
  <Kbd>
33
34
  <CornerDownLeft />
34
35
  </Kbd>
@@ -69,3 +70,6 @@ ação antes de informar seu atalho.
69
70
  |---|---|---|---|
70
71
  | `children` | `React.ReactNode` | | Teclas da combinação e conectores, como `+`, entre elas. |
71
72
  | `className` | `string` | | Classes adicionais aplicadas ao agrupador. |
73
+
74
+ `KbdGroup` renderiza um `kbd` que envolve as teclas, a marcação HTML de uma combinação, e aceita as
75
+ props desse elemento.
@@ -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.