@softize/opus 16.0.0 → 17.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +39 -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 +7 -4
  9. package/docs/adr/0014-structural-headers-do-not-carry-description.md +35 -0
  10. package/package.json +2 -2
  11. package/registry/skills/build-opus-ui/references/evaluations.md +1 -1
  12. package/registry/skills/build-opus-ui/references/ui-patterns.md +16 -17
  13. package/registry/templates/app/package.json +1 -1
  14. package/src/core/presentation.ts +142 -13
  15. package/src/ui/components/patterns/form.tsx +29 -10
  16. package/src/ui/components/patterns/page.tsx +93 -48
  17. package/src/ui/components/patterns/presentation.tsx +487 -134
  18. package/src/ui/components/patterns/surface-header.tsx +4 -3
  19. package/src/ui/components/patterns/trigger.tsx +8 -1
  20. package/src/ui/components/patterns/view.tsx +2 -2
  21. package/src/ui/components/primitives/command.tsx +82 -42
  22. package/src/ui/components/primitives/dialog.tsx +180 -97
  23. package/src/ui/components/primitives/drawer.tsx +63 -22
  24. package/src/ui/docs/content/command.md +3 -1
  25. package/src/ui/docs/content/content.md +1 -1
  26. package/src/ui/docs/content/dialog.md +9 -9
  27. package/src/ui/docs/content/drawer.md +4 -4
  28. package/src/ui/docs/content/page.md +14 -29
  29. package/src/ui/docs/content/presentation.md +54 -46
  30. package/src/ui/meta.ts +2 -2
  31. package/src/ui/react.tsx +1 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,45 @@ 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.0.0 — 2026-09-11
11
+
12
+ A inspeção de `Presentation` passa a pertencer exclusivamente à Lens, que lê as definições estáticas
13
+ projetadas no manifest. `PresentationDevtoolsProvider`, `usePresentationRegistration` e
14
+ `PresentationDevtools` deixam de existir; a aplicação não mantém um segundo registro de execução
15
+ nem monta um launcher no shell. `PresentationInspector` permanece disponível para a Lens e para
16
+ bancadas isoladas que precisem visualizar um snapshot mascarado.
17
+
18
+ `Presentation` passa a consumir diretamente a definição, a invocação e o registry de contratos.
19
+ O renderer delega actions `simple`, `form`, `list` e `view` a `ActionTrigger`, `ActionForm`,
20
+ `ActionList` e `ActionView`; navegação e efeitos atualizam a invocação serializável. O gate cruza
21
+ bindings com o schema de input das actions antes de publicar o manifest.
22
+ Actions de formulário ocupam o footer estrutural da superfície e continuam associadas ao `<form>`.
23
+ O body pode declarar `submitLabel` para nomear a conclusão específica do formulário.
24
+ Durante uma action com `blocking: "surface"`, voltar, fechar, editar campos e submeter o formulário
25
+ permanecem bloqueados até a conclusão.
26
+
27
+ `PageShell` recebe navigation, title e actions do `PageHeader` descendente. `PageIntro` volta a ser
28
+ somente uma introdução opcional do body. Page, Dialog e Drawer deixam de oferecer Description no
29
+ header; contexto relevante começa no body.
30
+
31
+ O fechamento padrão de `Dialog` e `Drawer` passa a ser um `Button` ghost somente com ícone no final
32
+ do header. `showCloseButton` continua controlando sua presença. O `CommandDialog` adota um header
33
+ compacto para preservar a mesma anatomia; não existe uma segunda posição flutuante para o close.
34
+
35
+ **Breaking:** remova providers, registros e launchers de Presentation do shell. Use a projeção de
36
+ Presentations na Lens para inspecionar as definições disponíveis. Migre a composição manual de
37
+ `Presentation` para `definition`, `definitions`, `actions` e `invocation`. Remova `description` de
38
+ `Page` e `PageDescription`; mova apenas o contexto relevante para o início de `PageBody`.
39
+
40
+ ## 16.1.0 — 2026-09-11
41
+
42
+ `PresentationDevtoolsProvider`, `usePresentationRegistration` e `PresentationDevtools` permitem
43
+ que o shell reúna as Presentations ativas em um único launcher de desenvolvimento. Cada recurso
44
+ registra sua definição e o estado vivo da invocação; a pessoa escolhe qual snapshot inspecionar no
45
+ menu. O JSON continua mascarando dados sensíveis e só é materializado depois da escolha, evitando
46
+ serialização desnecessária durante a renderização. O consumidor deve montar o provider, os registros
47
+ e o launcher apenas em ambiente de desenvolvimento.
48
+
10
49
  ## 16.0.0 — 2026-09-11
