@softize/opus 15.2.2 → 16.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 (37) hide show
  1. package/CHANGELOG.md +51 -18
  2. package/bin/lib/check.mjs +98 -15
  3. package/bin/lib/copy.mjs +811 -314
  4. package/bin/lib/gen-manifest.mjs +24 -23
  5. package/bin/lib/gen-runner.mjs +188 -148
  6. package/docs/adr/0010-page-header-owns-page-chrome.md +3 -4
  7. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -3
  8. package/docs/adr/0012-modal-header-only-names-the-surface.md +45 -0
  9. package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +92 -0
  10. package/docs/code-style.md +24 -19
  11. package/package.json +5 -1
  12. package/registry/skills/build-opus-ui/references/ui-patterns.md +43 -26
  13. package/src/core/presentation.ts +512 -0
  14. package/src/presentation/index.ts +1 -0
  15. package/src/ui/components/patterns/action-list-dialog.tsx +26 -9
  16. package/src/ui/components/patterns/confirm.tsx +34 -29
  17. package/src/ui/components/patterns/form-dialog.tsx +20 -8
  18. package/src/ui/components/patterns/list.tsx +26 -9
  19. package/src/ui/components/patterns/page.tsx +99 -139
  20. package/src/ui/components/patterns/presentation.tsx +316 -0
  21. package/src/ui/components/patterns/sidebar.tsx +3 -3
  22. package/src/ui/components/patterns/trigger.tsx +38 -17
  23. package/src/ui/components/primitives/button-group.tsx +53 -43
  24. package/src/ui/components/primitives/command.tsx +30 -72
  25. package/src/ui/components/primitives/dialog.tsx +23 -89
  26. package/src/ui/components/primitives/drawer.tsx +8 -34
  27. package/src/ui/docs/content/action-form-dialog.md +12 -10
  28. package/src/ui/docs/content/action-list-dialog.md +22 -17
  29. package/src/ui/docs/content/button.md +52 -35
  30. package/src/ui/docs/content/communication.md +26 -26
  31. package/src/ui/docs/content/dialog.md +173 -154
  32. package/src/ui/docs/content/drawer.md +12 -11
  33. package/src/ui/docs/content/page.md +72 -91
  34. package/src/ui/docs/content/presentation.md +158 -0
  35. package/src/ui/docs/registry.tsx +6 -0
  36. package/src/ui/meta.ts +8 -2
  37. package/src/ui/react.tsx +10 -3
@@ -1,12 +1,11 @@
1
1
  # ADR 0010 — PageHeader também representa o chrome compacto da página
2
2
 
3
- - **Status:** aceita.
3
+ - **Status:** substituída pela ADR 0011.
4
4
  - **Data:** 2026-09-09.
5
5
  - **Complementa:** ADR 0005.
6
6
 
7
- > **Atualização (2026-09-10).** A ADR 0011 acrescenta `PageShell` para o caso em que shell e rota
8
- > conhecem partes diferentes da mesma página. A barra continua sendo a apresentação compacta do
9
- > cabeçalho, mas o título do conteúdo passa a `PageIntro` e as ações são coordenadas pelo Opus.
7
+ > **Atualização (2026-09-11).** A ADR 0011 torna `PageShell` o único componente responsável pela
8
+ > barra persistente. `PageHeader` volta a ter uma única apresentação dentro da página.
10
9
 
11
10
  ## Contexto
12
11
 
@@ -17,8 +17,9 @@ anatomias concorrentes e faz um estado integral remover também a navegação pe
17
17
  ## Decisão
18
18
 
19
19
  `PageShell` representa a moldura persistente de uma página dentro de um shell de aplicação. Ele
20
- renderiza a apresentação em barra de `PageHeader`, recebe a navegação conhecida pelo shell e oferece
21
- o alvo canônico para ações declaradas pela `Page` descendente.
20
+ renderiza a barra, recebe a navegação conhecida pelo shell e oferece o alvo canônico para ações
21
+ declaradas pela `Page` descendente. Essa barra existe somente por `PageShell`; `PageHeader` não
22
+ possui variante visual para reproduzi-la dentro da página.
22
23
 
23
24
  Quando uma `Page` está dentro de `PageShell`, sua forma curta transforma título e descrição em
