@softize/opus 18.4.0 → 19.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 (42) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/docs/adr/0020-icon-tiles-are-explicitly-composed.md +46 -0
  3. package/docs/adr/0021-content-carries-structure-and-semantic-element.md +65 -0
  4. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +293 -0
  5. package/package.json +1 -1
  6. package/src/ui/components/patterns/content-header.tsx +169 -50
  7. package/src/ui/components/patterns/heading-level.ts +16 -0
  8. package/src/ui/components/patterns/list.tsx +76 -27
  9. package/src/ui/components/patterns/pane.tsx +294 -0
  10. package/src/ui/components/patterns/presentation.tsx +29 -17
  11. package/src/ui/components/patterns/sidebar.tsx +13 -72
  12. package/src/ui/components/patterns/split.tsx +8 -35
  13. package/src/ui/components/patterns/surface-anatomy.ts +219 -0
  14. package/src/ui/components/patterns/surface-header.tsx +51 -6
  15. package/src/ui/components/patterns/surface-media.tsx +69 -0
  16. package/src/ui/components/primitives/card.tsx +91 -29
  17. package/src/ui/components/primitives/command.tsx +12 -4
  18. package/src/ui/components/primitives/detail.tsx +10 -13
  19. package/src/ui/components/primitives/dialog.tsx +138 -35
  20. package/src/ui/components/primitives/drawer.tsx +122 -23
  21. package/src/ui/components/primitives/empty.tsx +2 -1
  22. package/src/ui/components/primitives/field.tsx +38 -16
  23. package/src/ui/components/primitives/icon-tile.tsx +91 -0
  24. package/src/ui/components/primitives/item.tsx +2 -1
  25. package/src/ui/components/primitives/metric-card.tsx +98 -39
  26. package/src/ui/components/primitives/table.tsx +1 -1
  27. package/src/ui/docs/content/action-list.md +61 -52
  28. package/src/ui/docs/content/card.md +221 -30
  29. package/src/ui/docs/content/content.md +90 -8
  30. package/src/ui/docs/content/dialog.md +115 -7
  31. package/src/ui/docs/content/drawer.md +136 -9
  32. package/src/ui/docs/content/field.md +78 -30
  33. package/src/ui/docs/content/icon-tile.md +47 -0
  34. package/src/ui/docs/content/metric-card.md +57 -18
  35. package/src/ui/docs/content/page.md +8 -0
  36. package/src/ui/docs/content/pane.md +250 -0
  37. package/src/ui/docs/content/sidebar.md +22 -43
  38. package/src/ui/docs/content/skeleton.md +7 -3
  39. package/src/ui/docs/content/split.md +16 -21
  40. package/src/ui/docs/registry.tsx +14 -2
  41. package/src/ui/meta.ts +17 -5
  42. package/src/ui/react.tsx +31 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,100 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
