@softize/opus 21.0.1 → 22.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 (44) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/bin/lib/check.mjs +31 -28
  3. package/docs/adr/0004-page-content-state-is-composed.md +3 -2
  4. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +3 -0
  5. package/docs/adr/0006-semantic-context-precedes-visual-variant.md +23 -14
  6. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -0
  7. package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +7 -4
  8. package/docs/adr/0016-list-collection-header-belongs-to-content.md +13 -8
  9. package/docs/adr/0023-surfaces-share-one-anatomy-and-two-layouts.md +27 -26
  10. package/docs/adr/0025-content-frame-treatment-does-not-change-semantics.md +1 -0
  11. package/docs/adr/0029-page-header-is-the-single-page-introduction.md +71 -0
  12. package/docs/adr/0030-framed-content-can-use-sectioned-anatomy.md +89 -0
  13. package/docs/adr/0031-detail-dividers-own-the-tabular-label-treatment.md +61 -0
  14. package/docs/adr/0032-tab-baseline-respects-the-container-inset.md +59 -0
  15. package/docs/adr/0033-drawer-uses-an-inset-structural-frame.md +72 -0
  16. package/docs/adr/0034-tabs-can-own-a-frame-without-owning-a-surface.md +60 -0
  17. package/docs/adr/0035-button-group-separates-frame-relationship-and-distribution.md +63 -0
  18. package/docs/adr/0036-tabs-declares-trigger-distribution.md +58 -0
  19. package/docs/radius-scale.md +5 -2
  20. package/package.json +1 -1
  21. package/registry/skills/build-opus-ui/SKILL.md +9 -6
  22. package/registry/skills/build-opus-ui/references/evaluations.md +5 -2
  23. package/registry/skills/build-opus-ui/references/ui-patterns.md +14 -16
  24. package/src/ui/components/patterns/content-header.tsx +51 -13
  25. package/src/ui/components/patterns/list.tsx +51 -33
  26. package/src/ui/components/patterns/page.tsx +139 -251
  27. package/src/ui/components/patterns/presentation.tsx +2 -6
  28. package/src/ui/components/primitives/button-group.tsx +80 -8
  29. package/src/ui/components/primitives/button.tsx +85 -60
  30. package/src/ui/components/primitives/detail.tsx +63 -57
  31. package/src/ui/components/primitives/dialog.tsx +8 -11
  32. package/src/ui/components/primitives/drawer.tsx +16 -12
  33. package/src/ui/components/primitives/tabs.tsx +123 -54
  34. package/src/ui/docs/content/button.md +55 -29
  35. package/src/ui/docs/content/content.md +61 -36
  36. package/src/ui/docs/content/detail.md +28 -19
  37. package/src/ui/docs/content/drawer.md +53 -47
  38. package/src/ui/docs/content/input.md +51 -35
  39. package/src/ui/docs/content/page.md +84 -95
  40. package/src/ui/docs/content/pane.md +83 -63
  41. package/src/ui/docs/content/presentation.md +20 -20
  42. package/src/ui/docs/content/tabs.md +107 -36
  43. package/src/ui/meta.ts +5 -5
  44. package/src/ui/react.tsx +0 -1
