@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
package/src/ui/meta.ts CHANGED
@@ -27,13 +27,13 @@ export const componentMeta = {
27
27
  name: "alert",
28
28
  ancestry: "opus",
29
29
  whenToUse:
30
- "Aviso inline no fluxo da página — `context` declara neutral/info/success/warning/danger e `variant` escolhe subtle/outline (ADR 0006). Forma curta: `<Alert title description icon context />`; para conteúdo rico, componha AlertMedia + AlertHeader (AlertTitle e AlertDescription) + AlertActions. Ações usam Button ghost como ponto de partida; tentativas repetidas podem usar somente o ícone, com nome acessível e tooltip. A mídia mantém uma moldura tonal quadrada, mesmo com descrição multilinha. O texto comunica o significado sem depender só da cor. Para interromper cobrando decisão, use `dialog.confirm()`; para recado passageiro, `toast`.",
30
+ "Mantenha uma informação visível no contexto da página até que ela deixe de valer. `context` declara neutral, info, success, warning ou danger, e `variant` escolhe subtle ou outline. No caso comum, use a forma curta `<Alert title description icon context />`; para conteúdo rico, componha AlertMedia, AlertHeader (com AlertTitle e AlertDescription) e AlertActions. As ações partem de Button ghost; uma nova tentativa pode usar somente o ícone, com nome acessível e tooltip. A mídia mantém uma moldura tonal quadrada, mesmo com descrição de várias linhas, e o texto comunica o significado sem depender só da cor. Para cobrar uma decisão, use `dialog.confirm()`; para confirmar o resultado de uma ação sem ocupar espaço na página, use `toast`; para o erro de uma consulta, use DataState.",
31
31
  },
32
32
  badge: {
33
33
  name: "badge",
34
34
  ancestry: "shadcn",
35
35
  whenToUse:
36
- "Rótulo curto de status/categoria. `context` declara neutral/primary/info/success/warning/danger; `variant` escolhe solid/subtle/outline (ADR 0006). Não-interativo — para clique, use Button ou `asChild` em um <a>. Valor de dicionário com papel declarado usa DictionaryValue em vez de escolher contexto ou variante na tela.",
36
+ "Rotule um estado ou uma categoria com um texto curto que continua legível. `context` declara neutral, primary, info, success, warning ou danger, e `variant` escolhe solid, subtle ou outline. O Badge não é interativo: para uma ação, use Button; para navegar, use `asChild` com um `<a>`. Quando só a cor basta, ao lado de um texto que já nomeia o item, use Dot. Um valor de dicionário com papel declarado usa DictionaryValue em vez de escolher contexto ou variante na tela.",
37
37
  },
