@softize/opus 13.1.0 → 15.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 (72) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/bin/cli.mjs +2 -0
  3. package/bin/lib/check.mjs +33 -5
  4. package/bin/lib/cli-shared.mjs +30 -1
  5. package/bin/lib/copy.mjs +279 -6
  6. package/bin/lib/db.mjs +2 -0
  7. package/docs/adr/0004-page-content-state-is-composed.md +39 -5
  8. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +9 -3
  9. package/docs/adr/0009-page-title-does-not-carry-a-counter.md +57 -0
  10. package/docs/adr/0010-page-header-owns-page-chrome.md +73 -0
  11. package/docs/code-style.md +4 -1
  12. package/docs/data-layer.md +9 -0
  13. package/package.json +1 -1
  14. package/registry/instructions/opus.md +3 -3
  15. package/registry/skills/build-opus-ui/SKILL.md +3 -2
  16. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -6
  17. package/registry/templates/app/src/App.tsx +11 -6
  18. package/src/core/types.ts +3 -4
  19. package/src/ui/components/patterns/action-list-dialog.tsx +10 -3
  20. package/src/ui/components/patterns/confirm.tsx +2 -31
  21. package/src/ui/components/patterns/content-header.tsx +42 -141
  22. package/src/ui/components/patterns/data-state.tsx +42 -68
  23. package/src/ui/components/patterns/form.tsx +15 -15
  24. package/src/ui/components/patterns/list.tsx +55 -34
  25. package/src/ui/components/patterns/page-state.tsx +81 -51
  26. package/src/ui/components/patterns/page.tsx +228 -97
  27. package/src/ui/components/patterns/state-surface.tsx +262 -0
  28. package/src/ui/components/patterns/surface-header.tsx +204 -0
  29. package/src/ui/components/patterns/trigger.tsx +9 -10
  30. package/src/ui/components/patterns/view.tsx +14 -16
  31. package/src/ui/components/primitives/alert.tsx +1 -33
  32. package/src/ui/components/primitives/avatar.tsx +15 -5
  33. package/src/ui/components/primitives/badge.tsx +2 -43
  34. package/src/ui/components/primitives/button-group.tsx +34 -8
  35. package/src/ui/components/primitives/button.tsx +31 -35
  36. package/src/ui/components/primitives/control.ts +69 -0
  37. package/src/ui/components/primitives/dot.tsx +1 -30
  38. package/src/ui/components/primitives/input-group.tsx +11 -8
  39. package/src/ui/components/primitives/item.tsx +3 -1
  40. package/src/ui/components/primitives/menu.tsx +1 -7
  41. package/src/ui/components/primitives/pagination.tsx +16 -8
  42. package/src/ui/components/primitives/select.tsx +2 -2
  43. package/src/ui/components/primitives/spinner.tsx +13 -16
  44. package/src/ui/components/primitives/switch.tsx +4 -1
  45. package/src/ui/components/primitives/tabs.tsx +5 -3
  46. package/src/ui/components/primitives/toggle.tsx +9 -4
  47. package/src/ui/docs/content/action-form.md +26 -0
  48. package/src/ui/docs/content/action-list-dialog.md +2 -2
  49. package/src/ui/docs/content/action-list.md +35 -2
  50. package/src/ui/docs/content/action-trigger.md +5 -4
  51. package/src/ui/docs/content/action-view.md +3 -2
  52. package/src/ui/docs/content/alert.md +16 -4
  53. package/src/ui/docs/content/avatar.md +7 -3
  54. package/src/ui/docs/content/button.md +48 -17
  55. package/src/ui/docs/content/communication.md +36 -0
  56. package/src/ui/docs/content/content.md +5 -4
  57. package/src/ui/docs/content/data-state.md +17 -13
  58. package/src/ui/docs/content/dialog.md +1 -4
  59. package/src/ui/docs/content/input.md +1 -1
  60. package/src/ui/docs/content/item.md +1 -1
  61. package/src/ui/docs/content/page.md +160 -37
  62. package/src/ui/docs/content/pagination.md +11 -9
  63. package/src/ui/docs/content/semantic-context.md +3 -2
  64. package/src/ui/docs/content/sidebar.md +2 -42
  65. package/src/ui/docs/content/spinner.md +9 -6
  66. package/src/ui/docs/content/switch.md +1 -1
  67. package/src/ui/docs/content/tabs.md +1 -1
  68. package/src/ui/docs/content/toggle.md +1 -1
  69. package/src/ui/drivers/react.tsx +1 -6
  70. package/src/ui/meta.ts +8 -8
  71. package/src/ui/react.tsx +16 -18
  72. package/src/ui/components/patterns/shell-nav.tsx +0 -154
@@ -7,6 +7,9 @@ os filtros aplicados permanecem visíveis como chips removíveis.
7
7
 