@@ -0,0 +1,89 @@
1
+ # ADR 0030 — Content com moldura pode usar anatomia seccionada
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-28.
5
+ - **Substitui parcialmente:** ADR 0023, nas afirmações de que `Content` não expõe `layout` e é
6
+ sempre contínuo.
7
+ - **Complementa:** ADR 0025.
8
+
9
+ ## Contexto
10
+
11
+ `Content` já representa uma região nomeada como `section` ou `fieldset`, com header, body e moldura
12
+ opcional. Quando o body é uma estrutura que possui o próprio recuo — como `DetailGroup` com
13
+ divisores, uma lista ou uma tabela — o acabamento contínuo acrescenta padding ao redor dessa
14
+ estrutura e impede que suas linhas alcancem a moldura.
15
+
16
+ O protótipo do detalhe de Veículo no Evento de Pós-vendas confirmou uma composição útil: header com
17
+ mídia, título, recuo de `0.75rem` e divisor; body sem recuo; conteúdo interno responsável por suas
18
+ células. Produzir esse desenho exigiu classes no consumidor para remover o padding da raiz, recortar
19
+ os cantos e reconstruir o header.
20
+
21
+ As forças em tensão são:
22
+
23
+ - reutilizar o vocabulário `continuous | sectioned` de Card, Dialog, Drawer e Pane;
24
+ - preservar `Content` como região semântica interna, sem transformá-lo em Card;
25
+ - manter `frameVariant="legend"` como acabamento da borda, sem misturá-lo à anatomia;
26
+ - impedir combinações em que o body encosta nas bordas sem existir uma moldura para recortá-lo.
27
+
28
+ ## Decisão
29
+
30
+ `Content` passa a aceitar `layout="continuous" | "sectioned"`, com `continuous` como default.
31
+
32
+ O layout `sectioned` exige `framed` e forma um pacote indivisível:
33
+
34
+ - A raiz desenha a moldura, recorta os cantos e não aplica padding geral.
35
+ - `ContentHeader` aplica `p-3` e o divisor inferior.
36
+ - `ContentBody` não aplica padding; seu filho direto responde pelo recuo interno.
37
+ - Shorthand e composição explícita produzem a mesma anatomia.
38
+ - `ContentMedia`, título, descrição, contador e ações preservam seus contratos atuais.
39
+
40
+ `frameVariant` continua escolhendo somente o acabamento da moldura contínua. A combinação
41
+ `layout="sectioned"` com `frameVariant="legend"` é inválida: o primeiro coloca o header dentro da
42
+ moldura, enquanto o segundo o integra ao traço externo.
43
+
44
+ O layout não altera a semântica. `as="section"` continua emitindo section com heading e
45
+ `as="fieldset"` continua emitindo fieldset com legend nativa.
46
+
47
+ ### Emenda de 2026-09-28 — a moldura usa o raio das superfícies estruturais
48
+
49
+ Quando `framed` está ativo, `Content` desenha uma moldura estrutural completa e usa `rounded-xl`,
50
+ o mesmo degrau de `Card`. A semântica das duas famílias continua diferente, mas essa diferença não
51
+ produz uma geometria externa menor. Em composições aninhadas, evitar a moldura dupla em vez de
52
+ reduzir globalmente o raio de `Content`.
53
+
54
+ ## Consequências
55
+
56
+ - DetailGroup, listas e tabelas podem ocupar o body de ponta a ponta sem classes estruturais no
57
+ consumidor.
58
+ - Conteúdo livre que precisa de recuo continua usando o layout `continuous`, ou declara sua própria
59
+ região interna quando o body de ponta a ponta for deliberado.
60
+ - `Content` passa a compartilhar o vocabulário de anatomia das demais superfícies, mas continua sem
61
+ footer e sem assumir o papel de uma superfície autônoma.
62
+ - A API rejeita `sectioned` sem `framed` e rejeita a combinação com `legend`.
63
+
64
+ ## Alternativas consideradas
65
+
66
+ ### Adicionar `frameVariant="sectioned"`
67
+
68
+ Foi descartado porque misturaria anatomia de regiões com o acabamento da linha externa. Card,
69
+ Dialog, Drawer e Pane já usam `layout` para essa decisão.
70
+
71
+ ### Criar propriedades de borda e padding
72
+
73
+ Propriedades como `headerDivider` e `bodyInset` permitiriam combinações arbitrárias e devolveriam a
74
+ anatomia ao consumidor. O resultado validado é um pacote, não dois controles independentes.
75
+
76
+ ### Criar DetailCard
77
+
78
+ Foi descartado porque duplicaria a semântica e os slots de `Content` para um único tipo de filho.
79
+ A anatomia também é válida para listas e tabelas.
80
+
81
+ ## Verificação
82
+
83
+ - Testes de UI cobrem shorthand, composição explícita, section e fieldset.
84
+ - Testes confirmam header com divisor e `p-3`, body sem padding e raiz com recorte.
85
+ - Testes confirmam `rounded-xl` nas molduras `default`, `legend` e `sectioned`.
86
+ - Testes reprovam `sectioned` sem moldura e a combinação com `frameVariant="legend"`.
87
+ - A documentação demonstra o caso com `DetailGroup` e diferencia anatomia de acabamento.
88
+ - O consumidor de Pós-vendas usa somente props públicas do Opus, sem classes que reconstruam a
89
+ anatomia.
@@ -0,0 +1,61 @@
1
+ # ADR 0031 — Divisórias de DetailGroup definem o tratamento tabular dos rótulos
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-28.
5
+
6
+ ## Contexto
7
+
8
+ `DetailGroup` separa três decisões: orientação dos pares, divisórias internas e moldura externa.
9
+ Apesar disso, o fundo sutil dos rótulos horizontais era ativado por `variant="framed"`. Assim, a
10
+ mesma grade com divisórias mudava de aparência ao ser hospedada por um `Content` seccionado, onde a
11
+ moldura pertence ao `Content`, e um grupo independente, onde a moldura pertence ao próprio
12
+ `DetailGroup`.
13
+
14
+ O fundo do rótulo é um acabamento da grade tabular: ele ajuda a distinguir chave e valor quando os
15
+ campos formam células conectadas. Não comunica que o grupo possui uma borda externa.
16
+
17
+ ## Decisão
18
+
19
+ Na orientação horizontal, `dividers` ativa a composição tabular completa de cada campo:
20
+
21
+ - rótulo e valor recebem padding próprio;
22
+ - a coluna de rótulos usa fundo sutil;
23
+ - a largura padrão dos rótulos passa a `8.5rem`;
24
+ - as divisórias continuam delimitando as células.
25
+
26
+ `variant="framed"` controla somente a moldura externa do grupo. Sem `dividers`, um grupo horizontal
27
+ emoldurado mantém rótulo e valor sobre o mesmo fundo. Na orientação vertical, `dividers` continua
28
+ adicionando somente as separações entre campos, sem pintar o rótulo.
29
+
30
+ ## Consequências
31
+
32
+ - Grades horizontais com divisórias têm a mesma aparência dentro de `Content layout="sectioned"`
33
+ e como bloco principal independente.
34
+ - Moldura externa, orientação e acabamento tabular permanecem decisões combináveis.
35
+ - Não é necessária uma nova prop apenas para reproduzir um enfeite que já acompanha a grade.
36
+ - Consumidores que usavam `framed + horizontal` sem divisórias deixam de receber fundo no rótulo.
37
+
38
+ ## Alternativas consideradas
39
+
40
+ ### Manter o fundo associado a `framed`
41
+
42
+ Preservaria o comportamento atual, mas continuaria fazendo a moldura externa alterar o interior da
43
+ grade e manteria inconsistentes os grupos hospedados por outra superfície.
44
+
45
+ ### Criar `labelVariant="muted"`
46
+
47
+ Tornaria o acabamento explícito, mas adicionaria uma decisão a todos os consumidores sem haver um
48
+ caso de produto que precise variar o fundo independentemente das divisórias.
49
+
50
+ ### Aplicar o fundo a toda orientação horizontal
51
+
52
+ Uniformizaria os rótulos, mas daria aparência tabular também a grupos simples, sem células ou
53
+ divisórias que sustentem essa leitura.
54
+
55
+ ## Verificação
56
+
57
+ - Testes de UI confirmam fundo e padding separados em `horizontal + dividers`, com e sem moldura.
58
+ - Testes de UI confirmam ausência de fundo em `horizontal + framed` sem divisórias.
59
+ - Testes de UI confirmam que divisórias verticais não pintam o rótulo.
60
+ - A documentação de `DetailGroup` descreve `framed`, `dividers` e `orientation` como eixos
61
+ independentes.
@@ -0,0 +1,59 @@
1
+ # ADR 0032 — A linha-base das abas respeita o recuo do contêiner
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-28.
5
+ - **Relacionada:** ADR 0023, quanto ao alinhamento horizontal dentro de superfícies.
6
+
7
+ ## Contexto
8
+
9
+ A variante horizontal `line` de `TabsList` estendia a linha-base para fora da própria lista usando
10
+ o recuo de `DrawerBody` e `DialogBody`. Os rótulos permaneciam alinhados ao conteúdo, mas a linha
11
+ alcançava as laterais da superfície e rompia o contorno visual formado pelo corpo.
12
+
13
+ A tela de detalhes do Evento tornou a divergência evidente: cards, detalhes e conteúdo respeitam o
14
+ mesmo recuo lateral, enquanto apenas a navegação por abas terminava nas bordas do drawer.
15
+
16
+ ## Decisão
17
+
18
+ A linha-base horizontal ocupa somente a largura de `TabsList`. Ela respeita o recuo aplicado pelo
19
+ contêiner e acompanha o alinhamento do corpo em qualquer superfície.
20
+
21
+ `DrawerBody` e `DialogBody` deixam de publicar uma variável de gutter exclusiva para Tabs. O recuo
22
+ continua pertencendo à anatomia da superfície, e Tabs apenas ocupa a largura que recebe no fluxo.
23
+
24
+ ### Emenda de 2026-09-28 — a aba combina uma área de botão e o indicador
25
+
26
+ Na variante horizontal `line`, o gatilho mantém duas camadas: uma área interna com os estados
27
+ interativos e a mesma altura de um Button do tamanho correspondente, e o indicador ancorado na
28
+ linha-base da lista. A área interna permanece transparente e recebe fundo apenas no hover; a aba
29
+ ativa se distingue pelo indicador primário. Um respiro de `0.375rem` separa as duas camadas no uso
30
+ comum; dentro de `Tabs framed`, ele aumenta para `0.5rem` e acompanha o padding superior da moldura.
31
+
32
+ Essa apresentação pertence ao Opus: consumidores escolhem `variant="line"` e não repetem classes
33
+ de fundo, raio, padding ou interação para obter o estado selecionado.
34
+
35
+ ## Consequências
36
+
37
+ - A linha e os rótulos das abas compartilham os limites horizontais do corpo.
38
+ - A seleção permanece indicada pela linha inferior, sem manter um segundo fundo persistente.
39
+ - Drawer e Dialog não mantêm uma integração implícita com Tabs.
40
+ - Um consumidor que realmente precise de uma linha até as bordas deve compor essa apresentação de
41
+ forma explícita, pois ela não é mais o comportamento padrão da variante `line`.
42
+
43
+ ## Alternativas descartadas
44
+
45
+ ### Manter a linha até as bordas
46
+
47
+ Preservaria o comportamento anterior, mas manteria uma exceção visual exclusiva das abas e uma
48
+ dependência oculta entre as primitivas de corpo e Tabs.
49
+
50
+ ### Adicionar uma propriedade de full bleed
51
+
52
+ Permitiria escolher os dois comportamentos, mas aumentaria a API para preservar uma apresentação
53
+ sem consumidor conhecido. A composição explícita é suficiente caso essa necessidade apareça.
54
+
55
+ ## Verificação
56
+
57
+ O teste da variante `line` verifica que a linha usa `inset-inline: 0`, inclusive quando Tabs está
58
+ dentro de `DrawerBody`. O mesmo teste verifica a separação estrutural entre a superfície interna e
59
+ o indicador da aba ativa. A documentação viva demonstra a variante com os limites do contêiner.
@@ -0,0 +1,72 @@
1
+ # ADR 0033 — Drawer usa uma moldura estrutural recuada
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-28.
5
+ - **Substitui:** a decisão da ADR 0005 que prendia a moldura externa do Drawer à borda da viewport.
6
+
7
+ ## Contexto
8
+
9
+ O `Drawer` ocupava toda a altura ou largura da viewport e desenhava somente a borda voltada para o
10
+ conteúdo. No consumidor, o shell principal já delimita as superfícies de trabalho com recuo de
11
+ `0.5rem`, borda completa, fundo base e raio `xl`. Ao abrir um Drawer sobre esse shell, o painel
12
+ voltava à geometria antiga de uma lâmina presa à janela: perdia os cantos, encostava em três bordas
13
+ e não parecia pertencer ao mesmo sistema de molduras.
14
+
15
+ O painel continua precisando comunicar de qual lado entra, preservar sua animação e aproveitar a
16
+ altura disponível. Ao mesmo tempo, a moldura não pode ser reconstruída por cada consumidor, porque
17
+ posição, recorte e animação pertencem à primitive.
18
+
19
+ ## Decisão
20
+
21
+ `DrawerContent` passa a ser uma superfície recuada da viewport em todos os lados:
22
+
23
+ - usa `0.25rem` de distância das bordas aplicáveis;
24
+ - usa borda completa, fundo base, `rounded-xl` e `overflow-hidden`;
25
+ - calcula a dimensão que antes ocupava `100%` descontando o recuo dos dois lados;
26
+ - mantém `side` responsável pela direção, dimensão principal e animação de entrada e saída;
27
+ - mantém largura e altura extensíveis por `className`, como antes.
28
+
29
+ O princípio durável é que uma superfície modal lateral possui moldura própria e não se confunde
30
+ com a viewport. O valor do recuo e o degrau de raio continuam materializações substituíveis da
31
+ escala visual vigente.
32
+
33
+ ## Consequências
34
+
35
+ - Drawer e shell passam a compartilhar a mesma leitura de moldura estrutural.
36
+ - Header, body e footer full-bleed são recortados pelos cantos do painel.
37
+ - A área útil do Drawer perde `0.5rem` no eixo que antes preenchia a viewport.
38
+ - Consumidores deixam de precisar adicionar borda, raio ou margem para obter essa composição.
39
+ - A mudança visual alcança `top`, `right`, `bottom` e `left`, evitando quatro geometrias distintas
40
+ para a mesma primitive.
41
+
42
+ ## Alternativas descartadas
43
+
44
+ ### Manter o Drawer preso à viewport
45
+
46
+ Preservaria a geometria histórica, mas manteria a divergência mais visível justamente quando o
47
+ painel aparece sobre um shell emoldurado.
48
+
49
+ ### Ajustar somente o Drawer do consumidor
50
+
51
+ Validaria a tela atual com menor alcance, mas espalharia posição e recorte por `className` e
52
+ deixaria outros Drawers fora do mesmo contrato visual.
53
+
54
+ ### Arredondar apenas os cantos expostos
55
+
56
+ Manteria o painel preso à borda de entrada, mas produziria uma moldura incompleta e continuaria sem
57
+ o respiro que distingue a superfície da viewport.
58
+
59
+ ### Alterar somente o lado direito
60
+
61
+ Resolveria o caso mais frequente, mas faria `side` trocar também o contrato visual da superfície,
62
+ sem uma razão de produto para essa diferença.
63
+
64
+ ## Verificação
65
+
66
+ - Testes de `DrawerContent` verificam borda completa, raio, recorte e recuo para os quatro valores
67
+ de `side`.
68
+ - Os testes existentes continuam cobrindo anatomia interna, fechamento e animação.
69
+ - A documentação do Drawer descreve a moldura recuada e continua demonstrando a extensão de
70
+ largura por `className`.
71
+ - Um teste do consumidor confirma que o Drawer de Evento usa o default do Opus, sem reconstruir a
72
+ moldura localmente.
@@ -0,0 +1,60 @@
1
+ # ADR 0034 — Tabs pode ter moldura sem criar uma superfície
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-28.
5
+ - **Relacionadas:** ADR 0023, quanto à anatomia das superfícies; ADR 0032, quanto à linha-base das abas.
6
+
7
+ ## Contexto
8
+
9
+ Uma composição em que a navegação por abas e seus painéis formam um único bloco precisa de borda,
10
+ raio e recorte compartilhados. Até aqui, consumidores obtinham essa forma envolvendo `Tabs` em um
11
+ `Card`.
12
+
13
+ O `Card`, porém, não é apenas uma moldura: ele aplica `bg-card text-card-foreground` e cria uma
14
+ superfície semântica própria. Em um `Drawer`, essa superfície pode se distinguir do
15
+ `bg-background`, especialmente no tema escuro, embora a intenção seja somente delimitar as abas
16
+ dentro da superfície já existente.
17
+
18
+ ## Decisão
19
+
20
+ `Tabs` aceita a propriedade booleana `framed`, desativada por padrão.
21
+
22
+ Quando ativa, `framed` aplica ao root a moldura estrutural canônica para esse bloco: borda, raio
23
+ `rounded-xl` e recorte do conteúdo. Ela não aplica token de fundo nem de foreground. O conjunto
24
+ herda a superfície do contêiner em que foi composto.
25
+
26
+ Essa propriedade não altera a apresentação de `TabsList`, a seleção, o tamanho nem a orientação. O
27
+ consumidor continua escolhendo esses comportamentos nas APIs existentes, inclusive
28
+ `TabsList variant="line"`.
29
+
30
+ ## Consequências
31
+
32
+ - Abas podem formar um bloco único sem serem promovidas a `Card`.
33
+ - Em Drawer, Dialog ou Pane, a moldura acompanha o fundo da superfície externa nos temas claro e
34
+ escuro.
35
+ - `Card` continua correto quando as abas realmente precisarem de uma superfície semântica própria.
36
+ - `framed` permanece independente de `TabsList variant`, que descreve apenas a apresentação da
37
+ lista.
38
+
39
+ ## Alternativas descartadas
40
+
41
+ ### Manter Card como wrapper obrigatório
42
+
43
+ Preservaria a API atual, mas continuaria acoplando uma necessidade estrutural ao token de superfície
44
+ do Card.
45
+
46
+ ### Aplicar classes no consumidor
47
+
48
+ Resolveria uma tela, mas repetiria borda, raio e clipping sem registrar o contrato visual da
49
+ composição.
50
+
51
+ ### Fazer toda Tabs ser emoldurada
52
+
53
+ Removeria a escolha, mas quebraria usos em que Tabs participa do fluxo de uma página ou toolbar sem
54
+ formar um bloco autônomo.
55
+
56
+ ## Verificação
57
+
58
+ Testes da primitiva verificam que o padrão permanece sem moldura e que `framed` aplica borda, raio e
59
+ recorte sem qualquer classe de fundo. O teste da view de Evento verifica que a composição usa o
60
+ root emoldurado diretamente, sem um `Card` intermediário.
@@ -0,0 +1,63 @@
1
+ # ADR 0035 — ButtonGroup separa moldura, relação e distribuição
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-28.
5
+ - **Relacionadas:** ADR 0015, quanto à escala dos controles; ADR 0017, quanto à distribuição igual explícita.
6
+
7
+ ## Contexto
8
+
9
+ Um conjunto de ações do Evento foi composto com `Card`, `ButtonGroup mode="spaced"`, separadores
10
+ manuais e `flex-1` repetido em cada botão. O resultado visual é um único grupo emoldurado, com
11
+ ações de mesma largura e divisores entre elas, mas o consumidor precisa reconstruir essas três
12
+ decisões em componentes e classes diferentes.
13
+
14
+ `ButtonGroup` já distingue a relação entre controles com `mode` e a ocupação da largura com
15
+ `distribution`. Tratar os divisores como uma propriedade booleana criaria combinações sem sentido,
16
+ como controles conectados e divididos ao mesmo tempo. Usar `Card` apenas como borda também cria uma
17
+ superfície semântica e um fundo que não pertencem ao grupo de ações.
18
+
19
+ ## Decisão
20
+
21
+ `ButtonGroup` passa a aceitar:
22
+
23
+ - `framed`, booleano independente que aplica borda, raio estrutural e padding sem definir fundo;
24
+ - `mode="divided"`, além de `connected` e `spaced`, para preservar a forma individual dos
25
+ controles e inserir automaticamente um divisor com respiro entre filhos adjacentes;
26
+ - `distribution="equal"`, já existente, para dividir o espaço disponível somente entre os
27
+ controles, sem incluir os divisores no cálculo.
28
+
29
+ O modo `divided` deriva a orientação dos separadores da orientação do grupo. Em grupos horizontais,
30
+ os divisores são verticais; em grupos verticais, são horizontais. `ButtonGroupSeparator` continua
31
+ público para composições conectadas ou divisões seletivas.
32
+
33
+ ## Consequências
34
+
35
+ - Consumidores expressam o conjunto em uma única raiz: `<ButtonGroup framed mode="divided"
36
+ distribution="equal">`.
37
+ - A moldura herda a superfície externa e não substitui `Card` quando uma superfície semântica for
38
+ realmente necessária.
39
+ - Os botões não repetem classes de largura, e separadores uniformes não são montados manualmente.
40
+ - `mode` continua sendo o único eixo que descreve a relação visual entre controles.
41
+
42
+ ## Alternativas descartadas
43
+
44
+ ### Manter Card e separadores explícitos
45
+
46
+ Preservaria a API, mas manteria no consumidor uma composição repetitiva que representa uma única
47
+ decisão visual.
48
+
49
+ ### Adicionar `dividers` como booleano
50
+
51
+ Permitiria ativar os traços, mas faria dois mecanismos independentes disputarem a relação entre os
52
+ controles e aceitaria combinações contraditórias.
53
+
54
+ ### Criar uma variante visual única
55
+
56
+ Uma variante como `variant="framed-divided-equal"` reduziria a chamada, mas acoplaria três eixos
57
+ que têm usos independentes e impediria novas combinações legítimas.
58
+
59
+ ## Verificação
60
+
61
+ Testes da primitiva verificam moldura sem fundo, divisores automáticos nas duas orientações e
62
+ distribuição igual restrita aos controles. O teste da view de Evento verifica que o grupo usa a API
63
+ canônica sem `Card`, `ButtonGroupSeparator` ou classes de distribuição nos filhos.
@@ -0,0 +1,58 @@
1
+ # ADR 0036 — Tabs declara a distribuição dos gatilhos
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-28.
5
+ - **Relacionadas:** ADR 0032, quanto à composição da variante `line`; ADR 0034, quanto à moldura de Tabs; ADR 0035, quanto ao eixo de distribuição em grupos de controles.
6
+
7
+ ## Contexto
8
+
9
+ Uma lista de abas pode abraçar a largura dos rótulos ou repartir a largura disponível igualmente
10
+ entre os gatilhos. A view de Evento usa quatro abas no cabeçalho de um bloco emoldurado; larguras
11
+ derivadas do texto deixam esse cabeçalho visualmente desequilibrado, embora todas as seções tenham o
12
+ mesmo nível.
13
+
14
+ Hoje, a variante horizontal `line` força cada gatilho a usar a largura do próprio conteúdo. Um
15
+ consumidor que precise de larguras iguais teria de sobrescrever o `flex` interno ou reconstruir a
16
+ lista com grid, duplicando uma decisão que já existe em `ButtonGroup` como
17
+ `distribution="content" | "equal"`.
18
+
19
+ ## Decisão
20
+
21
+ `Tabs` aceita `distribution="content" | "equal"`, com `content` como padrão. A propriedade fica no
22
+ root porque descreve o conjunto de abas e, como `size` e `orientation`, flui por contexto até a
23
+ lista e os gatilhos.
24
+
25
+ Na orientação horizontal, `equal` faz a lista ocupar a largura disponível e atribui a cada gatilho
26
+ a mesma fração flexível, com largura mínima zero. A área interativa interna ocupa toda essa fração,
27
+ incluindo o fundo de hover. `content` preserva o comportamento atual. A distribuição não altera a
28
+ largura nem o layout dos painéis de conteúdo.
29
+
30
+ ## Consequências
31
+
32
+ - Consumidores escolhem a mesma linguagem de distribuição usada por `ButtonGroup`.
33
+ - `TabsList variant="line"` continua responsável pela linha e pelos estados visuais; não recebe
34
+ classes de layout no consumidor.
35
+ - A view de Evento declara `distribution="equal"` no `Tabs` sem alterar cada `TabsTrigger`.
36
+ - Usos existentes preservam a distribuição pelo conteúdo.
37
+
38
+ ## Alternativas descartadas
39
+
40
+ ### Declarar a propriedade em TabsList
41
+
42
+ É a parte que organiza os gatilhos, mas faria a distribuição escapar do mesmo contrato de conjunto
43
+ em que `Tabs` já declara tamanho e orientação.
44
+
45
+ ### Usar grid e número de colunas no consumidor
46
+
47
+ Exigiria conhecer a quantidade de abas e repetiria classes de layout em cada tela.
48
+
49
+ ### Tornar larguras iguais o padrão
50
+
51
+ Atenderia a view de Evento, mas esticaria navegações curtas e barras que deliberadamente abraçam os
52
+ rótulos.
53
+
54
+ ## Verificação
55
+
56
+ Testes da primitiva verificam o default `content`, a largura total da lista e a fração igual dos
57
+ gatilhos quando `equal` está ativo. O teste da view de Evento verifica a propriedade no root e a
58
+ distribuição dos quatro gatilhos sem classes locais equivalentes.
@@ -2,6 +2,9 @@
2
2
 
