@softize/opus 18.5.0 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/bin/cli.mjs +5 -0
  3. package/bin/lib/check.mjs +198 -10
  4. package/bin/lib/copy.mjs +29 -0
  5. package/bin/lib/mcp.mjs +1 -1
  6. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +298 -0
  7. package/docs/adr/0024-fill-height-propagates-by-context.md +118 -0
  8. package/docs/data-products.md +15 -9
  9. package/docs/protocol.md +3 -4
  10. package/docs/releasing.md +2 -2
  11. package/package.json +2 -2
  12. package/registry/skills/create-opus-action/scripts/scaffold.mjs +2 -1
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/runtime.ts +96 -8
  15. package/src/core/types.ts +5 -2
  16. package/src/ui/components/patterns/action-form-card.tsx +11 -1
  17. package/src/ui/components/patterns/content-header.tsx +16 -12
  18. package/src/ui/components/patterns/fill-region.ts +150 -0
  19. package/src/ui/components/patterns/heading-level.ts +16 -0
  20. package/src/ui/components/patterns/list.tsx +103 -73
  21. package/src/ui/components/patterns/page.tsx +20 -4
  22. package/src/ui/components/patterns/pane.tsx +294 -0
  23. package/src/ui/components/patterns/presentation.tsx +81 -54
  24. package/src/ui/components/patterns/sidebar.tsx +13 -72
  25. package/src/ui/components/patterns/split.tsx +51 -38
  26. package/src/ui/components/patterns/surface-anatomy.ts +246 -0
  27. package/src/ui/components/patterns/surface-header.tsx +51 -6
  28. package/src/ui/components/patterns/surface-media.tsx +69 -0
  29. package/src/ui/components/patterns/trigger.tsx +12 -2
  30. package/src/ui/components/primitives/alert.tsx +9 -1
  31. package/src/ui/components/primitives/avatar.tsx +7 -3
  32. package/src/ui/components/primitives/card.tsx +91 -29
  33. package/src/ui/components/primitives/carousel.tsx +34 -5
  34. package/src/ui/components/primitives/checkbox.tsx +11 -3
  35. package/src/ui/components/primitives/command.tsx +14 -5
  36. package/src/ui/components/primitives/copyable.tsx +120 -10
  37. package/src/ui/components/primitives/detail.tsx +10 -13
  38. package/src/ui/components/primitives/dialog.tsx +179 -45
  39. package/src/ui/components/primitives/drawer.tsx +131 -24
  40. package/src/ui/components/primitives/empty.tsx +1 -1
  41. package/src/ui/components/primitives/icon-picker.tsx +6 -2
  42. package/src/ui/components/primitives/icon-tile.tsx +7 -0
  43. package/src/ui/components/primitives/input-group.tsx +9 -2
  44. package/src/ui/components/primitives/input-otp.tsx +1 -1
  45. package/src/ui/components/primitives/kbd.tsx +1 -1
  46. package/src/ui/components/primitives/label.tsx +3 -0
  47. package/src/ui/components/primitives/menu.tsx +5 -0
  48. package/src/ui/components/primitives/metric-card.tsx +98 -39
  49. package/src/ui/components/primitives/popover.tsx +1 -1
  50. package/src/ui/components/primitives/progress.tsx +9 -1
  51. package/src/ui/components/primitives/slider.tsx +62 -11
  52. package/src/ui/components/primitives/truncate.tsx +10 -1
  53. package/src/ui/docs/content/accordion.md +74 -27
  54. package/src/ui/docs/content/action-form-card.md +24 -15
  55. package/src/ui/docs/content/action-form-dialog.md +17 -7
  56. package/src/ui/docs/content/action-form.md +92 -77
  57. package/src/ui/docs/content/action-list-dialog.md +75 -38
  58. package/src/ui/docs/content/action-list.md +134 -52
  59. package/src/ui/docs/content/action-trigger.md +27 -10
  60. package/src/ui/docs/content/action-view.md +14 -14
  61. package/src/ui/docs/content/actions.md +161 -66
  62. package/src/ui/docs/content/ai.md +43 -44
  63. package/src/ui/docs/content/alert.md +83 -29
  64. package/src/ui/docs/content/ask.md +51 -26
  65. package/src/ui/docs/content/aspect-ratio.md +22 -24
  66. package/src/ui/docs/content/audit.md +23 -19
  67. package/src/ui/docs/content/auth.md +38 -19
  68. package/src/ui/docs/content/avatar.md +54 -5
  69. package/src/ui/docs/content/badge.md +29 -9
  70. package/src/ui/docs/content/breadcrumb.md +21 -8
  71. package/src/ui/docs/content/button.md +73 -43
  72. package/src/ui/docs/content/calendar.md +134 -26
  73. package/src/ui/docs/content/card.md +252 -35
  74. package/src/ui/docs/content/carousel.md +37 -6
  75. package/src/ui/docs/content/chat.md +118 -37
  76. package/src/ui/docs/content/checkbox.md +73 -7
  77. package/src/ui/docs/content/cli.md +94 -55
  78. package/src/ui/docs/content/collapsible.md +43 -19
  79. package/src/ui/docs/content/command.md +30 -8
  80. package/src/ui/docs/content/composer.md +23 -8
  81. package/src/ui/docs/content/content.md +37 -7
  82. package/src/ui/docs/content/copyable.md +39 -8
  83. package/src/ui/docs/content/customization.md +55 -46
  84. package/src/ui/docs/content/cycle.md +33 -18
  85. package/src/ui/docs/content/data-products.md +115 -0
  86. package/src/ui/docs/content/data-state.md +20 -6
  87. package/src/ui/docs/content/data.md +20 -19
  88. package/src/ui/docs/content/detail.md +12 -5
  89. package/src/ui/docs/content/dialog.md +219 -28
  90. package/src/ui/docs/content/dictionary-value.md +4 -4
  91. package/src/ui/docs/content/dock.md +54 -19
  92. package/src/ui/docs/content/dot.md +35 -5
  93. package/src/ui/docs/content/drawer.md +193 -22
  94. package/src/ui/docs/content/empty-value.md +20 -6
  95. package/src/ui/docs/content/empty.md +24 -3
  96. package/src/ui/docs/content/events.md +76 -16
  97. package/src/ui/docs/content/executions.md +9 -9
  98. package/src/ui/docs/content/field.md +21 -6
  99. package/src/ui/docs/content/getting-started.md +44 -34
  100. package/src/ui/docs/content/icon-picker.md +19 -13
  101. package/src/ui/docs/content/icon-tile.md +28 -5
  102. package/src/ui/docs/content/input-otp.md +58 -7
  103. package/src/ui/docs/content/input.md +22 -3
  104. package/src/ui/docs/content/item.md +53 -6
  105. package/src/ui/docs/content/kbd.md +5 -1
  106. package/src/ui/docs/content/label.md +13 -4
  107. package/src/ui/docs/content/log.md +5 -5
  108. package/src/ui/docs/content/markdown.md +5 -4
  109. package/src/ui/docs/content/mcp.md +49 -23
  110. package/src/ui/docs/content/menu.md +95 -10
  111. package/src/ui/docs/content/metric-card.md +68 -21
  112. package/src/ui/docs/content/observability.md +10 -9
  113. package/src/ui/docs/content/page.md +125 -11
  114. package/src/ui/docs/content/pagination.md +13 -5
  115. package/src/ui/docs/content/pane.md +300 -0
  116. package/src/ui/docs/content/popover.md +42 -5
  117. package/src/ui/docs/content/presentation.md +91 -47
  118. package/src/ui/docs/content/progress.md +27 -14
  119. package/src/ui/docs/content/queue.md +39 -21
  120. package/src/ui/docs/content/radio-group.md +26 -4
  121. package/src/ui/docs/content/router.md +29 -10
  122. package/src/ui/docs/content/runtime.md +90 -54
  123. package/src/ui/docs/content/scheduler.md +53 -21
  124. package/src/ui/docs/content/scroll-area.md +42 -35
  125. package/src/ui/docs/content/select.md +78 -33
  126. package/src/ui/docs/content/semantic-context.md +21 -2
  127. package/src/ui/docs/content/separator.md +15 -9
  128. package/src/ui/docs/content/sidebar.md +25 -45
  129. package/src/ui/docs/content/skeleton.md +13 -4
  130. package/src/ui/docs/content/slider.md +37 -9
  131. package/src/ui/docs/content/spinner.md +4 -3
  132. package/src/ui/docs/content/split.md +34 -20
  133. package/src/ui/docs/content/storage.md +8 -9
  134. package/src/ui/docs/content/switch.md +10 -7
  135. package/src/ui/docs/content/table.md +46 -8
  136. package/src/ui/docs/content/tabs.md +14 -4
  137. package/src/ui/docs/content/testing.md +76 -31
  138. package/src/ui/docs/content/textarea.md +5 -4
  139. package/src/ui/docs/content/toast.md +48 -7
  140. package/src/ui/docs/content/toggle.md +3 -3
  141. package/src/ui/docs/content/tokens.md +15 -26
  142. package/src/ui/docs/content/tooltip.md +18 -2
  143. package/src/ui/docs/content/truncate.md +36 -6
  144. package/src/ui/docs/content/ui.md +74 -22
  145. package/src/ui/docs/content/upgrading.md +45 -36
  146. package/src/ui/docs/plugin.ts +36 -2
  147. package/src/ui/docs/registry.tsx +14 -2
  148. package/src/ui/drivers/react.tsx +16 -8
  149. package/src/ui/meta.ts +38 -32
  150. package/src/ui/react.tsx +14 -0
  151. package/src/vite/design.ts +16 -4