7
7
  `opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
8
8
  mudança cobra do seu código.
9
9
 
10
+ ## 19.0.0 — 2026-09-24
11
+
12
+ Card, Dialog, Drawer e Pane passam a derivar a anatomia da mesma infraestrutura que Page e Content
13
+ já usavam. Cada superfície declara `layout="continuous" | "sectioned"` no seu componente raiz, e o
14
+ layout responde por um pacote: recuo das regiões, ritmo vertical, escala do título, posição das
15
+ ações, apresentação da mídia e divisores. O divisor passa a ser derivado da vizinhança das regiões —
16
+ um header seguido de corpo desenha a linha; um header sozinho, não.
17
+
18
+ Os headers passam a posicionar sozinhos o que antes o consumidor montava: mídia, título, descrição
19
+ (onde a família a tem), ações no extremo oposto e o botão de fechar depois delas. `Dialog`, `Drawer`
20
+ e `Pane` ganham slots de ações e de navegação; `Card` e `Pane` ganham slot de mídia; `Card` ganha
21
+ título com nível semântico.
22
+
23
+ A família `Pane` deixa de viver em dois módulos: raiz e slots agora moram juntos, com título,
24
+ mídia, ações, descrição e página própria na documentação.
25
+
26
+ `MetricCard` ganha ativação: com `onActivate`, `active` e `activationLabel`, a medida vira um
27
+ controle que aplica o recorte que ela resume — o card passa a emitir `aria-pressed` e a anunciar o
28
+ próprio nome. As três propriedades andam juntas pelo tipo; declarar `active` sem `onActivate` não
29
+ compila. A marcação de ativo é a borda na cor primária: antes era um anel desenhado para fora da
30
+ borda, que qualquer ancestral com `overflow-hidden` cortava. O anel de foco também passa a ser
31
+ interno.
32
+
33
+ Na mesma versão, a lista em `layout="fill"` troca a rolagem montada com um contêiner posicionado
34
+ por uma grade de duas linhas: os resultados ocupam a folga, o rodapé se mede pelo conteúdo, e o
35
+ contêiner da tabela abre mão da rolagem horizontal para não roubar do cabeçalho a referência do
36
+ `sticky`. O rodapé encosta na última linha e mantém a mesma altura com e sem paginador. O detalhe
37
+ aproxima o ícone do rótulo, na escala densa das superfícies produtivas.
38
+
39
+ ### Breaking
40
+
41
+ **Headers validam seus slots.** `CardHeader`, `DialogHeader`, `DrawerHeader` e `PaneHeader` aceitam
42
+ apenas os slots da família e no máximo um de cada. Nó solto dentro do header passa a lançar erro com
43
+ a lista do que é aceito. Migração: envolva ações em `CardAction`, `DialogActions`, `DrawerActions`
44
+ ou `PaneActions`; navegação em `DialogNavigation` ou `DrawerNavigation`; ícone em `CardMedia`,
45
+ `DrawerMedia` ou `PaneMedia`. Descrição dentro do header de modal não tem slot e vai para o começo
46
+ do corpo, como as ADRs 0014 e 0018 já determinavam.
47
+
48
+ **`CardTitle` virou heading.** Era um `div` sem escala tipográfica; agora emite `h3` por padrão,
49
+ aceita `level` para a hierarquia correta e declara a escala pelo layout. Migração: remova o heading
50
+ que você embutia dentro dele e o `text-*` que aplicava por fora; passe `level` quando a página pedir
51
+ outra hierarquia.
52
+
53
+ **Divisores saem do consumidor.** `<CardHeader className="border-b">` e
54
+ `<CardFooter className="border-t">` deixam de ser necessários: use `layout="sectioned"` no `Card`.
55
+ As classes continuam funcionando, mas somam uma segunda linha sobre a derivada.
56
+
57
+ **`PaneHeader` e `PaneFooter` ganharam recuo próprio** e o `Pane` passa a usar `inset="none"`
58
+ sozinho quando contém slots estruturais. Migração: remova o `inset="none"` e o padding que a tela
59
+ aplicava no header; remova também o `border-b-0`/`border-t-0` de quem desligava o divisor para
60
+ redesenhá-lo à mão.
61
+
62
+ **O rodapé do `Drawer` recebeu a faixa sutil do `Dialog`.** A ausência era divergência não declarada
63
+ entre duas superfícies com a mesma anatomia; agora as duas usam o mesmo fundo. O título do Drawer
64
+ também perdeu o `text-foreground` que só ele tinha.
65
+
66
+ **Títulos de modal passam a truncar.** `DialogTitle` e `DrawerTitle` mantêm o nome da superfície em
67
+ uma linha, com reticências quando não cabe, em vez de empurrar o corpo para baixo. O
68
+ `mode="alert"` é a exceção: ali o título é centralizado e pode ocupar duas linhas, porque a
69
+ resposta obrigatória não tem corpo para empurrar. As telas que já
70
+ escreviam `truncate` podem remover a classe. Quando o nome precisar aparecer inteiro, ele pertence
71
+ ao começo do corpo, não ao cabeçalho (ADR 0018).
72
+
73
+ **Mídia com visual próprio.** Os slots de mídia aceitam `asChild`: uma marca que já se desenha —
74
+ um orbe, um logotipo — ocupa o slot sem ganhar a moldura por cima, e continua herdando a escala.
75
+
76
+ **O rodapé do `Drawer` mantém o `mt-auto`** que o `Dialog` não tem. A diferença passa a ser
77
+ declarada: o conteúdo lateral raramente preenche a altura da janela, e sem isso o rodapé subiria
78
+ atrás do corpo.
79
+
80
+ **Divisor não se desliga por classe.** `border-b-0` no consumidor não apaga a linha derivada — ela
81
+ vem de uma variante condicional, que vence uma classe simples. Uma superfície sem divisores é uma
82
+ superfície `continuous`: troque o layout no root em vez de apagar a borda região a região.
83
+
84
+ ### Migração rápida
85
+
86
+ 1. Rode a aplicação e siga os erros dos headers: cada um diz o slot que falta.
87
+ 2. Remova `p-*`, `border-b`, `border-t`, `flex-row`, `justify-between` e `text-*` que existiam só
88
+ para corrigir o default da família.
89
+ 3. Troque `<Card>` + classes de divisor por `<Card layout="sectioned">`.
90
+ 4. Onde a tela remontava header com `div`, use os slots — eles agora posicionam sozinhos.
91
+
92
+ ## 18.5.0 — 2026-09-22
93
+
94
+ `Content` passa a concentrar estrutura e apresentação de blocos com cabeçalho e corpo: aceita
95
+ `as="section" | "fieldset"`, `variant="page" | "section"` e moldura opcional com
96
+ `frameVariant="default" | "legend"`. O novo
97
+ `IconTile` mantém a moldura do ícone como composição explícita, permitindo usar o mesmo ícone com
98
+ ou sem esse apoio visual.
99
+
100
+ `ActionList` deixa de aplicar moldura por padrão. No layout `fill`, mantém o cabeçalho fixo sobre a
101
+ cor da superfície, com borda inferior durante a rolagem. Filtros avançados podem escolher o tratamento das seções sem
102
+ recriar o fieldset no consumidor. Labels de campos e detalhes compartilham a mesma escala tipográfica.
103
+
10
104
  ## 18.4.0 — 2026-09-21
11
105
 
12
106
  `executions` acrescenta infraestrutura experimental de acompanhamento: snapshots versionados,
@@ -0,0 +1,46 @@
1
+ # ADR 0020 — Molduras de ícone são compostas explicitamente
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-18.
5
+
6
+ ## Contexto
7
+
8
+ Várias interfaces apresentam um ícone dentro de uma pequena superfície quadrada. A mesma forma
9
+ aparecia incorporada em componentes como `FieldLegend`, `ItemMedia` e `EmptyMedia`, ou era
10
+ reconstruída pelo consumidor. Em `FieldLegend`, isso também vinculava a moldura à variante da
11
+ legenda: o consumidor conseguia escolher o ícone, mas não se ele deveria receber a superfície.
12
+
13
+ ## Decisão
14
+
15
+ `IconTile` passa a representar a moldura visual não interativa de um ícone. Ele compartilha a
16
+ escala de tamanhos dos controles, declara a cor por `context` e oferece os tratamentos `subtle` e
17
+ `outline`.
18
+
19
+ Slots que aceitam conteúdo, como `FieldLegend.icon`, continuam genéricos. O consumidor passa um
20
+ ícone diretamente quando quer somente o glifo ou compõe `<IconTile><Icon /></IconTile>` quando a
21
+ moldura reforça a hierarquia. `ItemMedia variant="icon"` e `EmptyMedia variant="icon"` reutilizam
22
+ a mesma base visual porque essas variantes já prometem a moldura.
23
+
24
+ `IconTile` é somente apresentação. Quando o ícone aciona algo, deve-se usar `Button` com tamanho
25
+ de ícone e nome acessível.
26
+
27
+ ## Consequências
28
+
29
+ - A presença da moldura deixa de ser uma regra implícita da legenda.
30
+ - Tamanho, arredondamento e preenchimento convergem entre consumidores.
31
+ - Um slot permanece flexível sem acumular propriedades como `iconFramed` ou `iconTile`.
32
+ - Migrações devem distinguir molduras decorativas de botões e indicadores semânticos.
33
+
34
+ ## Alternativas descartadas
35
+
36
+ - **Adicionar uma propriedade à `FieldLegend`:** resolveria apenas uma superfície e ampliaria o
37
+ contrato toda vez que outra composição precisasse escolher a moldura.
38
+ - **Manter classes livres em cada consumidor:** preservaria divergências de escala e acabamento.
39
+ - **Usar `Button` desabilitado:** comunicaria interatividade onde não existe ação.
40
+
41
+ ## Verificação
42
+
43
+ - teste estrutural cobre tamanho, variante e tamanho do glifo de `IconTile`;
44
+ - teste de `FieldLegend` confirma que o slot não adiciona moldura por conta própria;
45
+ - teste da API pública garante que o componente e seus metadados sejam exportados;
46
+ - documentação de `Field` mostra a composição explícita.
@@ -0,0 +1,65 @@
1
+ # ADR 0021 — Content reúne estrutura visual e elemento semântico
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-19.
5
+
6
+ ## Contexto
7
+
8
+ Regiões com título e corpo usam `Content`, mas grupos de formulário precisam reconstruir a mesma
9
+ estrutura com `FieldSet` e `FieldLegend`. Molduras aplicadas diretamente em `DetailGroup` também
10
+ separam visualmente o título dos dados que ele nomeia. Essa sobreposição aumenta a quantidade de
11
+ estruturas que a documentação e os consumidores precisam combinar.
12
+
13
+ ## Decisão
14
+
15
+ `Content` continua responsável pela anatomia `Header + Body` e passa a aceitar:
16
+
17
+ - `framed`, que aplica a moldura à região inteira;
18
+ - `as="fieldset"`, que renderiza a raiz como `fieldset` e o título como `legend`.
19
+ - `frameVariant="legend"`, que integra visualmente a legenda à borda quando esse acabamento for
20
+ escolhido de forma explícita.
21
+
22
+ O default permanece `as="section"` sem moldura. `FieldGroup` organiza campos dentro do corpo e
23
+ `DetailGroup` preserva a semântica de lista de definições sem assumir a moldura da região.
24
+ `FieldSet` permanece disponível para compatibilidade e para composições que não precisam da
25
+ anatomia completa de `Content`.
26
+
27
+ No modo `fieldset`, a legenda aceita somente o título. Contador, descrição e ações não são filhos
28
+ válidos de `legend`; consumidores que precisarem dessas regiões devem mantê-las no corpo ou usar
29
+ uma `section`. A escolha de `fieldset` não define o acabamento visual: quando `framed` estiver
30
+ presente, a moldura é contínua por padrão. Os filtros avançados preservam a legenda integrada à
31
+ borda declarando `frameVariant="legend"`.
32
+
33
+ ## Consequências
34
+
35
+ - A mesma API nomeia e delimita regiões de leitura e grupos de formulário.
36
+ - A escolha do elemento semântico não fica acoplada à presença da moldura.
37
+ - O recorte visual da borda deixa de ser consequência implícita do elemento semântico.
38
+ - A forma curta continua sendo o caminho comum, enquanto a composição explícita permanece
39
+ disponível nos dois elementos.
40
+ - A migração de `FieldSet` pode ser gradual e não exige remoção nesta versão.
41
+
42
+ ## Alternativas consideradas
43
+
44
+ ### Criar Panel
45
+
46
+ Separaria a moldura de `Content`, mas criaria outra raiz com header e body para documentar e
47
+ compor. Foi descartada porque `Content` já possui essa responsabilidade.
48
+
49
+ ### Renomear FieldSet para FieldGroup
50
+
51
+ Produziria simetria nominal com `DetailGroup`, mas perderia a distinção entre a fronteira semântica
52
+ do formulário e o layout interno dos campos. Foi descartada porque `FieldGroup` já representa esse
53
+ layout.
54
+
55
+ ### Adicionar um booleano fieldset
56
+
57
+ Permitiria alternar o elemento, mas trataria semântica como estado visual. Foi descartada em favor
58
+ de `as`, que explicita o elemento renderizado.
59
+
60
+ ## Verificação
61
+
62
+ - Testes de UI confirmam `section` e heading como defaults.
63
+ - Testes de UI confirmam `fieldset`, `legend`, props nativas e ausência de heading nesse modo.
64
+ - Testes reprovam slots incompatíveis com a legenda.
65
+ - A documentação demonstra `Content + DetailGroup` e `Content as="fieldset" + FieldGroup`.
@@ -0,0 +1,293 @@
1
+ # ADR 0023 — Superfícies compartilham uma anatomia e dois layouts
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-23.
5
+ - **Complementa:** ADRs 0005, 0014, 0018, 0019 e 0021.
6
+
7
+ ## Contexto
8
+
9
+ A ADR 0005 estabeleceu a gramática `Root > Header + Body + Footer` para as superfícies da casa. O
10
+ vocabulário foi adotado, mas a implementação não: `Page` e `Content` derivam o header de uma
11
+ infraestrutura interna comum, enquanto `Card`, `Dialog`, `Drawer` e `Pane` desenham o seu à mão.
12
+
13
+ O efeito acumulado é mensurável no código de hoje:
14
+
15
+ - **Três escalas de padding para o mesmo papel.** `p-3` no chrome de Page, `p-4` nos slots de Dialog
16
+ e Drawer, `px-5` no Card. `Card` é a única com ritmo vertical assimétrico (`pt-5 pb-3` no header,
17
+ `pt-3 pb-5` no footer). `Content`, `PageBody` e `PaneBody` não têm padding nenhum.
18
+ - **`PaneHeader` sem padding e `PaneFooter` com `p-2`** — assimetria dentro da mesma família.
19
+ - **`CardTitle` é um `div` sem escala tipográfica**, único título do conjunto que não é heading e
20
+ não declara tamanho; herda o que vier do contexto.
21
+ - **Divisor automático em quatro famílias e manual no Card.** A documentação pública ensina o
22
+ consumidor a escrever `border-b` no `CardHeader` e `border-t` no `CardFooter`.
23
+ - **`Dialog` e `Drawer` não têm slot de ações no header.** Quem precisa injeta nós soltos ao lado do
24
+ título, como `Presentation` faz hoje.
25
+ - **A família `Pane` está partida**: a raiz mora em `split.tsx`, os slots em `sidebar.tsx`, a
26
+ documentação está na página da Sidebar e nenhum teste cobre a anatomia.
27
+ - **`Drawer` duplica `Dialog`** com três divergências não declaradas: footer sem fundo, header sem a
28
+ supressão condicional do divisor e título com `text-foreground` a mais.
29
+ - **Nenhuma suíte transversal** compara as famílias; cada divergência só aparece quando alguém
30
+ compõe as duas superfícies lado a lado.
31
+
32
+ A evidência que motivou a decisão veio da tela do Evento de Pós-vendas: para aproximar dois Cards do
33
+ recorte já usado por Dialog e Content, a tela precisou escrever `flex-row items-center gap-4
34
+ border-b p-4` no header, `p-4` no body, `text-sm` no título, um `<h3>` dentro do `CardTitle` e o
35
+ ícone dentro do texto. São cinco decisões estruturais tomadas pelo consumidor.
36
+
37
+ As forças em tensão:
38
+
39
+ - **Uniformidade × identidade da família.** Um modal, um card e um painel não precisam ter as mesmas
40
+ medidas; precisam ter a mesma gramática e o mesmo comportamento estrutural.
41
+ - **Compatibilidade × correção.** Alterar o default de uma família muda todas as telas que já a
42
+ usam. Preservar o default errado perpetua o defeito que motivou a decisão.
43
+ - **Uma API genérica × seis APIs específicas.** Um `<Surface>` público reduziria código e apagaria a
44
+ semântica de cada família; seis implementações independentes preservam a semântica e produzem a
45
+ divergência atual.
46
+
47
+ ## Decisão
48
+
49
+ ### 1. Uma infraestrutura interna, seis fachadas públicas
50
+
51
+ As telas continuam usando `CardHeader`, `DialogHeader`, `DrawerHeader`, `PaneHeader`,
52
+ `ContentHeader` e `PageHeader`. Não existe superfície genérica pública. `SurfaceHeader` e o novo
53
+ módulo de anatomia permanecem internos: a aplicação não os importa.
54
+
55
+ A anatomia compartilhada passa a viver em um único módulo interno, que responde por:
56
+
57
+ - as classes de cada região (`header`, `body`, `footer`) por família e layout;
58
+ - a escala visual do título, separada do nível semântico;
59
+ - a posição e o tamanho das ações;
60
+ - a apresentação da mídia;
61
+ - os divisores entre regiões adjacentes;
62
+ - o alinhamento horizontal comum entre as regiões da mesma superfície.
63
+
64
+ Nenhuma família reimplementa essas regras. Quando uma família precisa divergir, a divergência é um
65
+ valor declarado na tabela de anatomia, não uma classe escrita no componente.
66
+
67
+ ### 2. O layout é do root e chega aos slots por contexto
68
+
69
+ `layout` é propriedade do componente raiz: `Card`, `DialogContent`, `DrawerContent` e `Pane`. Os
70
+ slots não recebem `layout`; eles leem o contexto da superfície que os hospeda. Um slot usado fora da
71
+ sua família continua sendo erro, como hoje.
72
+
73
+ `Content` e `Page` não expõem `layout` de anatomia. `Content` é contínuo por definição e `Page`
74
+ mantém `layout="flow" | "fill"`, que descreve ocupação de altura, não anatomia. São nomes
75
+ diferentes para coisas diferentes e permanecem assim.
76
+
77
+ ### 3. O que cada layout significa
78
+
79
+ `continuous` e `sectioned` não são a ausência e a presença de uma borda. Cada um é um pacote:
80
+
81
+ | Aspecto | `continuous` | `sectioned` |
82
+ | --- | --- | --- |
83
+ | Divisores | nenhum | entre regiões adjacentes presentes |
84
+ | Ritmo vertical | header aproxima-se do corpo; o espaço separa | regiões simétricas; o divisor separa |
85
+ | Escala do título | a da família | a da família, um degrau mais contida quando a família tem duas |
86
+ | Fundo do footer | igual ao da superfície | sutil, quando a família declara |
87
+ | Alinhamento horizontal | idêntico entre header, body e footer | idêntico entre header, body e footer |
88
+
89
+ O alinhamento horizontal é invariante dos dois layouts: o recuo lateral de header, body e footer de
90
+ uma mesma superfície é sempre o mesmo valor. É o que a suíte transversal verifica.
91
+
92
+ ### 4. Os divisores são derivados, não escritos
93
+
94
+ No layout `sectioned`, o divisor existe quando há duas regiões adjacentes. Um header seguido de nada
95
+ não desenha linha; um footer que é a única região também não. A regra é estrutural e vive no
96
+ componente, derivada da presença dos irmãos — não uma classe que o consumidor aplica.
97
+
98
+ A recomendação de escrever `border-b` e `border-t` no Card sai da documentação.
99
+
100
+ ### 5. Defaults por família
101
+
102
+ Os defaults preservam o que cada família já entrega hoje, exceto onde o comportamento atual é o
103
+ defeito que esta decisão corrige:
104
+
105
+ | Família | Default | Por quê |
106
+ | --- | --- | --- |
107
+ | `Card` | `continuous` | é o que a família entrega hoje: sem divisores, ritmo assimétrico |
108
+ | `Dialog` (default) | `sectioned` | já tem divisores e faixas; o default nomeia o que existe |
109
+ | `Dialog` (`mode="alert"`) | composição própria | a resposta obrigatória mantém sua anatomia centrada |
110
+ | `Drawer` | `sectioned` | idem Dialog |
111
+ | `Pane` | `sectioned` | header e footer já têm divisor |
112
+ | `Content` | contínuo, sem opção | região semântica dentro de outra superfície |
113
+ | `Page` | contínuo, sem opção | a separação do chrome pertence ao shell |
114
+
115
+ Nenhum default muda de valor efetivo com esta ADR. O que muda é que o valor passa a ter nome,
116
+ implementação única e teste.
117
+
118
+ ### 5.1. O que o inventário dos consumidores mostrou
119
+
120
+ A migração foi decidida sobre 374 usos reais das seis famílias na plataforma. O que eles revelam:
121
+
122
+ - **Nenhuma tela usa `CardDescription`, `ContentDescription`, `DialogDescription` ou equivalente.**
123
+ Toda descrição é um `<p className="text-sm text-muted-foreground">` escrito à mão. Quando esse
124
+ parágrafo está dentro de um `DialogHeader`, ele contraria a ADR 0018 e precisa ir para o corpo na
125
+ migração — não ganhar um slot novo no header.
126
+ - **`Card` é usado como caixa sem slots em várias telas**, com o padding escolhido por elas (`p-3`,
127
+ `p-5`, `px-3 py-2`). O root do Card continua sem padding próprio: quem não usa slots continua
128
+ decidindo. O padding pertence aos slots, como hoje.
129
+ - **Os dois `PaneHeader` do produto escrevem quase a mesma linha** (`flex h-[3.75rem] items-center
130
+ … p-3 … border-b-0`) em contextos diferentes. É a prova de que padding e divisor pertencem à
131
+ primitiva.
132
+ - **Um comentário no código do login documenta o defeito**: o `py-3` do `CardBody` "conta com um
133
+ CardFooter, que o login não tem". O ritmo do layout contínuo precisa reagir à ausência do rodapé.
134
+ - **Três testes de consumidor contradizem a unificação** e precisam de decisão explícita, não de
135
+ ajuste mecânico: um proíbe `p-4` no Card, outro exige `p-0` no `DrawerBody`, outro proíbe
136
+ `ContentHeader` em uma seção. Os dois primeiros fixam contornos; o terceiro fixa uma escolha de
137
+ composição que continua válida.
138
+
139
+ ### 6. Slots por família
140
+
141
+ A anatomia é comum; o conjunto de slots continua sendo decisão de cada família, porque decisões
142
+ anteriores já os delimitaram:
143
+
144
+ | Slot | Card | Content | Page | Dialog | Drawer | Pane |
145
+ | --- | --- | --- | --- | --- | --- | --- |
146
+ | Media | sim | não | não | sim (`DialogMedia`) | sim | sim |
147
+ | Title | sim | sim | sim | sim | sim | sim |
148
+ | Description | sim | sim | não (ADR 0014) | não (ADR 0018) | não (ADR 0018) | não |
149
+ | Meta | não | sim (contador) | não (ADR 0009) | não | não | não |
150
+ | Actions | sim | sim | sim | sim | sim | sim |
151
+ | Body | sim | sim | sim | sim | sim | sim |
152
+ | Footer | sim | não | sim | sim | sim | sim |
153
+
154
+ As ADRs 0009, 0014 e 0018 não são reabertas: Page, Dialog e Drawer continuam sem descrição no
155
+ header, e Page continua sem contador. `Dialog`, `Drawer` e `Pane` ganham o slot de ações que hoje
156
+ não têm — a ausência obrigava a injetar nós soltos ao lado do título, e o `Studio` a manter três
157
+ anatomias paralelas para a mesma entrada declarativa. O botão de fechar continua sendo o último
158
+ elemento da linha; as ações da tela ficam antes dele.
159
+
160
+ `Pane` ganha também título e mídia. Hoje ele não tem nenhum dos dois, e as duas telas que precisam
161
+ de um cabeçalho remontam a linha com `span`, incluindo o ícone dentro do texto.
162
+
163
+ `Content` permanece sem footer: é uma região nomeada dentro de outra superfície, e um rodapé
164
+ pertence à superfície que a hospeda.
165
+
166
+ ### 7. Título: hierarquia e tamanho são independentes
167
+
168
+ O nível semântico vem de `level` (`1`–`6`) ou de `asChild`, e nunca do tamanho visual. O tamanho vem
169
+ da família e do layout. `CardTitle` deixa de ser um `div` sem escala e passa a declarar as duas
170
+ coisas, com `h3` como default — um card é uma seção dentro de uma página que já tem `h1`.
171
+
172
+ ### 8. Mídia reutiliza a moldura existente
173
+
174
+ O slot de mídia usa `IconTile` ou a mesma infraestrutura visual dele. Nenhuma família cria outra
175
+ escala de molduras.
176
+
177
+ Quando a marca já tem visual próprio — um orbe, um logotipo —, `asChild` deixa o slot ceder o
178
+ elemento para ela: a escala continua vindo da mesma fonte, e a moldura sai de cena em vez de ser
179
+ desenhada por baixo.
180
+
181
+ `DialogMedia` é a exceção declarada: no modo de alerta a mídia não acompanha uma linha de título,
182
+ ela é o elemento de reconhecimento da resposta, e mantém a moldura maior e centrada. A exceção vale
183
+ só nesse modo, deriva da mesma infraestrutura das demais e é coberta por teste. Fora do alerta, o
184
+ `DialogMedia` é a mesma moldura das outras famílias.
185
+
186
+ ### 7.1. O Pane aceita conteúdo livre; as demais não
187
+
188
+ `Card`, `Dialog`, `Drawer`, `Page` e `Content` sempre nomeiam alguma coisa: seus headers aceitam só
189
+ os slots da família e reprovam nó solto, com uma mensagem que lista o que é aceito.
190
+
191
+ `Pane` é a superfície genérica — a coluna que hospeda navegação, marca, busca ou chrome que não é
192
+ título nem ação. Seu header aceita conteúdo livre ao lado dos slots, e esse conteúdo ocupa o mesmo
193
+ espaço do heading, de modo que as ações continuam indo para o extremo oposto sem classe no
194
+ consumidor. A permissividade é dele, não uma brecha geral: forçar a marca de uma barra lateral a
195
+ virar "título" seria uniformidade sem semântica.
196
+
197
+ O conteúdo livre é renderizado **dentro** da região de heading, depois do título e da descrição,
198
+ qualquer que seja a ordem no JSX. Ele herda dali a largura que o heading absorve, o que é o que
199
+ permite às ações continuarem no extremo oposto — mas também significa que uma busca colocada no
200
+ header aparece **abaixo** do título, não ao lado dele. Um header que precise de algo antes do
201
+ título usa o slot de mídia; um que precise de algo ao lado usa o de ações.
202
+
203
+ ### 8.1. Nível semântico em todas as fachadas
204
+
205
+ Todo título de superfície aceita `level`. Nos modais, onde a primitiva de acessibilidade emite o
206
+ elemento, o nível troca a tag do mesmo nó por composição, nunca aninha um heading dentro de outro, e
207
+ não altera a escala. Sem `level`, vale o `h2` que a primitiva já emitia.
208
+
209
+ ### 9. `Pane` deixa de ser uma família partida
210
+
211
+ Raiz e slots passam a viver no mesmo módulo, com validação de pertencimento, documentação própria e
212
+ os mesmos testes das demais famílias. `split.tsx` continua dono do redimensionamento; `sidebar.tsx`
213
+ continua dono da navegação. A fronteira deixa de ser acidental e passa a ser declarada.
214
+
215
+ ### 10. Compatibilidade
216
+
217
+ Aliases depreciados existentes (`CardContent`, `PaneContent`) seguem seu plano de remoção; esta
218
+ decisão não cria novos.
219
+
220
+ Classes de recuo, ritmo e escala que o consumidor já aplica continuam funcionando: `className` é
221
+ mesclado por último e vence. Uma tela que ainda escreve `p-4` no `CardHeader` não quebra, apenas
222
+ deixa de precisar.
223
+
224
+ **Os divisores são a exceção, e é uma exceção real.** Eles nascem de variantes condicionais
225
+ (`:not(:last-child)`), que têm especificidade maior que uma classe simples: `border-b-0` no
226
+ consumidor não desliga a linha derivada. Desligar o divisor de uma região isolada não é, portanto,
227
+ uma decisão do consumidor — é decisão da superfície, e se expressa trocando o layout dela para
228
+ `continuous`. A paleta de comandos é o caso dentro do próprio SDK: ela é contínua, e não um
229
+ seccionado com a primeira linha apagada.
230
+
231
+ Pela mesma razão, a regra que suprime o divisor entre um header e um rodapé adjacentes precisa
232
+ vencer a que o cria — não basta vir depois dela na folha, porque a ordem das variantes não é nossa.
233
+
234
+ ## Consequências
235
+
236
+ - Uma mudança de espaçamento, divisor ou escala passa a ter um lugar só; hoje tem seis.
237
+ - Card ganha um modo que a aplicação já construía à mão, e a documentação deixa de ensinar o
238
+ contorno.
239
+ - Dialog e Drawer ganham ações no header; `Presentation` deixa de injetar nós soltos.
240
+ - Pane deixa de ser exceção não testada.
241
+ - Consumidores que aplicavam classes de correção podem removê-las; quem não remover continua
242
+ funcionando, o que permite migração incremental.
243
+ - O custo é concentrado: quase toda a superfície visual do SDK muda de implementação ao mesmo tempo,
244
+ e a rede de proteção precisa existir antes da migração, não depois.
245
+ - Uma família que precise divergir passa a precisar declarar a divergência na tabela e cobri-la por
246
+ teste. Isso é deliberado: torna a diferença uma decisão, não um acidente.
247
+
248
+ ## Alternativas descartadas
249
+
250
+ ### Manter cada família com seu header
251
+
252
+ Preservaria compatibilidade absoluta e evitaria o custo de migração. Foi descartada porque é o
253
+ estado atual: produziu três escalas de padding, um título sem escala, divisores manuais em uma
254
+ família e a documentação pública ensinando o contorno.
255
+
256
+ ### Expor um `<Surface>` genérico
257
+
258
+ Reduziria o código a uma implementação e uma API. Foi descartada porque apagaria a semântica de cada
259
+ família — um card, um modal e um painel deixariam de se distinguir no código da tela — e porque o
260
+ ganho real está na infraestrutura interna, que este ADR unifica sem expor.
261
+
262
+ ### Unificar também as medidas entre famílias
263
+
264
+ Um único recuo para todas as superfícies eliminaria a tabela de anatomia. Foi descartada porque as
265
+ famílias têm densidades legítimas diferentes: o chrome de página é mais compacto que um card, e um
266
+ modal tem largura controlada. O invariante que importa é o alinhamento dentro de cada superfície,
267
+ não a igualdade entre superfícies diferentes.
268
+
269
+ ### Tratar `layout` como propriedade de cada slot
270
+
271
+ Permitiria um header seccionado com um corpo contínuo. Foi descartada porque o layout é a relação
272
+ entre as regiões: um divisor depende de quem está do outro lado. Propriedade por slot permitiria
273
+ compor superfícies incoerentes sem erro.
274
+
275
+ ### Derivar os divisores por CSS no consumidor
276
+
277
+ Manteria o componente menor. Foi descartada porque devolve ao consumidor a decisão que esta ADR
278
+ retira dele, e porque a regra depende de presença de irmãos, que o componente conhece e a tela
279
+ não.
280
+
281
+ ## Verificação
282
+
283
+ - Uma suíte transversal parametrizada percorre as famílias configuráveis e verifica, para cada
284
+ layout: alinhamento horizontal idêntico entre header, body e footer; divisor apenas entre regiões
285
+ adjacentes presentes; escala do título independente do nível semântico; header com e sem mídia,
286
+ descrição e ações; ações no extremo oposto sem classe no consumidor; body sem header; header sem
287
+ footer; footer com e sem ações.
288
+ - A mesma suíte verifica que `Page` e `Content` não expõem `sectioned` e que nenhuma família
289
+ reimplementa as classes de anatomia localmente.
290
+ - Testes de família continuam cobrindo o que é específico de cada uma.
291
+ - Exemplos vivos no catálogo apresentam a matriz de layouts e slots.
292
+ - `opus check` continua reprovando slots fora da família.
293
+ - Um teste de API pública reprova a exportação de `SurfaceHeader` ou do módulo de anatomia.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "18.4.0",
3
+ "version": "19.0.0",
4
4
  "description": "End-to-end action protocol for TypeScript. Single package with subpath exports (core + adapters).",
5
5
  "license": "MIT",
6
6
  "type": "module",