3
3
  > **Status: decidido (ago/2026).** A forma usa uma escala contínua do Tailwind; o nome do
4
4
  > componente não cria um segundo conjunto de tokens.
5
+ >
6
+ > **Atualização (2026-09-28).** A ADR 0033 conclui a validação que este documento deixou pendente e
7
+ > aplica `rounded-xl` ao Drawer junto da sua nova moldura recuada.
5
8
 
6
9
  ## Contexto
7
10
 
@@ -42,8 +45,8 @@ voltaria a acoplar forma e tipo de componente, problema removido na versão 10.0
42
45
  `--radius` alcançar também os extremos usados pelo Opus. `rounded-3xl` continua nativo do
43
46
  Tailwind; aplicações que usam esse degrau precisam revisar a fronteira ao alterar a base.
44
47
  - Novos componentes podem escolher uma utility da escala sem inventar vocabulário próprio.
45
- - Drawer e outras superfícies presas à viewport não mudam neste entregável. Arredondar somente os
46
- cantos expostos exige validar clipping, scroll e conteúdo full-bleed em uma mudança própria.
48
+ - Naquele entregável, Drawer e outras superfícies presas à viewport não mudaram. A ADR 0033 fez a
49
+ validação própria de clipping, scroll e conteúdo full-bleed e passou o Drawer para `rounded-xl`.
47
50
 