8
8
  A barra se adapta ao espaço disponível. A busca cede largura primeiro e filtros que deixam de caber
9
9
  migram para o modal; a linha só quebra quando nenhum controle restante puder ceder espaço.
10
+ `toolbarActions` acrescenta ações do consumidor ao fim da barra. Recarregar e exibição formam um
11
+ `ButtonGroup` espaçado; as ações do consumidor vêm depois, com um intervalo maior para preservar a
12
+ hierarquia entre ferramentas e a ação principal.
10
13
 
11
14
  ```tsx preview col
12
15
  render(
@@ -18,6 +21,20 @@ render(
18
21
  )
19
22
  ```
20
23
 
24
+ Uma ação icon-only mantém o nome no tooltip e no `aria-label`:
25
+
26
+ ```tsx
27
+ <ActionList
28
+ action={workspaceList}
29
+ input={{}}
30
+ toolbarActions={
31
+ <Button context="primary" size="icon" aria-label="Novo workspace">
32
+ <Plus />
33
+ </Button>
34
+ }
35
+ />
36
+ ```
37
+
21
38
  ## Barra de filtros compartilhada
22
39
 
23
40
  `ActionFilterBar` expõe a mesma linguagem declarativa de busca, filtros e período para
@@ -235,6 +252,19 @@ render(
235
252
  )
236
253
  ```
237
254
 
255
+ ## Propriedades de ActionFilterBar
256
+
257
+ | Propriedade | Tipo | Padrão | Descrição |
258
+ |---|---|---|---|
259
+ | `action` | `Pick<ListAction, 'filters' \| 'text' \| 'periods'>` | | Declara busca, filtros e períodos disponíveis. |
260
+ | `state` | `ActionFilterState` | | Estado atual da barra. |
261
+ | `onStateChange` | `(next) => void` | | Recebe o estado completo depois de cada alteração. |
262
+ | `filterOptions` | `Record<string, SelectOption[]>` | | Fornece opções de runtime para filtros select e lookup. |
263
+ | `onRefresh` | `() => Promise<void> \| void` | | Exibe a ação de recarregar e executa a consulta do consumidor. |
264
+ | `refreshing` | `boolean` | `false` | Desabilita e anima a ação de recarregar durante a consulta. |
265
+ | `controls` | `ReactNode` | | Controles auxiliares agrupados com recarregar em um `ButtonGroup` espaçado. |
266
+ | `actions` | `ReactNode` | | Ações do consumidor exibidas depois do grupo de controles, com um intervalo maior. |
267
+
238
268
  ## Declaração na ListAction
239
269
 
240
270
  | Chave | O que declara |
@@ -267,6 +297,9 @@ chamar a action.
267
297
  | `rowId` | `(item) => string` | `item.id` | Identidade da linha para seleção. |
268
298
  | `pageSize` | `number` | `50` | Itens por página padrão (vira `limit`/`page` no input; handler devolve `total`). O usuário troca no popover de exibição. |
269
299
  | `state / onStateChange` | `ActionListState / (next) => void` | interno | Estado da toolbar controlado — para quem embala sincronizar com a URL. |
270
- | `emptyMessage` | `string` | `'Nenhum resultado.'` | O texto do estado vazio. |
300
+ | `emptyMessage` | `string` | `'Nenhum resultado.'` | Frase do estado vazio (o `Empty` do `DataState`). |
301
+ | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro; a ação de tentar de novo refaz a consulta. |
302
+ | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
271
303
  | `onRowClick` | `(item: TItem) => void` | | Clique na linha (ex.: navegar para o detalhe) — só na tabela. |
272
- | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita) — apresentação de quem chama, como `cells`; cliques ali não disparam o `onRowClick`. |
304
+ | `toolbarActions` | `ReactNode` | | Ações do consumidor no fim da barra, separadas do grupo de controles. |
305
+ | `rowActions` | `(item: TItem) => ReactNode` | | Ações por linha (coluna final, à direita), agrupadas automaticamente com intervalo compacto; cliques ali não disparam o `onRowClick`. |
@@ -37,9 +37,10 @@ confirm: {
37
37
 
38
38
  ## Ação somente com ícone
39
39
 
40
- Com `icon`, o botão exibe somente o ícone e usa `label`, ou `action.label`, no tooltip e no nome
41
- acessível. O clique não aciona o item clicável ao redor. `itemLabel` identifica o registro na
42
- mensagem de confirmação.
40
+ Com `icon`, o botão exibe somente o ícone no quadrado `icon-xs` da escala (1.5rem, a ação que mora
41
+ dentro de uma linha ou card) e usa `label`, ou `action.label`, no tooltip e no nome acessível. Uma
42
+ composição que peça mais presença, como a barra do `PageHeader`, declara `size="icon-sm"`. O clique
43
+ não aciona o item clicável ao redor. `itemLabel` identifica o registro na mensagem de confirmação.
43
44
 
44
45
  ```tsx
