@softize/opus 12.10.0 → 12.11.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.
- package/CHANGELOG.md +27 -0
- package/bin/lib/check.mjs +1103 -310
- package/bin/lib/copy.mjs +11 -0
- package/docs/adr/0003-dictionary-presentation-is-declared.md +3 -0
- package/docs/adr/0004-page-content-state-is-composed.md +65 -0
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +97 -0
- package/docs/adr/0006-semantic-context-precedes-visual-variant.md +182 -0
- package/package.json +1 -1
- package/registry/instructions/opus.md +5 -0
- package/registry/skills/build-opus-ui/SKILL.md +27 -16
- package/registry/skills/build-opus-ui/references/evaluations.md +16 -5
- package/registry/skills/build-opus-ui/references/ui-patterns.md +38 -15
- package/registry/skills/model-opus-dictionary/SKILL.md +4 -2
- package/registry/skills/model-opus-dictionary/references/evaluations.md +4 -3
- package/registry/templates/app/src/App.tsx +1 -1
- package/src/core/dictionary.ts +52 -14
- package/src/core/index.ts +10 -0
- package/src/core/ui-context.ts +29 -0
- package/src/schema/drivers/zod.ts +17 -8
- package/src/ui/components/patterns/action-form-card.tsx +18 -12
- package/src/ui/components/patterns/confirm.tsx +26 -3
- package/src/ui/components/patterns/content-header.tsx +335 -61
- package/src/ui/components/patterns/data-state.tsx +23 -10
- package/src/ui/components/patterns/list.tsx +1096 -777
- package/src/ui/components/patterns/page-state.tsx +115 -0
- package/src/ui/components/patterns/page.tsx +231 -41
- package/src/ui/components/patterns/sidebar.tsx +354 -80
- package/src/ui/components/patterns/trigger.tsx +13 -9
- package/src/ui/components/patterns/view.tsx +7 -11
- package/src/ui/components/primitives/alert-dialog.tsx +7 -5
- package/src/ui/components/primitives/alert.tsx +298 -110
- package/src/ui/components/primitives/ask.tsx +2 -1
- package/src/ui/components/primitives/badge.tsx +91 -30
- package/src/ui/components/primitives/button.tsx +99 -60
- package/src/ui/components/primitives/calendar.tsx +39 -39
- package/src/ui/components/primitives/card.tsx +96 -23
- package/src/ui/components/primitives/detail.tsx +2 -2
- package/src/ui/components/primitives/dictionary-value.tsx +9 -14
- package/src/ui/components/primitives/dot.tsx +74 -21
- package/src/ui/components/primitives/drawer.tsx +33 -20
- package/src/ui/components/primitives/item.tsx +137 -81
- package/src/ui/components/primitives/menu.tsx +11 -3
- package/src/ui/components/primitives/metric-card.tsx +133 -0
- package/src/ui/components/primitives/table.tsx +2 -2
- package/src/ui/docs/DocBrowser.tsx +3 -3
- package/src/ui/docs/changelog.tsx +1 -1
- package/src/ui/docs/content/action-form-card.md +1 -1
- package/src/ui/docs/content/alert-dialog.md +8 -8
- package/src/ui/docs/content/alert.md +49 -25
- package/src/ui/docs/content/badge.md +18 -19
- package/src/ui/docs/content/button.md +12 -9
- package/src/ui/docs/content/card.md +5 -5
- package/src/ui/docs/content/content.md +44 -0
- package/src/ui/docs/content/customization.md +2 -2
- package/src/ui/docs/content/detail.md +5 -2
- package/src/ui/docs/content/dialog.md +2 -2
- package/src/ui/docs/content/dictionary-value.md +11 -10
- package/src/ui/docs/content/dot.md +7 -7
- package/src/ui/docs/content/drawer.md +6 -3
- package/src/ui/docs/content/input-group.md +3 -2
- package/src/ui/docs/content/item.md +47 -21
- package/src/ui/docs/content/menu.md +5 -4
- package/src/ui/docs/content/metric-card.md +41 -0
- package/src/ui/docs/content/page-state.md +45 -0
- package/src/ui/docs/content/page.md +48 -10
- package/src/ui/docs/content/semantic-context.md +63 -0
- package/src/ui/docs/content/sidebar.md +4 -4
- package/src/ui/docs/content/skeleton.md +2 -2
- package/src/ui/docs/content/table.md +3 -3
- package/src/ui/docs/content/tokens.md +28 -0
- package/src/ui/docs/doc-client.tsx +2 -2
- package/src/ui/docs/registry.tsx +596 -228
- package/src/ui/lib/semantic-context.ts +30 -0
- package/src/ui/meta.ts +292 -270
- package/src/ui/react.tsx +378 -111
- package/src/ui/theme.css +66 -0
package/bin/lib/copy.mjs
CHANGED
|
@@ -84,6 +84,8 @@ const JSX_CHILD_ROLES = new Map([
|
|
|
84
84
|
['CardTitle', 'title'],
|
|
85
85
|
['CommandEmpty', 'empty-state'],
|
|
86
86
|
['CommandItem', 'menu-item'],
|
|
87
|
+
['ContentDescription', 'description'],
|
|
88
|
+
['ContentTitle', 'title'],
|
|
87
89
|
['DialogDescription', 'dialog-body'],
|
|
88
90
|
['DialogTitle', 'title'],
|
|
89
91
|
['DrawerDescription', 'dialog-body'],
|
|
@@ -104,6 +106,8 @@ const JSX_CHILD_ROLES = new Map([
|
|
|
104
106
|
['MenuRadioItem', 'menu-item'],
|
|
105
107
|
['PopoverDescription', 'description'],
|
|
106
108
|
['PopoverTitle', 'title'],
|
|
109
|
+
['PageDescription', 'description'],
|
|
110
|
+
['PageTitle', 'title'],
|
|
107
111
|
['TableCaption', 'description'],
|
|
108
112
|
['TableHead', 'heading'],
|
|
109
113
|
['TabsTrigger', 'tab'],
|
|
@@ -130,6 +134,8 @@ const JSX_PROP_ROLES = new Map([
|
|
|
130
134
|
['ActionTrigger', new Map([['label', 'button']])],
|
|
131
135
|
['Alert', new Map([['title', 'title'], ['description', 'message']])],
|
|
132
136
|
['CommandInput', new Map([['placeholder', 'placeholder']])],
|
|
137
|
+
['Content', new Map([['title', 'title'], ['description', 'description']])],
|
|
138
|
+
['ContentHeader', new Map([['title', 'title'], ['description', 'description']])],
|
|
133
139
|
['DataState', new Map([['emptyText', 'empty-state'], ['errorText', 'error']])],
|
|
134
140
|
// A Dock nomeia a barra e cada ação por prop. Sem estas linhas, a copy sairia do inventário
|
|
135
141
|
// exatamente quando uma superfície migra de <Button aria-label> para <DockAction label>.
|
|
@@ -138,6 +144,7 @@ const JSX_PROP_ROLES = new Map([
|
|
|
138
144
|
['Input', new Map([['placeholder', 'placeholder']])],
|
|
139
145
|
['InputGroupInput', new Map([['placeholder', 'placeholder']])],
|
|
140
146
|
['InputGroupTextarea', new Map([['placeholder', 'placeholder']])],
|
|
147
|
+
['MetricCard', new Map([['label', 'label'], ['description', 'description']])],
|
|
141
148
|
['Page', new Map([['title', 'title'], ['description', 'description']])],
|
|
142
149
|
['Select', new Map([
|
|
143
150
|
['placeholder', 'placeholder'],
|
|
@@ -183,9 +190,12 @@ const JSX_PROP_CLASS = new Map([
|
|
|
183
190
|
['ActionTrigger', new Map([['label', 'className']])],
|
|
184
191
|
['Alert', new Map([['title', 'className'], ['description', 'className']])],
|
|
185
192
|
['CommandInput', new Map([['placeholder', 'className']])],
|
|
193
|
+
['Content', new Map([['title', 'className'], ['description', 'className']])],
|
|
194
|
+
['ContentHeader', new Map([['title', 'className'], ['description', 'className']])],
|
|
186
195
|
['Input', new Map([['placeholder', 'className']])],
|
|
187
196
|
['InputGroupInput', new Map([['placeholder', 'className']])],
|
|
188
197
|
['InputGroupTextarea', new Map([['placeholder', 'className']])],
|
|
198
|
+
['MetricCard', new Map([['label', 'className'], ['description', 'className']])],
|
|
189
199
|
['Page', new Map([['title', 'className'], ['description', 'className']])],
|
|
190
200
|
['Select', new Map([['placeholder', 'className']])],
|
|
191
201
|
['Textarea', new Map([['placeholder', 'className']])],
|
|
@@ -200,6 +210,7 @@ const JSX_PROP_STYLE = new Map([
|
|
|
200
210
|
['Input', new Map([['placeholder', 'style']])],
|
|
201
211
|
['InputGroupInput', new Map([['placeholder', 'style']])],
|
|
202
212
|
['InputGroupTextarea', new Map([['placeholder', 'style']])],
|
|
213
|
+
['MetricCard', new Map([['label', 'style'], ['description', 'style']])],
|
|
203
214
|
['Textarea', new Map([['placeholder', 'style']])],
|
|
204
215
|
])
|
|
205
216
|
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
- Status: aceita
|
|
4
4
|
- Data: 2026-09-02
|
|
5
5
|
|
|
6
|
+
> A ADR 0006 substitui o nome `tone` por `context` para a família semântica e define sua migração
|
|
7
|
+
> compatível. O princípio desta ADR — apresentação declarada e não inferida — permanece vigente.
|
|
8
|
+
|
|
6
9
|
## Contexto e forças
|
|
7
10
|
|
|
8
11
|
`t.dict` declara vocabulário fechado uma única vez: código estável, rótulo, documentação de
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# ADR 0004 — O estado integral do conteúdo é composto dentro de Page
|
|
2
|
+
|
|
3
|
+
## Contexto
|
|
4
|
+
|
|
5
|
+
`Page` padroniza o `<main>`, o cabeçalho e o container de uma página. Hoje, consumidores que ainda
|
|
6
|
+
não têm conteúdo para mostrar repetem spinners, alertas e vazios diretamente em `children`. Essas
|
|
7
|
+
composições divergem visualmente e nem sempre distinguem carregamento, ausência e falha ou oferecem
|
|
8
|
+
uma ação de recuperação.
|
|
9
|
+
|
|
10
|
+
Ao mesmo tempo, uma página pode agregar várias fontes independentes. Fazer `Page` receber flags de
|
|
11
|
+
carregamento ou erro faria o esqueleto decidir quando toda a página deve desaparecer por causa de
|
|
12
|
+
uma única fonte.
|
|
13
|
+
|
|
14
|
+
## Decisão
|
|
15
|
+
|
|
16
|
+
`Page` continua presentacional e sem conhecimento de dados. O Opus fornece `PageState` como pattern
|
|
17
|
+
composto para o estado integral da área de conteúdo. Na forma curta ele pode ser escrito como filho
|
|
18
|
+
direto de `Page`, que materializa `PageBody`; na composição explícita, `PageState` fica dentro de
|
|
19
|
+
`PageBody`. Ele é usado quando o conteúdo principal inteiro está carregando, falhou ou está vazio.
|
|
20
|
+
|
|
21
|
+
`PageState`:
|
|
22
|
+
|
|
23
|
+
- recebe um estado discriminado entre `loading`, `error`, `empty` e `ready`;
|
|
24
|
+
- preserva o cabeçalho da página em todos os estados;
|
|
25
|
+
- compõe `Spinner`, `Alert` e `Empty` em vez de recriar suas superfícies;
|
|
26
|
+
- aceita título, descrição, ícone e ação contextual sem exibir erro técnico;
|
|
27
|
+
- expõe `data-slot="page-state"` e `data-status` para testes e análise estrutural;
|
|
28
|
+
- renderiza o conteúdo sem moldura adicional em `ready`.
|
|
29
|
+
|
|
30
|
+
Estados parciais continuam pertencendo a `DataState`, `ActionView`, `ActionList` ou `Alert`, conforme
|
|
31
|
+
a fronteira afetada. Uma coleção vazia continua dentro da estrutura da coleção; `Empty` representa
|
|
32
|
+
uma região disponível, uma escolha pendente ou uma próxima ação.
|
|
33
|
+
|
|
34
|
+
## Alternativas consideradas
|
|
35
|
+
|
|
36
|
+
### Flags diretamente em Page
|
|
37
|
+
|
|
38
|
+
Rejeitada porque mistura layout com aquisição de dados e torna ambígua a precedência entre várias
|
|
39
|
+
fontes da mesma página.
|
|
40
|
+
|
|
41
|
+
### Usar somente DataState
|
|
42
|
+
|
|
43
|
+
Rejeitada como contrato único porque o modo bloco de `DataState` também atende seções menores e não
|
|
44
|
+
expressa que o conteúdo principal inteiro está indisponível. `PageState` pode compartilhar as mesmas
|
|
45
|
+
primitivas sem confundir as duas escalas.
|
|
46
|
+
|
|
47
|
+
### Manter composições locais
|
|
48
|
+
|
|
49
|
+
Rejeitada porque mantém diferenças de altura, borda, copy, recuperação e semântica acessível entre
|
|
50
|
+
telas equivalentes.
|
|
51
|
+
|
|
52
|
+
## Consequências
|
|
53
|
+
|
|
54
|
+
- Consumidores ganham uma composição uniforme sem acoplar `Page` a hooks ou actions.
|
|
55
|
+
- A copy específica do fluxo permanece no consumidor; defaults seguros cobrem usos simples.
|
|
56
|
+
- Migrações precisam distinguir estado integral de estado parcial antes de substituir a composição.
|
|
57
|
+
- O gate estrutural do consumidor deve impedir novos estados integrais montados manualmente.
|
|
58
|
+
|
|
59
|
+
## Verificação
|
|
60
|
+
|
|
61
|
+
- Testes do Opus cobrem os quatro estados, slots, precedência do conteúdo e ação contextual.
|
|
62
|
+
- A documentação do catálogo demonstra o uso na forma curta e sua posição dentro de `PageBody` na
|
|
63
|
+
composição explícita.
|
|
64
|
+
- Consumidores verificam `data-slot="page-state"` nos estados integrais e mantêm testes próprios para
|
|
65
|
+
recuperação e distinção entre erro e vazio.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# ADR 0005 — Superfícies estruturais compartilham uma anatomia explícita
|
|
2
|
+
|
|
3
|
+
- **Status:** aceita.
|
|
4
|
+
- **Data:** 2026-09-04.
|
|
5
|
+
|
|
6
|
+
## Contexto
|
|
7
|
+
|
|
8
|
+
Os componentes estruturais da UI descrevem regiões equivalentes com APIs diferentes. `Dialog`
|
|
9
|
+
expõe header, título, descrição, body e footer; `Card` chama o corpo de `Content`; e `Page`
|
|
10
|
+
recebe título, descrição e ações somente por propriedades, sem expor seus elementos estruturais.
|
|
11
|
+
`ContentHeader`, por sua vez, pode aparecer solto, embora seus nomes sugiram uma família `Content`.
|
|
12
|
+
|
|
13
|
+
Essa variação obriga quem consome a biblioteca a reaprender a composição em cada superfície e
|
|
14
|
+
impede que análise estática verifique relações como “um título pertence ao header da sua família”.
|
|
15
|
+
Ao mesmo tempo, a forma curta de `Page` e `ContentHeader` atende bem ao caso comum e não precisa ser
|
|
16
|
+
perdida para obter uma estrutura explícita.
|
|
17
|
+
|
|
18
|
+
## Decisão
|
|
19
|
+
|
|
20
|
+
As superfícies mantidas pela casa adotam a gramática `Root > Header + Body + Footer`, com
|
|
21
|
+
`Title`, `Description`, `Meta` e `Actions` pertencendo ao `Header` da mesma família.
|
|
22
|
+
|
|
23
|
+
- `Page` oferece `PageHeader`, `PageTitle`, `PageDescription`, `PageMeta`, `PageActions` e
|
|
24
|
+
`PageBody`.
|
|
25
|
+
- `Content` representa uma região de conteúdo semanticamente nomeada e oferece `ContentHeader`,
|
|
26
|
+
`ContentTitle`, `ContentDescription`, `ContentMeta`, `ContentActions` e `ContentBody`.
|
|
27
|
+
- `CardContent` passa a ter `CardBody` como nome canônico.
|
|
28
|
+
- `Drawer` recebe `DrawerBody`.
|
|
29
|
+
- `PaneContent` passa a ter `PaneBody` como nome canônico.
|
|
30
|
+
- `Dialog` permanece como referência porque já possui `DialogHeader` e `DialogBody`.
|
|
31
|
+
- `Alert` oferece `AlertMedia`, `AlertHeader`, `AlertTitle`, `AlertDescription` e
|
|
32
|
+
`AlertActions`; a forma curta por propriedades materializa esses mesmos slots.
|
|
33
|
+
- `Item` oferece `ItemMedia`, `ItemHeader`, `ItemTitle`, `ItemDescription`, `ItemBody`,
|
|
34
|
+
`ItemActions` e `ItemFooter`. `ItemContent` permanece temporariamente como alias legado de
|
|
35
|
+
corpo, mas deixa de envolver título e descrição no código novo.
|
|
36
|
+
|
|
37
|
+
`Page` e `Content` aceitam também uma forma curta com `title`, `description`, `meta` e `actions`.
|
|
38
|
+
Essa forma é açúcar sintático: produz a mesma árvore semântica, os mesmos estilos e os mesmos
|
|
39
|
+
`data-slot` da composição explícita. Um consumidor não pode misturar as duas formas na mesma raiz.
|
|
40
|
+
|
|
41
|
+
`ContentHeader` deixa de ser uma região solta e passa a pertencer a `Content`. A forma histórica
|
|
42
|
+
por propriedades continua disponível temporariamente dentro de `Content`, mas a composição
|
|
43
|
+
explícita usa os slots da família.
|
|
44
|
+
|
|
45
|
+
`Content` técnico mantém seu nome quando representa o contêiner montado por uma primitiva ou um
|
|
46
|
+
painel controlado, como `DialogContent`, `PopoverContent`, `TabsContent` e `AccordionContent`.
|
|
47
|
+
`EmptyContent` e `CarouselContent` também permanecem porque não representam o body de uma
|
|
48
|
+
superfície estrutural.
|
|
49
|
+
|
|
50
|
+
Nas superfícies horizontais, `Media` e `Header` formam a mesma linha estrutural. A região visual
|
|
51
|
+
de `Media` tem largura estável e se estende pela altura útil do header, mantendo seu conteúdo
|
|
52
|
+
centralizado. Assim, título e descrição de uma linha não deixam uma sobra inferior ao lado da
|
|
53
|
+
moldura; conteúdo textual maior continua determinando naturalmente a altura da linha.
|
|
54
|
+
|
|
55
|
+
## Consequências
|
|
56
|
+
|
|
57
|
+
- A API comum fica previsível sem obrigar o caso simples a escrever todos os slots.
|
|
58
|
+
- A forma explícita permite composição e extensão sem reconstruir o layout da biblioteca.
|
|
59
|
+
- Em `Page` e `Content`, tipos separam as props das duas formas, contexto em runtime protege os
|
|
60
|
+
slots e `opus check` verifica a anatomia JSX completa. Nas famílias históricas, o lint reconhece
|
|
61
|
+
wrappers transparentes e render props: reprova uma família visivelmente errada sem proibir que
|
|
62
|
+
um slot seja encapsulado por um componente reutilizável.
|
|
63
|
+
- Aliases históricos permanecem durante a versão 12 e podem ser removidos numa versão major.
|
|
64
|
+
- Consumidores precisam migrar `ContentHeader` solto para `Content` e podem migrar as demais formas
|
|
65
|
+
gradualmente enquanto os aliases existirem.
|
|
66
|
+
- Alertas e itens simples ganham uma hierarquia igual sem perder suas semânticas distintas:
|
|
67
|
+
`Alert` comunica estado e `Item` representa uma entidade ou opção numa coleção.
|
|
68
|
+
|
|
69
|
+
## Alternativas consideradas
|
|
70
|
+
|
|
71
|
+
### Manter APIs diferentes por componente
|
|
72
|
+
|
|
73
|
+
Preservaria compatibilidade total, mas manteria a carga cognitiva e impediria uma regra estrutural
|
|
74
|
+
comum. Foi descartada porque as diferenças não representam comportamentos distintos.
|
|
75
|
+
|
|
76
|
+
### Exigir somente composição explícita
|
|
77
|
+
|
|
78
|
+
Produziria uma API uniforme, mas tornaria páginas e regiões simples desnecessariamente verbosas.
|
|
79
|
+
Foi descartada porque shorthand e slots podem convergir para uma única implementação.
|
|
80
|
+
|
|
81
|
+
### Renomear `ContentHeader` sem criar `Content`
|
|
82
|
+
|
|
83
|
+
Nomes como `SectionHeader` pressupõem outro pai; nomes como `GenericHeader` descrevem ausência de
|
|
84
|
+
semântica; e `HeadingBlock` abandona a gramática das demais famílias. Foi descartada em favor de
|
|
85
|
+
dar a `ContentHeader` um pai estrutural real.
|
|
86
|
+
|
|
87
|
+
## Verificação
|
|
88
|
+
|
|
89
|
+
- Testes de UI comparam DOM, acessibilidade e `data-slot` das formas curta e explícita.
|
|
90
|
+
- Testes de runtime proíbem a mistura das formas e os slots de `Page`/`Content` usados fora da
|
|
91
|
+
família correspondente; essa relação entre elementos JSX não é representável somente pelo tipo
|
|
92
|
+
de `children` do React.
|
|
93
|
+
- `opus check` reprova relações JSX estruturais inválidas nos consumidores.
|
|
94
|
+
- Testes de layout verificam que `AlertMedia` e `ItemMedia` se estendem pela linha do header e
|
|
95
|
+
centralizam o ícone, sem fixar a altura do conteúdo textual.
|
|
96
|
+
- Documentação e metadados apresentam a forma curta como caminho comum e a composição explícita
|
|
97
|
+
como caminho de extensão.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# ADR 0006 — Contexto semântico precede variante visual
|
|
2
|
+
|
|
3
|
+
- Status: aceita
|
|
4
|
+
- Data: 2026-09-04
|
|
5
|
+
|
|
6
|
+
## Contexto e forças
|
|
7
|
+
|
|
8
|
+
Os componentes do Opus usam `variant` para eixos diferentes. Em Button, Badge, Alert e Dot, a
|
|
9
|
+
prop mistura hierarquia (`default`, `secondary`), contexto semântico (`success`, `warning`,
|
|
10
|
+
`destructive`) e tratamento visual (`outline`, `ghost`, `link`). Em Table, Detail, Tabs e Item,
|
|
11
|
+
`variant` descreve apenas uma alternativa estrutural ou visual local (`plain`, `framed`, `line`).
|
|
12
|
+
|
|
13
|
+
Ao mesmo tempo, entradas de `t.dict` usam `tone` para selecionar a família semântica de status e
|
|
14
|
+
estágios. Essa família não coincide com os componentes: o estado vermelho é `danger` no dicionário,
|
|
15
|
+
`destructive` em alguns primitives, e `MetricCard tone="warning"` atualmente usa tokens vermelhos.
|
|
16
|
+
Um agente ou consumidor precisa conhecer cada exceção para obter a mesma linguagem visual.
|
|
17
|
+
|
|
18
|
+
As forças em tensão são:
|
|
19
|
+
|
|
20
|
+
- o significado precisa permanecer independente da forma concreta com que cada componente o
|
|
21
|
+
apresenta;
|
|
22
|
+
- a API deve ser previsível entre primitives sem transformar todas as combinações em opções
|
|
23
|
+
válidas para todos os componentes;
|
|
24
|
+
- `destructive` precisa continuar descrevendo o risco comportamental de uma ação, sem se tornar o
|
|
25
|
+
nome da família visual vermelha;
|
|
26
|
+
- dicionários e componentes existentes precisam de uma migração explícita e verificável;
|
|
27
|
+
- agentes precisam aprender a regra por contratos, documentação e avaliações, não por memória ou
|
|
28
|
+
inferência a partir de exemplos isolados;
|
|
29
|
+
- light mode e dark mode são temas, não significados de produto.
|
|
30
|
+
|
|
31
|
+
## Alternativas consideradas
|
|
32
|
+
|
|
33
|
+
### Manter `tone` nos dicionários e `variant` nos componentes
|
|
34
|
+
|
|
35
|
+
Preserva compatibilidade imediata, mas mantém dois nomes para o mesmo eixo e deixa `variant`
|
|
36
|
+
misturar significado com apresentação. Cada novo componente precisaria repetir mapas locais.
|
|
37
|
+
Rejeitada.
|
|
38
|
+
|
|
39
|
+
### Copiar literalmente as variantes contextuais do Bootstrap
|
|
40
|
+
|
|
41
|
+
O vocabulário `primary`, `secondary`, `success`, `danger`, `warning`, `info`, `light` e `dark` é
|
|
42
|
+
conhecido e cobre grande parte dos casos. Porém, o Bootstrap chama de variante tanto o contexto
|
|
43
|
+
quanto sua materialização (`btn-danger`, `btn-outline-danger`), mistura hierarquia com semântica e
|
|
44
|
+
inclui `light`/`dark`, que pertencem ao tema. Copiar a API manteria a ambiguidade que queremos
|
|
45
|
+
remover. Rejeitada como contrato literal e aceita como referência de vocabulário.
|
|
46
|
+
|
|
47
|
+
### Declarar contexto e variante como eixos independentes
|
|
48
|
+
|
|
49
|
+
`context` escolhe a família de tokens e responde por que existe o realce. `variant` escolhe como a
|
|
50
|
+
família aparece naquele componente. Cada primitive aceita somente o subconjunto coerente com seu
|
|
51
|
+
papel e fornece defaults. É uma mudança maior, mas torna combinações e exceções explícitas e permite
|
|
52
|
+
que domínio, UI e agentes compartilhem o mesmo modelo. Aceita.
|
|
53
|
+
|
|
54
|
+
## Decisão
|
|
55
|
+
|
|
56
|
+
### Vocabulário contextual
|
|
57
|
+
|
|
58
|
+
O Opus define o vocabulário canônico:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
type UiContext =
|
|
62
|
+
| 'neutral'
|
|
63
|
+
| 'primary'
|
|
64
|
+
| 'info'
|
|
65
|
+
| 'success'
|
|
66
|
+
| 'warning'
|
|
67
|
+
| 'danger'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- `neutral`: estado normal, inativo ou sem julgamento positivo/negativo;
|
|
71
|
+
- `primary`: ação ou elemento de maior destaque no contexto atual;
|
|
72
|
+
- `info`: informação ou processo em andamento sem alerta;
|
|
73
|
+
- `success`: resultado positivo ou estado saudável;
|
|
74
|
+
- `warning`: condição que pede atenção, mas não representa falha;
|
|
75
|
+
- `danger`: falha, impedimento ou consequência perigosa.
|
|
76
|
+
|
|
77
|
+
`secondary` não é contexto universal: ação secundária é hierarquia, enquanto estado neutro é
|
|
78
|
+
semântica. `light` e `dark` permanecem modos de cor. Nenhum dos três entra em `UiContext`.
|
|
79
|
+
|
|
80
|
+
Entradas de dicionário aceitam `DictContext`, o subconjunto sem `primary`, porque um estado de
|
|
81
|
+
domínio não se torna a ação principal da interface. A metadata canônica passa de `tone` para
|
|
82
|
+
`context`.
|
|
83
|
+
|
|
84
|
+
### Variante visual
|
|
85
|
+
|
|
86
|
+
Para componentes semânticos, `variant` descreve somente o tratamento visual:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
type SemanticVariant = 'solid' | 'subtle' | 'outline' | 'ghost' | 'link'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Cada componente aceita apenas as variantes que consegue materializar com coerência. Os defaults
|
|
93
|
+
iniciais são:
|
|
94
|
+
|
|
95
|
+
| Componente | Contexto padrão | Variante padrão | Contextos permitidos |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| Button | `primary` | `solid` | `neutral`, `primary`, `danger` |
|
|
98
|
+
| Badge | `neutral` | `subtle` | todos |
|
|
99
|
+
| Alert | `neutral` | `subtle` | `neutral`, `info`, `success`, `warning`, `danger` |
|
|
100
|
+
| Dot | `neutral` | `solid` | `neutral`, `primary`, `info`, `success`, `warning`, `danger` |
|
|
101
|
+
| MetricCard | `neutral` | `subtle` no ícone | `neutral`, `info`, `success`, `warning`, `danger` |
|
|
102
|
+
|
|
103
|
+
`solid`, `subtle` e `outline` podem compartilhar um contexto sem compartilhar classes. `ghost` e
|
|
104
|
+
`link` ficam restritos a controles interativos. Cor e ícone continuam reforços: texto ou nome
|
|
105
|
+
acessível comunica o significado.
|
|
106
|
+
|
|
107
|
+
Componentes cuja `variant` é exclusivamente estrutural ou local, como `plain | framed` e
|
|
108
|
+
`default | line`, podem mantê-la. Ao criar API nova, preferir uma prop específica quando o nome do
|
|
109
|
+
eixo for mais claro (`frame`, `layout`, `appearance`); não renomear primitives existentes sem ganho
|
|
110
|
+
observável.
|
|
111
|
+
|
|
112
|
+
### Ações destrutivas
|
|
113
|
+
|
|
114
|
+
`destructive` permanece em contratos de ação e confirmação para declarar risco, confirmação e
|
|
115
|
+
comportamento. A projeção visual desse risco usa `context="danger"`. Portanto, `destructive` não é
|
|
116
|
+
um `UiContext` nem uma variante visual canônica.
|
|
117
|
+
|
|
118
|
+
### Compatibilidade
|
|
119
|
+
|
|
120
|
+
A migração ocorre em uma janela explícita:
|
|
121
|
+
|
|
122
|
+
1. componentes e `t.dict` passam a aceitar a API canônica e os nomes antigos como aliases
|
|
123
|
+
depreciados;
|
|
124
|
+
2. informar os dois eixos antigo e novo ao mesmo tempo é inválido quando houver ambiguidade;
|
|
125
|
+
3. renderers normalizam aliases antes de escolher tokens;
|
|
126
|
+
4. manifest, documentação e exemplos gerados projetam somente `context` como forma canônica;
|
|
127
|
+
5. consumidores são migrados e um gate impede novos usos semânticos de `tone` e de variantes como
|
|
128
|
+
`success`, `warning`, `danger` ou `destructive`;
|
|
129
|
+
6. os aliases são removidos somente em uma versão major posterior, depois de o inventário chegar a
|
|
130
|
+
zero.
|
|
131
|
+
|
|
132
|
+
Usos locais de `tone` que não representam contexto semântico não são convertidos automaticamente.
|
|
133
|
+
Por exemplo, relações de diagrama com valores `optional`, `same` e `return` devem receber um nome
|
|
134
|
+
de domínio como `kind` ou `relation`, não `UiContext`.
|
|
135
|
+
|
|
136
|
+
## Orientação para agentes
|
|
137
|
+
|
|
138
|
+
A regra precisa alcançar uma IA por fontes complementares e verificáveis:
|
|
139
|
+
|
|
140
|
+
1. **Contrato compilável:** `UiContext`, `DictContext` e os tipos de props limitam as combinações
|
|
141
|
+
possíveis e são a fonte primária.
|
|
142
|
+
2. **Catálogo do Opus:** documentação e metadata de cada componente explicam contexto, variante,
|
|
143
|
+
defaults e exceções com exemplos canônicos.
|
|
144
|
+
3. **Skills:** `build-opus-ui` ensina a matriz `context × variant`; `model-opus-dictionary` exige
|
|
145
|
+
`context` em status e estágios e proíbe inferência pela chave ou pelo nome do dicionário.
|
|
146
|
+
4. **Avaliações:** casos positivos, negativos e de execução reprovam `variant="success"`,
|
|
147
|
+
`tone="warning"` sem compatibilidade justificada, `destructive` como cor e comunicação somente
|
|
148
|
+
por cor.
|
|
149
|
+
5. **Instrução permanente:** a instrução materializada do Opus resume a regra e encaminha às
|
|
150
|
+
skills; não replica toda a tabela.
|
|
151
|
+
6. **Gate estático:** o check do Opus detecta novas ocorrências legadas fora de arquivos de
|
|
152
|
+
compatibilidade, testes de migração e exemplos negativos explicitamente marcados.
|
|
153
|
+
7. **Materialização:** mudanças no registry são propagadas por `opus setup`; o projeto consumidor
|
|
154
|
+
valida que `.agents` e os adapters suportados não divergiram da versão declarada.
|
|
155
|
+
|
|
156
|
+
Essa redundância é intencional: tipos impedem combinações inválidas no código, documentação apoia
|
|
157
|
+
decisões humanas, skills orientam o fluxo e o gate detecta regressão mesmo quando uma instrução não
|
|
158
|
+
for consultada.
|
|
159
|
+
|
|
160
|
+
## Consequências
|
|
161
|
+
|
|
162
|
+
- Primitives semânticos passam a compartilhar um vocabulário e tokens contextuais.
|
|
163
|
+
- Defaults podem variar por componente, mas a mesma palavra nunca muda de significado.
|
|
164
|
+
- A API fica mais explícita em chamadas que precisam dos dois eixos.
|
|
165
|
+
- A transição aumenta temporariamente tipos, testes e normalização por causa dos aliases.
|
|
166
|
+
- Temas precisam oferecer tokens de superfície, borda, texto de ênfase e sólido para cada contexto,
|
|
167
|
+
em light e dark mode.
|
|
168
|
+
- Consumidores com classes Tailwind semânticas literais não são automaticamente corretos; a
|
|
169
|
+
migração precisa classificar se representam contexto, visualização de dados ou linguagem própria
|
|
170
|
+
de um diagrama.
|
|
171
|
+
|
|
172
|
+
## Verificação
|
|
173
|
+
|
|
174
|
+
- testes puros cobrem o vocabulário, aliases e combinações inválidas;
|
|
175
|
+
- testes de cada primitive cobrem a matriz aceita e seus defaults;
|
|
176
|
+
- testes de `t.dict`, descriptor e manifest comprovam `context` e a compatibilidade de leitura;
|
|
177
|
+
- testes de renderização provam que `DictionaryValue` usa `context` sem a tela escolher classes;
|
|
178
|
+
- avaliações das skills cobrem seleção, execução correta e exemplos que devem ser recusados;
|
|
179
|
+
- o validador de skills passa no registry e nas cópias materializadas;
|
|
180
|
+
- o gate estático reprova novas variantes semânticas e novos `tone` públicos;
|
|
181
|
+
- `opus check`, testes, typecheck, build, `opus copy --check` e gates do consumidor permanecem
|
|
182
|
+
verdes.
|
package/package.json
CHANGED
|
@@ -15,6 +15,11 @@ artefatos de agentes fica em `base.json`; cada app Opus mantém seu marcador `op
|
|
|
15
15
|
- Manter contrato compartilhável separado de banco, segredo e driver server-only; usar
|
|
16
16
|
`defineContract` com `bindAction` quando cliente e servidor consomem a mesma action.
|
|
17
17
|
- Não duplicar schemas, tipos de transporte, validação ou fetch que o contrato já fornece.
|
|
18
|
+
- Em UI semântica, declarar primeiro `context` (`neutral`, `primary`, `info`, `success`, `warning`
|
|
19
|
+
ou `danger`) e usar `variant` somente para o tratamento visual (`solid`, `subtle`, `outline`,
|
|
20
|
+
`ghost` ou `link`). Dicionários de status e estágio declaram `context`; `tone` e variantes
|
|
21
|
+
semânticas antigas são apenas compatibilidade de migração. `destructive` permanece uma
|
|
22
|
+
propriedade comportamental de actions e se projeta visualmente como `danger`.
|
|
18
23
|
- Declarar datasets persistentes com `defineSeed` + `bindSeed`, registrá-los em `opus.config.ts`
|
|
19
24
|
e operá-los por `opus seed`; não criar comandos de seed paralelos nem reset implícito.
|
|
20
25
|
- Regenerar o inventário com `opus copy` quando mudar copy em contrato ou componente Opus
|
|
@@ -19,46 +19,54 @@ existentes, mantendo navegação observável e componentes reutilizáveis sem la
|
|
|
19
19
|
mensagem de vocabulário fechado vêm do dicionário do domínio (`labelFor`/`metaFor`); ao
|
|
20
20
|
encontrar catálogo repetido à mão ou vocabulário ainda sem dicionário, carregar
|
|
21
21
|
`$model-opus-dictionary`.
|
|
22
|
-
3.
|
|
22
|
+
3. Em componentes semânticos, declarar `context` antes de escolher `variant`: `context` comunica
|
|
23
|
+
`neutral`, `primary`, `info`, `success`, `warning` ou `danger`; `variant` descreve somente o
|
|
24
|
+
tratamento `solid`, `subtle`, `outline`, `ghost` ou `link`, conforme o subconjunto aceito pelo
|
|
25
|
+
componente. Não usar `variant="success"`, `variant="destructive"` nem `tone` em código novo.
|
|
26
|
+
`destructive` permanece metadata comportamental de action e se projeta como `context="danger"`.
|
|
27
|
+
4. Apresentar valor de dicionário por `DictionaryValue` ou pela coluna de `ActionList`, que
|
|
23
28
|
aplicam o papel declarado em `presentation` (classificação em badge `outline`, status e estágio
|
|
24
|
-
em badge
|
|
29
|
+
em badge `context + subtle`, `plain` em texto). Não escolher badge, contexto ou ícone pelo nome do dicionário:
|
|
25
30
|
dicionário sem papel declarado renderiza texto e pede classificação por
|
|
26
31
|
`$model-opus-dictionary` antes de qualquer destaque visual. Sobrepor os defaults só com motivo
|
|
27
32
|
explícito nas props do renderer.
|
|
28
|
-
|
|
33
|
+
5. Dar a cada dimensão independente usada para comparação ou filtro um campo, coluna ou espaço
|
|
29
34
|
identificável próprio, com rótulo. Hierarquia tipográfica (texto secundário sob um nome) não
|
|
30
35
|
pode fazer uma dimensão parecer explicação de outra. Cor e ícone reforçam; o texto do valor
|
|
31
36
|
permanece sempre presente.
|
|
32
|
-
|
|
37
|
+
6. Deixar ausência, paginação e largura de filtro com o pattern: célula e `DetailField` já
|
|
33
38
|
representam valor ausente (`EmptyValue`, `empty` para o significado do domínio); listas
|
|
34
39
|
paginam pela primitiva `Pagination`; selects inline de filtro têm largura fixa. Não reescrever
|
|
35
40
|
esses defaults na tela.
|
|
36
|
-
|
|
41
|
+
7. Tratar página, filtro, seleção e modal importante como estado navegável por URL quando
|
|
37
42
|
o produto precisa de deep link, back/forward ou refresh.
|
|
38
|
-
|
|
39
|
-
`
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
8. Compor superfícies pela gramática estrutural do catálogo: `Page` contém `PageHeader` e
|
|
44
|
+
`PageBody`; `Content` contém `ContentHeader` e `ContentBody`; Card, Drawer e Pane usam seus
|
|
45
|
+
respectivos `*Body`. Para o caso direto, usar a sintaxe abreviada de `Page` ou `Content` com
|
|
46
|
+
`title`, `description`, `meta` e `actions`; não misturá-la com o header explícito. Ajustar o
|
|
47
|
+
nível do heading pela hierarquia semântica, não pelo destaque visual. O `Page` mantém seu teto
|
|
48
|
+
centralizado padrão de `80rem`.
|
|
49
|
+
9. Manter margem e posicionamento no consumidor; componente reutilizável controla apenas
|
|
42
50
|
seu interior.
|
|
43
|
-
|
|
51
|
+
10. Não definir a fonte raiz em uma biblioteca ou componente. O navegador e a aplicação são
|
|
44
52
|
responsáveis por `font-size` em `html`; medidas escaláveis da UI usam `rem` ou a escala
|
|
45
53
|
relativa do Tailwind. Reservar `px` a hairlines e compensações presas à geometria da borda,
|
|
46
54
|
com justificativa e cobertura explícitas.
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
11. Evoluir um pattern compartilhado apenas quando a recorrência e o contrato estiverem claros.
|
|
56
|
+
12. Tratar tokens de superfície como pares indivisíveis no mesmo fragmento de classes:
|
|
49
57
|
`bg-card text-card-foreground` e `bg-popover text-popover-foreground`. Não depender da
|
|
50
58
|
igualdade atual com `--foreground`, porque o app pode sobrescrever cada par.
|
|
51
|
-
|
|
59
|
+
13. Usar diretamente a escala `rounded-*`; não criar radius por nome de componente quando
|
|
52
60
|
`rounded-xs` a `rounded-2xl` já expressam a forma. Escolher o degrau pela escala visual:
|
|
53
61
|
detalhes e itens internos tendem a `xs`/`sm`, controles e flutuantes compactos a `md`,
|
|
54
62
|
molduras estruturais a `lg`, e superfícies principais ou modais a `xl`. Essa associação
|
|
55
63
|
orienta o default, não cria uma restrição semântica. Em aninhamento, evitar moldura dupla e
|
|
56
64
|
reduzir o raio interno; em grupos conectados, remover os raios das arestas internas. Tamanho
|
|
57
65
|
e forma permanecem eixos separados; usar `shape="pill"` quando a pílula for intencional.
|
|
58
|
-
|
|
66
|
+
14. Distinguir uma região disponível para criação ou vínculo, representada por `Empty` com
|
|
59
67
|
moldura tracejada, de um resultado vazio dentro de uma estrutura existente, que preserva
|
|
60
68
|
a moldura sólida dessa estrutura.
|
|
61
|
-
|
|
69
|
+
15. Testar estados de loading, vazio, erro, sucesso, permissão e interação relevante.
|
|
62
70
|
|
|
63
71
|
## Verificação
|
|
64
72
|
|
|
@@ -76,8 +84,11 @@ Inspecionar visualmente a rota real e validar navegação por URL quando aplicá
|
|
|
76
84
|
- Não criar fetch, schema ou tipo paralelo ao contrato.
|
|
77
85
|
- Não copiar componente da lib para customizar sem antes verificar extensão/composição.
|
|
78
86
|
- Não forçar modal roteável quando o estado é efêmero e sem valor de navegação.
|
|
79
|
-
- Não inferir apresentação de dicionário: nem badge para todo valor, nem
|
|
87
|
+
- Não inferir apresentação de dicionário: nem badge para todo valor, nem contexto ou ícone
|
|
80
88
|
inventados, nem tooltip que repete o rótulo.
|
|
89
|
+
- Não usar nomes de compatibilidade (`tone`, `variant="success"`, `variant="destructive"`) em
|
|
90
|
+
código novo; consultar a página “Contexto & Variante” do catálogo quando a combinação não estiver
|
|
91
|
+
clara.
|
|
81
92
|
- Não repetir fallback de ausência, paginador ou largura de filtro que o pattern já resolve.
|
|
82
93
|
|
|
83
94
|
## Recursos
|
|
@@ -3,11 +3,14 @@
|
|
|
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 estrutural: montar uma página de relatório com `Page
|
|
7
|
-
`ActionFilterBar` separado do renderer, `ItemGroup` para
|
|
8
|
-
`ActionFormDialog`; provar teto padrão de `
|
|
9
|
-
sólido, `Empty` apenas para criação ou vínculo, números sem alinhamento
|
|
10
|
-
`ghost` no modal.
|
|
6
|
+
- Execução estrutural: montar uma página de relatório com `Page > PageHeader + PageBody`, uma seção
|
|
7
|
+
`Content > ContentHeader + ContentBody`, `ActionFilterBar` separado do renderer, `ItemGroup` para
|
|
8
|
+
uma coleção secundária e um `ActionFormDialog`; provar teto padrão de `80rem`, hierarquia por
|
|
9
|
+
`level`, vazio estrutural sólido, `Empty` apenas para criação ou vínculo, números sem alinhamento
|
|
10
|
+
inferido e cancelamento `ghost` no modal.
|
|
11
|
+
- Execução abreviada: montar outra página com `<Page title description actions>` e uma seção com
|
|
12
|
+
`<Content title description actions>`, provando que ambas produzem a mesma anatomia e que o lint
|
|
13
|
+
rejeita a mistura entre props abreviadas e headers explícitos.
|
|
11
14
|
- Execução de forma: compor uma tabela dentro de Card sem moldura duplicada, manter a moldura
|
|
12
15
|
estrutural standalone em `rounded-lg` e os controles internos em `rounded-md`.
|
|
13
16
|
- Reprova: criar uma casca `bg-card` que herda o texto global ou introduzir `rounded-widget`
|
|
@@ -17,11 +20,19 @@
|
|
|
17
20
|
- Reprova: reconstruir manualmente o container de página, usar `Empty` tracejado como vazio de
|
|
18
21
|
tabela, alinhar toda coluna numérica à direita por inferência ou destacar “Cancelar” como ação
|
|
19
22
|
primária em modal.
|
|
23
|
+
- Reprova: deixar `ContentHeader` fora de `Content`, omitir `PageBody`/`ContentBody` na composição
|
|
24
|
+
explícita, usar `CardContent`/`PaneContent` em código novo ou tratar `DialogContent` como Body.
|
|
20
25
|
- Execução de dicionários: montar a lista de clientes com “Pessoa física/Empresa” e
|
|
21
26
|
“Prospect/Cliente”; provar que o tipo ocupa a coluna “Tipo” como classificação (badge `outline`
|
|
22
27
|
com os ícones declarados `user` e `building`), que o estágio ocupa a coluna “Estágio” como badge
|
|
23
28
|
tonal sem `outline`, que nenhuma das duas usa `cells`, que o texto do valor está presente e que
|
|
24
29
|
não há tooltip em “Pessoa física/Empresa” quando a descrição não acrescenta ao rótulo.
|
|
30
|
+
- Execução de contexto: montar ações, badges, alertas e indicadores com o mesmo estado `warning`;
|
|
31
|
+
exigir `context="warning"`, variantes visuais coerentes por componente e tokens da mesma família.
|
|
32
|
+
Uma exclusão continua `destructive` no contrato, mas usa `context="danger"` na projeção visual.
|
|
33
|
+
- Reprova: usar `tone` em componente novo, `variant="success"`, `variant="warning"` ou
|
|
34
|
+
`variant="destructive"`; usar `light`/`dark` como contexto; ou tratar `secondary` como sinônimo
|
|
35
|
+
de estado neutro.
|
|
25
36
|
- Reprova: envolver todo valor de dicionário em badge sem papel declarado, ou escolher a variante
|
|
26
37
|
pelo nome do dicionário.
|
|
27
38
|
- Reprova: colocar duas dimensões independentes na mesma célula sem identificação, como o tipo em
|