@softize/opus 12.1.0 → 12.2.1

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ 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
+ ## 12.2.1 — 2026-08-23
11
+
12
+ O cabeçalho de `Page` passa a alinhar `actions` à base do bloco formado por título e descrição.
13
+ Assim, os botões permanecem junto ao fim do contexto quando a página apresenta uma linha
14
+ explicativa. Múltiplas ações conservam o espaçamento entre si. A API pública não muda.
15
+
16
+ ## 12.2.0 — 2026-08-23
17
+
18
+ A escala de arredondamento ganha uma base mais presente sem transformar todo componente em uma
19
+ superfície principal. `--radius` passa de `0.625rem` para `0.75rem`; os degraus de
20
+ `rounded-xs` a `rounded-2xl` derivam dessa base e permanecem crescentes. Cards e modais continuam
21
+ em `rounded-xl`, enquanto controles compactos e molduras estruturais preservam seus degraus.
22
+
23
+ A mudança é visual e não exige migração de código. Aplicações que já sobrescrevem `--radius`
24
+ mantêm sua própria identidade; as demais recebem automaticamente a nova escala. A documentação e
25
+ a skill de UI explicam aninhamento, grupos conectados e a separação entre tamanho e forma.
26
+
10
27
  ## 12.1.0 — 2026-08-22
11
28
 
12
29
  Interfaces que combinam copy fixa com nomes, contagens ou opções vindas da aplicação não
@@ -0,0 +1,66 @@
1
+ # Escala de arredondamento
2
+
3
+ > **Status: decidido (ago/2026).** A forma usa uma escala contínua do Tailwind; o nome do
4
+ > componente não cria um segundo conjunto de tokens.
5
+
6
+ ## Contexto
7
+
8
+ Cards e modais precisam ter presença suficiente para organizar a página sem parecerem caixas
9
+ rígidas. Controles, itens internos e superfícies compactas precisam continuar menores para que a
10
+ interface preserve hierarquia. A base anterior de `0.625rem` deixava o primeiro grupo adequado,
11
+ mas mantinha os demais degraus visualmente mais retos do que a identidade desejada.
12
+
13
+ Alterar cada componente isoladamente resolveria exemplos visíveis, mas deixaria consumidores e
14
+ novos componentes sem uma referência comum. Reintroduzir tokens como `--radius-card` também
15
+ voltaria a acoplar forma e tipo de componente, problema removido na versão 10.0.0.
16
+
17
+ ## Decisão
18
+
19
+ - `--radius` passa a `0.75rem`. Aplicações podem sobrescrever essa base para ajustar a identidade
20
+ inteira sem forkar componentes.
21
+ - `rounded-xs` até `rounded-2xl` formam uma progressão derivada da mesma base. `rounded-full`
22
+ continua reservado a círculos e variantes explicitamente pill.
23
+ - Componentes escolhem o degrau pela escala visual. Em geral, detalhes e itens internos usam
24
+ `xs` ou `sm`; controles e superfícies compactas usam `md`; molduras estruturais usam `lg`; e
25
+ superfícies principais ou modais usam `xl`. Essa associação orienta o default, mas não cria um
26
+ token semântico por componente.
27
+ - Superfícies aninhadas evitam moldura dupla e normalmente usam um degrau menor que o contêiner.
28
+ Elementos conectados removem os raios nas arestas internas.
29
+ - Card, Dialog e AlertDialog permanecem em `xl`. Molduras estruturais com cantos, como a tabela
30
+ standalone, permanecem em `lg`; Page, Split e Sidebar não ganham uma moldura por essa regra.
31
+ - Calendar e Command preservam a geometria embutível. Um consumidor standalone pode ajustar o
32
+ contêiner por `className` quando a composição pedir mais presença.
33
+ - Tamanho e forma continuam eixos separados nos controles. Variantes `sm`, `default` e `lg` não
34
+ mudam o radius automaticamente; `shape="pill"` continua sendo a escolha explícita para pílulas.
35
+
36
+ ## Consequências
37
+
38
+ - A mudança da base afeta todos os consumidores que não sobrescrevem `--radius`. O efeito é
39
+ visual e intencional; nenhuma migração de código é necessária.
40
+ - A progressão derivada evita que `rounded-xl` ultrapasse `rounded-2xl` e faz a customização por
41
+ `--radius` alcançar também os extremos usados pelo Opus. `rounded-3xl` continua nativo do
42
+ Tailwind; aplicações que usam esse degrau precisam revisar a fronteira ao alterar a base.
43
+ - Novos componentes podem escolher uma utility da escala sem inventar vocabulário próprio.
44
+ - Drawer e outras superfícies presas à viewport não mudam neste entregável. Arredondar somente os
45
+ cantos expostos exige validar clipping, scroll e conteúdo full-bleed em uma mudança própria.
46
+
47
+ ## Alternativas descartadas
48
+
49
+ - **Aplicar `rounded-xl` a toda superfície:** aproxima os componentes do Card, mas elimina a
50
+ diferença entre controles compactos, molduras estruturais e superfícies principais.
51
+ - **Editar cada componente sem mudar a base:** oferece controle pontual, mas espalha a decisão e
52
+ não beneficia consumidores que usam diretamente a escala do Tailwind.
53
+ - **Restaurar tokens por papel:** fixa defaults, mas volta a criar uma taxonomia paralela para a
54
+ mesma propriedade.
55
+ - **Escalar radius junto com o tamanho de cada controle:** parece proporcional isoladamente, mas
56
+ quebra grupos conectados e mistura o eixo de forma com o eixo de densidade.
57
+
58
+ ## Verificação
59
+
60
+ - Um teste de contrato lê o tema canônico e confirma a base, as fórmulas e a ordem crescente de
61
+ `rounded-xs` até `rounded-2xl`.
62
+ - A documentação de tokens demonstra a escala completa e explica como uma aplicação altera a
63
+ identidade pelo token base.
64
+ - A skill `build-opus-ui` orienta o uso da escala, o aninhamento e as junções sem prescrever tokens
65
+ por componente.
66
+ - Os testes e o build da UI confirmam que as utilities continuam materializadas pelo Tailwind.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "12.1.0",
3
+ "version": "12.2.1",
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",
@@ -24,7 +24,12 @@ existentes, mantendo navegação observável e componentes reutilizáveis sem la
24
24
  `bg-card text-card-foreground` e `bg-popover text-popover-foreground`. Não depender da