45
46
  <ActionTrigger
@@ -83,4 +84,4 @@ um atalho, um arrastar, um item de menu.
83
84
  | `onSuccess` | `(data: TData) => void` | | Pós-sucesso (cache já invalidado pelo action.invalidates). |
84
85
  | `icon` | `React.ReactNode` | | Torna o botão icon-only: rótulo no tooltip e no `aria-label`, clique que não vaza para o item. |
85
86
  | `itemLabel` | `string` | | Nome do alvo na pergunta (sai entre aspas, em destaque, antes da mensagem do contrato). |
86
- | `className` | `string` | | Classes do botão (ex.: apertar o tamanho em uma linha densa). |
87
+ | `className` | `string` | | Classes do botão. O tamanho vem de `size` (escala única); com `icon`, o default é `icon-xs`. |
@@ -21,7 +21,7 @@ render(
21
21
  <div className="space-y-1.5">
22
22
  <div className="flex items-center gap-2">
23
23
  <h3 className="text-sm font-semibold">{ws.name}</h3>
24
- <Badge variant={ws.status === 'active' ? 'success' : 'warning'}>{ws.status}</Badge>
24
+ <Badge context={ws.status === 'active' ? 'success' : 'warning'}>{ws.status}</Badge>
25
25
  </div>
26
26
  <p className="text-sm text-muted-foreground">
27
27
  Cliente {ws.client} · {ws.agents} agentes vinculados.
@@ -50,5 +50,6 @@ mais de uma região da tela ou quando o carregamento precisa ser orquestrado por
50
50
  | `children` | `(data: TData, refetch) => ReactNode` | | Conteúdo apresentado quando os dados estão disponíveis. `refetch` permite recarregar por código. |
51
51
  | `render` | `(data: TData, refetch) => ReactNode` | | Alias de compatibilidade de `children`; `children` tem precedência. |
52
52
  | `loading` | `ReactNode \| boolean` | `3 skeletons` | Sobrescreve o carregamento: um nó próprio, `true` para o padrão ou `false` para não renderizar nada enquanto carrega. |
53
- | `empty` | `ReactNode` | `nada` | Sobrescreve o estado vazio (200 sem dado). |
53
+ | `empty` | `ReactNode` | `emptyMessage` ou nada | Sobrescreve o estado vazio (200 sem dado). |
54
+ | `emptyMessage` | `string` | | Atalho do vazio: a frase na mesma superfície de `DataState` (`Empty` com moldura sólida). |
54
55
  | `error` | `(err, retry) => ReactNode` | `"Não foi possível carregar" + Tentar de novo` | Sobrescreve o estado de erro padrão. Sem ele, a frase do servidor só aparece quando é legível pela pessoa (`conflict`, `validation`, `not_found`, `authorization`, `authentication`); código técnico nunca vira título, e o botão de tentar de novo some quando o problema é de permissão ou sessão. |
@@ -54,6 +54,11 @@ Quando a mensagem precisa de conteúdo rico, componha os slots da família. `Ale
54
54
  `AlertDescription` pertencem ao header. As ações ficam no fim lógico da superfície e centralizadas
55
55
  verticalmente na mesma linha do conteúdo — a mesma posição usada pelas ações do Toast.
56
56
 
57
+ Use `ghost` como tratamento padrão para ações dentro do alert: a mensagem já fornece a superfície e
58
+ o botão não precisa desenhar outra moldura. Mantenha texto quando a ação expressa um resultado
59
+ específico. Para tentar novamente, recarregar ou repetir a operação descrita pelo aviso, prefira um
60
+ botão só com ícone, nome acessível e tooltip.
61
+
57
62
  ```tsx preview col
58
63
  <Alert context="danger">
59
64
  <AlertMedia>
@@ -67,9 +72,16 @@ verticalmente na mesma linha do conteúdo — a mesma posição usada pelas aç
67
72
  </AlertDescription>
68
73
  </AlertHeader>
69
74
  <AlertActions>
70
- <Button size="sm">
71
- Tentar novamente
72
- </Button>
75
+ <TooltipProvider>
76
+ <Tooltip>
77
+ <TooltipTrigger asChild>
78
+ <Button variant="ghost" size="icon" aria-label="Tentar novamente">
79
+ <RefreshCw />
80
+ </Button>
81
+ </TooltipTrigger>
82
+ <TooltipContent>Tentar novamente</TooltipContent>
83
+ </Tooltip>
84
+ </TooltipProvider>
73
85
  </AlertActions>
74
86
  </Alert>
75
87
  ```
@@ -83,7 +95,7 @@ ação.
83
95
  title="Sessão presa"
84
96
  description="O ambiente não subiu no tempo esperado."
85
97
  >
86
- <Button size="sm">
98
+ <Button variant="ghost">
87
99
  Reiniciar
88
100
  </Button>
89
101
  </Alert>
@@ -28,10 +28,14 @@ para agente.
28
28
 
29
29
  ## Tamanhos
30
30
 
31
- size sm/default/lg o fallback acompanha o tamanho. sm para listas densas, lg para cabeçalho de
32
- workspace.
31
+ `size` usa a escala única dos controles: o avatar de uma linha mede o mesmo que o controle ao lado
32
+ (`sm` 2rem, `default` 2.25rem, `lg` 2.5rem); `xs` (1.5rem) é a lista densa. O fallback e o selo
33
+ acompanham o tamanho.
33
34
 
34
35
  ```tsx preview
36
+ <Avatar size="xs">
37
+ <AvatarFallback>AL</AvatarFallback>
38
+ </Avatar>
35
39
  <Avatar size="sm">
36
40
  <AvatarFallback>AL</AvatarFallback>
37
41
  </Avatar>
@@ -86,7 +90,7 @@ excedente — os membros do workspace Empresa X.
86
90
 
87
91
  | Propriedade | Tipo | Padrão | Descrição |
88
92
  |---|---|---|---|
89
- | `size` | `'sm' \| 'default' \| 'lg'` | `'default'` | Diâmetro do retrato; o fallback e o selo acompanham a escala. |
93
+ | `size` | `'xs' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Diâmetro do retrato na escala única dos controles (1.5 · 2 · 2.25 · 2.5rem); o fallback e o selo acompanham. |
90
94
 
91
95
  ## Propriedades de AvatarImage
92
96
 
@@ -2,8 +2,7 @@
2
2
 
3
3
  `context` declara a hierarquia ou o risco da ação; `variant` escolhe o tratamento visual. Use
4
4
  `primary` para a ação principal, `neutral` para ações de apoio e `danger` quando a ação tiver uma
5
- consequência perigosa. `default`, `secondary` e `destructive` permanecem apenas como aliases de
6
- compatibilidade.
5
+ consequência perigosa.
7
6
 
8
7
  ```tsx preview
9
8
  <Button>Criar workspace</Button>
@@ -16,28 +15,45 @@ compatibilidade.
16
15
 
17
16
  ## Tamanhos
18
17
 
19
- O tamanho icon exige `aria-label`, porque não texto visível. Os botões só-ícone vêm em três tamanhos: `icon` (2.25rem), `icon-sm` (2rem, para uma fileira densa como o cabeçalho) e `icon-xs` (1.5rem, para uma ação dentro de um campo, como o `trailing` de Input ou Select).
18
+ `size` usa a escala única dos controles: o mesmo nome tem a mesma medida em `Button`, `Select`,
19
+ `Tabs`, `Toggle`, `Switch`, `Avatar`, `Spinner` e nos botões embutidos. Os tamanhos de texto dão a
20
+ altura da linha; os `icon-*` são quadrados para botões só de ícone, que exigem `aria-label` porque não
21
+ há texto visível. O glifo dentro do controle acompanha o tamanho (0.875rem em `xs` e `sm`, 1rem em
22
+ `default`, 1.25rem em `lg`), a menos que o ícone traga um `size-*` próprio.
23
+
24
+ | Nome | Medida | Uso |
25
+ |---|---|---|
26
+ | `xs` | 1.5rem | Ação dentro de um campo (`InputGroupButton`). |
27
+ | `sm` | 2rem | Toolbar e cabeçalho densos, ao lado de `Select` e `Tabs` `sm`. |
28
+ | `default` | 2.25rem | A linha padrão, a mesma de `Input`. |
29
+ | `lg` | 2.5rem | Chamada principal com mais área de toque. |
30
+ | `icon-xs` | 1.5rem | Ação só de ícone dentro de um campo ou de uma linha densa; é o quadrado de `ActionTrigger` com `icon`. |
31
+ | `icon-sm` | 1.75rem | Ação só de ícone em composições compactas que precisam de mais presença; também é o quadrado das setas do pager de `ActionList`. |
32
+ | `icon` | 2.25rem | Ação só de ícone na linha padrão. |
33
+ | `icon-lg` | 2.5rem | Ação só de ícone ao lado de um `lg`. |
20
34
 
21
35
  ```tsx preview
36
+ <Button size="xs">Mínimo</Button>
22
37
  <Button size="sm">Pequeno</Button>
23
38
  <Button>Padrão</Button>
24
39
  <Button size="lg">Grande</Button>
25
- <Button size="icon" aria-label="Novo"><Plus /></Button>
26
- <Button size="icon-sm" variant="ghost" aria-label="Novo"><Plus /></Button>
27
40
  <Button size="icon-xs" variant="ghost" aria-label="Novo"><Plus /></Button>
41
+ <Button size="icon-sm" variant="ghost" aria-label="Novo"><Plus /></Button>
42
+ <Button size="icon" aria-label="Novo"><Plus /></Button>
43
+ <Button size="icon-lg" aria-label="Novo"><Plus /></Button>
28
44
  ```
29
45
 
30
46
  ## Estados
31
47
 
32
- busy = ação em andamento (DEPOIS do clique): o Spinner e o disabled vêm do botão. Com icon, o
33
- Spinner TROCA o ícone (não soma). Não confunda com carregar conteúdo (ANTES) — isso é Spinner
34
- centralizado/Skeleton em um nível de página.
48
+ busy = ação em andamento (DEPOIS do clique): o Spinner e o disabled vêm do botão. `icon` recebe o
49
+ elemento do ícone e o dimensiona pelo `size`; com icon, o Spinner TROCA o ícone (não soma). Não
50
+ confunda com carregar conteúdo (ANTES) — isso é `DataState` ou `Skeleton` em um nível de página.
35
51
 
36
52
  ```tsx preview
37
53
  <Button disabled>Desabilitado</Button>
38
54
  <Button busy>Salvando…</Button>
39
- <Button icon={Plus}>Novo</Button>
40
- <Button icon={Plus} busy>Novo</Button>
55
+ <Button icon={<Plus />}>Novo</Button>
56
+ <Button icon={<Plus />} busy>Novo</Button>
41
57
  ```
42
58
 
43
59
  ## Como outro elemento (asChild)
@@ -57,16 +73,16 @@ buttonVariants serve para o caso sem filho único.
57
73
  |---|---|---|---|
58
74
  | `context` | `'neutral' \| 'primary' \| 'danger'` | `'primary'` | A hierarquia ou o risco comunicado pela ação. |
59
75
  | `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'solid'` | O tratamento visual aplicado ao contexto. |
60
- | `size` | `'default' \| 'sm' \| 'lg' \| 'icon' \| 'icon-sm' \| 'icon-xs'` | `'default'` | O tamanho. Os icon* são quadrados (2.25/2/1.5rem) para botões só de ícone, com `aria-label`. |
76
+ | `size` | `'xs' \| 'sm' \| 'default' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon' \| 'icon-lg'` | `'default'` | A medida na escala única dos controles (tabela acima). Os `icon-*` são quadrados para botões só de ícone, com `aria-label`. |
61
77
  | `asChild` | `boolean` | `false` | Renderiza como o filho (Radix Slot) em vez de `<button>` — para âncoras e afins. |
62
78
  | `busy` | `boolean` | `false` | Ação em andamento (depois do clique): mostra Spinner + desabilita. Não é "carregando" de conteúdo (que é Spinner/Skeleton em um nível de página). |
63
- | `icon` | `React.ElementType` | | Ícone à esquerda (ex.: icon={Plus}). No busy é trocado pelo Spinner — não soma. |
79
+ | `icon` | `React.ReactNode` | | Ícone à esquerda, como nó (ex.: `icon={<Plus />}`); o glifo segue o `size`. No busy é trocado pelo Spinner — não soma. |
64
80
 
65
81
  ## ButtonGroup
66
82
 
67
- Use `ButtonGroup` quando ações relacionadas precisarem formar um bloco contínuo. As bordas internas
68
- colapsam e somente as pontas externas permanecem arredondadas. Mantenha a mesma variante nos filhos
69
- para preservar a unidade visual.
83
+ Use `ButtonGroup` para manter ações relacionadas juntas. No modo `connected`, que permanece como
84
+ padrão, as bordas internas colapsam e somente as pontas externas ficam arredondadas. Mantenha a
85
+ mesma variante nos filhos para preservar a unidade visual.
70
86
 
71
87
  ```tsx preview
72
88
  <ButtonGroup>
@@ -76,6 +92,20 @@ para preservar a unidade visual.
76
92
  </ButtonGroup>
77
93
  ```
78
94
 
95
+ ### Ações espaçadas
96
+
97
+ Use `mode="spaced"` quando os controles pertencem ao mesmo conjunto, mas cada um precisa preservar
98
+ sua própria forma e superfície de hover. O grupo aplica o intervalo compacto de `0.25rem` sem
99
+ conectar bordas. Esse modo atende ações icon-only em barras e linhas de listagem.
100
+
101
+ ```tsx preview
102
+ <ButtonGroup mode="spaced">
103
+ <Button variant="ghost" size="icon" aria-label="Recarregar"><RotateCw /></Button>
104
+ <Button variant="ghost" size="icon" aria-label="Exibição"><SlidersHorizontal /></Button>
105
+ <Button context="primary" size="icon" aria-label="Novo item"><Plus /></Button>
106
+ </ButtonGroup>
107
+ ```
108
+
79
109
  ### Ação dividida
80
110
 
81
111
  Combine a ação principal, um separador e um botão de ícone quando o mesmo comando oferecer
@@ -83,7 +113,7 @@ variações.
83
113
 
84
114
  ```tsx preview
85
115
  <ButtonGroup>
86
- <Button icon={Play}>Rodar agente developer</Button>
116
+ <Button icon={<Play />}>Rodar agente developer</Button>
87
117
  <ButtonGroupSeparator />
88
118
  <Button size="icon" aria-label="Mais opções">
89
119
  <ChevronDown />
@@ -102,7 +132,7 @@ semântica de outro elemento, como `label`.
102
132
  <GitBranch />
103
133
  empresa-x-api
104
134
  </ButtonGroupText>
105
- <Button variant="outline" icon={RotateCw}>Sincronizar</Button>
135
+ <Button variant="outline" icon={<RotateCw />}>Sincronizar</Button>
106
136
  </ButtonGroup>
107
137
  ```
108
138
 
@@ -123,6 +153,7 @@ semântica de outro elemento, como `label`.
123
153
  | Propriedade | Tipo | Padrão | Descrição |
124
154
  |---|---|---|---|
125
155
  | `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direção do bloco e do colapso das bordas. |
156
+ | `mode` | `'connected' \| 'spaced'` | `'connected'` | Conecta as bordas dos controles ou preserva cada controle com intervalo compacto. |
126
157
  | `shape` | `'default' \| 'pill'` | `'default'` | Geometria das extremidades externas do grupo. |
127
158
 
128
159
  ### Propriedades de ButtonGroupSeparator
@@ -50,6 +50,42 @@ frase. Não copie as regras da Base para cá.
50
50
  | placeholder de campo ou busca | `placeholder` |
51
51
  | `Select.emptyText` e grupos de opção | `empty-state` e `heading` |
52
52
  | confirmação local do `ActionTrigger` | `title`, `dialog-body` e `button` |
53
+ | `dialog.alert/confirm/prompt/choose` — `title`, `description`/`body`, `action`/`cancel`, `actions[].label` | `title`, `dialog-body`, `button` (e `placeholder` no `prompt`) |
54
+ | filhos de `TooltipContent` | `label` |
55
+ | `LabelHelp.help` | `helper-text` |
56
+ | `emptyMessage`, `errorMessage`, `retryLabel` de `DataState`, `PageState`, `ActionList`, `ActionListDialog` e `ActionView` | `empty-state`, `error`, `button` |
57
+
58
+ O tooltip entra como `label` porque nomeia um controle icon-only: é um fragmento curto, sem
59
+ ponto final, e a Base não define um papel próprio para tooltip. Texto de ajuda que precisa de
60
+ frase completa pertence ao `help` de um campo (contrato ou `LabelHelp`), não ao tooltip.
61
+
62
+ ## Alcance do inventário além das props
63
+
64
+ Quem vê o gate reprovar ou precisa de uma dispensa deve saber o que o extrator alcança sem
65
+ declaração adicional:
66
+
67
+ - **Diálogos imperativos.** `dialog.confirm({ title, description, action })` e as demais
68
+ respostas (`alert`, `prompt`, `choose`) entram no inventário como se fossem props de um
69
+ `ActionFormDialog`; o campo do diagnóstico é `dialog.confirm().title`,
70
+ `dialog.choose().actions[0].label` etc. Constantes locais resolvem normalmente. Uma função
71
+ que devolve template (`description: describe(nome)`) gera o diagnóstico `content` habitual e
72
+ se declara em `copy.dynamic` como `message-template`. Opções construídas em tempo de execução
73
+ ou mutadas depois de declaradas reprovam como estrutura, porque nenhum texto pode ser
74
+ atribuído a elas.
75
+ - **`LabelHelp` como filho.** `<FieldLabel>Nome<LabelHelp help="…" /></FieldLabel>` inventaria
76
+ `Nome` como label e a ajuda como helper-text; o ícone não torna o label opaco. Um wrapper
77
+ local com o mesmo nome continua opaco, porque o extrator não infere o que ele renderiza.
78
+ - **Rótulos em array literal local.** Quando um componente mapeado lê `item.label` (ou a chave
79
+ destruturada) dentro de `map`, `forEach`, `filter` ou `for…of` sobre um array literal do
80
+ mesmo arquivo, cada literal do array vira um texto na linha em que foi escrito, em vez de um
81
+ diagnóstico sobre a prop. `filter`, `slice`, `toSorted` e `toReversed` podem ficar entre o
82
+ array e a iteração. O limite é deliberado: o array precisa ser literal e declarado no mesmo
83
+ arquivo; o elemento só pode ser lido (`item.chave`), nunca passado adiante, espalhado ou
84
+ atribuído; o callback não pode receber o próprio array como terceiro parâmetro; e nenhum
85
+ elemento pode ter a chave dinâmica. Arrays importados, derivados por spread ou por
86
+ concatenação continuam gerando o diagnóstico da prop.
87
+ - **Elementos JSX genéricos.** `<ActionList<Input, Row> emptyMessage="…">` extrai igual a
88
+ `<ActionList emptyMessage="…">`.
53
89
 
54
90
  Copy visual e nome acessível são superfícies cumulativas. Um `aria-label` estático nomeia
55
91
  um controle icon-only, mas não torna aceitável nem invisível ao gate um texto visual opaco.
@@ -22,7 +22,8 @@ render(
22
22
 
23
23
  Use os slots quando o header precisar de composição própria. A árvore aceita exatamente um
24
24
  `ContentHeader` e um `ContentBody` como filhos diretos. O header exige um `ContentTitle` e aceita
25
- uma descrição, um metadado e uma região de ações.
25
+ uma descrição, um contador (`ContentMeta`) e uma região de ações. O contador é próprio de
26
+ `Content`; `PageHeader` reconhece somente título, descrição e ações.
26
27
 
27
28
  ```tsx preview col
28
29
  render(
@@ -48,12 +49,12 @@ do pai correto, filhos estruturais indiretos e a mistura de shorthand com compos
48
49
  | Propriedade | Tipo | Padrão | Descrição |
49
50
  |---|---|---|---|
50
51
  | `title` | `ReactNode` | | Forma curta: título da região (vira o heading ligado à `section`). |
51
- | `meta` | `ReactNode` | | Forma curta: complemento ao lado do título, como uma contagem. |
52
+ | `count` | `number` | | Forma curta: total de itens ao lado do título da região. |
52
53
  | `description` | `ReactNode` | | Forma curta: frase de apoio sob o título. |
53
54
  | `actions` | `ReactNode` | | Forma curta: ações alinhadas à direita do header. |
54
55
  | `level` | `1 \| 2 \| 3 \| 4 \| 5 \| 6` | herdado | Nível semântico do heading, independente do destaque visual. |
55
- | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual; `page` permanece por compatibilidade da série 12. |
56
+ | `variant` | `'page' \| 'section'` | `'section'` | Hierarquia visual: `page` reproduz o cabeçalho de `Page`; `section` é a região dentro de uma superfície. |
56
57
 
57
58
  Na composição explícita, `ContentHeader` recebe `ContentTitle`, `ContentMeta`, `ContentDescription` e
58
59
  `ContentActions`, e `ContentBody` recebe o conteúdo; nenhum desses slots aceita `title` ou `level`
59
- próprios — a hierarquia é declarada em `Content`.
60
+ próprios — a hierarquia é declarada em `Content`. `ContentHeader` não existe fora de `Content`.
@@ -1,19 +1,21 @@
1
1
  ## Estados
2
2
 
3
- Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta. O
4
- carregamento usa `Spinner`; o vazio preserva uma moldura sólida; e o erro apresenta uma mensagem
5
- segura, sem expor detalhes técnicos. Para uma ação em andamento depois do clique, use `busy` em
6
- `Button`.
3
+ Use `DataState` para apresentar carregamento, erro, vazio e conteúdo de uma mesma consulta. As
4
+ superfícies são as mesmas de `PageState`, na escala de uma seção: o carregamento é um contêiner
5
+ `role="status"` com o `Spinner`; o vazio compõe `Empty` com moldura sólida e `emptyMessage` como
6
+ título; o erro é um `Alert` de contexto `danger` com uma mensagem segura, sem expor detalhes
7
+ técnicos, e uma ação `ghost` só com ícone quando há `onRetry`. `retryLabel` fornece o nome acessível
8
+ e o tooltip dessa ação. Para uma ação em andamento depois do clique, use `busy` em `Button`.
7
9
 
8
10
  ```tsx preview col
9
11
  <div className="w-full space-y-3">
10
12
  <DataState loading>
11
13
  <div />
12
14
  </DataState>
13
- <DataState empty emptyText="Nenhum papel.">
15
+ <DataState empty emptyMessage="Nenhum papel.">
14
16
  <div />
15
17
  </DataState>
16
- <DataState error={{ message: 'detalhe técnico fica no console' }}>
18
+ <DataState error={{ message: 'detalhe técnico fica no console' }} onRetry={() => undefined}>
17
19
  <div />
18
20
  </DataState>
19
21
  </div>
@@ -22,13 +24,13 @@ segura, sem expor detalhes técnicos. Para uma ação em andamento depois do cli
22
24
  ## Dentro de uma tabela
23
25
 
24
26
  Em uma tabela já emoldurada, passe `colSpan` para ocupar uma linha inteira dentro de `<tbody>`. A
25
- tabela continua responsável pela borda, evitando uma segunda moldura no estado vazio. Para uma
26
- região disponível para criação ou vínculo, use `Empty`.
27
+ tabela continua responsável pela borda: o `Empty` dentro dela vem sem moldura. Para uma região
28
+ disponível para criação ou vínculo, use `Empty` diretamente, com a moldura tracejada.
27
29
 
28
30
  ```tsx preview col
29
31
  <table className="w-full overflow-hidden rounded-lg border border-border text-sm">
30
32
  <tbody>
31
- <DataState empty emptyText="Nenhum usuário." colSpan={3}>
33
+ <DataState empty emptyMessage="Nenhum usuário." colSpan={3}>
32
34
  <tr>
33
35
  <td />
34
36
  </tr>
@@ -42,8 +44,10 @@ região disponível para criação ou vínculo, use `Empty`.
42
44
  | Propriedade | Tipo | Padrão | Descrição |
43
45
  |---|---|---|---|
44
46
  | `loading` | `boolean` | | Carregando (antes do conteúdo) — mostra o Spinner centralizado. |
45
- | `empty` | `boolean` | | Sem itens — mostra o emptyText. |
46
- | `emptyText` | `string` | | Texto do vazio (pt-BR, ex.: "Nenhum papel."). |
47
- | `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica não vai para tela (use errorText). |
48
- | `errorText` | `string` | `'Não foi possível carregar.'` | Aviso de erro, orientado ao usuário. |
47
+ | `empty` | `boolean` | | Sem itens — compõe `Empty` com o `emptyMessage`. |
48
+ | `emptyMessage` | `string` | `'Nada por aqui.'` | Frase do vazio (ex.: "Nenhum papel."). |
49
+ | `error` | `{ message?: string } \| null` | | Erro da carga — aviso calmo. A mensagem técnica não vai para tela (use `errorMessage`). |
50
+ | `errorMessage` | `string` | `'Não foi possível carregar.'` | Título do aviso de erro, orientado à pessoa. |
51
+ | `onRetry` | `() => void \| Promise<void>` | | Recuperação: mostra a ação de tentar de novo somente com ícone no estado de erro. |
52
+ | `retryLabel` | `string` | `'Tentar de novo'` | Nome acessível e tooltip da ação de recuperação. |
49
53
  | `colSpan` | `number` | | Em tabela: renderiza o estado como `<tr><td colSpan>` (cabe direto no tbody). |
@@ -305,7 +305,7 @@ avançada; ele recebe `children` e `container` conforme o portal do Radix.
305
305
  ## Propriedades de DialogHost
306
306
 
307
307
  `DialogHost` não recebe propriedades. Monte uma instância no shell para atender toda a API
308
- imperativa. `ConfirmHost` permanece como alias de migração.
308
+ imperativa.
309
309
 
310
310
  ## Opções de dialog.alert
311
311
 
@@ -363,6 +363,3 @@ imperativa. `ConfirmHost` permanece como alias de migração.
363
363
  | `context` | `ButtonContext` | Última ação: `primary`; demais: `neutral` | Define o significado semântico. |
364
364
  | `variant` | `ButtonVariant` | Última ação: `solid`; demais: `ghost` | Define o tratamento visual. |
365
365
  | `disabled` | `boolean` | `false` | Impede a escolha desta ação. |
366
-
367
- `confirm()` e `ConfirmHost` continuam disponíveis apenas como aliases de migração para
368
- `dialog.confirm()` e `DialogHost`.
@@ -166,7 +166,7 @@ Com `InputGroupTextarea`, um addon em `block-end` forma uma região de ações a
166
166
 
167
167
  | Propriedade | Tipo | Padrão | Descrição |
168
168
  |---|---|---|---|
169
- | `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | Tamanho do botão embutido; as variantes `icon-*` são quadradas. |
169
+ | `size` | `'xs' \| 'sm' \| 'icon-xs' \| 'icon-sm'` | `'xs'` | O subconjunto da escala única que cabe num campo (1.5 · 2 · 1.5 · 1.75rem); os `icon-*` são quadrados. |
170
170
  | `context` | `'neutral' \| 'primary' \| 'danger'` | `'primary'` | Contexto semântico herdado de Button. |
171
171
  | `variant` | `'solid' \| 'subtle' \| 'outline' \| 'ghost' \| 'link'` | `'ghost'` | Tratamento visual; use `solid` quando a ação precisar de ênfase. |
172
172
 
@@ -105,7 +105,7 @@ usa `ItemHeader` ou `ItemBody` conforme o papel do conteúdo.
105
105
  | Propriedade | Tipo | Padrão | Descrição |
106
106
  |---|---|---|---|
107
107
  | `variant` | `'default' \| 'outline' \| 'muted'` | `'default'` | O fundo da linha: transparente, com borda ou levemente tingido. |
108
- | `size` | `'default' \| 'sm'` | `'default'` | O respiro interno; `sm` atende listas densas. |
108
+ | `size` | `'default' \| 'sm'` | `'default'` | O respiro interno, com os nomes da escala única; `sm` atende listas densas. |
109
109
  | `asChild` | `boolean` | `false` | Funde o Item no filho para que a linha inteira assuma sua semântica. |
110
110
 
111
111
  ## Propriedades de ItemGroup