48
51
  ## Alternativas descartadas
49
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "21.0.1",
3
+ "version": "22.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",
@@ -43,18 +43,21 @@ existentes, mantendo navegação observável e componentes reutilizáveis sem la
43
43
  7. Tratar página, filtro, seleção e modal importante como estado navegável por URL quando
44
44
  o produto precisa de deep link, back/forward ou refresh. Em `Presentation`, declarar `route`
45
45
  no artefato e deixar o adaptador apenas conectar a spec ao router da aplicação.
46
- 8. Compor superfícies pela gramática estrutural do catálogo: `PageHeader` declara o chrome,
47
- `PageIntro` a introdução opcional e `PageBody` o conteúdo. Na composição explícita, declare
48
- exatamente um heading principal com `PageTitle` no header/intro, `Content level={1}` ou um
49
- `PageState` ativo no body;
50
- dentro de `PageShell`, os slots de
51
- `PageHeader` são projetados na barra. `Content` contém `ContentHeader` e `ContentBody`; Card,
46
+ 8. Compor superfícies pela gramática estrutural do catálogo: `PageHeader` é a única introdução
47
+ estrutural e reúne navegação contextual opcional, título e ações; `PageBody` contém o conteúdo.
48
+ Na composição explícita, declare exatamente um heading principal com `PageTitle` no header,
49
+ `Content level={1}` ou um `PageState` ativo no body. `PageShell` pode fornecer navegação e ações
50
+ conhecidas pela aplicação, mas não cria uma barra visual. `Content` contém `ContentHeader` e `ContentBody`; Card,
52
51
  Drawer e Pane usam seus