25
25
  igualdade atual com `--foreground`, porque o app pode sobrescrever cada par.
26
26
  7. Usar diretamente a escala `rounded-*`; não criar radius por nome de componente quando
27
- `rounded-sm` a `rounded-xl` já expressam a forma.
27
+ `rounded-xs` a `rounded-2xl` já expressam a forma. Escolher o degrau pela escala visual:
28
+ detalhes e itens internos tendem a `xs`/`sm`, controles e flutuantes compactos a `md`,
29
+ molduras estruturais a `lg`, e superfícies principais ou modais a `xl`. Essa associação
30
+ orienta o default, não cria uma restrição semântica. Em aninhamento, evitar moldura dupla e
31
+ reduzir o raio interno; em grupos conectados, remover os raios das arestas internas. Tamanho
32
+ e forma permanecem eixos separados; usar `shape="pill"` quando a pílula for intencional.
28
33
  8. Testar estados de loading, vazio, erro, sucesso, permissão e interação relevante.
29
34
 
30
35
  ## Verificação
@@ -3,5 +3,9 @@
3
3
  - Dispara: “Monte a tela de edição usando a form action do Opus.”
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
+ - Execução de forma: compor uma tabela dentro de Card sem moldura duplicada, manter a moldura
7
+ estrutural standalone em `rounded-lg` e os controles internos em `rounded-md`.
6
8
  - Reprova: criar uma casca `bg-card` que herda o texto global ou introduzir `rounded-widget`
7
9
  quando a escala `rounded-*` atende ao papel.
10
+ - Reprova: trocar automaticamente o radius de um controle quando apenas seu tamanho muda ou
11
+ manter cantos arredondados nas arestas internas de um grupo conectado.
@@ -8,4 +8,10 @@
8
8
  - Superfície semântica e foreground são um par local (`bg-card text-card-foreground`,
9
9
  `bg-popover text-popover-foreground`); herança do foreground global não substitui o par.
10
10
  - Forma usa a escala `rounded-*`; nome de componente não cria uma segunda escala de radius.
