@softize/opus 16.1.0 → 17.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/bin/lib/check.mjs +9 -13
  3. package/bin/lib/copy.mjs +29 -4
  4. package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +6 -3
  5. package/docs/adr/0009-page-title-does-not-carry-a-counter.md +5 -2
  6. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +12 -14
  7. package/docs/adr/0012-modal-header-only-names-the-surface.md +8 -4
  8. package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +20 -11
  9. package/docs/adr/0014-structural-headers-do-not-carry-description.md +35 -0
  10. package/docs/adr/0015-action-size-follows-interaction-density.md +64 -0
  11. package/package.json +2 -2
  12. package/registry/skills/build-opus-ui/references/evaluations.md +1 -1
  13. package/registry/skills/build-opus-ui/references/ui-patterns.md +19 -18
  14. package/registry/templates/app/package.json +1 -1
  15. package/src/core/presentation.ts +142 -13
  16. package/src/ui/components/patterns/form.tsx +35 -10
  17. package/src/ui/components/patterns/page.tsx +93 -48
  18. package/src/ui/components/patterns/presentation.tsx +489 -329
  19. package/src/ui/components/patterns/surface-header.tsx +4 -3
  20. package/src/ui/components/patterns/trigger.tsx +8 -1
  21. package/src/ui/components/patterns/view.tsx +2 -2
  22. package/src/ui/components/primitives/command.tsx +82 -42
  23. package/src/ui/components/primitives/dialog.tsx +180 -97
  24. package/src/ui/components/primitives/drawer.tsx +63 -22
  25. package/src/ui/docs/content/button.md +8 -2
  26. package/src/ui/docs/content/command.md +3 -1
  27. package/src/ui/docs/content/content.md +1 -1
  28. package/src/ui/docs/content/dialog.md +9 -9
  29. package/src/ui/docs/content/drawer.md +4 -4
  30. package/src/ui/docs/content/page.md +16 -31
  31. package/src/ui/docs/content/presentation.md +73 -80
  32. package/src/ui/meta.ts +2 -2
  33. package/src/ui/react.tsx +1 -8
package/CHANGELOG.md CHANGED
@@ -7,6 +7,47 @@ 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
+ ## 17.1.0 — 2026-09-12
11
+
12
+ Formulários de `Presentation` em Dialog e Drawer passam a oferecer `Cancelar` ao lado da ação
13
+ principal. O retorno no header modal aparece somente quando a surface anterior também é um Dialog
14
+ ou Drawer; a Page mantida ao fundo não cria uma etapa de navegação.
15
+
16
+ Em Page hospedada por `PageShell`, a `Presentation` mantém navegação e ações na barra e apresenta o
17
+ título no `PageIntro`. Ações que abrem outra Presentation usam o botão default para assumir a
18
+ hierarquia primária do fluxo. Ações textuais de header e footer usam o tamanho `default`; `sm` fica
19
+ reservado às operações densas de toolbar, seção e coleção.
20
+
21
+ ## 17.0.0 — 2026-09-11
22
+
23
+ A inspeção de `Presentation` passa a pertencer exclusivamente à Lens, que lê as definições estáticas
24
+ projetadas no manifest. `PresentationDevtoolsProvider`, `usePresentationRegistration` e
25
+ `PresentationDevtools` deixam de existir; a aplicação não mantém um segundo registro de execução
26
+ nem monta um launcher no shell. `PresentationInspector` permanece disponível para a Lens e para
27
+ bancadas isoladas que precisem visualizar um snapshot mascarado.
28
+
29
+ `Presentation` passa a consumir diretamente a definição, a invocação e o registry de contratos.
30
+ O renderer delega actions `simple`, `form`, `list` e `view` a `ActionTrigger`, `ActionForm`,
31
+ `ActionList` e `ActionView`; navegação e efeitos atualizam a invocação serializável. O gate cruza
32
+ bindings com o schema de input das actions antes de publicar o manifest.
33
+ Actions de formulário ocupam o footer estrutural da superfície e continuam associadas ao `<form>`.
34
+ O body pode declarar `submitLabel` para nomear a conclusão específica do formulário.
35
+ Durante uma action com `blocking: "surface"`, voltar, fechar, editar campos e submeter o formulário
36
+ permanecem bloqueados até a conclusão.
37
+
38
+ `PageShell` recebe navigation, title e actions do `PageHeader` descendente. `PageIntro` volta a ser
39
+ somente uma introdução opcional do body. Page, Dialog e Drawer deixam de oferecer Description no
40
+ header; contexto relevante começa no body.
41
+
42
+ O fechamento padrão de `Dialog` e `Drawer` passa a ser um `Button` ghost somente com ícone no final
43
+ do header. `showCloseButton` continua controlando sua presença. O `CommandDialog` adota um header
44
+ compacto para preservar a mesma anatomia; não existe uma segunda posição flutuante para o close.
45
+
46
+ **Breaking:** remova providers, registros e launchers de Presentation do shell. Use a projeção de
47
+ Presentations na Lens para inspecionar as definições disponíveis. Migre a composição manual de
48
+ `Presentation` para `definition`, `definitions`, `actions` e `invocation`. Remova `description` de
49
+ `Page` e `PageDescription`; mova apenas o contexto relevante para o início de `PageBody`.
50
+
10
51
  ## 16.1.0 — 2026-09-11
11
52
 
12
53
  `PresentationDevtoolsProvider`, `usePresentationRegistration` e `PresentationDevtools` permitem