53
52
  respectivos `*Body`. Para o caso direto, usar a sintaxe abreviada de `Page` (`title`,
54
53
  `actions`) ou de `Content` (`title`, `description`, `actions` e `count`); não misturá-la com o
55
54
  header explícito. O título da página não carrega contador. Ajustar o
56
55
  nível do heading pela hierarquia semântica, não pelo destaque visual. O `Page` mantém seu teto
57
56
  centralizado padrão de `80rem`.
57
+ Em `Content framed`, usar `layout="sectioned"` quando o filho direto já controlar o recuo do
58
+ body, como `DetailGroup` com divisores, lista ou tabela; não reconstruir divisor, padding ou
59
+ recorte por classes no consumidor. `frameVariant="legend"` continua sendo um acabamento da
60
+ borda contínua e não combina com o layout seccionado.
58
61
  9. Manter margem e posicionamento no consumidor; componente reutilizável controla apenas
59
62
  seu interior.
60
63
  10. Não definir a fonte raiz em uma biblioteca ou componente. O navegador e a aplicação são
@@ -4,8 +4,8 @@
4
4
  - Não dispara: “Ajuste o CSS de um e-mail estático.”
5
5
  - Execução: implementar uma lista com modal roteável e provar loading, erro, vazio e back.
6
6
  - Execução estrutural: montar uma página de relatório isolada com `Page > PageHeader + PageBody`,