24
25
  `PageIntro`, dentro do conteúdo. `PageActions` continua declarado pela página, mas aparece na barra.
@@ -41,7 +42,8 @@ Ele apenas coordena a barra e a área em que uma única `Page` é renderizada.
41
42
  - título e descrição deixam de ser mascarados por seletores globais;
42
43
  - ações de página têm um único destino oficial na barra;
43
44
  - estados integrais preservam a barra e escondem somente a introdução do conteúdo;
44
- - a composição anterior de `Page` permanece compatível fora de `PageShell`;
45
+ - a composição de `Page` com `PageHeader` permanece disponível fora de `PageShell`, sem uma segunda
46
+ forma de produzir a barra;
45
47
  - `PageActionsTarget` continua disponível apenas para workspaces imersivos que não usam
46
48
  `PageShell`.
47
49
 
@@ -0,0 +1,45 @@
1
+ # ADR 0012 — O cabeçalho modal apenas nomeia a superfície
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-11.
5
+
6
+ ## Contexto
7
+
8
+ `DialogDescription` e `DrawerDescription` tornavam natural preencher o cabeçalho com uma segunda
9
+ linha mesmo quando ela apenas repetia o título ou explicava a ação óbvia. O resultado era uma
10
+ hierarquia mais pesada, mais espaço fixo antes da tarefa e textos que raramente ajudavam a pessoa a
11
+ decidir.
12
+
13
+ Há, porém, contexto que não pode desaparecer: consequências, restrições e instruções que mudam a
14
+ decisão. Esse conteúdo pertence à tarefa, não ao nome da superfície. Também pode precisar participar
15
+ da descrição acessível do modal.
16
+
17
+ ## Decisão
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`.
21
+ - Contexto relevante aparece no início de `DialogBody` ou `DrawerBody`, como texto, `Alert` ou uma
22
+ composição própria. Texto que apenas repete o título ou a ação é omitido.
23
+ - A API imperativa usa somente `body` para esse conteúdo. `ActionFormDialog` e `ActionListDialog`
24
+ oferecem `intro` no início do corpo; não oferecem `description`.
25
+ - `DialogContent` e `DrawerContent` não inferem uma descrição. Quando um trecho conciso do corpo deve
26
+ descrever a superfície para tecnologias assistivas, o consumidor relaciona seu `id` por
27
+ `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.
30
+
31
+ ## Consequências
32
+
33
+ - Modais comuns começam mais perto da tarefa e não pedem texto de preenchimento.
34
+ - Informações importantes continuam visíveis, mas ocupam a região rolável e podem usar o componente
35
+ semântico adequado.
36
+ - A migração troca `description` por `body` na API imperativa, por `intro` nos wrappers e move conteúdo
37
+ declarativo relevante para o começo do corpo.
38
+ - O checker reprova as APIs removidas com orientação de migração.
39
+
40
+ ## Verificação
41
+
42
+ - o barrel público não exporta `DialogDescription` nem `DrawerDescription`;
43
+ - os tipos das APIs imperativas não aceitam `description`;
44
+ - testes cobrem `body`, `intro`, a relação acessível e os diagnósticos de migração;
45
+ - a auditoria de docs, o inventário de copy, o typecheck e a suíte do pacote permanecem verdes.
@@ -0,0 +1,92 @@
1
+ # ADR 0013 — Presentation é um artefato portátil orientado a actions
2
+
3
+ - **Status:** aceita.
4
+ - **Data:** 2026-09-11.
5
+
6
+ ## Contexto
7
+
8
+ Page, Dialog e Drawer compartilham uma anatomia estrutural, mas consumidores ainda repetem a
9
+ coordenação entre título, navegação, corpo, ações, estados e ciclo de abertura. Essa repetição
10
+ dificulta apresentar o mesmo recurso em superfícies diferentes e impede que ferramentas analisem
11
+ a interface antes de executar React.
12
+
13
+ O Opus já declara actions `simple`, `form`, `list` e `view`. Um artefato de interface não deve
14
+ reimplementar seus contratos, validação, autorização ou transporte; deve apenas compor essas
15
+ capacidade em uma apresentação verificável.
16
+
17
+ ## Decisão
18
+
19
+ `Presentation` é o artefato declarativo que descreve um recurso independentemente da superfície em
20
+ que aparece. `Surface` escolhe `page`, `dialog` ou `drawer` no momento da renderização.
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.
27
+
28
+ Uma action assume um de dois papéis:
29
+
30
+ - conteúdo no body: actions `form`, `list` e `view` usam seus patterns canônicos;
31
+ - comando no header ou footer: `simple` executa por `ActionTrigger`; os demais kinds abrem ou
32
+ navegam para outra Presentation que os apresenta como conteúdo.
33
+
34
+ O kind declarado pela action determina o pattern. A Presentation não repete `presentation: form`
35
+ ou `presentation: trigger`. Bindings declarativos fornecem input a partir de rota, registro, item,
36
+ seleção, sessão, resultado, valor fixo ou relógio. Autorização permanece no servidor.
37
+
38
+ Efeitos posteriores e coordenação de bloqueio são dados declarativos, não callbacks persistidos. O
39
+ renderer pode coordenar os escopos `action`, `group` e `surface`, mas cada pattern continua dono de
40
+ loading, erro, vazio, validação e repetição próprios.
41
+
42
+ Presentations possuem identificador e versão de schema, são registradas no `opus.config.ts` e
43
+ projetadas no manifest. A lens lê essa projeção; não infere a interface pelo JSX. Combinações
44
+ inválidas, referências ausentes e bindings incompatíveis produzem diagnósticos antes do runtime.
45
+
46
+ `PresentationDefinition` e `PresentationInvocation` têm ciclos de vida distintos. A definição é
47
+ estática, publicável no manifest e inspecionável pela Lens. A invocação é o estado serializável de
48
+ uma execução: identifica a Presentation, escolhe a surface, carrega somente input JSON e mantém a
49
+ pilha de frames necessária para voltar. Ela não é publicada no manifest.
50
+
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.
56
+
57
+ ## Consequências
58
+
59
+ - O mesmo recurso pode ser apresentado como page, dialog ou drawer sem três implementações.
60
+ - Novas interfaces convencionais concentram código na declaração e na composição específica.
61
+ - `ActionFormDialog`, `ActionListDialog` e wrappers equivalentes permanecem atalhos manuais, mas não
62
+ formam novos kinds do artefato.
63
+ - Interfaces específicas continuam possíveis por nós de extensão explícitos; uma extensão não é
64
+ considerada portátil até possuir renderer registrado para as superfícies declaradas.
65
+ - O schema evolui por versão e migração; consumidores não dependem de detalhes internos do renderer.
66
+
67
+ ## Alternativas consideradas
68
+
69
+ ### Persistir JSX ou callbacks
70
+
71
+ Preservaria liberdade irrestrita, mas não seria serializável, verificável nem seguro para autoria
72
+ por ferramentas. Foi descartada em favor de bindings e efeitos fechados.
73
+
74
+ ### Criar schemas separados para Page, Dialog e Drawer
75
+
76
+ Representaria diretamente a aparência atual, mas triplicaria a composição e faria a superfície
77
+ definir o recurso. Foi descartada porque as diferenças pertencem ao renderer.
78
+
79
+ ### Tratar toda action como ActionTrigger
80
+
81
+ Uniformizaria comandos visualmente, mas perderia os ciclos próprios de formulário, lista e view.
82
+ Foi descartada porque compatibilidade exige delegar cada kind ao pattern canônico.
83
+
84
+ ## Verificação
85
+
86
+ - testes de schema cobrem versão, referências, kinds, placements e bindings;
87
+ - testes do renderer exercitam `simple`, `form`, `list` e `view` nas superfícies válidas;
88
+ - o manifest projeta Presentations sem callbacks nem dados server-only;
89
+ - a lens lista e expõe o JSON integral de cada Presentation;
90
+ - testes de invocação cobrem push, replace, back, close e rejeição de input não JSON;
91
+ - o inspetor mascara segredo, token, cookie, autorização, senha e chave de API;
92
+ - uma aplicação consumidora usa o pacote local por symlink durante a migração página por página.
@@ -61,7 +61,12 @@ Base instalada é dona do catálogo, dos kinds aceitos e do fundamento de cada r
61
61
  "allowedUppercase": ["CPF"],