package/bin/lib/check.mjs CHANGED
@@ -109,9 +109,8 @@ const UI_STRUCTURAL_PARENTS = new Map([
109
109
  ["PageBody", new Set(["Page"])],
110
110
  ["PageFooter", new Set(["Page"])],
111
111
  ["PageTitle", new Set(["PageHeader", "PageIntro"])],
112
- ["PageBack", new Set(["PageHeader", "PageIntro"])],
113
- ["PageNavigation", new Set(["PageHeader", "PageIntro"])],
114
- ["PageDescription", new Set(["PageHeader", "PageIntro"])],
112
+ ["PageBack", new Set(["PageHeader"])],
113
+ ["PageNavigation", new Set(["PageHeader"])],
115
114
  ["PageActions", new Set(["PageHeader", "PageIntro"])],
116
115
  ["ContentHeader", new Set(["Content"])],
117
116
  ["ContentBody", new Set(["Content"])],
@@ -171,12 +170,13 @@ const UI_STRUCTURAL_ROOTS = new Map([
171
170
  alternateHeader: "PageIntro",
172
171
  body: "PageBody",
173
172
  footer: "PageFooter",
174
- shorthand: new Set(["title", "description", "actions"]),
173
+ shorthand: new Set(["title", "actions"]),
175
174
  // O contador saiu do título da página (ADR 0009). Tirar `count` do shorthand não proíbe
176
175
  // nada sozinho — só apaga o marcador que fazia o gate enxergar aquele Page —, então a
177
176
  // prop removida é cobrada aqui, nas duas formas de composição.
178
177
  removed: new Map([
179
178
  ["count", "o total pertence ao conteúdo que o explica"],
179
+ ["description", "mova contexto relevante para o início do body"],
180
180
  ]),
181
181
  },
182
182
  ],
@@ -199,7 +199,6 @@ const UI_STRICT_DIRECT_COMPONENTS = new Set([
199
199
  "PageTitle",
200
200
  "PageBack",
201
201
  "PageNavigation",
202
- "PageDescription",
203
202
  "PageActions",
204
203
  "ContentHeader",
205
204
  "ContentBody",
@@ -240,6 +239,10 @@ const UI_REMOVED_PROPS = new Map([
240
239
  ]);
241
240
 
242
241
  const UI_REMOVED_COMPONENTS = new Map([
242
+ [
243
+ "PageDescription",
244
+ "mova contexto relevante para o início de `PageBody`",
245
+ ],
243
246
  [
244
247
  "DialogDescription",
245
248
  "mova contexto relevante para o início de `DialogBody`",
@@ -260,7 +263,6 @@ const UI_STRUCTURAL_HEADERS = new Map([
260
263
  optional: new Set([
261
264
  "PageBack",
262
265
  "PageNavigation",
263
- "PageDescription",
264
266
  "PageActions",
265
267
  ]),
266
268
  exclusive: [new Set(["PageBack", "PageNavigation"])],
@@ -271,13 +273,7 @@ const UI_STRUCTURAL_HEADERS = new Map([
271
273
  "PageIntro",
272
274
  {
273
275
  title: "PageTitle",
274
- optional: new Set([
275
- "PageBack",
276
- "PageNavigation",
277
- "PageDescription",
278
- "PageActions",
279
- ]),
280
- exclusive: [new Set(["PageBack", "PageNavigation"])],
276
+ optional: new Set(["PageActions"]),
281
277
  shorthand: new Set(),
282
278
  },
283
279
  ],
package/bin/lib/copy.mjs CHANGED
@@ -78,6 +78,7 @@ const AUDIT_REFERENCE =
78
78
  /^(?:https?:\/\/\S+|[a-z][a-z0-9-]*:\S+|(?:[A-Za-z0-9._-]+\/)+[A-Za-z0-9._#/-]+|[A-Za-z0-9._-]+\.(?:md|json|ya?ml)(?:#[^\s]+)?)$/u
79
79
  const CONTRACT_MODULES = new Set(['@softize/opus', '@softize/opus/core'])
80
80
  const CONTRACT_FACTORIES = new Set(['defineAction', 'defineContract'])
81
+ const PRESENTATION_MODULES = new Set(['@softize/opus', '@softize/opus/presentation'])
81
82
  const SCHEMA_ZOD_MODULES = new Set(['@softize/opus/schema/zod'])
82
83
  const ACTION_BINDING_KEYS = new Set(['authorize', 'background', 'emits', 'handler', 'idempotency', 'loads'])
83
84
 
@@ -119,7 +120,6 @@ const JSX_CHILD_ROLES = new Map([
119
120
  ['MenuRadioItem', 'menu-item'],
120
121
  ['PopoverDescription', 'description'],
121
122
  ['PopoverTitle', 'title'],
122
- ['PageDescription', 'description'],
123
123
  // O texto do PageBack é o NOME do destino ('Clientes'), como um item de trilha — não um
124
124
  // comando. Classificado como `button`, a política universal cobraria verbo de ação.
125
125
  ['PageBack', 'breadcrumb'],
@@ -268,7 +268,6 @@ const JSX_PROP_ROLES = new Map([
268
268
  'Page',
269
269
  new Map([
270
270
  ['title', 'title'],
271
- ['description', 'description'],
272
271
  ]),
273
272
  ],
274
273
  [
@@ -386,7 +385,6 @@ const JSX_PROP_CLASS = new Map([
386
385
  'Page',
387
386
  new Map([
388
387
  ['title', 'className'],
389
- ['description', 'className'],
390
388
  ]),
391
389
  ],
392
390
  ['Select', new Map([['placeholder', 'className']])],
@@ -1419,6 +1417,7 @@ export function extractCopyFromSource(file, sourceText) {
1419
1417
  const diagnostics = []
1420
1418
  let hasContract = false
1421
1419
  let hasDictionary = false
1420
+ let hasPresentation = false
1422
1421
  const hasUiImport = sourceFile.statements.some(
1423
1422
  (statement) =>
1424
1423
  ts.isImportDeclaration(statement) &&
@@ -1550,6 +1549,22 @@ export function extractCopyFromSource(file, sourceText) {
1550
1549
  nestedObject(object, 'errors', (error, prefix) => addProperty(error, 'description', 'error', prefix))
1551
1550
  }
1552
1551
 
1552
+ function extractPresentation(object) {
1553
+ addProperty(object, 'title', 'title')
1554
+ nestedObject(object, 'body', (body, prefix) => {
1555
+ addProperty(body, 'submitLabel', 'button', prefix)
1556
+ nestedObject(
1557
+ body,
1558
+ 'fields',
1559
+ (field, fieldPrefix) => {
1560
+ addProperty(field, 'label', 'label', fieldPrefix)
1561
+ addProperty(field, 'empty', 'empty-state', fieldPrefix)
1562
+ },
1563
+ prefix,
1564
+ )
1565
+ })
1566
+ }
1567
+
1553
1568
  function extractDictionary(call) {
1554
1569
  const entriesNode = call.arguments[0]
1555
1570
  const entries = resolveBinding(entriesNode, checker)
@@ -1595,6 +1610,10 @@ export function extractCopyFromSource(file, sourceText) {
1595
1610
  return name !== null && CONTRACT_FACTORIES.has(name) ? name : null
1596
1611
  }
1597
1612
 
1613
+ function presentationFactory(expression) {
1614
+ return importedMember(expression, (module) => PRESENTATION_MODULES.has(module)) === 'definePresentation'
1615
+ }
1616
+
1598
1617
  function opusComponent(tagName) {
1599
1618
  return importedMember(tagName, uiModule)
1600
1619
  }
@@ -2552,6 +2571,12 @@ export function extractCopyFromSource(file, sourceText) {
2552
2571
  else diagnostics.push(diagnostic(file, sourceFile, node.arguments[0], `${factory}(...)`, 'structure'))
2553
2572
  }
2554
2573
  }
2574
+ if (ts.isCallExpression(node) && presentationFactory(node.expression) && node.arguments.length > 0) {
2575
+ hasPresentation = true
2576
+ const object = resolveBinding(node.arguments[0], checker)
2577
+ if (ts.isObjectLiteralExpression(object)) extractPresentation(object)
2578
+ else diagnostics.push(diagnostic(file, sourceFile, node.arguments[0], 'definePresentation(...)', 'structure'))
2579
+ }
2555
2580
  if (ts.isCallExpression(node) && knownDictionaryFactory(node, checker) && node.arguments.length > 0) {
2556
2581
  hasDictionary = true
2557
2582
  extractDictionary(node)
@@ -2563,7 +2588,7 @@ export function extractCopyFromSource(file, sourceText) {
2563
2588
 
2564
2589
  return {
2565
2590
  hasContract,
2566
- hasCopySurface: hasContract || hasDictionary || hasUiImport,
2591
+ hasCopySurface: hasContract || hasDictionary || hasPresentation || hasUiImport,
2567
2592
  entries,
2568
2593
  diagnostics,
2569
2594
  }
@@ -6,6 +6,9 @@
6
6
  > **Atualização (2026-09-09).** Parcialmente substituída pela ADR 0009 quanto a `PageMeta` e ao
7
7
  > `count` de `Page`, e complementada pela ADR 0010, que acrescenta as apresentações `default` e
8
8
  > `bar` ao mesmo header. O corpo abaixo já traz a anatomia vigente.
9
+ >
10
+ > **Atualização (2026-09-11).** As ADRs 0012 e 0014 removem Description dos headers de Page,
11
+ > Dialog e Drawer. Contexto relevante começa no body.
9
12
 
10
13
  ## Contexto
11
14
 
@@ -24,7 +27,7 @@ perdida para obter uma estrutura explícita.
24
27
  As superfícies mantidas pela casa adotam a gramática `Root > Header + Body + Footer`, com
25
28
  `Title`, `Description`, `Meta` e `Actions` pertencendo ao `Header` da mesma família.
26
29
 
27
- - `Page` oferece `PageHeader`, `PageBack`, `PageNavigation`, `PageTitle`, `PageDescription`,
30
+ - `Page` oferece `PageHeader`, `PageBack`, `PageNavigation`, `PageTitle`,
28
31
  `PageActions` e `PageBody`. A ADR 0010 acrescenta as apresentações `default` e `bar` ao mesmo
29
32
  header.
30
33
  - `Content` representa uma região de conteúdo semanticamente nomeada e oferece `ContentHeader`,
@@ -42,8 +45,8 @@ As superfícies mantidas pela casa adotam a gramática `Root > Header + Body + F
42
45
  `ItemActions` e `ItemFooter`. `ItemContent` permanece temporariamente como alias legado de
43
46
  corpo, mas deixa de envolver título e descrição no código novo.
44
47
 
45
- `Page` e `Content` aceitam também uma forma curta: `title`, `description` e `actions` nos dois,
46
- mais `count` no `Content`. A ADR 0009 tirou o contador do título da página.
48
+ `Page` aceita também uma forma curta com `title` e `actions`. `Content` mantém `title`,
49
+ `description`, `actions` e `count`. A ADR 0009 tirou o contador do título da página.
47
50
  Essa forma é açúcar sintático: produz a mesma árvore semântica, os mesmos estilos e os mesmos
48
51
  `data-slot` da composição explícita. Um consumidor não pode misturar as duas formas na mesma raiz.
49
52
 
@@ -4,6 +4,9 @@
4
4
  - **Data:** 2026-09-09.
5
5
  - **Substitui parcialmente:** ADR 0005, somente quanto a `PageMeta` e `count` em `Page`.
6
6
 
7
+ > **Atualização (2026-09-11).** A ADR 0014 remove `PageDescription`; contexto útil pertence ao
8
+ > início do body, e contadores continuam na seção que explicam.
9
+
7
10
  ## Contexto
8
11
 
9
12
  `Page` permitia colocar um total imediatamente ao lado do título por `count` ou `PageMeta`. O
@@ -16,7 +19,7 @@ ao título local e continuar compreensível dentro da própria seção.
16
19
  ## Decisão
17
20
 
18
21
  `Page` deixa de aceitar `count` e de exportar `PageMeta`. O cabeçalho da página reconhece somente
19
- `PageTitle`, `PageDescription` e `PageActions`. Totais e outros indicadores pertencem ao conteúdo
22
+ `PageTitle` e `PageActions`. Totais e outros indicadores pertencem ao conteúdo
20
23
  que os explica, como uma listagem, métrica ou seção composta com `Content`.
21
24
 
22
25
  `ContentMeta` e o `count` de `Content` permanecem disponíveis. A anatomia compartilhada continua
@@ -54,4 +57,4 @@ quando o total for realmente necessário.
54
57
  - O tipo de `Page` não aceita `count` e o barrel público não exporta `PageMeta`.
55
58
  - Testes de UI verificam a anatomia curta e explícita sem `page-meta`.
56
59
  - Busca estrutural impede usos de `count` em `Page` nos consumidores migrados.
57
- - Documentação e metadata públicas apresentam somente título, descrição e ações.
60
+ - Documentação e metadata públicas apresentam somente título e ações.
@@ -21,16 +21,15 @@ renderiza a barra, recebe a navegação conhecida pelo shell e oferece o alvo ca
21
21
  declaradas pela `Page` descendente. Essa barra existe somente por `PageShell`; `PageHeader` não
22
22
  possui variante visual para reproduzi-la dentro da página.
23
23
 
24
- Quando uma `Page` está dentro de `PageShell`, sua forma curta transforma título e descrição em
25
- `PageIntro`, dentro do conteúdo. `PageActions` continua declarado pela página, mas aparece na barra.
26
- Na forma explícita, a página usa `PageIntro` e `PageBody`. Fora de `PageShell`, a composição anterior
27
- com `PageHeader` continua válida; `PageIntro` também pode ser escolhido quando o conteúdo precisa de
28
- uma introdução sem navegação própria. As duas regiões não são equivalentes: `PageHeader` reúne o
29
- cabeçalho completo, enquanto `PageIntro` dá mais presença ao título dentro do conteúdo.
24
+ Quando uma `Page` está dentro de `PageShell`, `PageHeader` continua sendo seu header estrutural.
25
+ `PageTitle`, `PageNavigation`, `PageBack` e `PageActions` são projetados nos alvos da barra sem
26
+ duplicar a anatomia no body. Na forma explícita, a página usa `PageHeader` e `PageBody`, com
27
+ `PageIntro` opcional para contexto adicional no conteúdo. Fora de `PageShell`, `PageHeader` aparece
28
+ dentro do container da página.
30
29
 
31
- `PageState` oculta somente `PageIntro`. A barra do `PageShell` permanece visível porque preserva
32
- navegação e ações globais mesmo quando o conteúdo carrega, falha ou está vazio. Uma ação que depende
33
- do conteúdo deve se omitir por estado na própria rota.
30
+ `PageState` oculta somente `PageIntro`. A barra do `PageShell` permanece visível com navegação,
31
+ título e ações mesmo quando o conteúdo carrega, falha ou está vazio. Uma ação que depende do
32
+ conteúdo deve se omitir por estado na própria rota.
34
33
 
35
34
  `PageShell` não substitui o shell completo da aplicação, não inclui sidebar e não cria navegação.
36
35
  Ele apenas coordena a barra e a área em que uma única `Page` é renderizada.
@@ -39,11 +38,10 @@ Ele apenas coordena a barra e a área em que uma única `Page` é renderizada.
39
38
 
40
39
  - shell e rota podem continuar conhecendo partes diferentes da página sem criar componentes locais
41
40
  de chrome;
42
- - título e descrição deixam de ser mascarados por seletores globais;
43
- - ações de página têm um único destino oficial na barra;
41
+ - título e ações têm destinos oficiais na barra;
44
42
  - estados integrais preservam a barra e escondem somente a introdução do conteúdo;
45
- - a composição de `Page` com `PageHeader` permanece disponível fora de `PageShell`, sem uma segunda
46
- forma de produzir a barra;
43
+ - a composição de `Page` com `PageHeader` é a mesma dentro e fora de `PageShell`; apenas seu destino
44
+ visual muda;
47
45
  - `PageActionsTarget` continua disponível apenas para workspaces imersivos que não usam
48
46
  `PageShell`.
49
47
 
@@ -66,7 +64,7 @@ estado do conteúdo. A moldura do shell não deve oscilar com a consulta da rota
66
64
 
67
65
  ## Verificação
68
66
 
69
- - testes cobrem a projeção das ações, a permanência da barra e a ocultação de `PageIntro` em estados
67
+ - testes cobrem a projeção de navegação, título e ações, a permanência da barra e a ocultação de `PageIntro` em estados
70
68
  integrais;
71
69
  - a documentação demonstra a composição de `PageShell`, `PageIntro` e `Page` sem CSS externo;
72
70
  - consumidores removem barras paralelas e `PageActionsTarget` ao adotar o novo contrato.
@@ -16,8 +16,9 @@ da descrição acessível do modal.
16
16
 
17
17
  ## Decisão
18
18
 
19
- - `DialogHeader` e `DrawerHeader` contêm o título e, quando necessário, mídia. Não existe API pública
20
- `DialogDescription` ou `DrawerDescription`.
19
+ - `DialogHeader` e `DrawerHeader` contêm o título e as ações da faixa superior. O close padrão é uma
20
+ action ghost somente com ícone, inserida por último quando `showCloseButton` está ativo. Não existe
21
+ uma posição flutuante alternativa nem API pública `DialogDescription` ou `DrawerDescription`.
21
22
  - Contexto relevante aparece no início de `DialogBody` ou `DrawerBody`, como texto, `Alert` ou uma
22
23
  composição própria. Texto que apenas repete o título ou a ação é omitido.
23
24
  - A API imperativa usa somente `body` para esse conteúdo. `ActionFormDialog` e `ActionListDialog`
@@ -25,12 +26,13 @@ da descrição acessível do modal.
25
26
  - `DialogContent` e `DrawerContent` não inferem uma descrição. Quando um trecho conciso do corpo deve
26
27
  descrever a superfície para tecnologias assistivas, o consumidor relaciona seu `id` por
27
28
  `aria-describedby`.
28
- - `CommandDialog` mantém internamente uma instrução apenas para tecnologias assistivas, porque ela
29
- explica o funcionamento do controle e não é copy visual do cabeçalho.
29
+ - `CommandDialog` mantém um header compacto com título e close. A instrução que explica o controle
30
+ permanece disponível apenas para tecnologias assistivas.
30
31
 
31
32
  ## Consequências
32
33
 
33
34
  - Modais comuns começam mais perto da tarefa e não pedem texto de preenchimento.
35
+ - Dialog, Drawer e CommandDialog mantêm a mesma anatomia de fechamento no header.
34
36
  - Informações importantes continuam visíveis, mas ocupam a região rolável e podem usar o componente
35
37
  semântico adequado.
36
38
  - A migração troca `description` por `body` na API imperativa, por `intro` nos wrappers e move conteúdo
@@ -41,5 +43,7 @@ da descrição acessível do modal.
41
43
 
42
44
  - o barrel público não exporta `DialogDescription` nem `DrawerDescription`;
43
45
  - os tipos das APIs imperativas não aceitam `description`;
46
+ - `DialogContent` e `DrawerContent` não aceitam outra posição para o close;
47
+ - testes confirmam que o close automático é um `Button` ghost dentro do header;
44
48
  - testes cobrem `body`, `intro`, a relação acessível e os diagnósticos de migração;
45
49
  - a auditoria de docs, o inventário de copy, o typecheck e a suíte do pacote permanecem verdes.
@@ -12,18 +12,19 @@ a interface antes de executar React.
12
12
 
13
13
  O Opus já declara actions `simple`, `form`, `list` e `view`. Um artefato de interface não deve
14
14
  reimplementar seus contratos, validação, autorização ou transporte; deve apenas compor essas
15
- capacidade em uma apresentação verificável.
15
+ capacidades em uma apresentação verificável.
16
16
 
17
17
  ## Decisão
18
18
 
19
19
  `Presentation` é o artefato declarativo que descreve um recurso independentemente da superfície em
20
20
  que aparece. `Surface` escolhe `page`, `dialog` ou `drawer` no momento da renderização.
21
21
 
22
- A anatomia portátil é `Header + Body + Footer?`. O header organiza horizontalmente
23
- `Navigation? + Title + Actions?`; o body contém o recurso; o footer contém ações de conclusão.
24
- Dialog e Drawer acrescentam o fechamento ao final das ações do header. Page pode ainda usar
25
- navegação persistente fornecida pelo shell e uma introdução opcional dentro do body, sem mover nem
26
- duplicar o título estrutural.
22
+ A anatomia portátil é `Header + Body + Footer?`, mas cada surface materializa seu header de acordo
23
+ com o espaço disponível. Dialog e Drawer organizam horizontalmente
24
+ `Navigation? + Title + Actions? + Close` e mantêm as ações de conclusão no footer. Em Page
25
+ hospedada por `PageShell`, a barra persistente reúne navegação e ações; o título inicia o conteúdo
26
+ em `PageIntro`, sem repetir a página atual ao lado do breadcrumb. Fora do shell, a Page materializa
27
+ seu header completo no próprio container.
27
28
 
28
29
  Uma action assume um de dois papéis:
29
30
 
@@ -48,11 +49,15 @@ estática, publicável no manifest e inspecionável pela Lens. A invocação é
48
49
  uma execução: identifica a Presentation, escolhe a surface, carrega somente input JSON e mantém a
49
50
  pilha de frames necessária para voltar. Ela não é publicada no manifest.
50
51
 
51
- `back` restaura o último frame da pilha. `close` encerra apenas dialog ou drawer e é inválido para
52
- page. `navigate` abre outra invocação por push ou substitui a atual; sincronizar esse estado com URL
53
- é responsabilidade do adaptador da aplicação quando refresh, deep link ou histórico forem
54
- necessários. O inspetor de desenvolvimento reúne definição, invocação, bindings resolvidos e
55
- diagnósticos, mascarando chaves sensíveis antes de expor o JSON.
52
+ `back` restaura o último frame da pilha. Em Dialog ou Drawer, ele é apresentado quando o frame
53
+ anterior também é modal; a Page mantida ao fundo não cria uma etapa intermediária. `close` encerra
54
+ apenas dialog ou drawer e é inválido para page. Formulários modais oferecem `Cancelar` como ação de
55
+ abandono no footer, ao lado da conclusão. `navigate` abre outra invocação por push ou substitui a
56
+ atual; sincronizar esse estado com URL é responsabilidade do adaptador da aplicação quando refresh,
57
+ deep link ou histórico forem necessários. O inspetor de desenvolvimento reúne definição,
58
+ invocação, bindings resolvidos e diagnósticos, mascarando chaves sensíveis antes de expor o JSON. O
59
+ inventário estático e seu JSON ficam disponíveis na Lens; a aplicação não adiciona um launcher
60
+ flutuante ao shell.
56
61
 
57
62
  ## Consequências
58
63
 
@@ -85,8 +90,12 @@ Foi descartada porque compatibilidade exige delegar cada kind ao pattern canôni
85
90
 
86
91
  - testes de schema cobrem versão, referências, kinds, placements e bindings;
87
92
  - testes do renderer exercitam `simple`, `form`, `list` e `view` nas superfícies válidas;
93
+ - validação cruza bindings, chaves obrigatórias e valores fixos com o schema de input da action;
88
94
  - o manifest projeta Presentations sem callbacks nem dados server-only;
89
95
  - a lens lista e expõe o JSON integral de cada Presentation;
90
96
  - testes de invocação cobrem push, replace, back, close e rejeição de input não JSON;
97
+ - testes do renderer cobrem a ausência de retorno entre Page e modal, o retorno entre modais e as
98
+ ações Cancelar/concluir do formulário;
91
99
  - o inspetor mascara segredo, token, cookie, autorização, senha e chave de API;
100
+ - a Lens lista as definições registradas e abre o JSON integral sem depender da árvore React;
92
101
  - uma aplicação consumidora usa o pacote local por symlink durante a migração página por página.
@@ -0,0 +1,35 @@
1
+ # ADR 0014 — Headers estruturais não carregam descrição
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-11.
5
+ - **Complementa:** ADRs 0005, 0011 e 0012.
6
+
7
+ ## Contexto
8
+
9
+ Uma segunda linha no header de Page, Dialog ou Drawer frequentemente repetia o título, antecipava
10
+ o conteúdo óbvio ou existia apenas para preencher a composição. Isso aumentava a altura fixa da
11
+ superfície e misturava o nome do recurso com instruções e consequências da tarefa.
12
+
13
+ ## Decisão
14
+
15
+ Headers estruturais contêm navegação opcional, título e actions. Page não oferece a propriedade
16
+ `description` nem `PageDescription`; Dialog e Drawer seguem a mesma regra conforme a ADR 0012.
17
+
18
+ Contexto que altera compreensão ou decisão aparece no início do body. Ele pode ser texto, `Alert`,
19
+ `Content` ou outra composição adequada. `PageState`, `Alert`, `Empty`, campos, métricas e seções
20
+ continuam podendo ter descrição porque ela qualifica o estado ou conteúdo local, não o header da
21
+ superfície.
22
+
23
+ ## Consequências
24
+
25
+ - Page, Dialog e Drawer compartilham `Navigation? + Title + Actions?` no header.
26
+ - A ausência de copy de preenchimento reduz altura e variação entre superfícies.
27
+ - Informações úteis continuam possíveis, mas pertencem ao body e à semântica que explicam.
28
+ - A remoção é incompatível e exige migrar contexto relevante para o início do body.
29
+
30
+ ## Verificação
31
+
32
+ - os barrels não exportam `PageDescription`, `DialogDescription` ou `DrawerDescription`;
33
+ - shorthand e tipos públicos não aceitam `description` nessas superfícies;
34
+ - checker, documentação e testes orientam a migração para o body;
35
+ - `PageState` e componentes de conteúdo local preservam suas descrições próprias.
@@ -0,0 +1,64 @@
1
+ # ADR 0015 — O tamanho da ação acompanha a densidade da interação
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-12.
5
+
6
+ ## Contexto
7
+
8
+ O uso de `default` e `sm` passou a variar entre headers de Page, footers de Dialog e Drawer,
9
+ toolbars e ações de listagem. Como tamanho também era usado para sugerir importância, duas decisões
10
+ equivalentes podiam ganhar alturas diferentes, enquanto ações repetitivas ocupavam o mesmo espaço
11
+ de uma conclusão da superfície.
12
+
13
+ O Opus já separa contexto semântico, variante visual e tamanho. Misturar esses eixos impede que a
14
+ mesma regra funcione quando uma ação muda de secundária para primária ou quando um recurso troca de
15
+ Page para Dialog ou Drawer.
16
+
17
+ ## Decisão
18
+
19
+ O tamanho comunica densidade e alcance da interação; `context` e `variant` comunicam hierarquia e
20
+ risco.
21
+
22
+ - ações textuais que decidem a superfície usam `default`: header de Page e footer de Page, Dialog
23
+ ou Drawer;
24
+ - ações operacionais em toolbar, header de seção ou coleção densa usam `sm`;
25
+ - ações internas de linha, célula ou campo usam `xs` ou `icon-xs`;
26
+ - ações somente com ícone que pertencem ao chrome da superfície, como voltar e fechar, usam
27
+ `icon-sm`;
28
+ - `lg` fica reservado a chamadas que deliberadamente precisam de uma área de toque maior, não a
29
+ uma ação primária comum.
30
+
31
+ Patterns que conhecem a região aplicam o tamanho. Componentes estruturais genéricos não clonam nem
32
+ reescrevem filhos arbitrários: em composição manual, o consumidor segue a mesma matriz. Alterar a
33
+ ênfase de uma ação não altera seu tamanho dentro da região.
34
+
35
+ ## Consequências
36
+
37
+ - Page, Dialog e Drawer mantêm proporção equivalente nas decisões principais;
38
+ - toolbars e grids preservam densidade sem reduzir ações de conclusão;
39
+ - `primary`, `neutral` e `danger` podem mudar sem provocar salto de altura;
40
+ - exemplos e skills deixam de recomendar `sm` por todo cabeçalho compacto.
41
+
42
+ ## Alternativas consideradas
43
+
44
+ ### Usar `sm` em toda barra ou header
45
+
46
+ Preservaria a menor altura possível, mas trataria uma decisão da página como operação repetitiva e
47
+ criaria diferença em relação ao footer modal do mesmo recurso.
48
+
49
+ ### Fazer a ação primária sempre maior
50
+
51
+ Reforçaria hierarquia, mas misturaria importância com densidade. A variante `solid` e o contexto
52
+ `primary` já comunicam essa relação sem deslocar o layout.
53
+
54
+ ### Fazer `ButtonGroup` redimensionar qualquer filho
55
+
56
+ Centralizaria parte da aparência, mas introduziria comportamento implícito e frágil para filhos que
57
+ não são `Button`. A região ou o pattern conhece melhor o tamanho adequado.
58
+
59
+ ## Verificação
60
+
61
+ - testes da `Presentation` conferem `default` em ações textuais do header e do footer;
62
+ - testes de `ActionList` continuam cobrindo `sm` em toolbar e `icon-xs` nas ações de linha;
63
+ - a documentação de Button e Page apresenta a mesma matriz;
64
+ - revisão de consumidor trata divergência como exceção explícita, não como novo default.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "16.1.0",
3
+ "version": "17.1.0",
4
4
  "description": "End-to-end action protocol for TypeScript. Single package with subpath exports (core + adapters).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -227,7 +227,7 @@
227
227
  "@radix-ui/react-slot": "^1.1.1",
228
228
  "@radix-ui/react-tabs": "^1.1.2",
229
229
  "@radix-ui/react-tooltip": "^1.1.6",
230
- "@softize/base": "^2.2.1",
230
+ "@softize/base": "^2.3.0",
231
231
  "@tailwindcss/typography": "^0.5.20",
232
232
  "@types/markdown-it": "^14.1.2",
233
233
  "class-variance-authority": "^0.7.1",
@@ -8,7 +8,7 @@
8
8
  uma coleção secundária e um `ActionFormDialog`; provar teto padrão de `80rem`, hierarquia por
9
9
  `level`, vazio estrutural sólido, `Empty` apenas para criação ou vínculo, números sem alinhamento
10
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
11
+ - Execução abreviada: montar outra página com `<Page title actions>` e uma seção com
12
12
  `<Content title description actions>`, provando que ambas produzem a mesma anatomia e que o lint
13
13
  rejeita a mistura entre props abreviadas e headers explícitos.
14
14
  - Execução de forma: compor uma tabela dentro de Card sem moldura duplicada, manter a moldura
@@ -5,14 +5,13 @@
5
5
  operação, não a uma tela isolada.
6
6
  - URL representa estado que precisa sobreviver a refresh, deep link ou histórico.
7
7
  - `Page` fornece o `<main>` e o container centralizado com teto padrão de `80rem`. Sua forma
8
- explícita é `Page > PageHeader (PageBack? | PageNavigation?, PageTitle, PageDescription?,
9
- PageActions?) + PageBody`; `title`, `description` e `actions` no próprio `Page` são a abreviação
8
+ explícita é `Page > PageHeader (PageBack? | PageNavigation?, PageTitle,
9
+ PageActions?) + PageBody`; `title` e `actions` no próprio `Page` são a abreviação
10
10
  para o caso direto.
11
11
  Não misturar as duas formas. Alterar `className` apenas quando a superfície tiver uma necessidade
12
12
  real de largura; não reconstruir esse container em cada rota.
13
- - Fora de `PageShell`, a composição explícita também pode trocar `PageHeader` por `PageIntro` quando
14
- o conteúdo precisar somente de título, descrição e ações, sem retorno ou breadcrumb. `PageIntro`
15
- dá mais presença ao título e não é uma abreviação visual de `PageHeader`.
13
+ - `PageIntro` é uma região opcional do body para contexto realmente útil; não substitui nem duplica
14
+ o título estrutural do `PageHeader`.
16
15
  - `PageHeader` é a região de cabeçalho dentro de uma `Page` isolada e organiza navegação, título e
17
16
  ações na mesma linha. Em uma subpágina simples, `PageBack` recebe o destino pai explícito e aparece
18
17
  antes do título como controle somente com ícone. Para mais de um ancestral relevante, use
@@ -21,18 +20,18 @@ PageActions?) + PageBody`; `title`, `description` e `actions` no próprio `Page`
21
20
  possuam chrome próprio.
22
21
  - Quando shell e rota conhecem partes diferentes da mesma página, use `PageShell` ao redor da rota.
23
22
  O shell é o único responsável pela barra: fornece `navigation`; a `Page` descendente continua
24
- declarando `title`, `description` e `actions`. O Opus mantém a barra de `3rem`, projeta as
25
- ações nela e apresenta título e descrição como `PageIntro` no conteúdo. Ações com texto na barra
26
- usam `Button size="sm"`; ações somente com ícone usam `icon-sm`. Na forma explícita dentro do
27
- shell, use `Page > PageIntro (PageTitle, PageDescription?, PageActions?) + PageBody`. Não monte
23
+ declarando `title` e `actions`. O Opus mantém a barra de `3rem` e projeta título e ações nela.
24
+ Ações com texto na barra usam o tamanho `default`, como as ações de footer de Dialog e Drawer;
25
+ ações somente com ícone de chrome usam `icon-sm`. O tamanho `sm` fica para ações operacionais em
26
+ toolbar, seção ou coleção densa, e `xs`/`icon-xs` para ações internas de linha ou célula. Contexto
27
+ e variante resolvem a hierarquia visual sem alterar essa medida. Na forma explícita dentro do
28
+ shell, use `Page > PageHeader (PageNavigation?, PageTitle, PageActions?) + PageBody`, com
29
+ `PageIntro` opcional antes do body. Não monte
28
30
  `PaneHeader`, portal ou seletor global para reconstruir essa composição.
29
- - Quando a rota precisa de navegação própria além da navegação persistente do shell, declare
30
- `PageNavigation` no `PageIntro`. Esse slot permanece com o recurso ao alternar entre Page, Dialog
31
- e Drawer; não replique nele a navegação global já fornecida pelo shell.
32
31
  - `PageState` substitui todo o conteúdo principal quando a página carrega, falha ou está vazia. Na
33
32
  forma curta ele pode ser escrito como filho direto de `Page`, que cria o `PageBody`; na forma
34
33
  explícita, fica sozinho dentro de `PageBody`. Nos três estados ativos, o cabeçalho da Page isolada
35
- some inteiro — incluindo o `PageBack`; dentro de `PageShell`, somente `PageIntro` some e a barra
34
+ some inteiro — incluindo o `PageBack`; dentro de `PageShell`, `PageIntro` some e a barra
36
35
  permanece. O estado ocupa a área disponível e seu título assume o heading
37
36
  principal, inclusive quando um componente intermediário renderiza o estado. Uma subpágina que
38
37
  dependa do retorno ao pai oferece essa saída pelo `action` do próprio `PageState`. O erro mantém
@@ -67,11 +66,13 @@ ContentDescription, ContentMeta, ContentActions) + ContentBody`. `ContentHeader`
67
66
  controles customizados pelo contexto. `ActionFormCard` e `ActionFormDialog` acrescentam a
68
67
  casca; não duplicar o form para obter card ou modal. Cancelamento em forms, confirmações e
69
68
  modais usa `ghost`, deixando o destaque visual para a ação principal.
70
- - Cabeçalhos de `Dialog` e `Drawer` apenas nomeiam a superfície com o título. Não preencher uma
71
- segunda linha por hábito. Consequência, restrição ou instrução que realmente mude a tarefa entra
72
- no início de `DialogBody`/`DrawerBody`, como texto ou `Alert`; wrappers usam `intro` e a API
73
- imperativa usa `body`. Relacione texto conciso por `aria-describedby` somente quando ele também
74
- precisar descrever a superfície para tecnologias assistivas.
69
+ - Cabeçalhos de `Dialog` e `Drawer` nomeiam a superfície com o título e organizam suas ações. O close
70
+ padrão é uma action ghost somente com ícone no final do header; não crie uma posição flutuante
71
+ alternativa. Não preencher uma segunda linha por hábito. Consequência, restrição ou instrução que
72
+ realmente mude a tarefa entra no início de `DialogBody`/`DrawerBody`, como texto ou `Alert`;
73
+ wrappers usam `intro` e a API imperativa usa `body`. Relacione texto conciso por
74
+ `aria-describedby` somente quando ele também precisar descrever a superfície para tecnologias
75
+ assistivas.
75
76
  - `ActionList` mantém fetch, toolbar, loading, erro, retry, vazio, seleção e paginação enquanto
76
77
  permite três composições de resultado: tabela por `columns`, renderer completo por `children`
77
78
  ou views nomeadas. Usar `ActionFilterBar` isoladamente só quando outra superfície assumir a
@@ -30,7 +30,7 @@
30
30
  "zod": "^3.24.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@softize/base": "^2.2.1",
33
+ "@softize/base": "^2.3.0",
34
34
  "@tailwindcss/vite": "^4.1.0",
35
35
  "@types/node": "^22.0.0",
36
36
  "@types/react": "^19.0.0",