@@ -10,20 +10,18 @@ as ferramentas usam essas definições sem exigir schemas e tipos paralelos.
10
10
 
11
11
  ## O modelo
12
12
 
13
- > Comece pelo problema que o modelo compartilhado resolve antes de conhecer seus arquivos.
14
-
15
13
  As regras de uma aplicação costumam aparecer em vários lugares: validação, transporte, API,
16
- formulários e documentação. No Opus, entidades, dicts e contratos de action descrevem essas
14
+ formulários e documentação. No Opus, entidades, dicionários e contratos de action descrevem essas
17
15
  regras uma vez. A API e a web consomem a mesma definição, enquanto runtime, adapters, UI, CLI
18
16
  e verificações ajudam a mantê-la consistente. Cada projeto acrescenta seu domínio sobre essa
19
17
  base versionada.
20
18
 
21
19
  ## Começar do zero
22
20
 
23
- > Projeto novo não se monta à mão: o esqueleto canônico sai do `opus create`, com os
24
- > pré-requisitos do protocolo, da UI e do preview já plugados — e os gates verdes.
21
+ Para um projeto novo, use `opus create`: ele gera o app com os pré-requisitos do protocolo, da UI e
22
+ do preview já configurados, e os gates passam desde o início.
25
23
 
26
- O Opus e a Base ficam no npm público: leitura não exige token nem configuração de escopo.
24
+ O Opus e a Base ficam no npm público: a instalação não exige token nem configuração de escopo.
27
25
  Crie o projeto e materialize as duas camadas antes dos gates:
