@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.
- package/CHANGELOG.md +41 -0
- package/bin/lib/check.mjs +9 -13
- package/bin/lib/copy.mjs +29 -4
- package/docs/adr/0005-structural-surfaces-share-an-explicit-anatomy.md +6 -3
- package/docs/adr/0009-page-title-does-not-carry-a-counter.md +5 -2
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +12 -14
- package/docs/adr/0012-modal-header-only-names-the-surface.md +8 -4
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +20 -11
- package/docs/adr/0014-structural-headers-do-not-carry-description.md +35 -0
- package/docs/adr/0015-action-size-follows-interaction-density.md +64 -0
- package/package.json +2 -2
- package/registry/skills/build-opus-ui/references/evaluations.md +1 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +19 -18
- package/registry/templates/app/package.json +1 -1
- package/src/core/presentation.ts +142 -13
- package/src/ui/components/patterns/form.tsx +35 -10
- package/src/ui/components/patterns/page.tsx +93 -48
- package/src/ui/components/patterns/presentation.tsx +489 -329
- package/src/ui/components/patterns/surface-header.tsx +4 -3
- package/src/ui/components/patterns/trigger.tsx +8 -1
- package/src/ui/components/patterns/view.tsx +2 -2
- package/src/ui/components/primitives/command.tsx +82 -42
- package/src/ui/components/primitives/dialog.tsx +180 -97
- package/src/ui/components/primitives/drawer.tsx +63 -22
- package/src/ui/docs/content/button.md +8 -2
- package/src/ui/docs/content/command.md +3 -1
- package/src/ui/docs/content/content.md +1 -1
- package/src/ui/docs/content/dialog.md +9 -9
- package/src/ui/docs/content/drawer.md +4 -4
- package/src/ui/docs/content/page.md +16 -31
- package/src/ui/docs/content/presentation.md +73 -80
- package/src/ui/meta.ts +2 -2
- 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"
|
|
113
|
-
["PageNavigation", new Set(["PageHeader"
|
|
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", "
|
|
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`,
|
|
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`
|
|
46
|
-
|
|
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
|
|
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
|
|
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`,
|
|
25
|
-
`
|
|
26
|
-
Na forma explícita, a página usa `
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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`
|
|
46
|
-
|
|
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
|
|
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
|
|
20
|
-
`
|
|
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
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
`back` restaura o último frame da pilha. Em Dialog ou Drawer, ele só é 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": "
|
|
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.
|
|
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
|
|
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,
|
|
9
|
-
PageActions?) + PageBody`; `title
|
|
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
|
-
-
|
|
14
|
-
o
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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`,
|
|
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`
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|