62
62
  "exclude": ["scripts/fixtures/**"],
63
63
  "transforms": [
64
- { "source": "src/account.ts", "line": 12, "role": "button", "transform": "uppercase" }
64
+ {
65
+ "source": "src/account.ts",
66
+ "line": 12,
67
+ "role": "button",
68
+ "transform": "uppercase"
69
+ }
65
70
  ],
66
71
  "exemptions": [
67
72
  {
@@ -78,24 +83,24 @@ Base instalada é dona do catálogo, dos kinds aceitos e do fundamento de cada r
78
83
  }
79
84
  ```
80
85
 
81
- | Propriedade Opus | Papel no protocolo Base |
82
- |---|---|
83
- | `label`, `confirm.*Label`, `ActionTrigger.label` | `button` |
84
- | `title`, `examples[].name` | `title` |
85
- | `summary`, `description`, `examples[].description`, `expand.*.description` | `description` |
86
- | `messages.success`, `messages.error`, `messages.confirmation` | `success`, `error`, `message` |
87
- | `confirm.message` | `dialog-body` |
88
- | `fields.*.label`, `filters.*.label` | `label` |
89
- | `placeholder`, `help` | `placeholder`, `helper-text` |
90
- | opções estáticas | `menu-item` |
91
- | `columns[].label`, `periods[].label` | `heading`, `tab` |
92
- | `Select.emptyText`, `Select.searchPlaceholder` | `empty-state`, `placeholder` |
93
- | `Select.options[].hint/triggerLabel/group` | `label`, `label`, `heading` |
94
- | `ActionTrigger.confirm.*` | papel correspondente do diálogo |
95
- | `t.dict` — `label`, `description`, `doc` das entradas e `doc` do dicionário | `label`, `description` |
86
+ | Propriedade Opus | Papel no protocolo Base |
87
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
88
+ | `label`, `confirm.*Label`, `ActionTrigger.label` | `button` |
89
+ | `title`, `examples[].name` | `title` |
90
+ | `summary`, `description`, `examples[].description`, `expand.*.description` | `description` |
91
+ | `messages.success`, `messages.error`, `messages.confirmation` | `success`, `error`, `message` |
92
+ | `confirm.message` | `dialog-body` |
93
+ | `fields.*.label`, `filters.*.label` | `label` |
94
+ | `placeholder`, `help` | `placeholder`, `helper-text` |
95
+ | opções estáticas | `menu-item` |
96
+ | `columns[].label`, `periods[].label` | `heading`, `tab` |
97
+ | `Select.emptyText`, `Select.searchPlaceholder` | `empty-state`, `placeholder` |
98
+ | `Select.options[].hint/triggerLabel/group` | `label`, `label`, `heading` |
99
+ | `ActionTrigger.confirm.*` | papel correspondente do diálogo |
100
+ | `t.dict` — `label`, `description`, `doc` das entradas e `doc` do dicionário | `label`, `description` |
96
101
  | `DataState`/`PageState`/`ActionList`/`ActionListDialog` — `emptyMessage`, `errorMessage`, `retryLabel`; `PageState`/`ActionListDialog` — `title`, `description`; `ActionView.emptyMessage` | `empty-state`, `error`, `button`, `title`, `description` |
97
- | `dialog.alert/confirm/prompt/choose()` — `title`, `description`, `body`, `action`, `cancel`, `placeholder`, `actions[].label` | papel correspondente do diálogo |
98
- | `TooltipContent` (children), `LabelHelp.help` | `label`, `helper-text` |
102
+ | `dialog.alert/confirm/prompt/choose()` — `title`, `description`, `body`, `action`, `cancel`, `placeholder`, `actions[].label` | papel correspondente do diálogo |
103
+ | `TooltipContent` (children), `LabelHelp.help` | `label`, `helper-text` |
99
104
 
100
105
  ## Cobertura e significado do gate verde
101
106
 
@@ -109,7 +114,7 @@ O extrator cobre três superfícies, sem heurística de nome:
109
114
  `@softize/opus/schema/zod`; metadata livre não é classificada por nome, declarações dinâmicas
110
115
  reprovam em vez de serem executadas e o dicionário mantém um snapshot imutável das entradas;
111
116
  3. texto estático em filhos e props de uma allowlist de componentes importados diretamente
112
- de `@softize/opus/ui*` — por exemplo, `Button`, `DialogTitle`, `DialogDescription`,
117
+ de `@softize/opus/ui*` — por exemplo, `Button`, `DialogTitle`, `DialogBody`,
113
118
  `FieldLabel`, `TabsTrigger`, `Page`, `Input` e `DataState`. Alias e namespace de import
114
119
  continuam rastreáveis. `uppercase`, variantes como `sm:hover:uppercase`, modificadores
115
120
  `!uppercase`/`uppercase!` e classes em descendentes são projetados na superfície que
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "15.2.2",
3
+ "version": "16.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",
@@ -153,6 +153,10 @@
153
153
  "types": "./src/seed/index.ts",
154
154
  "default": "./src/seed/index.ts"
155
155
  },
156
+ "./presentation": {
157
+ "types": "./src/presentation/index.ts",
158
+ "default": "./src/presentation/index.ts"
159
+ },
156
160
  "./server": {
157
161
  "types": "./src/server/index.ts",
158
162
  "default": "./src/server/index.ts"
@@ -6,26 +6,29 @@
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
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
9
+ PageActions?) + PageBody`; `title`, `description` 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
13
  - Fora de `PageShell`, a composição explícita também pode trocar `PageHeader` por `PageIntro` quando
14
14
  o conteúdo precisar somente de título, descrição e ações, sem retorno ou breadcrumb. `PageIntro`
15
15
  dá mais presença ao título e não é uma abreviação visual de `PageHeader`.
16
- - `PageHeader` é a única região de cabeçalho da página. O default acompanha o container;
17
- `variant="bar"` apresenta a mesma anatomia como faixa compacta no topo. Em uma subpágina simples,
18
- `PageBack` recebe o destino pai explícito: aparece acima do título no default e como icon-only com
19
- tooltip na barra. Para mais de um ancestral relevante, use `Breadcrumb` dentro de
20
- `PageNavigation`. Não combine retorno e breadcrumb nem crie um chrome paralelo para uma `Page`.
21
- Ações com texto na barra usam `Button size="sm"`; ações somente com ícone usam `icon-sm`.
22
- `PageActionsTarget` fica reservado a workspaces imersivos que já possuam chrome próprio.
16
+ - `PageHeader` é a região de cabeçalho dentro de uma `Page` isolada e organiza navegação, título e
17
+ ações na mesma linha. Em uma subpágina simples, `PageBack` recebe o destino pai explícito e aparece
18
+ antes do título como controle somente com ícone. Para mais de um ancestral relevante, use
19
+ `Breadcrumb` dentro de `PageNavigation`. Não combine retorno e breadcrumb nem crie um chrome
20
+ paralelo para uma `Page`. `PageActionsTarget` fica reservado a workspaces imersivos que
21
+ possuam chrome próprio.
23
22
  - Quando shell e rota conhecem partes diferentes da mesma página, use `PageShell` ao redor da rota.
24
- O shell fornece `navigation`; a `Page` descendente continua declarando `title`, `description` e
25
- `actions`. O Opus mantém a barra de `3rem`, projeta as ações nela e apresenta título e descrição
26
- como `PageIntro` no conteúdo. Na forma explícita dentro do shell, use
27
- `Page > PageIntro (PageTitle, PageDescription?, PageActions?) + PageBody`. Não monte `PaneHeader`,
28
- portal ou seletor global para reconstruir essa composição.
23
+ 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
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.
29
32
  - `PageState` substitui todo o conteúdo principal quando a página carrega, falha ou está vazia. Na
30
33
  forma curta ele pode ser escrito como filho direto de `Page`, que cria o `PageBody`; na forma
31
34
  explícita, fica sozinho dentro de `PageBody`. Nos três estados ativos, o cabeçalho da Page isolada
@@ -37,7 +40,7 @@
37
40
  recuperação como botão `outline` textual. Estados de seção ou coleção continuam em `DataState`,
38
41
  `ActionView`, `ActionList` ou `Alert`; não elevar uma falha parcial a estado da página.
39
42
  - `Content` delimita uma seção e segue a mesma anatomia: `Content > ContentHeader (ContentTitle,
40
- ContentDescription, ContentMeta, ContentActions) + ContentBody`. `ContentHeader` nunca fica
43
+ ContentDescription, ContentMeta, ContentActions) + ContentBody`. `ContentHeader` nunca fica
41
44
  solto. `title`, `description`, `count` e `actions` no `Content` são a abreviação para o caso
42
45
  direto e não podem ser misturados ao header explícito. `level` preserva a hierarquia semântica
43
46
  do heading.
@@ -64,6 +67,11 @@
64
67
  controles customizados pelo contexto. `ActionFormCard` e `ActionFormDialog` acrescentam a
65
68
  casca; não duplicar o form para obter card ou modal. Cancelamento em forms, confirmações e
66
69
  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
75
  - `ActionList` mantém fetch, toolbar, loading, erro, retry, vazio, seleção e paginação enquanto
68
76
  permite três composições de resultado: tabela por `columns`, renderer completo por `children`
69
77
  ou views nomeadas. Usar `ActionFilterBar` isoladamente só quando outra superfície assumir a
@@ -100,12 +108,12 @@
100
108
  O dicionário declara o papel; a tela só escolhe onde o valor fica. Tabela de decisão aplicada
101
109
  por `DictionaryValue` e pelas colunas de `ActionList`:
102
110
 
103
- | Papel declarado | Exemplos | Forma | Contexto | Variante | Ícone | Tooltip |
104
- |---|---|---|---|---|---|---|
105
- | `classification` | Tipo de cliente, categoria, natureza | Badge | `neutral` | `outline`; ignora `context` da entrada | se a entrada declara `icon` do catálogo | se `description` acrescenta ao rótulo |
106
- | `status` | Aberto, resolvido, degradado | Badge | pela entrada; `neutral` por padrão | `subtle`; nunca `outline` | idem | idem |
107
- | `stage` | Prospect, cliente; etapa do funil | Badge | igual a `status` | `subtle` | idem | idem |
108
- | `plain` ou ausente | Fonte, formato, período | Texto | — | — | idem | idem |
111
+ | Papel declarado | Exemplos | Forma | Contexto | Variante | Ícone | Tooltip |
112
+ | ------------------ | ------------------------------------ | ----- | ---------------------------------- | -------------------------------------- | --------------------------------------- | ------------------------------------- |
113
+ | `classification` | Tipo de cliente, categoria, natureza | Badge | `neutral` | `outline`; ignora `context` da entrada | se a entrada declara `icon` do catálogo | se `description` acrescenta ao rótulo |
114
+ | `status` | Aberto, resolvido, degradado | Badge | pela entrada; `neutral` por padrão | `subtle`; nunca `outline` | idem | idem |
115
+ | `stage` | Prospect, cliente; etapa do funil | Badge | igual a `status` | `subtle` | idem | idem |
116
+ | `plain` ou ausente | Fonte, formato, período | Texto | — | — | idem | idem |
109
117
 
110
118
  Regras que não dependem da tabela:
111
119
 
@@ -123,13 +131,22 @@ Exemplo correto — tipo e estágio em colunas próprias, apresentação do dici
123
131
 
124
132
  ```ts
