@softize/opus 28.0.0 → 30.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.
- package/CHANGELOG.md +79 -0
- package/bin/cli.mjs +68 -3
- package/bin/lib/check.mjs +86 -10
- package/bin/lib/copy.mjs +505 -67
- package/bin/lib/materialize.mjs +99 -1
- package/docs/adr/0028-schedules-run-actions-as-the-system-by-action-opt-in.md +1 -1
- package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
- package/docs/adr/0045-surface-replaces-content-and-card.md +3 -0
- package/docs/adr/0061-claude-md-imports-agents-md.md +41 -0
- package/docs/adr/0062-listas-contidas-e-texto-flexivel.md +68 -0
- package/docs/adr/0063-metadados-das-superficies.md +44 -0
- package/docs/adr/README.md +6 -0
- package/docs/code-style.md +47 -8
- package/docs/protocol.md +1 -1
- package/docs/releasing.md +7 -0
- package/package.json +1 -1
- package/registry/instructions/opus.md +3 -1
- package/registry/skills/build-opus-ui/SKILL.md +6 -4
- package/registry/skills/build-opus-ui/references/evaluations.md +10 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +10 -2
- package/registry/skills/maintain-opus-docs/SKILL.md +2 -1
- package/registry/skills/maintain-opus-docs/references/editorial-standard.md +9 -0
- package/registry/skills/maintain-opus-docs/references/evaluations.md +8 -0
- package/registry/templates/app/pnpm-workspace.yaml +1 -1
- package/src/core/system-actor.ts +1 -1
- package/src/core/types.ts +1 -1
- package/src/ui/components/patterns/list.tsx +19 -10
- package/src/ui/components/patterns/page.tsx +27 -0
- package/src/ui/components/patterns/structural-header.tsx +25 -3
- package/src/ui/components/patterns/surface.tsx +36 -8
- package/src/ui/components/primitives/dialog.tsx +74 -42
- package/src/ui/components/primitives/drawer.tsx +69 -42
- package/src/ui/components/primitives/table.tsx +3 -3
- package/src/ui/docs/content/action-list.md +15 -8
- package/src/ui/docs/content/actions.md +1 -1
- package/src/ui/docs/content/auth.md +3 -3
- package/src/ui/docs/content/cli.md +6 -0
- package/src/ui/docs/content/communication.md +10 -8
- package/src/ui/docs/content/dialog.md +39 -0
- package/src/ui/docs/content/drawer.md +36 -0
- package/src/ui/docs/content/events.md +13 -13
- package/src/ui/docs/content/installation.md +4 -0
- package/src/ui/docs/content/log.md +1 -1
- package/src/ui/docs/content/page.md +30 -1
- package/src/ui/docs/content/queue.md +2 -2
- package/src/ui/docs/content/router.md +1 -1
- package/src/ui/docs/content/scheduler.md +15 -15
- package/src/ui/docs/content/surface.md +41 -8
- package/src/ui/docs/content/table.md +7 -0
- package/src/ui/docs/content/testing.md +4 -4
- package/src/ui/react.tsx +4 -0
- package/src/vite/design.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,85 @@ 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
|
+
## 30.0.0 — 2026-10-16
|
|
11
|
+
|
|
12
|
+
PageMeta, DrawerMeta, DialogMeta e SurfaceMeta organizam badges, datas e autoria em
|
|
13
|
+
faixa própria sob o título e a descrição. Page e Surface também aceitam a prop `meta`.
|
|
14
|
+
|
|
15
|
+
### Breaking
|
|
16
|
+
|
|
17
|
+
**Migração:** SurfaceMeta deixa de ocupar a linha do título. Contadores explícitos usam
|
|
18
|
+
SurfaceCount; a prop `count` conserva o comportamento anterior. Metadados contextuais
|
|
19
|
+
existentes permanecem em SurfaceMeta e passam para a faixa inferior. Revise manualmente
|
|
20
|
+
os usos existentes de SurfaceMeta: os gates não distinguem um contador de metadados.
|
|
21
|
+
Descrições textuais usam SurfaceDescription. DialogMeta não se aplica ao modo de alerta.
|
|
22
|
+
|
|
23
|
+
Tabelas quebram texto entre palavras, preservando palavras curtas inteiras, e ActionList
|
|
24
|
+
mantém a rolagem dentro dos resultados,
|
|
25
|
+
sem alargar a superfície. Filtros inline podem ocupar mais de uma linha. Colunas `fit`
|
|
26
|
+
preservam os valores sem quebra; a distribuição automática considera também os cabeçalhos,
|
|
27
|
+
que podem quebrar quando falta espaço. Colunas de dados não recebem largura mínima forçada;
|
|
28
|
+
seleção, ações de linha e células contendo apenas um switch permanecem compactas. Em tabelas
|
|
29
|
+
compostas diretamente, use `whitespace-nowrap` para valores que devem permanecer inteiros.
|
|
30
|
+
|
|
31
|
+
## 29.0.0 — 2026-10-15
|
|
32
|
+
|
|
33
|
+
O `CLAUDE.md` passa a importar o `AGENTS.md`, e as declarações de copy passam a ser ancoradas pelo
|
|
34
|
+
conteúdo, não pela linha. A seção **Breaking** traz a única migração obrigatória, que atinge só quem
|
|
35
|
+
mantém instruções no `CLAUDE.md` que não estão no `AGENTS.md`.
|
|
36
|
+
|
|
37
|
+
### Breaking
|
|
38
|
+
|
|
39
|
+
`opus setup` mantém o bloco do Opus só no `AGENTS.md` e grava o `CLAUDE.md` como a linha
|
|
40
|
+
`@AGENTS.md`, que importa as instruções para o Claude Code (ADR 0061, no mesmo formato da ADR 0006 da
|
|
41
|
+
Base). Na conversão, o `setup` remove do `CLAUDE.md` o bloco do Opus e descarta o texto do projeto
|
|
42
|
+
que repete o do `AGENTS.md`. Texto que só existe no `CLAUDE.md` é preservado, e o `setup` e o
|
|
43
|
+
`opus pre-push materialization` reprovam até que ele vá para o `AGENTS.md`. Blocos de outros
|
|
44
|
+
pacotes ficam depois do import até que o pacote dono os remova: com uma Base anterior à ADR 0006, o
|
|
45
|
+
`CLAUDE.md` fica com o import e o bloco da Base.
|
|
46
|
+
|
|
47
|
+
Migre movendo para o `AGENTS.md` as instruções que estão só no `CLAUDE.md` e rodando `opus setup`.
|
|
48
|
+
|
|
49
|
+
### Âncora de copy pelo conteúdo
|
|
50
|
+
|
|
51
|
+
- **`copy.dynamic` por trecho.** A declaração seleciona o diagnóstico por `source`, `field` e
|
|
52
|
+
`expression`, o trecho de código que o extrator não resolveu, e continua válida quando linhas
|
|
53
|
+
entram ou saem acima dela. Espaços e quebras de linha do trecho são normalizados e a vírgula
|
|
54
|
+
final antes de `)`, `]` ou `}` é ignorada, para a âncora sobreviver à reformatação. A declaração
|
|
55
|
+
vale para toda ocorrência do mesmo trecho no mesmo campo do arquivo, e `dynamicSurfaces` registra
|
|
56
|
+
as linhas atuais de cada uma. O diagnóstico de conteúdo traz o `field` e a `expression` prontos
|
|
57
|
+
para a declaração.
|
|
58
|
+
- **`copy.exemptions` e `copy.transforms` por texto.** Sem `line`, `source`, `role` e `text`
|
|
59
|
+
selecionam toda ocorrência desse texto com esse papel no arquivo.
|
|
60
|
+
- **Seletores por linha continuam aceitos.** Eles selecionam exatamente um alvo, são aplicados
|
|
61
|
+
antes dos seletores por conteúdo e geram um aviso no `opus copy`. `opus copy --reanchor` converte
|
|
62
|
+
o `base.json` agrupando as declarações por arquivo e campo, ou por arquivo, papel e texto. Um
|
|
63
|
+
grupo só é convertido quando tem uma declaração para cada alvo, para que a âncora por conteúdo não
|
|
64
|
+
cubra um alvo que ninguém declarou; dentro dele, declarações e alvos são pareados em ordem de
|
|
65
|
+
linha, e as que mudaram de linha são listadas para revisão. Declarações idênticas se fundem, e
|
|
66
|
+
duas com motivos diferentes para o mesmo conteúdo continuam por linha, já corrigida. Os seletores
|
|
67
|
+
gravados passam na extração do `opus copy`; a política da Base não é avaliada, então rode
|
|
68
|
+
`opus copy --check` e `base copy check` depois e revise o diff.
|
|
69
|
+
- **`dynamicSurfaces` no inventário.** Uma declaração por conteúdo aparece com `expression` e
|
|
70
|
+
`lines`, as linhas atuais de cada ocorrência, no lugar de `line`. Uma lista `lines` que cresce no
|
|
71
|
+
diff indica que o trecho passou a cobrir uma ocorrência nova.
|
|
72
|
+
- **Erros que dizem onde.** Cada seletor que deixa de encontrar seu alvo aparece na mesma execução,
|
|
73
|
+
com o arquivo, o campo, o trecho e os diagnósticos daquele campo ainda sem declaração, ou com o
|
|
74
|
+
aviso de que o arquivo não existe mais no escopo.
|
|
75
|
+
|
|
76
|
+
### Instruções, documentação e changelog
|
|
77
|
+
|
|
78
|
+
- A instrução gerenciada do Opus passa a dizer que o inventário de copy guarda o hash de todo
|
|
79
|
+
arquivo JS/TS do escopo, inclusive scripts sem copy como `scripts/*.mjs`, e que alterar qualquer
|
|
80
|
+
um deles também exige `opus copy`.
|
|
81
|
+
- Os exemplos da documentação usam identificadores, nomes de action, domínios, eventos e permissões
|
|
82
|
+
em inglês (`ledger.close`, `ledger:close`, `invoice.issued`); rótulos, mensagens e comentários
|
|
83
|
+
continuam em pt-BR. O padrão editorial e a lista de verificação da skill `maintain-opus-docs`
|
|
84
|
+
passam a exigir isso, e um teste do repositório reprova identificadores com palavra em português.
|
|
85
|
+
- Entradas do changelog que já estão na `origin/main` do Opus não mudam mais: uma correção de
|
|
86
|
+
informação publicada entra como entrada nova. O pre-push e o `pnpm release` do repositório
|
|
87
|
+
reprovam a alteração.
|
|
88
|
+
|
|
10
89
|
## 28.0.0 — 2026-10-13
|
|
11
90
|
|
|
12
91
|
### Breaking
|
package/bin/cli.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { cmdGen, helpGen } from './lib/gen.mjs'
|
|
|
15
15
|
import { cmdDb } from './lib/db.mjs'
|
|
16
16
|
import { scanDir, emptyScanVerdict } from './lib/check.mjs'
|
|
17
17
|
import { log } from './lib/cli-shared.mjs'
|
|
18
|
-
import { checkCopyInventory, writeCopyInventory } from './lib/copy.mjs'
|
|
18
|
+
import { checkCopyInventory, reanchorCopySelectors, writeCopyInventory } from './lib/copy.mjs'
|
|
19
19
|
import { createMonorepo, createProject } from './lib/create.mjs'
|
|
20
20
|
import { initProject, repoRootOf, setupUiFoundation } from './lib/init.mjs'
|
|
21
21
|
import { introspect } from './lib/introspect.mjs'
|
|
@@ -63,9 +63,9 @@ async function cmdCheck(dir) {
|
|
|
63
63
|
log('error', `✗ opus check: nenhuma action encontrada em ${rel} (${files} arquivos .ts); ${verdict.message}`)
|
|
64
64
|
process.exit(1)
|
|
65
65
|
}
|
|
66
|
-
const
|
|
66
|
+
const contractSuffix = contracts > 0 ? ` (${contracts} contrato(s))` : ''
|
|
67
67
|
if (findings.length === 0) {
|
|
68
|
-
log('success', `✓ opus check: ${actions} action(s)${
|
|
68
|
+
log('success', `✓ opus check: ${actions} action(s)${contractSuffix}; nenhuma violação encontrada.`)
|
|
69
69
|
return
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -97,10 +97,28 @@ async function cmdMaterializationCheck() {
|
|
|
97
97
|
async function cmdCopy(dir, flags) {
|
|
98
98
|
const selected = path.resolve(process.cwd(), dir ?? '.')
|
|
99
99
|
const root = (await repoRootOf(selected)) ?? selected
|
|
100
|
+
if (flags.reanchor) {
|
|
101
|
+
if (flags.check) {
|
|
102
|
+
log('error', '✗ opus copy: --reanchor grava o base.json e não combina com --check.')
|
|
103
|
+
process.exitCode = 1
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
if (!reportReanchor(reanchorCopySelectors(root))) {
|
|
107
|
+
process.exitCode = 1
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
}
|
|
100
111
|
const result = flags.check ? checkCopyInventory(root) : writeCopyInventory(root)
|
|
101
112
|
for (const diagnostic of result.diagnostics) {
|
|
102
113
|
log('error', `✗ opus copy: ${diagnostic.source}:${diagnostic.line} — ${diagnostic.message}`)
|
|
103
114
|
}
|
|
115
|
+
if (result.legacySelectors > 0) {
|
|
116
|
+
log(
|
|
117
|
+
'warn',
|
|
118
|
+
`! opus copy: ${result.legacySelectors} seletor(es) em base.json ainda usam \`line\`, que quebra a cada edição acima dele; ` +
|
|
119
|
+
'rode `opus copy --reanchor` para ancorá-los pelo conteúdo.',
|
|
120
|
+
)
|
|
121
|
+
}
|
|
104
122
|
if (!result.ok) {
|
|
105
123
|
if (result.reason === 'missing') {
|
|
106
124
|
log('error', `✗ opus copy: inventário ausente em ${path.relative(root, result.path)}; rode \`opus copy\` e versione o resultado.`)
|
|
@@ -121,6 +139,40 @@ async function cmdCopy(dir, flags) {
|
|
|
121
139
|
else log('success', `✓ opus copy: ${location} já estava atualizado (${summary}).`)
|
|
122
140
|
}
|
|
123
141
|
|
|
142
|
+
function reportReanchor(result) {
|
|
143
|
+
if (!result.ok) {
|
|
144
|
+
for (const diagnostic of result.diagnostics) {
|
|
145
|
+
log('error', `✗ opus copy --reanchor: ${diagnostic.source}:${diagnostic.line} — ${diagnostic.message}`)
|
|
146
|
+
}
|
|
147
|
+
log('error', '✗ opus copy --reanchor: resolva os problemas de leitura acima antes de reancorar; base.json não foi alterado.')
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
const { converted, realigned, relined, merged, unresolved } = result.report
|
|
151
|
+
const anchor = (item) => (item.anchor.expression === undefined ? `"${item.anchor.text}"` : `\`${item.anchor.expression}\``)
|
|
152
|
+
if (converted.length > 0) log('success', `✓ opus copy --reanchor: ${converted.length} seletor(es) passaram a ser ancorados pelo conteúdo.`)
|
|
153
|
+
// copy.dynamic identifica o alvo pelo campo; exceções e transformações, pelo papel do texto.
|
|
154
|
+
const target = (item) => (item.list === 'dynamic' ? `\`${item.selector.field}\`` : `(${item.selector.role})`)
|
|
155
|
+
if (realigned.length > 0) {
|
|
156
|
+
log('warn', `! opus copy --reanchor: ${realigned.length} âncora(s) quebradas pela mudança de linha foram convertidas; revise cada uma no diff:`)
|
|
157
|
+
for (const item of realigned) {
|
|
158
|
+
console.log(` copy.${item.list}[${item.index}] ${item.selector.source} ${target(item)} linha ${item.from} → ${item.to}: ${anchor(item)}`)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (relined.length > 0) {
|
|
162
|
+
log('warn', `! opus copy --reanchor: ${relined.length} seletor(es) continuam por linha, com a linha corrigida; revise cada um no diff:`)
|
|
163
|
+
for (const item of relined) {
|
|
164
|
+
console.log(` copy.${item.list}[${item.index}] ${item.selector.source} linha ${item.from} → ${item.to}: ${item.reason}`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (merged.length > 0) log('success', `✓ opus copy --reanchor: ${merged.length} declaração(ões) repetida(s) fundida(s) com a idêntica.`)
|
|
168
|
+
if (unresolved.length > 0) {
|
|
169
|
+
log('warn', `! opus copy --reanchor: ${unresolved.length} seletor(es) continuam por linha:`)
|
|
170
|
+
for (const item of unresolved) console.log(` copy.${item.list}[${item.index}]: ${item.reason}`)
|
|
171
|
+
}
|
|
172
|
+
if (!result.changed) log('success', '✓ opus copy --reanchor: nada a converter em base.json.')
|
|
173
|
+
return true
|
|
174
|
+
}
|
|
175
|
+
|
|
124
176
|
async function cmdCreate(dir, flags) {
|
|
125
177
|
if (!dir) {
|
|
126
178
|
log('error', 'Uso: opus create <dir> — o nome do diretório vira o nome do app (--monorepo: a raiz do workspace).')
|
|
@@ -273,6 +325,10 @@ function parseArgs(argv) {
|
|
|
273
325
|
flags.json = true
|
|
274
326
|
continue
|
|
275
327
|
}
|
|
328
|
+
if (arg === '--reanchor') {
|
|
329
|
+
flags.reanchor = true
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
276
332
|
if (arg === '--monorepo') {
|
|
277
333
|
flags.monorepo = true
|
|
278
334
|
continue
|
|
@@ -334,6 +390,7 @@ Flags
|
|
|
334
390
|
--help, -h Ajuda do comando.
|
|
335
391
|
--check (copy) Compara sem escrever; falha se o inventário estiver ausente ou
|
|
336
392
|
desatualizado.
|
|
393
|
+
--reanchor (copy) Ancora pelo conteúdo os seletores do base.json que usam linha.
|
|
337
394
|
--json (introspect, seed) Saída estruturada em JSON.
|
|
338
395
|
--monorepo (create) Cria a raiz do workspace em vez de um app.
|
|
339
396
|
--config <path> (gen, db, seed) Caminho do opus.config.ts. Default: ./opus.config.ts.
|
|
@@ -483,6 +540,14 @@ comprova que o extrator executou sobre uma fonte.
|
|
|
483
540
|
|
|
484
541
|
Flags:
|
|
485
542
|
--check Compara sem escrever; falha em ausência, drift ou copy dinâmica
|
|
543
|
+
sem declaração.
|
|
544
|
+
--reanchor Converte os seletores de copy.dynamic, copy.exemptions e
|
|
545
|
+
copy.transforms que ainda usam \`line\` para seletores pelo
|
|
546
|
+
conteúdo, grava o base.json e regenera o inventário. Só
|
|
547
|
+
converte quando há uma declaração para cada alvo, para não
|
|
548
|
+
ampliar nenhuma; as que mudaram de linha são listadas para
|
|
549
|
+
revisão, e o que não dá para converter com segurança fica
|
|
550
|
+
como está. Depois, rode base copy check e revise o diff.
|
|
486
551
|
`)
|
|
487
552
|
return
|
|
488
553
|
}
|
package/bin/lib/check.mjs
CHANGED
|
@@ -159,6 +159,7 @@ const UI_STRUCTURAL_PARENTS = new Map([
|
|
|
159
159
|
["PageFooter", new Set(["Page"])],
|
|
160
160
|
["PageTitle", new Set(["PageHeader", "PageIntro"])],
|
|
161
161
|
["PageDescription", new Set(["PageHeader", "PageIntro"])],
|
|
162
|
+
["PageMeta", new Set(["PageHeader", "PageIntro"])],
|
|
162
163
|
["PageBack", new Set(["PageHeader"])],
|
|
163
164
|
["PageNavigation", new Set(["PageHeader"])],
|
|
164
165
|
["PageActions", new Set(["PageHeader"])],
|
|
@@ -171,6 +172,7 @@ const UI_STRUCTURAL_PARENTS = new Map([
|
|
|
171
172
|
["SurfaceTitle", new Set(["SurfaceHeader"])],
|
|
172
173
|
["SurfaceDescription", new Set(["SurfaceHeader"])],
|
|
173
174
|
["SurfaceMeta", new Set(["SurfaceHeader"])],
|
|
175
|
+
["SurfaceCount", new Set(["SurfaceHeader"])],
|
|
174
176
|
["SurfaceActions", new Set(["SurfaceHeader"])],
|
|
175
177
|
["DialogHeader", new Set(["DialogContent"])],
|
|
176
178
|
["DialogBody", new Set(["DialogContent"])],
|
|
@@ -181,6 +183,7 @@ const UI_STRUCTURAL_PARENTS = new Map([
|
|
|
181
183
|
["DialogMedia", new Set(["DialogHeader"])],
|
|
182
184
|
["DialogTitle", new Set(["DialogHeader", "DialogIntro"])],
|
|
183
185
|
["DialogDescription", new Set(["DialogHeader", "DialogIntro"])],
|
|
186
|
+
["DialogMeta", new Set(["DialogHeader", "DialogIntro"])],
|
|
184
187
|
["DialogActions", new Set(["DialogHeader"])],
|
|
185
188
|
["DrawerHeader", new Set(["DrawerContent"])],
|
|
186
189
|
["DrawerBody", new Set(["DrawerContent"])],
|
|
@@ -191,6 +194,7 @@ const UI_STRUCTURAL_PARENTS = new Map([
|
|
|
191
194
|
["DrawerMedia", new Set(["DrawerHeader"])],
|
|
192
195
|
["DrawerTitle", new Set(["DrawerHeader", "DrawerIntro"])],
|
|
193
196
|
["DrawerDescription", new Set(["DrawerHeader", "DrawerIntro"])],
|
|
197
|
+
["DrawerMeta", new Set(["DrawerHeader", "DrawerIntro"])],
|
|
194
198
|
["DrawerActions", new Set(["DrawerHeader"])],
|
|
195
199
|
["AlertMedia", new Set(["Alert"])],
|
|
196
200
|
["AlertHeader", new Set(["Alert"])],
|
|
@@ -224,7 +228,7 @@ const UI_STRUCTURAL_ROOTS = new Map([
|
|
|
224
228
|
body: "PageBody",
|
|
225
229
|
footer: "PageFooter",
|
|
226
230
|
headerRequired: false,
|
|
227
|
-
shorthand: new Set(["title", "description", "actions"]),
|
|
231
|
+
shorthand: new Set(["title", "description", "meta", "actions"]),
|
|
228
232
|
// O contador saiu do título da página (ADR 0009). Tirar `count` do shorthand não proíbe
|
|
229
233
|
// nada sozinho — só apaga o marcador que fazia o gate enxergar aquele Page —, então a
|
|
230
234
|
// prop removida é cobrada aqui, nas duas formas de composição.
|
|
@@ -242,7 +246,14 @@ const UI_STRUCTURAL_ROOTS = new Map([
|
|
|
242
246
|
// Cada região é opcional, a forma curta convive com SurfaceBody e SurfaceFooter, e sem
|
|
243
247
|
// região nenhuma a Surface é uma caixa de filhos livres (ADR 0045).
|
|
244
248
|
flexible: true,
|
|
245
|
-
shorthand: new Set([
|
|
249
|
+
shorthand: new Set([
|
|
250
|
+
"title",
|
|
251
|
+
"media",
|
|
252
|
+
"description",
|
|
253
|
+
"meta",
|
|
254
|
+
"count",
|
|
255
|
+
"actions",
|
|
256
|
+
]),
|
|
246
257
|
},
|
|
247
258
|
],
|
|
248
259
|
]);
|
|
@@ -260,6 +271,10 @@ const UI_STRICT_DIRECT_COMPONENTS = new Set([
|
|
|
260
271
|
"SurfaceTitle",
|
|
261
272
|
"SurfaceDescription",
|
|
262
273
|
"SurfaceMeta",
|
|
274
|
+
"SurfaceCount",
|
|
275
|
+
"PageMeta",
|
|
276
|
+
"DrawerMeta",
|
|
277
|
+
"DialogMeta",
|
|
263
278
|
"SurfaceActions",
|
|
264
279
|
"AlertMedia",
|
|
265
280
|
"AlertHeader",
|
|
@@ -289,10 +304,6 @@ const UI_REMOVED_PROPS = new Map([
|
|
|
289
304
|
["layout", "use `Surface` para anatomia visual contínua ou seccionada"],
|
|
290
305
|
]),
|
|
291
306
|
],
|
|
292
|
-
[
|
|
293
|
-
"Surface",
|
|
294
|
-
new Map([["meta", "use `count` para o total ao lado do título"]]),
|
|
295
|
-
],
|
|
296
307
|
[
|
|
297
308
|
"DialogMedia",
|
|
298
309
|
new Map([
|
|
@@ -340,7 +351,7 @@ const UI_REMOVED_COMPONENTS = new Map([
|
|
|
340
351
|
],
|
|
341
352
|
["ContentTitle", "use `SurfaceTitle`"],
|
|
342
353
|
["ContentDescription", "use `SurfaceDescription`"],
|
|
343
|
-
["ContentMeta", "use `
|
|
354
|
+
["ContentMeta", "use `SurfaceCount`"],
|
|
344
355
|
["ContentActions", "use `SurfaceActions`"],
|
|
345
356
|
["ContentBody", "use `SurfaceBody`"],
|
|
346
357
|
["Card", 'use `Surface framed elevation="raised"`'],
|
|
@@ -364,7 +375,7 @@ const UI_STRUCTURAL_HEADERS = new Map([
|
|
|
364
375
|
"PageIntro",
|
|
365
376
|
{
|
|
366
377
|
title: "PageTitle",
|
|
367
|
-
optional: new Set(["PageIntroActions", "PageDescription"]),
|
|
378
|
+
optional: new Set(["PageIntroActions", "PageDescription", "PageMeta"]),
|
|
368
379
|
shorthand: new Set(),
|
|
369
380
|
},
|
|
370
381
|
],
|
|
@@ -372,7 +383,11 @@ const UI_STRUCTURAL_HEADERS = new Map([
|
|
|
372
383
|
"DialogIntro",
|
|
373
384
|
{
|
|
374
385
|
title: "DialogTitle",
|
|
375
|
-
optional: new Set([
|
|
386
|
+
optional: new Set([
|
|
387
|
+
"DialogIntroActions",
|
|
388
|
+
"DialogDescription",
|
|
389
|
+
"DialogMeta",
|
|
390
|
+
]),
|
|
376
391
|
shorthand: new Set(),
|
|
377
392
|
},
|
|
378
393
|
],
|
|
@@ -380,7 +395,11 @@ const UI_STRUCTURAL_HEADERS = new Map([
|
|
|
380
395
|
"DrawerIntro",
|
|
381
396
|
{
|
|
382
397
|
title: "DrawerTitle",
|
|
383
|
-
optional: new Set([
|
|
398
|
+
optional: new Set([
|
|
399
|
+
"DrawerIntroActions",
|
|
400
|
+
"DrawerDescription",
|
|
401
|
+
"DrawerMeta",
|
|
402
|
+
]),
|
|
384
403
|
shorthand: new Set(),
|
|
385
404
|
},
|
|
386
405
|
],
|
|
@@ -393,6 +412,7 @@ const UI_STRUCTURAL_HEADERS = new Map([
|
|
|
393
412
|
"PageNavigation",
|
|
394
413
|
"PageActions",
|
|
395
414
|
"PageDescription",
|
|
415
|
+
"PageMeta",
|
|
396
416
|
]),
|
|
397
417
|
exclusive: [new Set(["PageBack", "PageNavigation"])],
|
|
398
418
|
shorthand: new Set(),
|
|
@@ -407,6 +427,7 @@ const UI_STRUCTURAL_HEADERS = new Map([
|
|
|
407
427
|
"SurfaceMedia",
|
|
408
428
|
"SurfaceDescription",
|
|
409
429
|
"SurfaceMeta",
|
|
430
|
+
"SurfaceCount",
|
|
410
431
|
"SurfaceActions",
|
|
411
432
|
]),
|
|
412
433
|
shorthand: new Set(),
|
|
@@ -2116,6 +2137,30 @@ export function checkUiStructure(file, text) {
|
|
|
2116
2137
|
"ui-structure-removed-component",
|
|
2117
2138
|
);
|
|
2118
2139
|
}
|
|
2140
|
+
if (component === "DialogMeta") {
|
|
2141
|
+
let ancestor = node.parent;
|
|
2142
|
+
while (ancestor !== undefined) {
|
|
2143
|
+
if (
|
|
2144
|
+
ts.isJsxElement(ancestor) &&
|
|
2145
|
+
imports.get(jsxLocalName(ancestor)) === "Dialog"
|
|
2146
|
+
) {
|
|
2147
|
+
const modes = jsxStringAttributeValues(
|
|
2148
|
+
jsxOpening(ancestor),
|
|
2149
|
+
"mode",
|
|
2150
|
+
);
|
|
2151
|
+
if (modes.length === 1 && modes[0] === "alert") {
|
|
2152
|
+
add(
|
|
2153
|
+
node,
|
|
2154
|
+
component,
|
|
2155
|
+
'DialogMeta não está disponível em Dialog com mode="alert"; coloque as informações no corpo do alerta.',
|
|
2156
|
+
"ui-structure-meta",
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
break;
|
|
2160
|
+
}
|
|
2161
|
+
ancestor = ancestor.parent;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2119
2164
|
const parents =
|
|
2120
2165
|
component === null ? undefined : UI_STRUCTURAL_PARENTS.get(component);
|
|
2121
2166
|
if (parents !== undefined) {
|
|
@@ -2234,6 +2279,37 @@ export function checkUiStructure(file, text) {
|
|
|
2234
2279
|
}
|
|
2235
2280
|
}
|
|
2236
2281
|
|
|
2282
|
+
if (
|
|
2283
|
+
[
|
|
2284
|
+
"PageHeader",
|
|
2285
|
+
"PageIntro",
|
|
2286
|
+
"DialogHeader",
|
|
2287
|
+
"DialogIntro",
|
|
2288
|
+
"DrawerHeader",
|
|
2289
|
+
"DrawerIntro",
|
|
2290
|
+
"SurfaceHeader",
|
|
2291
|
+
].includes(component)
|
|
2292
|
+
) {
|
|
2293
|
+
const family = component.replace(/(?:Header|Intro)$/, "");
|
|
2294
|
+
const children = directStructuralChildren(node, imports);
|
|
2295
|
+
const metas = children.names.filter(
|
|
2296
|
+
(name) => name === `${family}Meta`,
|
|
2297
|
+
).length;
|
|
2298
|
+
if (
|
|
2299
|
+
metas > 1 ||
|
|
2300
|
+
(metas > 0 &&
|
|
2301
|
+
!children.names.includes(`${family}Title`) &&
|
|
2302
|
+
!children.hasOpaque)
|
|
2303
|
+
) {
|
|
2304
|
+
add(
|
|
2305
|
+
node,
|
|
2306
|
+
component,
|
|
2307
|
+
`${component} aceita um único ${family}Meta e exige ${family}Title para exibi-lo.`,
|
|
2308
|
+
"ui-structure-meta",
|
|
2309
|
+
);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2237
2313
|
const header =
|
|
2238
2314
|
component === null ? undefined : UI_STRUCTURAL_HEADERS.get(component);
|
|
2239
2315
|
if (header !== undefined) {
|