11
+ Detalhes e itens internos tendem a `xs`/`sm`, controles e flutuantes compactos a `md`,
12
+ molduras estruturais a `lg`, e superfícies principais ou modais a `xl`. Essa associação é
13
+ um default visual, não uma restrição semântica.
14
+ - Superfície aninhada evita moldura dupla e normalmente usa um degrau abaixo do contêiner.
15
+ Grupos conectados removem os raios internos. O tamanho do controle não muda sua forma;
16
+ `shape="pill"` declara a pílula quando ela for intencional.
11
17
  - Catálogo e API efetivos vêm dos exports da versão instalada, não de memória ou exemplo antigo.
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Padroniza o que toda página repete: <main> + container de largura CHEIA (o caso
5
5
  * comum do back-office; quem quiser estreito passa `className="max-w-5xl"`) + header
6
- * (título, descrição e a ação à direita — em geral o botão de criar). O conteúdo é
6
+ * (título, descrição e ações à direita, alinhadas ao fim desse bloco — em geral o botão
7
+ * de criar). O conteúdo é
7
8
  * children, sem nada imposto. Presentacional (sem contrato) de propósito: uma página
8
9
  * agrega N fontes; o que é action-driven mora dentro (ActionList, listas query-backed).
9
10
  */
@@ -17,7 +18,7 @@ export interface PageProps {
17
18
  count?: number
18
19
  /** Linha de contexto sob o título. */
19
20
  description?: ReactNode
20
- /** Ação à direita do header em geral o botão de criar. */
21
+ /** Ações à direita do header, alinhadas ao fim do bloco de contexto. */
21
22
  actions?: ReactNode
22
23
  /** Classes do container. Default: largura cheia; `max-w-5xl` estreita e centra. */
23
24
  className?: string
@@ -28,16 +29,22 @@ export function Page({ title, count, description, actions, className, children }
28
29
  return (
29
30
  <main data-slot="page" className="min-w-0 flex-1">
30
31
  <div className={cn('mx-auto px-8 py-8', className)}>
31
- {/* As ações alinham com o TÍTULO; a descrição vem abaixo, em linha própria. */}
32
+ {/* As ações acompanham o fim do contexto, mesmo quando uma descrição sob o título. */}
32
33
  <div data-slot="page-header" className="mb-6">
33
- <div className={cn(actions !== undefined && 'flex items-center justify-between gap-4')}>
34
- <h1 className="flex items-baseline gap-2 text-2xl font-semibold tracking-tight">
35
- {title}
36
- {count !== undefined && <span className="font-mono text-base text-muted-foreground/60">{count}</span>}
37
- </h1>
38
- {actions}
34
+ <div className={cn(actions !== undefined && 'flex items-end justify-between gap-4')}>
35
+ <div data-slot="page-heading" className="min-w-0">
36
+ <h1 className="flex items-baseline gap-2 text-2xl font-semibold tracking-tight">
37
+ {title}
38
+ {count !== undefined && <span className="font-mono text-base text-muted-foreground/60">{count}</span>}
39
+ </h1>
40
+ {description !== undefined && <p className="mt-1 text-sm text-muted-foreground">{description}</p>}
41
+ </div>
42
+ {actions !== undefined && (
43
+ <div data-slot="page-actions" className="flex shrink-0 items-center gap-4">
44
+ {actions}
45
+ </div>
46
+ )}
39
47
  </div>
40
- {description !== undefined && <p className="mt-1 text-sm text-muted-foreground">{description}</p>}
41
48
  </div>
42
49
  {children}
43
50
  </div>
@@ -21,10 +21,13 @@ divergência declarada), pra valer pra casa toda.
21
21
  :root {
22
22
  --primary: hsl(262 83% 58%); /* A cor da marca do projeto. */
23
23
  --ring: hsl(262 83% 58%);
24
- --radius: 0.5rem; /* Cantos mais retos, se for a praia do projeto. */
24
+ --radius: 0.5rem; /* Uma base menor deixa toda a escala mais contida. */
25
25
  }
26
26
  ```
27
27
 
28
+ O Opus deriva de `rounded-xs` a `rounded-2xl` dessa base. Assim, a marca muda a presença dos
29
+ cantos sem substituir as utilities escolhidas pelos componentes nem criar tokens por papel.
30
+
28
31
  ## 2 · className em tudo
29
32
 
30
33
  > Todo componente termina em `cn(base, className)` com tailwind-merge: o utilitário do consumidor
@@ -1,6 +1,10 @@
1
1
  ## Esqueleto de página
2
2
 
3
- O que toda página do back-office repete, num pattern só: `<main>` + container de largura cheia (o caso comum; `className="max-w-5xl"` estreita e centra) + header com título, descrição e a ação à direita. O conteúdo é children, sem nada imposto. Presentacional de propósito — uma página agrega N fontes; o que é action-driven (listas, forms) mora dentro.
3
+ Use `Page` para manter título, contexto, ações e conteúdo no mesmo ritmo visual nas telas do
4
+ back-office. As ações ficam à direita e acompanham a base do conjunto formado pelo título e pela
5
+ descrição. O componente ocupa a largura disponível; passe `className="max-w-5xl"` quando a tela
6
+ pedir conteúdo mais estreito e centralizado. A área abaixo do cabeçalho permanece livre para
7
+ tabelas, cards ou outras composições.
4
8
 
5
9
  ```tsx preview col
6
10
  render(
@@ -8,6 +12,7 @@ render(
8
12
  <Page
9
13
  title="Workspaces"
10
14
  count={3}
15
+ description="Ambientes compartilhados pela equipe."
11
16
  actions={
12
17
  <Button>
13
18
  <Plus /> Novo workspace
@@ -29,6 +34,6 @@ render(
29
34
  | `title` | `string` | | O h1 da página. |
30
35
  | `count` | `number` | | Total de itens ao lado do título (mono, esmaecido). |
31
36
  | `description` | `ReactNode` | | Linha de contexto sob o título (ex.: "N no total · X ativos"). |
32
- | `actions` | `ReactNode` | | Ação à direita do header em geral o botão de criar. |
37
+ | `actions` | `ReactNode` | | Ações à direita do cabeçalho, alinhadas à base do título e da descrição. |
33
38
  | `className` | `string` | largura cheia | Classes do container (`max-w-5xl` estreita e centra). |
34
39
  | `children` | `ReactNode` | | O conteúdo — espaçamento e diagramação são seus. |
@@ -109,19 +109,21 @@ render(
109
109
 
110
110
  ## Radius
111
111
 
112
- > Escala mapeada no `@theme inline` preservando o sentido pré-v4 dos utilitários `rounded-*`
113
- > (neutraliza o rename do Tailwind v4). Componentes e superfícies usam diretamente essa
114
- > escala: a forma não cria uma segunda taxonomia por componente. A identidade por app
115
- > sobrescreve `--radius` e escala tudo junto.
112
+ > A escala mapeada no `@theme inline` preserva o sentido pré-v4 dos utilitários `rounded-*`
113
+ > e mantém uma progressão contínua de `xs` a `2xl`. Componentes e superfícies escolhem um
114
+ > degrau pela escala visual; a forma não cria uma segunda taxonomia por componente. A
115
+ > identidade por app sobrescreve `--radius` e move esses degraus em conjunto.
116
116
 
117
117
  ```tsx preview
118
118
  render(
119
119
  <div className="flex flex-wrap gap-4">
120
120
  {[
121
+ ['rounded-xs', 'radius − 6px'],
121
122
  ['rounded-sm', 'radius − 4px'],
122
123
  ['rounded-md', 'radius − 2px'],
123
- ['rounded-lg', 'radius (0.625rem)'],
124
+ ['rounded-lg', 'radius (0.75rem)'],
124
125
  ['rounded-xl', 'radius + 4px'],
126
+ ['rounded-2xl', 'radius + 8px'],
125
127
  ].map(([cls, calc]) => (
126
128
  <div key={cls} className="flex items-center gap-3">
127
129
  <div className={'h-12 w-12 border-2 border-foreground/30 bg-muted ' + cls} />
@@ -144,7 +146,9 @@ render(
144
146
  valores acompanham o upstream, só o formato difere.
145
147
  2. Elevação no dark: `--card`/`--popover` ficam ACIMA de `--background` (10% vs 3.9%) — identidade
146
148
  da casa; dialog e card "sobem" da página de verdade.
147
- 3. Radius mapeado (sm/md/lg/xl) preservando o sentido pré-v4 dos utilitários `rounded-*`.
149
+ 3. Radius mapeado (`xs` a `2xl`) preservando o sentido pré-v4 dos utilitários `rounded-*`. Esse
150
+ trecho derivado permanece monotônico quando a base muda; aplicações que também usam `3xl`
151
+ precisam revisar a transição para esse degrau nativo ao customizar `--radius`.
148
152
 
149
153
  4. Escala `shadow-*` recomposta em duas camadas claras e difusas — preserva os degraus do
150
154
  Tailwind e troca somente o peso visual. Os aliases antigos por papel foram removidos;
@@ -153,6 +157,11 @@ render(
153
157
  5. Bordas mais leves no light: `--border`/`--input` em 92.5% (upstream 89.8%) — par do canvas
154
158
  quase-branco do body; superfícies elevadas usam essa mesma borda real.
155
159
 
160
+ 6. Radius base em `0.75rem` — superfícies principais ganham cantos mais presentes sem aproximar
161
+ controles compactos de modais. A decisão detalhada acompanha o pacote em
162
+ `docs/radius-scale.md`, com os defaults, o comportamento de superfícies aninhadas e as
163
+ alternativas descartadas.
164
+
156
165
  ## Identidade por app
157
166
 
158
167
  > O tema do Opus é a base; cada app sobrescreve as vars pra ter a própria identidade sem forkar componente.
package/src/ui/theme.css CHANGED
@@ -10,13 +10,15 @@
10
10
  * nossas; os VALORES acompanham o upstream, só o formato difere.
11
11
  * 2. Elevação dark: --card/--popover ACIMA de --background (10% vs 3.9%) — identidade
12
12
  * da casa (upstream v4 também elevou; calibramos pelo site deles em 2026-06).
13
- * 3. Radius mapeado (sm/md/lg/xl em @theme inline) preservando o sentido pré-v4 dos
14
- * utilitários `rounded-*` — neutraliza o rename do v4.
13
+ * 3. Radius mapeado (xs a 2xl em @theme inline) preservando o sentido pré-v4 dos
14
+ * utilitários `rounded-*` — neutraliza o rename do v4 e mantém a escala monotônica.
15
15
  * 4. Escala shadow-* recomposta diretamente em duas camadas claras, difusas e com spread
16
16
  * negativo — preserva inclusive modifiers como `shadow-lg/30` (ver docs/elevation-scale.md).
17
17
  * 5. Bordas mais leves no light: --border/--input em 92.5% (upstream 89.8%) — par do
18
18
  * canvas quase-branco; superfícies elevadas usam a mesma borda real dos demais papéis.
19
- * Tudo o mais (escala neutra, radius base 0.625rem, semânticas) segue o upstream.
19
+ * 6. Radius base em 0.75rem identidade mais arredondada; os componentes continuam usando
20
+ * diretamente a escala Tailwind, sem tokens por papel (ver docs/radius-scale.md).
21
+ * Tudo o mais (escala neutra, semânticas) segue o upstream.
20
22
  */
21
23
  @import 'tailwindcss';
22
24
  @import 'tw-animate-css';
@@ -50,7 +52,7 @@
50
52
  /* Ring de foco LEVE (pedido da casa): aro sutil, não um halo. Com ring-[3px] + /50,
51
53
  ~86% encosta na borda (89.8%) — um sussurro de foco. */
52
54
  --ring: hsl(0 0% 86%);
53
- --radius: 0.625rem;
55
+ --radius: 0.75rem;
54
56
  }
55
57
 
56
58
  .dark {
@@ -132,10 +134,12 @@
132
134
  --color-input: var(--input);
133
135
  --color-ring: var(--ring);
134
136
 
137
+ --radius-xs: calc(var(--radius) - 6px);
135
138
  --radius-sm: calc(var(--radius) - 4px);
136
139
  --radius-md: calc(var(--radius) - 2px);
137
140
  --radius-lg: var(--radius);
138
141
  --radius-xl: calc(var(--radius) + 4px);
142
+ --radius-2xl: calc(var(--radius) + 8px);
139
143
 
140
144
  /* Escala Tailwind, com receita direta para o compilador preservar modifiers `/opacity`.
141
145
  `light-dark()` mantém o peso adequado em cada tema sem uma variável intermediária. */