38
38
  "empty-value": {
39
39
  name: "empty-value",
@@ -51,7 +51,7 @@ export const componentMeta = {
51
51
  name: "dot",
52
52
  ancestry: "opus",
53
53
  whenToUse:
54
- "Sinal compacto de estado. `context` declara neutral/primary/info/success/warning/danger e `variant` escolhe solid/outline. Passe `label` quando a cor reforçar informação; sem label, o ponto é decorativo. Para texto visível, use Badge.",
54
+ "Sinalize um estado com um ponto compacto quando o texto ao redor já dá o contexto. `context` declara neutral, primary, info, success, warning ou danger, e `variant` escolhe solid ou outline. Passe `label` quando a cor reforçar informação; sem `label`, o ponto é decorativo e fica oculto dos leitores de tela. Quando o estado precisar continuar legível sozinho, use Badge.",
55
55
  },
56
56
  "icon-tile": {
57
57
  name: "icon-tile",
@@ -63,7 +63,7 @@ export const componentMeta = {
63
63
  name: "detail",
64
64
  ancestry: "opus",
65
65
  whenToUse:
66
- 'Dados somente leitura em pares chave/valor. Compõe DetailGroup > DetailField com semântica de lista de definições; `columns` controla a grade, `orientation` posiciona chave e valor e alinha uma coluna compartilhada de rótulos no modo horizontal, `variant="framed"` aplica moldura e `dividers` acrescenta somente as divisórias internas. Para entrada, validação e erro, use Field; para uma coleção pesquisável, use ActionList.',
66
+ 'Apresente dados somente leitura como pares de rótulo e valor. DetailGroup organiza os DetailField com a semântica de uma lista de definições; `columns` controla a grade, `orientation` posiciona rótulo e valor e, no modo horizontal, alinha os rótulos em uma coluna compartilhada. `variant="framed"` aplica a moldura e `dividers` acrescenta somente as divisórias internas. Para entrada, validação e erro, use Field; para uma coleção pesquisável, use ActionList.',
67
67
  },
68
68
  dock: {
69
69
  name: "dock",
@@ -87,7 +87,7 @@ export const componentMeta = {
87
87
  name: "metric-card",
88
88
  ancestry: "opus",
89
89
  whenToUse:
90
- "Medida resumida com rótulo, valor em destaque e descrição opcional. Use `secondaryMetric` para uma segunda leitura diretamente relacionada, exibida como lâmina posterior com explicação própria. `context` realça semanticamente somente o ícone; aceita ação relacionada e estado de carregamento. O consumidor calcula e formata os valores; para conteúdo geral ou estrutura livre, use Card.",
90
+ "Apresente uma medida resumida, como um total ou uma contagem, com rótulo, valor em destaque e descrição opcional. O consumidor calcula e formata os valores. Use `secondaryMetric` para uma segunda leitura da mesma dimensão, exibida como lâmina posterior com rótulo próprio; `context` realça somente o ícone, `action` acrescenta uma ação diretamente relacionada e `loading` substitui rótulo e valor durante a carga. Com `onActivate`, `active` e `activationLabel`, informados juntos, o card vira um controle que aplica o recorte que a medida resume e expõe o estado por `aria-pressed`. Para conteúdo geral ou estrutura livre, use Card.",
91
91
  },
92
92
  chat: {
93
93
  name: "chat",
@@ -99,7 +99,7 @@ export const componentMeta = {
99
99
  name: "checkbox",
100
100
  ancestry: "shadcn",
101
101
  whenToUse:
102
- "Caixa de marcação booleana (Radix). Controlado por `checked`/`onCheckedChange`. Parear com Label. Para escolha única de várias opções, use Select/RadioGroup.",
102
+ "Permita marcar uma opção que só vale ao salvar um formulário, como aceitar uma condição ou incluir uma etapa. Associe cada caixa a um Label e controle o estado com `checked` e `onCheckedChange`, ou defina o inicial com `defaultChecked`. Para uma preferência que entra em vigor no clique, use Switch; para uma escolha única entre várias opções, use RadioGroup ou Select.",
103
103
  },
104
104
  "icon-picker": {
105
105
  name: "icon-picker",
@@ -111,7 +111,7 @@ export const componentMeta = {
111
111
  name: "command",
112
112
  ancestry: "shadcn",
113
113
  whenToUse:
114
- "Lista filtrável com teclado (cmdk) — base de command-palettes. Use CommandDialog para palette modal (⌘K). Para escolha simples, use o Select direto.",
114
+ "Permita encontrar um item ou uma ação digitando parte do nome e escolhê-lo pelo teclado. A lista filtra enquanto a busca muda, organiza os itens em grupos e mostra um estado vazio quando nada corresponde. Para uma paleta de comandos em modal, use CommandDialog; o aplicativo registra o atalho que a abre, como ⌘K. Para escolher um valor em um formulário, use Select.",
115
115
  },
116
116
  composer: {
117
117
  name: "composer",
@@ -123,7 +123,7 @@ export const componentMeta = {
123
123
  name: "content-header",
124
124
  ancestry: "opus",
125
125
  whenToUse:
126
- 'Estruture uma região com título e corpo. No caso comum, declare título, descrição, contador e ações diretamente em Content; use `framed` quando a região precisar de limite próprio, `as="fieldset"` para agrupar campos sob uma legenda nativa e `frameVariant="legend"` somente quando essa legenda precisar integrar visualmente a borda. Componha ContentHeader apenas quando precisar controlar a anatomia.',
126
+ 'Estruture uma região com título e corpo. No caso comum, declare título, mídia, descrição, contador e ações diretamente em Content; use `framed` quando a região precisar de limite próprio, `frameVariant="legend"` para integrar visualmente o header à borda e `as="fieldset"` somente para agrupar campos sob uma legenda nativa. Componha ContentHeader apenas quando precisar controlar a anatomia.',
127
127
  },
128
128
  copyable: {
129
129
  name: "copyable",
@@ -147,25 +147,25 @@ export const componentMeta = {
147
147
  name: "label",
148
148
  ancestry: "shadcn",
149
149
  whenToUse:
150
- "Rótulo acessível de um campo. `htmlFor` aponta para o id do controle. Parear com Input/Textarea/Select.",
150
+ "Nomeie um controle de formulário. Associe `htmlFor` ao `id` do controle: o rótulo passa a ser o nome acessível do campo, e clicar nele move o foco para o controle. Quando o campo também tiver ajuda e mensagem de erro, use Field.",
151
151
  },
152
152
  markdown: {
153
153
  name: "markdown",
154
154
  ancestry: "opus",
155
155
  whenToUse:
156
- "Renderize Markdown como HTML semântico com a tipografia do tema. HTML recebido no texto é escapado, por isso o componente pode apresentar conteúdo de pessoas ou modelos com segurança. Para código com destaque e cópia, use CodeBlock.",
156
+ "Exiba texto formatado que chega como Markdown, como a resposta de um agente no Chat ou uma descrição escrita por uma pessoa. Passe o texto em `content`; o resultado é HTML semântico com a tipografia do tema. HTML presente no texto é escapado, não interpretado, por isso o componente serve para conteúdo de pessoas ou de modelos, e endereços soltos viram links.",
157
157
  },
158
158
  menu: {
159
159
  name: "menu",
160
160
  ancestry: "opus",
161
161
  whenToUse:
162
- 'O menu de ações da casa: lista flutuante ancorada em um gatilho (Radix DropdownMenu). Compõe Menu > MenuTrigger + MenuContent e seus itens. `context="danger"` sinaliza item com consequência perigosa. Para escolher um valor, use Select; para busca por teclado, Command; para conteúdo livre ancorado, Popover.',
162
+ 'Reúna ações relacionadas em uma lista flutuante aberta por um gatilho. Componha Menu > MenuTrigger + MenuContent com seus itens; `onSelect` executa a ação do item e fecha o menu, e `context="danger"` sinaliza um item com consequência perigosa. Para escolher um valor de formulário, use Select; para buscar pelo teclado, Command; para conteúdo livre ancorado, Popover.',
163
163
  },
164
164
  popover: {
165
165
  name: "popover",
166
166
  ancestry: "shadcn",
167
167
  whenToUse:
168
- "Painel flutuante ancorado em um gatilho, sem modal (Radix). Para conteúdo livre (form curto, detalhes). Use PopoverHeader/PopoverTitle/PopoverDescription para estruturar. Para lista de ações, use Menu; para modal, Dialog.",
168
+ "Apresente conteúdo livre junto a um gatilho sem abrir um modal, como um formulário curto ou detalhes. PopoverHeader agrupa PopoverTitle e PopoverDescription. Para uma lista de ações, use Menu; para uma dica curta ao passar o ponteiro, Tooltip; para uma tarefa que exige atenção exclusiva, Dialog.",
169
169
  },
170
170
  select: {
171
171
  name: "select",
@@ -177,7 +177,7 @@ export const componentMeta = {
177
177
  name: "separator",
178
178
  ancestry: "shadcn",
179
179
  whenToUse:
180
- "Linha divisória entre seções/itens (Radix). `orientation` horizontal|vertical. Decorativa por padrão (a11y).",
180
+ 'Marque a fronteira entre blocos de conteúdo que não pertencem a uma superfície com regiões. A linha horizontal fica entre blocos empilhados; com `orientation="vertical"`, ela separa itens lado a lado e herda a altura do contêiner. A linha é decorativa por padrão e fica fora da árvore de acessibilidade; use `decorative={false}` quando a divisão tiver significado. Não use Separator entre cabeçalho, corpo e rodapé de Card, Dialog ou Drawer: nessas superfícies, os divisores são derivados das regiões com `layout="sectioned"`.',
181
181
  },
182
182
  skeleton: {
183
183
  name: "skeleton",
@@ -195,7 +195,7 @@ export const componentMeta = {
195
195
  name: "table",
196
196
  ancestry: "shadcn",
197
197
  whenToUse:
198
- 'Tabela de dados. Compõe Table > (TableHeader > TableRow > TableHead, TableBody > TableRow > TableCell). `variant="plain"` vem sem borda externa; `variant="framed"` aplica a moldura canônica, recorta o scroll e destaca o cabeçalho. Para uma coleção com busca, filtros e paginação, use ActionList.',
198
+ 'Organize em linhas e colunas os dados que a tela já tem em mãos. Componha Table > (TableHeader > TableRow > TableHead, TableBody > TableRow > TableCell). `variant="plain"` vem sem borda externa; `variant="framed"` aplica a moldura, recorta a rolagem e destaca o cabeçalho. Para uma coleção com busca, filtros e paginação, use ActionList.',
199
199
  },
200
200
  tabs: {
201
201
  name: "tabs",
@@ -207,7 +207,7 @@ export const componentMeta = {
207
207
  name: "textarea",
208
208
  ancestry: "shadcn",
209
209
  whenToUse:
210
- "Campo de texto multilinha. Aceita os atributos nativos de <textarea> (rows, placeholder, disabled). Cresce com o conteúdo via `field-sizing-content`.",
210
+ "Receba texto livre com várias linhas, como uma descrição ou um contexto. O campo aceita os atributos nativos de `<textarea>` e cresce com o conteúdo a partir da altura mínima. Associe um Label; para uma caixa de envio com botão integrado, use Composer; para uma única linha, use Input.",
211
211
  },
212
212
  toast: {
213
213
  name: "toast",
@@ -231,7 +231,7 @@ export const componentMeta = {
231
231
  name: "accordion",
232
232
  ancestry: "shadcn",
233
233
  whenToUse:
234
- "Lista de seções empilhadas que abrem/fecham (Radix). `type` single (um painel por vez — combine com `collapsible` para permitir fechar todos) ou multiple (vários abertos). Cada AccordionItem precisa de `value`; o chevron já vem no AccordionTrigger. Para alternar conteúdo lado a lado, use Tabs; para um único bloco recolhível solto, use Collapsible.",
234
+ 'Divida um conteúdo longo em painéis que a pessoa expande sob demanda, cada um com seu título. `type="single"` mantém um painel aberto por vez — combine com `collapsible` para permitir fechar todos — e `type="multiple"` permite vários abertos. Cada AccordionItem precisa de `value`, e o AccordionTrigger já traz o ícone de estado. Para um único bloco recolhível, use Collapsible; para alternar entre painéis do mesmo nível, Tabs.',
235
235
  },
236
236
  "aspect-ratio": {
237
237
  name: "aspect-ratio",
@@ -249,25 +249,25 @@ export const componentMeta = {
249
249
  name: "breadcrumb",
250
250
  ancestry: "shadcn",
251
251
  whenToUse:
252
- "Trilha de navegação hierárquica (workspace → repositório → sessão): mostra onde o usuário está e o caminho de volta. `BreadcrumbLink` para os níveis navegáveis, `BreadcrumbPage` para o atual (não clicável), `BreadcrumbSeparator` entre eles e `BreadcrumbEllipsis` para colapsar trilhas longas. Para alternar painéis no mesmo nível, use Tabs.",
252
+ "Mostre onde a pessoa está quando a página fica vários níveis abaixo da entrada da seção e permita voltar a um nível intermediário. Use BreadcrumbLink nos níveis navegáveis, BreadcrumbPage no nível atual, que não é clicável, BreadcrumbSeparator entre eles e BreadcrumbEllipsis para colapsar trilhas longas. Quando basta voltar ao nível imediatamente acima, use PageBack; para alternar painéis no mesmo nível, Tabs.",
253
253
  },
254
254
  calendar: {
255
255
  name: "calendar",
256
256
  ancestry: "shadcn",
257
257
  whenToUse:
258
- 'Grade de datas (react-day-picker) para escolher um dia ou um intervalo. `mode` define a seleção (single/multiple/range) e o formato de `selected`/`onSelect` (Date, Date[] ou { from, to }). `captionLayout="dropdown"` troca o título do mês por seletores de mês/ano (pular para um período distante); `numberOfMonths` mostra meses lado a lado; `disabled` (Matcher) corta datas. Para exibir em um popover de campo, ancore no Popover.',
258
+ 'Permita escolher datas vendo o mês inteiro, como a data de uma entrega ou o período de um relatório. `mode` define a seleção — `single`, `multiple` ou `range` — e o formato de `selected` e `onSelect` (Date, Date[] ou { from, to }). `captionLayout="dropdown"` troca o título do mês por seletores de mês e ano, útil para chegar a um período distante; `numberOfMonths` mostra meses lado a lado e `disabled` impede a escolha de datas específicas. Os textos usam pt-BR por padrão. Para abrir o calendário a partir de um campo, ancore-o em um Popover.',
259
259
  },
260
260
  carousel: {
261
261
  name: "carousel",
262
262
  ancestry: "shadcn",
263
263
  whenToUse:
264
- "Trilho de slides deslizáveis (embla). Compõe Carousel > CarouselContent > CarouselItem + CarouselPrevious/CarouselNext; o `basis` do item controla quantos cabem na vista. `orientation` (horizontal/vertical), `opts` repassa o embla (loop, align), `setApi` expõe a instância. Para lista paginada de dados, use Table; para navegação entre painéis, Tabs.",
264
+ "Percorra uma sequência de itens do mesmo tipo em um espaço limitado, um ou alguns por vez. Componha Carousel > CarouselContent > CarouselItem com CarouselPrevious e CarouselNext; a largura de cada item define quantos cabem na área visível. `orientation` escolhe o eixo, `opts` configura o deslocamento, como `loop` e `align`, e `setApi` permite controlar o carrossel por código. Nomeie o carrossel com `aria-label`. Para consultar e comparar muitos registros, use ActionList; para navegar entre painéis, Tabs.",
265
265
  },
266
266
  collapsible: {
267
267
  name: "collapsible",
268
268
  ancestry: "shadcn",
269
269
  whenToUse:
270
- "Seção que abre e fecha (Radix): um CollapsibleTrigger revela ou esconde o CollapsibleContent. Compõe Collapsible > (CollapsibleTrigger + CollapsibleContent) — o Trigger já é o `<button>`. `defaultOpen` para o modo não controlado; `open`/`onOpenChange` para o controlado (ex.: girar o chevron); `disabled` trava o gatilho. Para alternar entre vários painéis, use Tabs; para menu de ações ancorado, Menu.",
270
+ "Esconda um bloco secundário atrás de um gatilho, mantendo o restante da tela visível. Componha Collapsible > (CollapsibleTrigger + CollapsibleContent); o gatilho já renderiza o `<button>`. Use `defaultOpen` no modo não controlado e `open` com `onOpenChange` quando outra parte da tela precisar conhecer o estado; `disabled` trava o gatilho. Para vários painéis recolhíveis, use Accordion; para alternar entre painéis, Tabs; para ações ancoradas, Menu.",
271
271
  },
272
272
  drawer: {
273
273
  name: "drawer",
@@ -291,7 +291,7 @@ export const componentMeta = {
291
291
  name: "input-otp",
292
292
  ancestry: "shadcn",
293
293
  whenToUse:
294
- "Receba um código numérico dividido em posições, como uma confirmação de acesso ou segundo fator. InputOTP define o comprimento, os slots representam as posições e o separador organiza blocos. Para texto livre, use Input.",
294
+ "Receba um código curto enviado por outro canal, como uma confirmação de acesso ou um segundo fator. `maxLength` define o comprimento, cada InputOTPSlot representa uma posição e InputOTPSeparator divide os blocos. Sem `pattern`, qualquer caractere é aceito; passe `pattern` para restringir a entrada, por exemplo a dígitos. Para texto livre, use Input.",
295
295
  },
296
296
  item: {
297
297
  name: "item",
@@ -309,7 +309,7 @@ export const componentMeta = {
309
309
  name: "pagination",
310
310
  ancestry: "shadcn",
311
311
  whenToUse:
312
- "Navegação entre páginas montada por composição: Pagination › PaginationContent › PaginationItem com PaginationLink, mais PaginationPrevious/PaginationNext e PaginationEllipsis. `page` dá o número e o nome acessível (“Página N”); `isActive` marca a atual. Com `href` o link é `<a>`; sem `href` vira `<button>` (modo controlado, com foco e `disabled`). Os números têm largura mínima quadrada e crescem com os dígitos; as setas seguem quadradas (`iconOnly`). `label` localiza as setas. É o único paginador: ActionList compõe esta primitiva na escala densa do rodapé. Para rolagem infinita ou listas curtas, dispense a barra.",
312
+ "Permita saltar entre as páginas de uma coleção dividida em partes. Em uma listagem derivada de uma action, ActionList já monta esta paginação no rodapé; componha-a diretamente apenas em coleções próprias, com Pagination › PaginationContent › PaginationItem e PaginationLink, PaginationPrevious, PaginationNext e PaginationEllipsis. `page` fornece o número e o nome acessível (“Página N”), e `isActive` marca a página atual. Com `href`, o link é um `<a>`; sem `href`, vira `<button>` para o modo controlado, com foco e `disabled`. `label` localiza o texto das setas. Para rolagem infinita ou listas curtas, dispense a paginação.",
313
313
  },
314
314
  progress: {
315
315
  name: "progress",
@@ -321,7 +321,7 @@ export const componentMeta = {
321
321
  name: "radio-group",
322
322
  ancestry: "shadcn",
323
323
  whenToUse:
324
- "Escolha única entre opções mutuamente exclusivas, todas visíveis ao mesmo tempo (Radix). Cada RadioGroupItem tem um `value`; o item escolhido vira o `value` do RadioGroup, controlado por `value`/`onValueChange` (ou `defaultValue` no modo não controlado). Pareie cada item com um Label. Para poucas opções que cabem na tela; com muitas, prefira Select; para ligar/desligar um único item, Checkbox ou Switch.",
324
+ "Permita uma escolha única entre opções mutuamente exclusivas que precisam permanecer visíveis. Cada RadioGroupItem declara um `value` e se associa a um Label; o RadioGroup guarda a escolha em `value` com `onValueChange`, ou em `defaultValue` no modo não controlado. Com muitas opções, prefira Select; para ligar ou desligar um único item, use Checkbox ou Switch.",
325
325
  },
326
326
  split: {
327
327
  name: "split",
@@ -339,25 +339,25 @@ export const componentMeta = {
339
339
  name: "pane",
340
340
  ancestry: "opus",
341
341
  whenToUse:
342
- "Estruture a coluna ou faixa encaixada num Split: PaneHeader nomeia a região e leva mídia e ações, PaneBody rola e PaneFooter fixa o que precisa ficar à vista. `layout` escolhe entre regiões seccionadas, com divisores derivados da vizinhança, e o fluxo contínuo. O `inset` vale para o pane sem slots; com eles, o recuo é de cada região.",
342
+ "Estruture a coluna ou faixa encaixada em um Split. PaneHeader nomeia a região e acomoda mídia e ações; ao lado desses slots, também aceita conteúdo livre, como uma busca ou a marca de uma barra lateral. PaneBody rola e PaneFooter mantém à vista o que não pode sair da tela. `layout` escolhe entre regiões seccionadas, com divisores derivados da vizinhança, e o fluxo contínuo. `inset` vale para o pane sem slots; com eles, o recuo pertence a cada região.",
343
343
  },
344
344
  "scroll-area": {
345
345
  name: "scroll-area",
346
346
  ancestry: "shadcn",
347
347
  whenToUse:
348
- 'Região rolável com barra estilizada da casa (Radix), no lugar da scrollbar do sistema. Dê altura (ou largura) ao ScrollArea via className e ponha o conteúdo dentro; a barra vertical já vem por padrão. Para rolagem horizontal, acrescente `<ScrollBar orientation="horizontal" />` como filho. Para a página inteira rolar, deixe o navegador cuidar — isto é para um painel com altura fixa (lista de sessões, log, trilho de skills).',
348
+ 'Mantenha a altura de um bloco, como uma lista, um log ou um histórico, e role o conteúdo que passar dela com a barra do tema no lugar da barra do sistema. Dê a altura ao ScrollArea por `className` e coloque o conteúdo dentro; a barra vertical já vem incluída. Para rolar na horizontal, acrescente `<ScrollBar orientation="horizontal" />` como filho. Quando a página inteira precisar rolar, deixe a rolagem com o navegador.',
349
349
  },
350
350
  slider: {
351
351
  name: "slider",
352
352
  ancestry: "shadcn",
353
353
  whenToUse:
354
- "Controle de valor em uma faixa contínua, arrastado pelo thumb (Radix). `min`/`max`/`step` delimitam a faixa; `value`/`onValueChange` controlam (array de números — `[n]` para um thumb, `[a, b]` para um intervalo) ou `defaultValue` no modo não controlado. `orientation` horizontal/vertical, `disabled` esmaece. Para um número exato digitado, use Input type=number; para ligar/desligar, Switch.",
354
+ 'Permita escolher um valor ou um intervalo dentro de uma faixa contínua, arrastando o controle (thumb). `min`, `max` e `step` delimitam a faixa; `value` com `onValueChange` controla o estado — `[n]` para um valor, `[a, b]` para um intervalo — e `defaultValue` atende o modo não controlado. Nomeie o controle com `aria-label` ou `aria-labelledby` no Slider, que os repassa ao thumb; o `htmlFor` de um Label não o alcança. Em um intervalo, passe `thumbLabels` com um nome por thumb, na ordem dos valores. Para digitar um número exato, use Input com `type="number"`; para ligar ou desligar, Switch.',
355
355
  },
356
356
  switch: {
357
357
  name: "switch",
358
358
  ancestry: "shadcn",
359
359
  whenToUse:
360
- "Liga/desliga imediato de uma preferência booleana (Radix). Controlado por `checked`/`onCheckedChange` (boolean) e em par com Label. Use para estado que vale na hora (ativar agente, sincronizar); para confirmar dentro de um formulário, prefira Checkbox.",
360
+ "Permita ligar ou desligar uma preferência que entra em vigor imediatamente, como ativar um agente ou a sincronização. Associe o controle a um Label e controle o estado com `checked` e `onCheckedChange`, ou defina o inicial com `defaultChecked`. Quando a escolha só vale ao salvar um formulário, use Checkbox.",
361
361
  },
362
362
  toggle: {
363
363
  name: "toggle",
@@ -376,13 +376,13 @@ export const componentMeta = {
376
376
  name: "action-form",
377
377
  ancestry: "opus",
378
378
  whenToUse:
379
- "Renderize e envie uma FormAction com validação, estado de envio e notificação consistentes. Sem filhos, os campos seguem o schema do contrato; com ActionFormField, o consumidor controla o layout sem duplicar rótulos ou erros. Para uma ação sem campos, use ActionTrigger.",
379
+ "Renderize e envie uma FormAction com validação, estado de envio e notificação consistentes. Sem filhos, o formulário monta um campo para cada entrada de `fields` do contrato, na ordem declarada; um campo do schema sem entrada em `fields` não aparece. Com ActionFormField, o consumidor controla o layout sem duplicar rótulos ou erros. Para uma ação sem campos, use ActionTrigger.",
380
380
  },
381
381
  "action-form-dialog": {
382
382
  name: "action-form-dialog",
383
383
  ancestry: "opus",
384
384
  whenToUse:
385
- "ActionForm dentro de um Dialog (form em modal) — controla open/onOpenChange + title; fecha no sucesso. Aceita children (modo composição) como o ActionForm. Para form inline em uma página, use ActionForm direto.",
385
+ "Apresente um formulário de action em um modal, sem levar a pessoa para outra página. O consumidor controla `open` e `onOpenChange`, e o modal fecha depois de uma execução bem-sucedida. O título nomeia a operação e o recurso, como `Criar {recurso}` ou `Editar {recurso}`, e a ação principal mantém `Salvar`; use `submitLabel` somente para outro efeito, como `Renomear`. Assim como ActionForm, aceita ActionFormField como filhos. Para um formulário na própria página, use ActionForm; para uma seção delimitada, ActionFormCard.",
386
386
  },
387
387
  "action-form-card": {
388
388
  name: "action-form-card",
@@ -406,13 +406,13 @@ export const componentMeta = {
406
406
  name: "action-view",
407
407
  ancestry: "opus",
408
408
  whenToUse:
409
- "Carregue e apresente um recurso de uma ViewAction com estados de carregamento, erro e ausência consistentes. Os filhos definem o layout a partir dos dados e de `refetch`; para uma coleção, use ActionList.",
409
+ "Carregue e apresente um recurso de uma ViewAction com estados de carregamento e erro consistentes. Os filhos definem o layout a partir dos dados e de `refetch`. O handler deve devolver um valor: represente um recurso ausente com o erro `not_found`, cuja frase aparece para a pessoa. Quando o recurso alimentar mais de uma região, use `useViewAction`; para uma coleção, use ActionList.",
410
410
  },
411
411
  page: {
412
412
  name: "page",
413
413
  ancestry: "opus",
414
414
  whenToUse:
415
- "O esqueleto de página do back-office: <main> + container centralizado com teto padrão de 80rem (`max-w-7xl`). PageHeader declara navegação e ações globais; PageIntro é a introdução opcional; PageBody recebe o conteúdo. PageShell mantém a barra de 3.75rem e projeta os slots do PageHeader. Uma região interna pode fornecer o heading principal com Content. PageActionsTarget fica restrito a workspaces imersivos sem PageShell. PageState oculta PageIntro; informações importantes entram no corpo como Alert ou texto. Para uma região disponível à criação ou vínculo, use Empty.",
415
+ "Estruture uma página com cabeçalho, introdução, estado e conteúdo na mesma anatomia. Page renderiza o `<main>` com um container centralizado de até 80rem (`max-w-7xl`); PageHeader declara navegação e ações globais, PageIntro é a introdução opcional e PageBody recebe o conteúdo. PageShell mantém a barra persistente de 3.75rem e projeta nela os slots do PageHeader. Uma região interna pode fornecer o heading principal com Content. PageActionsTarget fica restrito a workspaces imersivos sem PageShell. PageState substitui o conteúdo inteiro durante carregamento, falha ou ausência e oculta PageIntro, por isso informações indispensáveis entram no corpo, como Alert ou texto. Para uma falha parcial, mantenha o estado na seção com DataState; para uma região disponível à criação ou vínculo, use Empty.",
416
416
  },
417
417
  presentation: {
418
418
  name: "presentation",
@@ -430,7 +430,7 @@ export const componentMeta = {
430
430
  name: "data-state",
431
431
  ancestry: "opus",
432
432
  whenToUse:
433
- "Coordene carregamento, erro, vazio e conteúdo de uma consulta assíncrona com as mesmas superfícies de PageState: `emptyMessage` compõe Empty com moldura sólida, o erro é um Alert com `onRetry` e `retryLabel`. Use DataState dentro da estrutura que receberá os dados; para uma região disponível à criação, use Empty. Ações em andamento pertencem ao estado `busy` do controle que as iniciou.",
433
+ "Coordene carregamento, erro, vazio e conteúdo de uma consulta assíncrona na escala de uma seção. O carregamento mostra um Spinner; o vazio compõe Empty com moldura sólida e `emptyMessage`; o erro é um Alert danger com `errorMessage`, sem expor o texto técnico, e `onRetry` com `retryLabel` acrescenta a nova tentativa. Em uma tabela, `colSpan` ocupa uma linha inteira. Para o estado da página inteira, use PageState; uma coleção carregada por ActionList já trata esses estados; para uma região disponível à criação, use Empty. Ações em andamento pertencem ao estado `busy` do controle que as iniciou.",
434
434
  },
435
435
  "action-list-dialog": {
436
436
  name: "action-list-dialog",
package/src/ui/react.tsx CHANGED
@@ -487,6 +487,7 @@ export type {
487
487
  export {
488
488
  Content,
489
489
  ContentHeader,
490
+ ContentMedia,
490
491
  ContentTitle,
491
492
  ContentDescription,
492
493
  ContentMeta,
@@ -15,10 +15,22 @@
15
15
  * dos entries são IGNORADOS (não registrados), senão o `start()` exige
16
16
  * eventbus/scheduler adapters que o design não tem por que montar.
17
17
  *
18
- * Uso (vite.config.ts do app):
19
- * import { opusDesign } from '@softize/opus/vite'
20
- * export default defineConfig({ plugins: [react(), opusDesign()] })
21
- * // vite --mode design
18
+ * Uso (vite.config.ts do app, como no template de `opus create`):
19
+ * const DESIGN_RUN = process.argv.includes('--mode=design') ||
20
+ * process.argv.some((arg, i) => (arg === '--mode' || arg === '-m') && process.argv[i + 1] === 'design')
21
+ * const designPlugins: PluginOption[] = DESIGN_RUN
22
+ * ? [(await import('@softize/opus/vite')).opusDesign()]
23
+ * : []
24
+ * export default defineConfig({ plugins: [react(), ...designPlugins] })
25
+ * // vite --mode design --configLoader runner
26
+ *
27
+ * Não importe o plugin de forma estática: o pacote publica TypeScript, e o
28
+ * carregador padrão de config do Vite o externaliza, o que o Node 22+ recusa
29
+ * dentro de `node_modules` (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). O
30
+ * import dinâmico no top-level, condicionado ao modo design e carregado com
31
+ * `--configLoader runner`, deixa o Vite transformar o TypeScript; build e
32
+ * preview nem chegam a carregá-lo. Detalhes em "Prototipar com dados
33
+ * simulados", no guia de actions (src/ui/docs/content/actions.md).
22
34
  */
23
35
 
24
36
  import { readdirSync } from 'node:fs'