7
- outra em `PageShell` com `Page > PageHeader? + PageIntro? + PageBody`, garantindo exatamente um
8
- `PageTitle` entre header/intro, e uma coleção nomeada com
7
+ outra em `PageShell` com navegação contextual acima de `Page > PageHeader + PageBody`, garantindo
8
+ exatamente um `PageTitle` no header, e uma coleção nomeada com
9
9
  `Content > ContentHeader + ContentBody > ActionList`. Provar que criação fica em
10
10
  `ContentActions`, no extremo oposto ao título, como botão textual `default` sem ícone e com
11
11
  `Criar recurso`, enquanto uma operação dependente do recorte usa `toolbarActions`; incluir
@@ -22,6 +22,9 @@
22
22
  rejeita a mistura entre props abreviadas e headers explícitos.
23
23
  - Execução de forma: compor uma tabela dentro de Card sem moldura duplicada, manter a moldura
24
24
  estrutural standalone em `rounded-lg` e os controles internos em `rounded-md`.
25
+ - Execução de Content seccionado: compor `Content framed layout="sectioned"` com mídia e
26
+ `DetailGroup` com divisores; provar header com divisor, body sem recuo e ausência de classes no
27
+ consumidor que reconstruam essa anatomia. Reprovar a combinação com `frameVariant="legend"`.
25
28
  - Reprova: criar uma casca `bg-card` que herda o texto global ou introduzir `rounded-widget`