28
26
 
29
27
  ```bash
@@ -43,15 +41,21 @@ com `pnpm dlx @softize/opus@<versão> create meu-app` ou desative a quarentena a
43
41
  com `pnpm --config.minimum-release-age=0 dlx …`. Dentro do projeto, o workspace exclui Opus e Base
44
42
  da quarentena para preservar o par validado.
45
43
 
46
- Nasce com: `opus.config.ts` + domínio-exemplo canônico (0 violações, spec documentada),
47
- vite + react + tema do Opus (Tailwind v4 CSS-first), dev server na porta que o Maestro
48
- injeta no preview, `opus.json` (pin), instruções, skills e hooks gerenciados. A Base é
49
- dona do método geral, memória e revisão; o Opus acrescenta somente o conhecimento do SDK.
44
+ O app criado inclui:
45
+
46
+ - `opus.config.ts` e um domínio de exemplo documentado, sem violações no `opus check`;
47
+ - Vite, React e o tema do Opus sobre Tailwind v4, configurado por CSS;
48
+ - um dev server que respeita a variável `PORT`, para que um orquestrador de preview escolha a
49
+ porta;
50
+ - `opus.json`, com a versão adotada, e as instruções, skills e hooks gerenciados.
51
+
52
+ A Base cuida do método geral de trabalho, da memória e da revisão; o Opus acrescenta o conhecimento
53
+ específico do SDK.
50
54
 
51
- **Monorepo:** a raiz nasce do `--monorepo` e cada app nasce dentro dela — o create
52
- detecta o `pnpm-workspace.yaml` e gera só o que é do app (sem workspace yaml aninhado),
53
- **avisando** o que a raiz precisa ter (glob de packages, allowBuilds e exclusões de
54
- quarentena para Opus/Base) sem tocar nos seus arquivos:
55
+ Em um monorepo, crie primeiro a raiz com `--monorepo` e depois cada app dentro dela. O `create`
56
+ detecta o `pnpm-workspace.yaml` e gera só os arquivos do app, sem um workspace aninhado. O que a raiz
57
+ precisa ter, como o glob de packages, `allowBuilds` e as exclusões de quarentena para Opus e Base,
58
+ aparece como aviso, sem alterar os arquivos da raiz:
55
59
 
56
60
  ```bash
57
61
  pnpm dlx @softize/opus create meu-cliente --monorepo # a raiz do workspace
@@ -61,13 +65,10 @@ pnpm dlx @softize/opus create apps/portal # o app (modo detectado)
61
65
 
62
66
  ## Carregar o Opus em um projeto existente
63
67
 