125
133
  export const customerKindDict = t.dict(
126
- { pf: { label: 'Pessoa física', icon: 'user' }, pj: { label: 'Empresa', icon: 'building' } },
127
- { doc: 'Natureza da parte no cadastro global.', presentation: 'classification' },
128
- )
134
+ {
135
+ pf: { label: "Pessoa física", icon: "user" },
136
+ pj: { label: "Empresa", icon: "building" },
137
+ },
138
+ {
139
+ doc: "Natureza da parte no cadastro global.",
140
+ presentation: "classification",
141
+ },
142
+ );
129
143
  export const customerStageDict = t.dict(
130
- { prospect: { label: 'Prospect' }, customer: { label: 'Cliente', context: 'success' } },
131
- { doc: 'Estágio comercial atual da parte.', presentation: 'stage' },
132
- )
144
+ {
145
+ prospect: { label: "Prospect" },
146
+ customer: { label: "Cliente", context: "success" },
147
+ },
148
+ { doc: "Estágio comercial atual da parte.", presentation: "stage" },
149
+ );
133
150
  // contrato:
134
151
  // columns: [{ key: 'name' }, { key: 'kind', label: 'Tipo' }, { key: 'stage', label: 'Estágio' }]
135
152
  // output: z.object({ kind: customerKindDict.zod(), stage: customerStageDict.zod() })