26
29
  quando a escala `rounded-*` atende ao papel.
27
30
  - Reprova: trocar automaticamente o radius de um controle quando apenas seu tamanho muda ou
@@ -8,25 +8,21 @@
8
8
  parâmetro podem declarar `route.record` para hidratar o item a partir de uma Presentation de
9
9
  lista; não reconstruir essa decisão com condicionais no componente da página.
10
10
  - `Page` fornece o `<main>` e o container centralizado com teto padrão de `80rem`. Sua forma
11
- explícita exige `PageBody`, exatamente um heading principal e aceita `PageHeader`, `PageIntro` e
12
- `PageFooter`. Declare esse heading com `PageTitle` em header/intro, `Content level={1}` ou um
13
- `PageState` ativo no body. `PageHeader`
14
- declara chrome; `PageIntro` declara uma introdução opcional no conteúdo. `title` e `actions` no
11
+ explícita exige `PageBody`, exatamente um heading principal e aceita `PageHeader` e `PageFooter`.
12
+ Declare esse heading com `PageTitle` no header, `Content level={1}` ou um `PageState` ativo no
13
+ body. `PageHeader` é a única introdução estrutural e reúne navegação, título e ações. `title` e `actions` no
15
14
  próprio `Page` são a abreviação para o caso direto.