64
- > O `opus.json` registra a versão usada pelo projeto. Se o arquivo ainda não existe, o setup
65
- > cria a estrutura necessária antes das demais etapas.
66
-
67
- A versão do Opus fica pinada em `opus.json`. O `opus setup` é per-app: grava o marcador,
68
- o inventário de copy e as projeções específicas do SDK. O `base setup` materializa o
69
- método geral, memória, agentes e revisão na raiz do repositório. Os componentes vêm do
70
- barrel `@softize/opus/ui/react`; o tema, por CSS.
68
+ O `opus.json` registra a versão do Opus adotada pelo app; se ele ainda não existe, o setup o cria.
69
+ `opus setup` roda em cada app e grava o marcador, o inventário de copy e as projeções específicas do
70
+ SDK. `base setup` materializa na raiz do repositório o método geral, a memória, os agentes e a
71
+ revisão. Os componentes vêm de `@softize/opus/ui/react`, e o tema, de um arquivo CSS.
71
72
 
72
73
  ```bash
73
74
  # Bootstrap idempotente de um projeto existente.
@@ -78,39 +79,48 @@ pnpm exec base setup
78
79
  ```
79
80
 
80
81
  ```css
81
- /* index.css do app — o tema canônico + os componentes do Opus no scan do Tailwind. */
82
+ /* index.css do app — o tema do Opus e os componentes do pacote no scan do Tailwind. */
82
83
  @import '@softize/opus/ui/theme.css';
83
84
  @source '../node_modules/@softize/opus/src/ui/**/*.{ts,tsx}';
84
85
  ```
85
86
 
86
- ## A stack do runtime
87
+ ## Montar o runtime
87
88
 
88
- > `createRuntime` compõe a stack a partir de adapters plugáveis. Cada capacidade (data, auth,
89
- > audit…) é um adapter — troca a implementação sem tocar o domínio.
89
+ No servidor, `createRuntime` reúne as capacidades que o app usa. Cada capacidade, como banco,
90
+ autenticação ou auditoria, é um adapter que pode ser trocado sem alterar o domínio. Os providers da
91
+ interface estão em [Como consumir](/ui/como-consumir).
90
92
 
91
93
  ```ts
92
94
  import { createRuntime } from '@softize/opus/core'
95
+ import { jwtAuth } from '@softize/opus/auth/jwt'
93
96
  import { kyselyData } from '@softize/opus/data/kysely'
94
97
  import { fastifyServer } from '@softize/opus/server/fastify'
95
98
  import { pgAudit } from '@softize/opus/audit/pg'
99
+ import { workspaceDomain } from './domains/workspace/index.ts'
96
100
 
97
101
  const runtime = createRuntime({
98
102
  server: fastifyServer({ app }),
99
- data: kyselyData({ db }), // Kysely + Postgres
100
- auth: makeAuthAdapter(), // gate (IdP)
103
+ data: kyselyData({ db }), // Kysely e Postgres
104
+ auth: jwtAuth({ secret: process.env.JWT_SECRET!, mapUser: (p) => ({ id: String(p.sub) }) }),
101
105
  audit: [pgAudit({ pool })], // trilha de auditoria
102
106
  config: { env, i18n: { defaultLocale: 'pt-BR' } },
103
107
  })
104
108
 
105
- // Domínios = grupos de actions. Não registrado = não existe.
106
- runtime.register([workspacesDomain, agentsDomain, skillsDomain])
109
+ // Só o que foi registrado existe para o runtime.
110
+ runtime.register([workspaceDomain])
107
111
  await runtime.start()
108
112
  ```
109
113
 
110
- ## Antes de mexer
114
+ Cada capacidade e o registro de domínios estão detalhados em [Runtime & adapters](/docs/runtime).
115
+
116
+ ## Antes de alterar o projeto
111
117
 
112
- > O protocolo de leitura: a verdade local mora nos docs do repo, não nesta página.
118
+ A documentação do próprio repositório descreve o que é específico dele; esta página cobre só o que
119
+ vale para todo projeto Opus.
113
120
 
114
- - `CLAUDE.md` do repo e do app — o índice local: mapa, gates, gotchas.
115
- - As `description` das entidades/actions — a spec de negócio, projetada no manifest pelo `opus gen`.
116
- - Gate de build: `opus check` (convenções das actions e da UI) + `opus copy --check` e `base copy check` (inventário e política de copy) + `opus db check` (drift entidade ↔ banco). Rode antes de entregar.
121
+ - O `CLAUDE.md` do repositório e de cada app reúne o mapa do código, os gates e os cuidados locais.
122
+ - A `description` de entidades e actions registra a regra de negócio, que `opus gen` projeta no
123
+ manifest.
124
+ - Antes de entregar, rode `opus check` (convenções das actions e da UI), `opus copy --check` e
125
+ `base copy check` (inventário e política de copy) e, com banco, `opus db check` (divergência entre
126
+ entidades e banco).
@@ -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. |