@softize/opus 15.2.2 → 16.1.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 +60 -18
- package/bin/lib/check.mjs +98 -15
- package/bin/lib/copy.mjs +811 -314
- package/bin/lib/gen-manifest.mjs +24 -23
- package/bin/lib/gen-runner.mjs +188 -148
- package/docs/adr/0010-page-header-owns-page-chrome.md +3 -4
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -3
- package/docs/adr/0012-modal-header-only-names-the-surface.md +45 -0
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +92 -0
- package/docs/code-style.md +24 -19
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +43 -26
- package/src/core/presentation.ts +512 -0
- package/src/presentation/index.ts +1 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +26 -9
- package/src/ui/components/patterns/confirm.tsx +34 -29
- package/src/ui/components/patterns/form-dialog.tsx +20 -8
- package/src/ui/components/patterns/list.tsx +26 -9
- package/src/ui/components/patterns/page.tsx +99 -139
- package/src/ui/components/patterns/presentation.tsx +539 -0
- package/src/ui/components/patterns/sidebar.tsx +3 -3
- package/src/ui/components/patterns/trigger.tsx +38 -17
- package/src/ui/components/primitives/button-group.tsx +53 -43
- package/src/ui/components/primitives/command.tsx +30 -72
- package/src/ui/components/primitives/dialog.tsx +23 -89
- package/src/ui/components/primitives/drawer.tsx +8 -34
- package/src/ui/docs/content/action-form-dialog.md +12 -10
- package/src/ui/docs/content/action-list-dialog.md +22 -17
- package/src/ui/docs/content/button.md +52 -35
- package/src/ui/docs/content/communication.md +26 -26
- package/src/ui/docs/content/dialog.md +173 -154
- package/src/ui/docs/content/drawer.md +12 -11
- package/src/ui/docs/content/page.md +72 -91
- package/src/ui/docs/content/presentation.md +188 -0
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/meta.ts +8 -2
- package/src/ui/react.tsx +16 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,15 +7,54 @@ 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
|
+
## 16.1.0 — 2026-09-11
|
|
11
|
+
|
|
12
|
+
`PresentationDevtoolsProvider`, `usePresentationRegistration` e `PresentationDevtools` permitem
|
|
13
|
+
que o shell reúna as Presentations ativas em um único launcher de desenvolvimento. Cada recurso
|
|
14
|
+
registra sua definição e o estado vivo da invocação; a pessoa escolhe qual snapshot inspecionar no
|
|
15
|
+
menu. O JSON continua mascarando dados sensíveis e só é materializado depois da escolha, evitando
|
|
16
|
+
serialização desnecessária durante a renderização. O consumidor deve montar o provider, os registros
|
|
17
|
+
e o launcher apenas em ambiente de desenvolvimento.
|
|
18
|
+
|
|
19
|
+
## 16.0.0 — 2026-09-11
|
|
20
|
+
|
|
21
|
+
Opus 16 introduz `Presentation` como artefato público declarativo, disponível também pelo subpath
|
|
22
|
+
`@softize/opus/presentation`, e sua composição React portátil entre Page, Dialog e Drawer. A
|
|
23
|
+
`PresentationInvocation` guarda o identificador do recurso, a surface, input estritamente JSON e a
|
|
24
|
+
pilha de frames usada por `back`; `openPresentation`, `backPresentation`, `closePresentation` e
|
|
25
|
+
`applyPresentationEffects` oferecem transições puras para o renderer. O efeito `navigate` declara
|
|
26
|
+
input por bindings e escolhe entre `push` e `replace`.
|
|
27
|
+
|
|
28
|
+
`PresentationInspector` oferece, sob demanda, um snapshot único da definição, invocação, bindings
|
|
29
|
+
resolvidos e diagnósticos. Chaves que indicam senha, segredo, token, cookie, autorização ou chave de
|
|
30
|
+
API são mascaradas antes de o JSON aparecer ou ser copiado. A Lens continua expondo somente a
|
|
31
|
+
definição estática projetada no manifest.
|
|
32
|
+
|
|
33
|
+
`PageFooter` entra na composição explícita de Page. `PageHeader` organiza retorno ou breadcrumb,
|
|
34
|
+
título e ações na mesma linha, acompanhando a anatomia usada pela Presentation; `PageIntro` também
|
|
35
|
+
aceita a navegação própria do recurso quando existe um `PageShell`. `PageBack` vira um controle
|
|
36
|
+
somente com ícone; o nome do destino continua no nome acessível e no tooltip. `ButtonGroup` ganha a
|
|
37
|
+
distribuição `equal` para ações compactas de mesma largura. A Sidebar recolhida passa a `4rem`, com
|
|
38
|
+
itens de `2.5rem` e respiro de `0.75rem`.
|
|
39
|
+
|
|
40
|
+
**Breaking:** cabeçalhos de Dialog e Drawer passam a nomear apenas a superfície. Remova
|
|
41
|
+
`DialogDescription` e `DrawerDescription` e leve explicações para o body ou para o `help` do campo
|
|
42
|
+
correspondente. Nos wrappers `ActionFormDialog` e `ActionListDialog`, remova `description` pelo
|
|
43
|
+
mesmo motivo. Nas APIs imperativas `alert`, `confirm`, `prompt` e `choose`, e em
|
|
44
|
+
`ActionTrigger.confirm`, renomeie `description` para `body`. `PageHeader.variant` e o tipo
|
|
45
|
+
`PageHeaderVariant` deixam de existir; use `PageShell` para a barra persistente e `PageIntro` para
|
|
46
|
+
a introdução do conteúdo.
|
|
47
|
+
|
|
48
|
+
|
|
10
49
|
## 15.2.2 — 2026-09-11
|
|
11
50
|
|
|
12
51
|
O acesso descritivo de Produtos de Dados usa `permissionContexts` no lugar do nome genérico
|
|
13
|
-
`contexts`. A qualificação evita confundir
|
|
52
|
+
`contexts`. A qualificação evita confundir contexto do framework, contexto de tela, domínio de
|
|
14
53
|
dados e vocabulário de autorização nas projeções do manifest e da Lens.
|
|
15
54
|
|
|
16
55
|
**Migração recomendada:** em declarações `defineDataProduct`, renomeie `access.contexts` para
|
|
17
56
|
`access.permissionContexts` e regenere o manifest. O nome anterior continua aceito e projetado em
|
|
18
|
-
toda a série 15.x como alias
|
|
57
|
+
toda a série 15.x como alias obsoleto; consumidores novos devem ler somente o nome qualificado.
|
|
19
58
|
|
|
20
59
|
## 15.2.1 — 2026-09-11
|
|
21
60
|
|
|
@@ -214,7 +253,7 @@ resizable e slider passam a ejetados no `registry.lock.json`.
|
|
|
214
253
|
Descontinuados, com aviso no editor e sem mudança de comportamento: as variantes legadas
|
|
215
254
|
(`default`, `secondary`, `destructive`, `success`, `warning`, `info`), `confirm()`, `ConfirmHost`,
|
|
216
255
|
`FieldSpec.hint` (use `help`), `ShellNav*` (use `SidebarNav`) e `TbdlibProvider`, renomeado para
|
|
217
|
-
`OpusProvider`. `'danger'` deixa de ser aceito como
|
|
256
|
+
`OpusProvider`. `'danger'` deixa de ser aceito como _variante_ legada de Badge e Alert no tipo (era
|
|
218
257
|
um contexto disfarçado): declare `context="danger"`. O botão de confirmar do `ActionTrigger` passa a
|
|
219
258
|
ser sempre `solid` — `danger` quando a action é `destructive`, senão o `context` do gatilho — e não
|
|
220
259
|
herda mais o `variant` do gatilho; a permissão negada (`authorization`/`authentication`) entra na
|
|
@@ -454,6 +493,7 @@ Os patterns existentes passam a reutilizar os primitives canônicos: `ActionForm
|
|
|
454
493
|
`FieldGroup`, descrições e erros associados ao controle; `ActionList` usa a moldura de `Table` e
|
|
455
494
|
os estados seguros de `DataState`, preservando a tentativa de recuperação; e a confirmação de uma
|
|
456
495
|
`ActionTrigger` destrutiva mantém o tratamento destrutivo mesmo quando o gatilho visual é discreto.
|
|
496
|
+
|
|
457
497
|
## 12.5.5 — 2026-08-25
|
|
458
498
|
|
|
459
499
|
Só documentação: leva ao pacote a correção de nota que a 12.5.4 recebeu no repositório depois de
|
|
@@ -800,6 +840,7 @@ todas as mensagens que apenas têm comportamento `sticky` disponível.
|
|
|
800
840
|
fronteira memoizada separada do estado do composer, e cada bloco Markdown reaproveita o
|
|
801
841
|
resultado enquanto seu conteúdo não muda. Conversas longas mantêm o custo de digitação
|
|
802
842
|
constante; durante streaming, somente a mensagem alterada volta a passar pelo parser.
|
|
843
|
+
|
|
803
844
|
## 9.0.3 — 2026-08-12
|
|
804
845
|
|
|
805
846
|
**O pre-push ignora worktrees internos do Maestro.** Projetos sob `.maestro/` pertencem
|
|
@@ -1033,11 +1074,11 @@ de backend de pé — nem, pior, falar com prod por um proxy esquecido no vite.c
|
|
|
1033
1074
|
`vite --mode design`, o vite.config traz `opusDesign({ devBackend: true })` (dev normal
|
|
1034
1075
|
serve o handler real via /api; design roteia pro mock) e o domínio-exemplo mostra o
|
|
1035
1076
|
split canônico: `defineContract` com `mockHandler` alimentado pelo schema (`fakeMany`)
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1077
|
+
- `bindAction` com o handler in-memory — o App consome o CONTRATO via `useListAction`
|
|
1078
|
+
(fim do "chama o handler direto, sem servidor ainda"), e o teste cobre o bound E o
|
|
1079
|
+
contrato puro em design. O plugin também marca `@softize/opus` como `ssr.noExternal`:
|
|
1080
|
+
o pacote embarca TS source e, externalizado, o node recusa TS sob node_modules no
|
|
1081
|
+
load SSR do entry.
|
|
1041
1082
|
- **`options: { kind: 'dictionary', ref }` agora resolve.** O `TbdlibProvider` ganha
|
|
1042
1083
|
`dicts` (`Record<ref, DictLike>` — o `DictType` do `t.dict` encaixa direto) e a UI
|
|
1043
1084
|
resolve a origem declarada no contrato: no `ActionList`, filtro e chips mostram os
|
|
@@ -1292,7 +1333,7 @@ todos. Elemento novo (table, figure, dl) entra sozinho.
|
|
|
1292
1333
|
|
|
1293
1334
|
**O respiro fantasma nas mensagens.** As bolhas do `Chat` tentavam zerar a margem do
|
|
1294
1335
|
primeiro e do último bloco com `[&>*:first-child]:mt-0`, mas o primeiro filho da bolha é o
|
|
1295
|
-
|
|
1336
|
+
_wrapper_ do `prose` — que não tem margem; quem tem é o `<p>` um nível abaixo. A
|
|
1296
1337
|
compensação nunca surtiu efeito, e cada mensagem carregava ~16px invisíveis em cima e
|
|
1297
1338
|
embaixo. Agora mora dentro do `Markdown`, onde alcança o bloco certo.
|
|
1298
1339
|
|
|
@@ -1644,7 +1685,7 @@ ver a nota de migração do t.json()).
|
|
|
1644
1685
|
- **`readOnlyContextPool` (`@softize/opus/data/readonly-pool`).** SQL escrito por LLM
|
|
1645
1686
|
com as QUATRO defesas dos ADRs 0003/0007 — todas no BANCO, nenhuma em regex: pool com
|
|
1646
1687
|
teto (`max` do pool e/ou `maxConcurrent` com fila própria), `BEGIN TRANSACTION READ
|
|
1647
|
-
|
|
1688
|
+
ONLY`, `SET LOCAL ROLE` por transação (alcance do contexto; roles/grants
|
|
1648
1689
|
default-fechado são migração sua) e protocolo estendido (o SQL roda sempre com array
|
|
1649
1690
|
de valores — multi-sentença é recusada pelo protocolo). Saindo, `DISCARD ALL` devolve
|
|
1650
1691
|
a conexão limpa; limpeza que falha DESTRÓI a conexão (nunca volta suja ao pool).
|
|
@@ -1676,7 +1717,7 @@ novos, todos aditivos).
|
|
|
1676
1717
|
mas roda qualquer um (o hook usa name/kind e lê `invalidates`) — disparar um
|
|
1677
1718
|
FormContract fora de form (modal composto que submete `role.update`) exigia
|
|
1678
1719
|
`as unknown as SimpleContract<...>`. Agora o parâmetro é `ActionContract<TInput,
|
|
1679
|
-
|
|
1720
|
+
TData>`; o cast morre. Nenhuma chamada existente muda.
|
|
1680
1721
|
- **`useActionFormContext()` (novo export, `ui/react`).** A válvula de escape pra
|
|
1681
1722
|
CONTROLE CUSTOM no modo composição do `<ActionForm>`: campo com UI própria (a grade
|
|
1682
1723
|
de permissões que forçou abandonar o form inteiro) participa do form do contrato —
|
|
@@ -1765,9 +1806,9 @@ o guard agora moram na base, então qualquer consumidor de IA do Opus nasce prot
|
|
|
1765
1806
|
breaking** (tipos/campos/exports novos, aditivos).
|
|
1766
1807
|
|
|
1767
1808
|
- **Contrato de pergunta (novos tipos, `@softize/opus`).** `AskQuestion` (enunciado + `header`
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1809
|
+
- `options` + `multiSelect`), `AskOption` e `AskAnswer` (rótulos escolhidos + texto livre) —
|
|
1810
|
+
a forma única que todo mundo fala. Espelha o AskUserQuestion do Claude Code, mas servida por
|
|
1811
|
+
um round-trip REAL.
|
|
1771
1812
|
- **`AiRunOptions.onAsk` / `BoundAiRunOptions.onAsk` (novo hook).** O round-trip de "perguntar
|
|
1772
1813
|
à pessoa": com ele, o driver INTERCEPTA a tool reservada `ask_user` (injetada automaticamente
|
|
1773
1814
|
no catálogo) e chama o handler, devolvendo as respostas ao modelo. Passa direto por `ctx.ai`
|
|
@@ -1859,8 +1900,8 @@ Correções da 2.35.0 (que saiu antes de passar pelo review). **Prefira esta à
|
|
|
1859
1900
|
|
|
1860
1901
|
## 2.35.0 — 2026-07-21
|
|
1861
1902
|
|
|
1862
|
-
Sidebar recolhível no `AppShell` e o **rem base passa a 15px**.
|
|
1863
|
-
que a 2.35.1 conserta — use a 2.35.1.)
|
|
1903
|
+
Sidebar recolhível no `AppShell` e o **rem base passa a 15px**. _(Publicada com os defeitos
|
|
1904
|
+
que a 2.35.1 conserta — use a 2.35.1.)_
|
|
1864
1905
|
|
|
1865
1906
|
- **Base do rem em 15px. Visual: GLOBAL — e o delta depende de onde você está.** Todo `rem`
|
|
1866
1907
|
deriva daqui, então tipografia, espaçamento e tamanho de controle mudam em toda tela no
|
|
@@ -1875,9 +1916,10 @@ que a 2.35.1 conserta — use a 2.35.1.)*
|
|
|
1875
1916
|
reincidência). Quem quiser a escala densa fixa `html { font-size: 14px }` no próprio entry.
|
|
1876
1917
|
**Consumidor que já sobrescrevia pra 15px pode remover o override** — vira redundante, não
|
|
1877
1918
|
conflitante (mesmo valor), então dá pra limpar sem pressa.
|
|
1878
|
-
<br
|
|
1919
|
+
<br>_Correção de registro:_ a entrada da 2.31.5 saiu com dois erros (chamava 15px de "a
|
|
1879
1920
|
escala anterior" e dizia "encolhe ~6,7%"), ambos por supor uma baseline de 15px que nunca
|
|
1880
1921
|
existiu. Corrigidos lá, com nota.
|
|
1922
|
+
|
|
1881
1923
|
- **`AppShell` ganha `collapsible` (opt-in).** Sem a prop, nada muda. Com ela, o shell
|
|
1882
1924
|
controla a **largura** e publica o estado; **o que some é decisão do conteúdo**, porque o
|
|
1883
1925
|
`sidebarNav` é do consumidor. Em vez de exigir estado no app, o aside expõe
|
|
@@ -1946,7 +1988,7 @@ para cá.**
|
|
|
1946
1988
|
empilhava uma entrada. Era o caso pt-BR inteiro.
|
|
1947
1989
|
- Some a última cópia à mão do mecanismo, que tinha sobrado dentro do próprio
|
|
1948
1990
|
`registry.tsx` (o redirect de `/docs/ui` pra `/ui` — agora `navigate('/ui', { replace:
|
|
1949
|
-
|
|
1991
|
+
true })`, porque redirect não merece entrada no histórico).
|
|
1950
1992
|
|
|
1951
1993
|
## 2.34.0 — 2026-07-21
|
|
1952
1994
|
|
package/bin/lib/check.mjs
CHANGED
|
@@ -107,9 +107,10 @@ const UI_STRUCTURAL_PARENTS = new Map([
|
|
|
107
107
|
["PageHeader", new Set(["Page"])],
|
|
108
108
|
["PageIntro", new Set(["Page"])],
|
|
109
109
|
["PageBody", new Set(["Page"])],
|
|
110
|
+
["PageFooter", new Set(["Page"])],
|
|
110
111
|
["PageTitle", new Set(["PageHeader", "PageIntro"])],
|
|
111
|
-
["PageBack", new Set(["PageHeader"])],
|
|
112
|
-
["PageNavigation", new Set(["PageHeader"])],
|
|
112
|
+
["PageBack", new Set(["PageHeader", "PageIntro"])],
|
|
113
|
+
["PageNavigation", new Set(["PageHeader", "PageIntro"])],
|
|
113
114
|
["PageDescription", new Set(["PageHeader", "PageIntro"])],
|
|
114
115
|
["PageActions", new Set(["PageHeader", "PageIntro"])],
|
|
115
116
|
["ContentHeader", new Set(["Content"])],
|
|
@@ -169,11 +170,14 @@ const UI_STRUCTURAL_ROOTS = new Map([
|
|
|
169
170
|
header: "PageHeader",
|
|
170
171
|
alternateHeader: "PageIntro",
|
|
171
172
|
body: "PageBody",
|
|
173
|
+
footer: "PageFooter",
|
|
172
174
|
shorthand: new Set(["title", "description", "actions"]),
|
|
173
175
|
// O contador saiu do título da página (ADR 0009). Tirar `count` do shorthand não proíbe
|
|
174
176
|
// nada sozinho — só apaga o marcador que fazia o gate enxergar aquele Page —, então a
|
|
175
177
|
// prop removida é cobrada aqui, nas duas formas de composição.
|
|
176
|
-
removed: new Map([
|
|
178
|
+
removed: new Map([
|
|
179
|
+
["count", "o total pertence ao conteúdo que o explica"],
|
|
180
|
+
]),
|
|
177
181
|
},
|
|
178
182
|
],
|
|
179
183
|
[
|
|
@@ -191,6 +195,7 @@ const UI_STRICT_DIRECT_COMPONENTS = new Set([
|
|
|
191
195
|
"PageHeader",
|
|
192
196
|
"PageIntro",
|
|
193
197
|
"PageBody",
|
|
198
|
+
"PageFooter",
|
|
194
199
|
"PageTitle",
|
|
195
200
|
"PageBack",
|
|
196
201
|
"PageNavigation",
|
|
@@ -207,6 +212,44 @@ const UI_STRICT_DIRECT_COMPONENTS = new Set([
|
|
|
207
212
|
"AlertActions",
|
|
208
213
|
]);
|
|
209
214
|
|
|
215
|
+
const UI_REMOVED_PROPS = new Map([
|
|
216
|
+
[
|
|
217
|
+
"PageHeader",
|
|
218
|
+
new Map([
|
|
219
|
+
["variant", "use `PageShell` para a barra persistente da página"],
|
|
220
|
+
]),
|
|
221
|
+
],
|
|
222
|
+
[
|
|
223
|
+
"ActionFormDialog",
|
|
224
|
+
new Map([
|
|
225
|
+
[
|
|
226
|
+
"description",
|
|
227
|
+
"use `intro` somente para contexto relevante no início do corpo",
|
|
228
|
+
],
|
|
229
|
+
]),
|
|
230
|
+
],
|
|
231
|
+
[
|
|
232
|
+
"ActionListDialog",
|
|
233
|
+
new Map([
|
|
234
|
+
[
|
|
235
|
+
"description",
|
|
236
|
+
"use `intro` somente para contexto relevante no início do corpo",
|
|
237
|
+
],
|
|
238
|
+
]),
|
|
239
|
+
],
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
const UI_REMOVED_COMPONENTS = new Map([
|
|
243
|
+
[
|
|
244
|
+
"DialogDescription",
|
|
245
|
+
"mova contexto relevante para o início de `DialogBody`",
|
|
246
|
+
],
|
|
247
|
+
[
|
|
248
|
+
"DrawerDescription",
|
|
249
|
+
"mova contexto relevante para o início de `DrawerBody`",
|
|
250
|
+
],
|
|
251
|
+
]);
|
|
252
|
+
|
|
210
253
|
const UI_STRUCTURAL_CONTAINERS = new Set(["PageShell"]);
|
|
211
254
|
|
|
212
255
|
const UI_STRUCTURAL_HEADERS = new Map([
|
|
@@ -228,7 +271,13 @@ const UI_STRUCTURAL_HEADERS = new Map([
|
|
|
228
271
|
"PageIntro",
|
|
229
272
|
{
|
|
230
273
|
title: "PageTitle",
|
|
231
|
-
optional: new Set([
|
|
274
|
+
optional: new Set([
|
|
275
|
+
"PageBack",
|
|
276
|
+
"PageNavigation",
|
|
277
|
+
"PageDescription",
|
|
278
|
+
"PageActions",
|
|
279
|
+
]),
|
|
280
|
+
exclusive: [new Set(["PageBack", "PageNavigation"])],
|
|
232
281
|
shorthand: new Set(),
|
|
233
282
|
},
|
|
234
283
|
],
|
|
@@ -295,14 +344,7 @@ const UI_LEGACY_VARIANTS = new Map([
|
|
|
295
344
|
],
|
|
296
345
|
[
|
|
297
346
|
"Alert",
|
|
298
|
-
new Set([
|
|
299
|
-
"default",
|
|
300
|
-
"destructive",
|
|
301
|
-
"info",
|
|
302
|
-
"success",
|
|
303
|
-
"warning",
|
|
304
|
-
"danger",
|
|
305
|
-
]),
|
|
347
|
+
new Set(["default", "destructive", "info", "success", "warning", "danger"]),
|
|
306
348
|
],
|
|
307
349
|
[
|
|
308
350
|
"Dot",
|
|
@@ -969,6 +1011,16 @@ export function checkUiStructure(file, text) {
|
|
|
969
1011
|
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
970
1012
|
const local = jsxLocalName(node);
|
|
971
1013
|
const component = local === null ? null : imports.get(local);
|
|
1014
|
+
const removedComponent =
|
|
1015
|
+
component === null ? undefined : UI_REMOVED_COMPONENTS.get(component);
|
|
1016
|
+
if (component !== null && removedComponent !== undefined) {
|
|
1017
|
+
add(
|
|
1018
|
+
node,
|
|
1019
|
+
component,
|
|
1020
|
+
`${component} não faz mais parte da API pública: ${removedComponent}.`,
|
|
1021
|
+
"ui-structure-removed-component",
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
972
1024
|
const parents =
|
|
973
1025
|
component === null ? undefined : UI_STRUCTURAL_PARENTS.get(component);
|
|
974
1026
|
if (parents !== undefined) {
|
|
@@ -990,6 +1042,29 @@ export function checkUiStructure(file, text) {
|
|
|
990
1042
|
}
|
|
991
1043
|
}
|
|
992
1044
|
|
|
1045
|
+
const removedProps =
|
|
1046
|
+
component === null ? undefined : UI_REMOVED_PROPS.get(component);
|
|
1047
|
+
if (removedProps !== undefined) {
|
|
1048
|
+
const opening = jsxOpening(node);
|
|
1049
|
+
const attributes = new Set(
|
|
1050
|
+
opening.attributes.properties.flatMap((attribute) =>
|
|
1051
|
+
ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name)
|
|
1052
|
+
? [attribute.name.text]
|
|
1053
|
+
: [],
|
|
1054
|
+
),
|
|
1055
|
+
);
|
|
1056
|
+
for (const [attribute, hint] of removedProps) {
|
|
1057
|
+
if (attributes.has(attribute)) {
|
|
1058
|
+
add(
|
|
1059
|
+
node,
|
|
1060
|
+
component,
|
|
1061
|
+
`${component} não aceita mais a propriedade \`${attribute}\`: ${hint}.`,
|
|
1062
|
+
"ui-structure-removed-prop",
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
993
1068
|
const header =
|
|
994
1069
|
component === null ? undefined : UI_STRUCTURAL_HEADERS.get(component);
|
|
995
1070
|
if (header !== undefined) {
|
|
@@ -1090,7 +1165,8 @@ export function checkUiStructure(file, text) {
|
|
|
1090
1165
|
).filter(Boolean);
|
|
1091
1166
|
const structural =
|
|
1092
1167
|
acceptedHeaders.some((header) => children.names.includes(header)) ||
|
|
1093
|
-
children.names.includes(root.body)
|
|
1168
|
+
children.names.includes(root.body) ||
|
|
1169
|
+
(root.footer !== undefined && children.names.includes(root.footer));
|
|
1094
1170
|
// A prop removida saiu do `shorthand`, então sozinha ela não marca o nó como forma curta:
|
|
1095
1171
|
// ele cai no ramo explícito, que cobra header e body — uma segunda mensagem inteiramente
|
|
1096
1172
|
// induzida pela prop. Sem filhos estruturais essa cobrança dispara de qualquer jeito, e
|
|
@@ -1113,19 +1189,26 @@ export function checkUiStructure(file, text) {
|
|
|
1113
1189
|
const bodies = children.names.filter(
|
|
1114
1190
|
(name) => name === root.body,
|
|
1115
1191
|
).length;
|
|
1192
|
+
const footers = children.names.filter(
|
|
1193
|
+
(name) => name === root.footer,
|
|
1194
|
+
).length;
|
|
1116
1195
|
const unexpected = children.names.some(
|
|
1117
|
-
(name) =>
|
|
1196
|
+
(name) =>
|
|
1197
|
+
!acceptedHeaders.includes(name) &&
|
|
1198
|
+
name !== root.body &&
|
|
1199
|
+
name !== root.footer,
|
|
1118
1200
|
);
|
|
1119
1201
|
if (
|
|
1120
1202
|
headers !== 1 ||
|
|
1121
1203
|
bodies !== 1 ||
|
|
1204
|
+
footers > 1 ||
|
|
1122
1205
|
unexpected ||
|
|
1123
1206
|
children.hasOpaque
|
|
1124
1207
|
) {
|
|
1125
1208
|
add(
|
|
1126
1209
|
node,
|
|
1127
1210
|
component,
|
|
1128
|
-
`${component} explícito exige exatamente um ${acceptedHeaders.join(" ou ")} e um ${root.
|
|
1211
|
+
`${component} explícito exige exatamente um ${acceptedHeaders.join(" ou ")}, um ${root.body} e no máximo um ${root.footer ?? "footer"} como filhos diretos.`,
|
|
1129
1212
|
);
|
|
1130
1213
|
}
|
|
1131
1214
|
}
|