11
50
 
12
51
  Opus 16 introduz `Presentation` como artefato público declarativo, disponível também pelo subpath
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,7 +12,7 @@ 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
 
@@ -21,8 +21,8 @@ que aparece. `Surface` escolhe `page`, `dialog` ou `drawer` no momento da render
21
21
 
22
22
  A anatomia portátil é `Header + Body + Footer?`. O header organiza horizontalmente
23
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
24
+ Dialog e Drawer acrescentam o fechamento ao final das ações do header. Em Page, `PageShell` hospeda
25
+ o mesmo header na barra persistente; uma introdução opcional pode existir no body sem mover nem
26
26
  duplicar o título estrutural.
27
27
 
28
28
  Uma action assume um de dois papéis:
@@ -52,7 +52,8 @@ pilha de frames necessária para voltar. Ela não é publicada no manifest.
52
52
  page. `navigate` abre outra invocação por push ou substitui a atual; sincronizar esse estado com URL
53
53
  é responsabilidade do adaptador da aplicação quando refresh, deep link ou histórico forem
54
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.
55
+ diagnósticos, mascarando chaves sensíveis antes de expor o JSON. O inventário estático e seu JSON
56
+ ficam disponíveis na Lens; a aplicação não adiciona um launcher flutuante ao shell.
56
57
 
57
58
  ## Consequências
58
59
 
@@ -85,8 +86,10 @@ Foi descartada porque compatibilidade exige delegar cada kind ao pattern canôni
85
86
 
86
87
  - testes de schema cobrem versão, referências, kinds, placements e bindings;
87
88
  - testes do renderer exercitam `simple`, `form`, `list` e `view` nas superfícies válidas;
89
+ - validação cruza bindings, chaves obrigatórias e valores fixos com o schema de input da action;
88
90
  - o manifest projeta Presentations sem callbacks nem dados server-only;
89
91
  - a lens lista e expõe o JSON integral de cada Presentation;
90
92
  - testes de invocação cobrem push, replace, back, close e rejeição de input não JSON;
91
93
  - o inspetor mascara segredo, token, cookie, autorização, senha e chave de API;
94
+ - a Lens lista as definições registradas e abre o JSON integral sem depender da árvore React;
92
95
  - 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "16.0.0",
3
+ "version": "17.0.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,16 @@ 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
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
26
25
  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
26
+ shell, use `Page > PageHeader (PageNavigation?, PageTitle, PageActions?) + PageBody`, com
27
+ `PageIntro` opcional antes do body. Não monte
28
28
  `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
29
  - `PageState` substitui todo o conteúdo principal quando a página carrega, falha ou está vazia. Na
33
30
  forma curta ele pode ser escrito como filho direto de `Page`, que cria o `PageBody`; na forma
34
31
  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
32
+ some inteiro — incluindo o `PageBack`; dentro de `PageShell`, `PageIntro` some e a barra
36
33
  permanece. O estado ocupa a área disponível e seu título assume o heading
37
34
  principal, inclusive quando um componente intermediário renderiza o estado. Uma subpágina que
38
35
  dependa do retorno ao pai oferece essa saída pelo `action` do próprio `PageState`. O erro mantém
@@ -67,11 +64,13 @@ ContentDescription, ContentMeta, ContentActions) + ContentBody`. `ContentHeader`
67
64
  controles customizados pelo contexto. `ActionFormCard` e `ActionFormDialog` acrescentam a
68
65
  casca; não duplicar o form para obter card ou modal. Cancelamento em forms, confirmações e
69
66
  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.
67
+ - Cabeçalhos de `Dialog` e `Drawer` nomeiam a superfície com o título e organizam suas ações. O close
68
+ padrão é uma action ghost somente com ícone no final do header; não crie uma posição flutuante
69
+ alternativa. Não preencher uma segunda linha por hábito. Consequência, restrição ou instrução que
70
+ realmente mude a tarefa entra no início de `DialogBody`/`DrawerBody`, como texto ou `Alert`;
71
+ wrappers usam `intro` e a API imperativa usa `body`. Relacione texto conciso por
72
+ `aria-describedby` somente quando ele também precisar descrever a superfície para tecnologias
73
+ assistivas.
75
74
  - `ActionList` mantém fetch, toolbar, loading, erro, retry, vazio, seleção e paginação enquanto
76
75
  permite três composições de resultado: tabela por `columns`, renderer completo por `children`
77
76
  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",