16
15
  Não misturar as duas formas. Alterar `className` apenas quando a superfície tiver uma necessidade
17
16
  real de largura; não reconstruir esse container em cada rota.
18
- - `PageIntro` é a região estrutural de uma introdução no conteúdo. Pode coexistir com
19
- `PageHeader`, porque as duas regiões têm papéis diferentes.
20
- - `PageHeader` declara navegação e ações globais; fora do shell também pode carregar o título. Em
21
- uma subpágina simples, `PageBack` recebe o destino pai explícito e aparece
22
- antes do título como controle somente com ícone. Para mais de um ancestral relevante, use
17
+ - `PageHeader` declara navegação contextual, título e ações. Em uma subpágina simples, `PageBack`
18
+ recebe o destino pai explícito e aparece acima da linha do título como controle somente com
19
+ ícone. Para mais de um ancestral relevante, use
23
20
  `Breadcrumb` dentro de `PageNavigation`. Não combine retorno e breadcrumb nem crie um chrome
24
21
  paralelo para uma `Page`. `PageActionsTarget` fica reservado a workspaces imersivos que já
25
22
  possuam chrome próprio.
26
23
  - Quando shell e rota conhecem partes diferentes da mesma página, use `PageShell` ao redor da rota.
27
- O shell é o único responsável pela barra e projeta os slots de `PageHeader`; `PageIntro` permanece
28
- no conteúdo.
29
- Ações com texto na barra usam o tamanho `default`, como as ações de footer de Dialog e Drawer;
24
+ O shell entrega navegação e ações ao `PageHeader` descendente sem criar uma barra visual.
25
+ Ações com texto no cabeçalho usam o tamanho `default`, como as ações de footer de Dialog e Drawer;
30
26
  ações somente com ícone de chrome usam `icon`. O tamanho `sm` fica para ações operacionais em
31
27
  toolbar, seção ou coleção densa, e `xs`/`icon-xs` para ações internas de linha ou célula. Contexto
32
28
  e variante resolvem a hierarquia visual sem alterar essa medida. Em uma coleção, a ação de
@@ -34,9 +30,8 @@
34
30
  `PaneHeader`, portal ou seletor global para reconstruir essa composição.
35
31
  - `PageState` substitui todo o conteúdo principal quando a página carrega, falha ou está vazia. Na
36
32
  forma curta ele pode ser escrito como filho direto de `Page`, que cria o `PageBody`; na forma
37
- explícita, fica sozinho dentro de `PageBody`. Nos três estados ativos, o cabeçalho da Page isolada
38
- some inteiro — incluindo o `PageBack`; dentro de `PageShell`, `PageIntro` some e a barra
39
- permanece. O estado ocupa a área disponível e seu título assume o heading
33
+ explícita, fica sozinho dentro de `PageBody`. Nos três estados ativos, o cabeçalho da Page
34
+ some inteiro — incluindo navegação e ações recebidas de `PageShell`. O estado ocupa a área disponível e seu título assume o heading
40
35
  principal, inclusive quando um componente intermediário renderiza o estado. Uma subpágina que
41
36
  dependa do retorno ao pai oferece essa saída pelo `action` do próprio `PageState`. O erro mantém
42
37
  `role="alert"`, usa a mesma composição central e sem moldura dos demais estados e apresenta a
@@ -48,7 +43,10 @@ ContentDescription, ContentMeta, ContentActions) + ContentBody`. `ContentHeader`
48
43
  direto e não podem ser misturados ao header explícito. Na variante `page`, ações da coleção ficam
49
44
  no extremo oposto ao título; a criação usa botão textual `default`, sem ícone, com `Criar recurso`.
50
45
  O diálogo de criação usa `Criar recurso` e o de edição, `Editar recurso`. `level` preserva a
51
- hierarquia semântica do heading.
46
+ hierarquia semântica do heading. `Content framed` aceita `layout="sectioned"` quando o filho
47
+ direto já possui recuo interno, como `DetailGroup` com divisores, lista ou tabela: o header recebe
48
+ divisor e o body fica sem padding. `frameVariant="legend"` trata a borda contínua e não combina
49
+ com esse layout.
52
50
  - Uma página centrada em coleção compõe `Content + ActionList`. O `ContentHeader` recebe título e
53
51
  criação; o `ActionList` mantém busca, filtros, atualização, resultados, estados e paginação.
54
52
  `toolbarActions` fica reservado a operações que dependem do recorte atual.