@softize/opus 29.0.0 → 31.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 (59) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/bin/lib/check.mjs +178 -94
  3. package/bin/lib/copy.mjs +2970 -1801
  4. package/docs/adr/0043-neutral-surfaces-and-interactions-use-opaque-role-tokens.md +52 -0
  5. package/docs/adr/0044-neutral-appearance-derives-from-bounded-axes-per-mode.md +27 -2
  6. package/docs/adr/0045-surface-replaces-content-and-card.md +3 -0
  7. package/docs/adr/0060-surface-composition-separates-chrome-and-intro.md +142 -0
  8. package/docs/adr/0062-listas-contidas-e-texto-flexivel.md +68 -0
  9. package/docs/adr/0063-metadados-das-superficies.md +44 -0
  10. package/docs/adr/0064-action-list-com-itens-e-fontes-externas.md +47 -0
  11. package/docs/adr/0065-presentation-composes-settings-sections.md +295 -0
  12. package/docs/adr/README.md +8 -0
  13. package/package.json +1 -1
  14. package/registry/skills/build-opus-ui/SKILL.md +21 -19
  15. package/registry/skills/build-opus-ui/references/evaluations.md +21 -10
  16. package/registry/skills/build-opus-ui/references/ui-patterns.md +33 -28
  17. package/src/core/presentation.ts +938 -32
  18. package/src/core/types.ts +2 -1
  19. package/src/ui/components/patterns/form.tsx +2 -0
  20. package/src/ui/components/patterns/grouped-list.ts +2 -2
  21. package/src/ui/components/patterns/list.tsx +588 -365
  22. package/src/ui/components/patterns/page-heading-context.tsx +12 -5
  23. package/src/ui/components/patterns/page.tsx +238 -181
  24. package/src/ui/components/patterns/presentation-fields.tsx +246 -0
  25. package/src/ui/components/patterns/presentation-sections.tsx +494 -0
  26. package/src/ui/components/patterns/presentation.tsx +1142 -229
  27. package/src/ui/components/patterns/structural-header.tsx +47 -34
  28. package/src/ui/components/patterns/surface-anatomy.ts +27 -217
  29. package/src/ui/components/patterns/surface.tsx +55 -37
  30. package/src/ui/components/patterns/trigger.tsx +10 -2
  31. package/src/ui/components/primitives/close-button.tsx +11 -3
  32. package/src/ui/components/primitives/command.tsx +2 -8
  33. package/src/ui/components/primitives/detail.tsx +1 -1
  34. package/src/ui/components/primitives/dialog.tsx +59 -89
  35. package/src/ui/components/primitives/drawer.tsx +52 -87
  36. package/src/ui/components/primitives/field.tsx +1 -1
  37. package/src/ui/components/primitives/pagination.tsx +3 -2
  38. package/src/ui/components/primitives/table.tsx +3 -3
  39. package/src/ui/components/primitives/tabs.tsx +13 -2
  40. package/src/ui/docs/content/action-form.md +2 -1
  41. package/src/ui/docs/content/action-list.md +146 -73
  42. package/src/ui/docs/content/action-trigger.md +5 -0
  43. package/src/ui/docs/content/button.md +5 -6
  44. package/src/ui/docs/content/command.md +62 -50
  45. package/src/ui/docs/content/compositions.md +129 -459
  46. package/src/ui/docs/content/dialog.md +72 -101
  47. package/src/ui/docs/content/drawer.md +69 -92
  48. package/src/ui/docs/content/field.md +2 -1
  49. package/src/ui/docs/content/page.md +207 -386
  50. package/src/ui/docs/content/presentation.md +371 -23
  51. package/src/ui/docs/content/separator.md +1 -1
  52. package/src/ui/docs/content/surface.md +94 -116
  53. package/src/ui/docs/content/table.md +7 -0
  54. package/src/ui/docs/content/tokens.md +18 -4
  55. package/src/ui/drivers/context.tsx +11 -4
  56. package/src/ui/drivers/react.tsx +27 -7
  57. package/src/ui/meta.ts +5 -5
  58. package/src/ui/react.tsx +38 -8
  59. package/src/ui/theme.css +44 -33
@@ -0,0 +1,295 @@
1
+ # ADR 0065 — Presentation compõe seções declaradas
2
+
3
+ - **Status:** implementado localmente; ainda não publicado.
4
+ - **Data:** 2026-10-18.
5
+
6
+ ## Contexto
7
+
8
+ A composição centralizada e a lista em itens foram validadas visualmente, mas a definição
9
+ publicável ainda conhece somente o título da página e uma boundary React opaca. Para migrar
10
+ Configurações gradualmente, a definição precisa descrever a estrutura sem incorporar funções,
11
+ credenciais ou uma segunda implementação do serviço de autenticação.
12
+
13
+ ## Decisão
14
+
15
+ Estender Presentation com `page.composition` e a alternativa `body.sections`. O piloto suporta
16
+ seções de itens estáticos e listas em itens conectadas a um adaptador externo registrado.
17
+ Títulos, descrições, ordem, campos da lista e mensagens de consulta pertencem ao JSON. Cada
18
+ seção tem id estável; a lista continua sendo renderizada por ActionList. A composição usa
19
+ Surface e ItemGroup existentes e preserva o ritmo e o visual aprovados.
20
+
21
+ Operações e conteúdo específico são extensões nomeadas no runtime: slots React para ações,
22
+ valores, feedback e overlays; adaptadores tipados para fontes externas e suas ações por item
23
+ ou coleção. Slots ausentes são erros, enquanto slots registrados com null são estados válidos.
24
+ Os adaptadores são código confiável do consumidor, nunca expressões ou imports obtidos do JSON.
25
+ A definição é publicável, mas essas extensões continuam dependentes do renderer registrado.
26
+
27
+ O piloto não cria um motor genérico de formulários ou expressões. Forms, views e listas de
28
+ actions continuam usando body.action. Abas e demais configurações serão avaliadas nos próximos
29
+ pilotos. body.component permanece uma extensão válida. O schemaVersion 1 recebe uma extensão
30
+ aditiva ainda local; consumidores antigos não interpretam sections e precisam ser atualizados
31
+ antes de adotar uma definição que a utilize.
32
+
33
+ ## Alternativas e consequências
34
+
35
+ Manter uma única boundary React não permitiria inspecionar a composição. Serializar JSX ou
36
+ callbacks confundiria conteúdo declarativo com execução. Criar actions fictícias para o IdP
37
+ violaria a fronteira existente. O modelo escolhido expõe as partes portáveis e nomeia cada
38
+ extensão, mantendo a migração reversível por página.
39
+
40
+ ## Verificação
41
+
42
+ Testar serialização e validação de ids, propriedades desconhecidas e campos inválidos; renderizar
43
+ uma definição reconstruída de JSON; falhar diante de extensões ausentes; preservar estados da
44
+ lista, comportamento de senha e revogação. O inventário de copy deve encontrar textos de seções
45
+ e itens sem tratar ids de adaptadores ou nomes de campos como linguagem humana.
46
+
47
+ ## Segundo piloto: CRUD de Unidades
48
+
49
+ Uma família de Presentations declara a lista, a criação e a edição com rotas próprias.
50
+ A página usa `page.composition: document`; formulários continuam Dialog e derivam campos,
51
+ validação, execução e invalidação dos contratos. Comandos `placement: row` declaram ações
52
+ por item, bindings e destino; exclusão delega confirmação e execução ao ActionTrigger.
53
+ Nesse recorte, bloqueio de comando por linha é sempre `action`, isolado por item.
54
+
55
+ `body.cells` referencia um adaptador tipado, vinculado ao nome da list action. Ícones dos
56
+ comandos de linha são referências ao registro runtime, sem importar uma biblioteca inteira.
57
+ A API `bindPresentationCells` preserva a tipagem do item no consumidor; o renderer confere
58
+ que o adaptador pertence à action antes de consultar. O switch Ativo permanece nessa
59
+ extensão e usa a operação já existente. Reimplementar o switch e os ícones em uma linguagem
60
+ arbitrária de expressões foi descartado: os contratos e componentes atuais já os resolvem.
61
+
62
+ O piloto mantém o mecanismo existente de lookup do registro pela primeira página sem
63
+ recorte. Um deep link fora dela continua exibindo ausência explícita; a migração não cria
64
+ uma operação de consulta por id. A composição declarativa não altera permissões de servidor.
65
+ Testes cobrem criar, editar, cancelar, validar, ativar/desativar e excluir com confirmação,
66
+ além de referências de linha incompatíveis e ausência de adaptadores.
67
+
68
+ ## Terceiro piloto: abas de E-mails
69
+
70
+ `body.tabs` declara ids, rótulos, ícones registrados, conteúdos nomeados e rotas opcionais;
71
+ `defaultTab` identifica a aba inicial e `tabLabel` nomeia a navegação acessível. A composição
72
+ usa Tabs e os slots de abas nativos dos headers de Page, Dialog e Drawer. O runtime pode
73
+ controlar a seleção para sincronizá-la com a URL; sem controle, vale o estado local.
74
+ As rotas são metadados consumidos pelo roteador da aplicação, sem imports ou execução no JSON.
75
+ Ids duplicados e aba inicial inexistente são inválidos. Conteúdos e ícones ausentes falham
76
+ antes de montar as consultas; apenas o painel ativo monta seu conteúdo.
77
+
78
+ E-mails adota a composição document. Envios, Remetentes e Modelos mantêm seus componentes
79
+ registrados, contratos, permissões, confirmações e formulários. Este passo declara a estrutura
80
+ das abas, não converte os comportamentos específicos em uma linguagem de expressões.
81
+ Testes verificam seleção, montagem do painel ativo, referências ausentes, ida e volta pela URL,
82
+ links de formulários e ausência de controles administrativos para quem tem somente leitura.
83
+
84
+ ## Consolidação: páginas declarativas como destino
85
+
86
+ A composição híbrida é uma etapa de migração, não o destino. E-mails deixa de registrar
87
+ conteúdos opacos por aba: cada aba referencia outra Presentation. O host resolve navegação,
88
+ área e permissões; a estrutura e as operações permanecem no artefato serializável.
89
+
90
+ O protocolo recebe recursos finitos necessários ao comportamento existente: apresentação
91
+ embutida, destino pai de rotas, exigência de permissão, condições de linha por comparação,
92
+ confirmação específica, células textuais ou switch de action, lookups de formulário e formatos
93
+ de view (texto, texto pré-formatado e HTML isolado). Não há eval, expressão JavaScript, import
94
+ arbitrário ou handler serializado. HTML usa iframe sem permissões e CSP que bloqueia recursos
95
+ externos, preservando a proteção da prévia anterior.
96
+
97
+ O renderer reutiliza ActionList, ActionForm, ActionView, ActionTrigger e seus drivers. Condições
98
+ visuais não concedem autorização; o servidor mantém os guards dos contratos. A seleção de abas
99
+ e formulários por URL pertence ao host genérico. Formulários de edição preservam defaults do
100
+ registro, ocultando campos conforme a metadata dos contratos. A recuperação por listagem mantém
101
+ a limitação conhecida à primeira página, explicitando ausência ou falha de consulta.
102
+
103
+ Critério de aceite: remover os quatro componentes específicos de E-mails sem movê-los para
104
+ slots equivalentes. Testar artefato reconstruído de JSON, navegação, permissões, confirmação de
105
+ risco de duplicidade, publicação, ativação, envio de teste e isolamento de HTML. Extensões
106
+ runtime continuam admitidas para necessidades reais, com exceção explícita; não contam como
107
+ migração completa de uma página.
108
+
109
+ ## Ampliação para Configurações — 18/10/2026
110
+
111
+ A estrutura das demais configurações usa as mesmas seções, sem wrappers de página opacos.
112
+ Controles próprios usam seções `content`; itens declaram associação acessível e posição do controle.
113
+ Listas de actions podem usar adaptadores tipados, preservando filtros e células existentes.
114
+ Cabeçalhos aceitam referências a dados dinâmicos, e a página transporta largura e layout.
115
+ O painel de teste do agente ocupa uma coluna declarada, com rolagem independente do conteúdo.
116
+ Abas podem referenciar tanto actions quanto seções; referências a componentes opacos continuam
117
+ reprovadas nessa modalidade. Loading, falha e ausência podem substituir o corpo temporariamente.
118
+ A autorização permanece nos contratos; as extensões não executam código vindo do JSON.
119
+
120
+ ## Arquivo autoral JSON e acesso — 18/10/2026
121
+
122
+ Declarações podem residir em `presentations/*.json`, importadas por registros estáticos e validadas
123
+ por `presentationSchema.parse`. O inventário de copy descobre esses arquivos e usa as mesmas regras
124
+ semânticas das declarações TypeScript, preservando caminho, linha e hash do JSON. JSON inválido
125
+ produz diagnóstico; não há execução de código ou resolução de imports a partir do documento.
126
+
127
+ O renderer herda `requires` do contrato da action para o corpo, comandos, destinos e switches.
128
+ A declaração pode adicionar uma restrição, mas não substituir a exigência do contrato. Uma lista
129
+ de permissões usa alternativas, conforme a semântica de `requires` nas actions; lista vazia é
130
+ inválida. Sem callback `can`, uma exigência explícita é negada. `canAccessPresentation` permite
131
+ que o roteador consumidor confira essa mesma regra antes de buscar um registro por URL.
132
+
133
+ A autorização efetiva continua em `authorize`. Condições de linha (`when`, como `canEdit` devolvido
134
+ pelo servidor) complementam essa avaliação, sem presumir acesso a um registro apenas pela área.
135
+
136
+ ## Detalhe local de coleção — 18/10/2026
137
+
138
+ Uma lista pode declarar `body.inspect` para abrir um detalhe especializado sem alterar a URL.
139
+ Esse destino referencia um adaptador estático vinculado ao mesmo contrato da lista, recebe o item
140
+ e um callback de fechamento. O renderer mantém a seleção e só monta o detalhe após o clique.
141
+ É uma alternativa exclusiva a `open`/`opens`, que continuam responsáveis por destinos roteados.
142
+ Permissão e condições de linha são verificadas ao abrir e ao renderizar; a autorização dos dados
143
+ continua nas actions do detalhe. Registro ausente ou contrato incompatível falha antes da consulta.
144
+
145
+ O caso atende transcrições de conversas e payloads de auditoria: conteúdos especializados legítimos,
146
+ sem manter um wrapper por página para seleção, filtros ou estado de abertura. Não adiciona imports
147
+ ou funções ao JSON nem converte um detalhe especializado em uma página opaca.
148
+
149
+ ## Opções de vínculos — 18/10/2026
150
+
151
+ Lookups de formulário podem declarar opções estáticas anteriores às consultadas (como “Todas as
152
+ unidades”) e um formatador de opção registrado e vinculado à list action. Isso preserva rótulos
153
+ compostos e a indicação de cadastros inativos sem mover os formulários de volta para JSX. O JSON
154
+ continua declarando campos, consultas, defaults, comandos e destinos. O formatador só projeta uma
155
+ linha em opção; não busca dados nem executa commands. O renderer verifica registro/contrato antes
156
+ de consultar e respeita o `requires` da action do lookup. Filtros iniciais de coleção usam a mesma
157
+ API `filterDefaults` do ActionList e não sobrescrevem filtros explícitos da URL.
158
+
159
+ A hidratação de detalhes roteados reutiliza `listStateToInput`, a mesma materialização que o
160
+ ActionList usa para consultar. O host conserva canal, busca, ordenação e página ao abrir o formulário;
161
+ a fonte de registro continua respeitando seu próprio contrato. Um link sem recorte ainda depende
162
+ da página padrão da consulta, e o estado de ausência deve explicitar essa limitação.
163
+
164
+ ## Campos especializados — 18/10/2026
165
+
166
+ `body.fieldComponents` associa campos de formulário a adaptadores registrados, vinculados à
167
+ action e ao campo por `bindPresentationField`. O componente monta dentro de ActionForm e usa
168
+ seu contexto; validação, envio, erros, footer e efeitos permanecem no formulário compartilhado.
169
+ O registro rejeita campo inexistente, colisão com lookup e adaptador de outro contrato antes
170
+ de montar controles. Não há imports nem funções no JSON.
171
+
172
+ O primeiro consumidor é o questionário de motivos de perda: a estrutura variável das perguntas
173
+ exige controles próprios, enquanto cadastro, edição, ativação e navegação seguem declarativos.
174
+ Testes devem provar que o componente altera os dados submetidos, que o schema rejeita conteúdo
175
+ inválido e que uma permissão negada impede a montagem do editor.
176
+
177
+ Coleções com totais especializados declaram `body.beforeContent`, vinculado à list action por
178
+ `bindPresentationListContent`. O adaptador recebe o input efetivo que ActionList materializa,
179
+ incluindo período, filtros e escopo fixo. Assim, cartões de consumo podem consultar o resumo
180
+ do período sem duplicar a montagem do recorte nem somar uma página da tabela. O conteúdo só
181
+ monta depois do gate da coleção. Ausência ou incompatibilidade do adaptador falha antes da consulta.
182
+
183
+ ### Registro único e comandos de seção
184
+
185
+ Uma Presentation pode declarar `source` com action `view` e bindings de entrada. O renderer
186
+ carrega o registro, apresenta estados de leitura e disponibiliza o dado em `record`. A permissão
187
+ da fonte é cumulativa com a da apresentação; acesso negado não consulta a fonte. Formulários
188
+ endereçáveis declaram a mesma fonte e recebem os valores por bindings explícitos, preservando
189
+ a lista de campos enviados. O cache do driver compartilha a leitura entre página e diálogo.
190
+
191
+ Itens de seção podem declarar `value.field`, `empty` e `suffix`; `content` permanece reservado
192
+ a controles específicos. Comandos com placement `section` são referenciados por action no
193
+ campo `actions` da seção. Mensagens condicionais usam as condições finitas já existentes.
194
+ Isso elimina wrappers de leitura/edição sem serializar funções nem enfraquecer autorização.
195
+
196
+ A fonte também admite uma action list com seleção explícita por campo e parâmetro de rota,
197
+ preservando o recorte e o limite do consumidor. Componentes registrados por
198
+ `bindPresentationComponent` recebem input, contexto e controles de fechamento/aside; cada
199
+ vínculo referencia um contrato existente e seu acesso é conferido antes da montagem.
200
+ Não se executa o contrato só por registrar o componente. Uma operação simple de leitura pode
201
+ abrir esse conteúdo; operações de escrita continuam executando pelo trigger.
202
+
203
+ `formGroups` declara agrupamentos de campos. `initialValues` representa o rascunho inicial,
204
+ que pode ainda ser inválido (por exemplo, nenhuma ferramenta selecionada); seus nomes são
205
+ validados contra o contrato e o envio continua usando a validação de ActionForm. Bindings
206
+ explícitos prevalecem sobre valores iniciais. Comandos de seção podem ter id próprio quando
207
+ vários grupos abrem a mesma action, e nomes acessíveis preservam a distinção entre grupos.
208
+
209
+ Rotas com registro podem declarar alternativas condicionais de mesma superfície, sem
210
+ encadeamento, como a consulta de uma revisão da plataforma por um endereço legado de edição.
211
+ O host verifica a permissão do destino e só então monta seu conteúdo. Fallbacks de criação
212
+ exigem campos declarados e vêm exclusivamente da transição em memória, nunca da querystring.
213
+
214
+ ### Fontes externas, navegação e detalhes na busca
215
+
216
+ A conta da pessoa continua sob responsabilidade do IdP. `externalSource` referencia um
217
+ adaptador de leitura registrado pelo host; não cria uma action fictícia nem permite executar
218
+ código no JSON. A leitura é local à montagem, cancela resultados obsoletos e respeita `requires`
219
+ antes de iniciar. `source` e `externalSource` são mutuamente exclusivos. Campos vindos do IdP
220
+ são conferidos pelo adaptador; não existe contrato Opus para inferir seu schema.
221
+
222
+ `bindPresentationContent` registra controles de integrações externas sem atribuir-lhes um
223
+ contrato inexistente. Formulários especializados podem declarar `body.footer`, recebendo o
224
+ alvo do rodapé e o controle de ocupação da superfície; validação e persistência continuam
225
+ no cliente oficial da integração. `links` declara navegação entre apresentações, sem simular
226
+ uma operação de domínio. O destino também precisa passar pelo controle de acesso.
227
+
228
+ Ações secundárias de seção usam `ghost`. Comandos e links podem declarar `icon`, uma
229
+ referência ao registro de ícones do runtime, para ações reconhecíveis como editar. Nesse
230
+ caso, o botão exibe somente o ícone e mantém o rótulo em tooltip e nome acessível. Links
231
+ usam seu próprio `label`; comandos podem distinguir seções com `accessibleLabel`.
232
+ Ações que precisam de texto para explicar o efeito conservam o rótulo visível. A escolha
233
+ é explícita na definição, sem inferir ícones pelo texto nem alterar ações primárias.
234
+
235
+ `route.query` identifica detalhes que compartilham o caminho da coleção. A seleção recebe
236
+ um nome de parâmetro público e um nome de input; o host conserva os filtros ao abrir e retira
237
+ somente a seleção ao fechar. Rotas de abas aceitam os mesmos parâmetros de caminho da página.
238
+ A assistência recebe o contexto já carregado e não oferece seu botão durante a carga ou recusa.
239
+
240
+ Atualizações de uma fonte já carregada não desmontam seu conteúdo. Consultas acompanhadas por
241
+ SSE podem declarar `source.retainOnError` para conservar o último resultado e apresentar um
242
+ aviso próprio. Isso não permite reutilizar um registro de outra chave nem libera comandos
243
+ quando o monitoramento ou a consulta falhar.
244
+
245
+
246
+ ## Consolidação local: listas, canvas e fontes
247
+
248
+ ### Coleções completas sem paginação
249
+
250
+ ActionList passa a respeitar `paginate: false`, já declarado no contrato ListAction.
251
+ Nesse modo não envia `page`/`limit`, ignora paginação legada da URL, não mostra rodapé
252
+ nem seletor de quantidade e preserva busca, ordenação, estados e recarga. O handler
253
+ precisa retornar o recorte completo; aumentar artificialmente o limite ou apenas
254
+ esconder o paginador foi descartado porque pode tornar registros inacessíveis.
255
+ O modo omitido, cursor ou offset conserva o comportamento anterior. Não há uma segunda
256
+ opção visual divergente no JSON de Presentation: a action é a fonte da decisão.
257
+
258
+ ### Recarga manual da lista
259
+
260
+ O ActionList não apresenta comando de recarga manual na toolbar nem em `Opções da lista`.
261
+ Invalidations de mutations e eventos do consumidor continuam atualizando os resultados;
262
+ o estado de erro mantém `Tentar novamente`. Não se adiciona polling genérico.
263
+ O menu reúne somente preferências e não aparece quando não há opções aplicáveis.
264
+ ActionFilterBar isolado conserva seu `onRefresh` explícito, sem mudar consumidores próprios.
265
+
266
+ ### Protótipo de catálogo de habilidades
267
+
268
+ `body.collection` referencia um renderer de coleção vinculado à action da lista. Ele recebe
269
+ os itens da página atual, recarga, abertura governada e ações declaradas por item. A consulta,
270
+ busca, paginação, estados e toolbar continuam no ActionList; permissões e destinos permanecem
271
+ no JSON. Isso projeta o slot `children` já existente, evitando uma segunda listagem ou
272
+ callbacks de navegação duplicados no consumidor. O renderer desenha apenas os resultados.
273
+
274
+ O primeiro consumidor agrupa cards de habilidades por uma configuração visual local.
275
+ Essas categorias não alteram o domínio nem a seleção feita pela IA. O agrupamento abrange
276
+ somente a página carregada; não promete totais globais por categoria. Testes devem preservar
277
+ destinos autorizados, comandos, busca e estados de lista.
278
+
279
+ O renderer admite `body.rowContent` e `body.toolbarContent`, vinculados à mesma list action
280
+ por adaptadores tipados. A linha personalizada convive com comandos declarados; não substitui
281
+ paginação, filtro ou estado do ActionList. Referências ausentes ou de outra action falham antes
282
+ da consulta. `successMessage` pertence ao comando e só é emitida após sucesso.
283
+
284
+ Regiões content podem declarar `layout: fill` para ocupar a altura restante sem depender de uma
285
+ lista. O elo registra a folha na cadeia de altura e interrompe sua propagação ao conteúdo próprio.
286
+ `page.flush` permite conteúdo com recuo próprio. Abas embedded mantêm o contexto Tabs; framed e
287
+ distribution são encaminhados à primitiva. Drawer respeita width wide, com expansão do assistente.
288
+
289
+ Rotas aceitam parâmetros finitos e seletores compostos de busca. Restrições valem no matching e
290
+ na geração de URL; nomes e bindings repetidos são inválidos. Fonte externa é identificada também
291
+ pelo adaptador, não apenas pela chave de rota. Refetch de uma view não desmonta conteúdo já lido.
292
+
293
+ Links com ícone podem declarar `showLabel: true` para manter o rótulo visível, usando
294
+ o tamanho textual do botão e sem tooltip redundante. O padrão dos links com ícone
295
+ continua sendo somente ícone, com rótulo acessível e tooltip.
@@ -16,3 +16,11 @@ estar documentado na superfície correspondente, sem exigir a reconstrução des
16
16
  - [ADR 0060 — Composição de superfícies com cabeçalho e intro](0060-surface-composition-separates-chrome-and-intro.md).
17
17
 
18
18
  - [ADR 0061 — O `CLAUDE.md` importa o `AGENTS.md`](0061-claude-md-imports-agents-md.md).
19
+
20
+ - [ADR 0062 — Listas contidas e texto flexível](0062-listas-contidas-e-texto-flexivel.md).
21
+
22
+ - [ADR 0063 — Metadados das superfícies em região própria](0063-metadados-das-superficies.md).
23
+
24
+ - [ADR 0064 — ActionList apresenta itens e conecta fontes externas](0064-action-list-com-itens-e-fontes-externas.md).
25
+
26
+ - [ADR 0065 — Presentation compõe seções declaradas](0065-presentation-composes-settings-sections.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "29.0.0",
3
+ "version": "31.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",
@@ -43,31 +43,33 @@ existentes, mantendo navegação observável e componentes reutilizáveis sem la
43
43
  7. Tratar página, filtro, seleção e modal importante como estado navegável por URL quando
44
44
  o produto precisa de deep link, back/forward ou refresh. Em `Presentation`, declarar `route`
45
45
  no artefato e deixar o adaptador apenas conectar a spec ao router da aplicação.
46
- 8. Compor Page, Dialog e Drawer pela mesma gramática: no formato compacto, título e orientação
47
- ficam no Header; com Intro, navegação e ações da superfície ficam no Header e a Intro abre o
48
- Body com título, orientação e ações locais em IntroActions. Declarar a escolha pelos slots,
49
- sem prop redundante. Usar PageIntro na Page explícita; a forma curta de Page já coloca title e
50
- description na intro e actions no header. Não misturar shorthand com regiões explícitas.
51
- Manter exatamente um heading principal, inclusive quando Surface level={1} nomeia uma coleção
52
- ou PageState substitui a página. PageShell fornece contexto sem criar barra paralela; header
53
- sem controles não reserva espaço. Preservar o teto de 80rem e separar composição de ocupação
54
- de altura (flow/fill). Surface mantém o cabeçalho do conteúdo; Pane distribui espaço e recebe
55
- uma superfície quando precisar de anatomia visual. Não criar Intro nem controles de navegação
56
- artificiais para Surface ou Pane.
57
- Em `Surface framed`, usar `layout="sectioned"` e `SurfaceBody flush` quando o filho direto já
58
- controlar o recuo do body, como `DetailGroup` com divisores, lista ou tabela; não reconstruir
59
- divisor, padding ou recorte por classes no consumidor. Em grupos de formulário,
60
- `as="fieldset"` transforma o header em `legend` nativa sem variante visual paralela.
46
+ 8. Compor Page, Dialog, Drawer e Surface pela mesma anatomia: título, descrição opcional,
47
+ metadados e ações gerais no Header; conteúdo no Body; decisões no Footer. Não existem
48
+ `compact`, `sectioned`, `surfaceLayout`, `*Intro` nem a escolha visual `layout` nesses roots.
49
+ Preservar exatamente um heading principal. Page usa uma coluna aberta, padding de `2.5rem`
50
+ e título de `1.875rem`, sem divisores entre regiões. `width` limita a coluna; omitido, ocupa a
51
+ largura disponível. `Page.layout="flow"` rola a página inteira; `fill` mantém o header visível
52
+ e entrega a altura restante à lista ou à rolagem do body. Não oferecer `composition`.
53
+ PageBack recebe destino pai explícito e aparece como primeira ação de ícone no Header.
54
+ Não criar barra de breadcrumb. PageShell fornece ações sem barra paralela.
55
+ Headers usam botões `default`/`icon` e linha mínima de `2.25rem`. Regiões delimitadas têm recuo
56
+ de `1rem`, e bodies governam o ritmo vertical de `1rem`. Divisores são automáticos entre
57
+ regiões existentes. Surface sem moldura se alinha ao hospedeiro, sem recuo lateral ou inferior.
58
+ Usar `flush` quando o filho já controla o recuo; não reconstruir padding ou divisores na tela.
59
+ `tabs` integra a navegação ao Header; ações da coleção ficam em `ActionList.toolbarActions`
60
+ dentro do painel ativo. Pane distribui espaço e recebe Surface quando precisar de anatomia.
61
+ `Surface as="fieldset"` mantém legend nativa na faixa do cabeçalho, sem variante de moldura.
61
62
  9. Manter margem e posicionamento no consumidor; componente reutilizável controla apenas
62
63
  seu interior.
63
64
  10. Não definir a fonte raiz em uma biblioteca ou componente. O navegador e a aplicação são
64
65
  responsáveis por `font-size` em `html`; medidas escaláveis da UI usam `rem` ou a escala
65
66
  relativa do Tailwind. Reservar `px` a hairlines e compensações presas à geometria da borda,
66
67
  com justificativa e cobertura explícitas.
67
- 11. Em uma página centrada em coleção, compor `Surface + ActionList`: título e criação ficam em
68
- extremos opostos do `SurfaceHeader`; a criação usa botão textual `default`, sem ícone, com
69
- `Criar recurso`. Busca, filtros, atualização, estados e paginação permanecem no `ActionList`.
70
- Reservar `toolbarActions` a operações ligadas ao recorte atual.
68
+ 11. Em uma página centrada em coleção, compor `Page + ActionList`; usar `Surface` para uma coleção
69
+ subordinada. Manter criação, importação e exportação em `ActionList.toolbarActions`, junto
70
+ de busca e filtros. Reservar ações do header à página ou superfície como um todo.
71
+ Usar botão textual normal, sem ícone, com `Criar`; preservar `Criar recurso` no nome acessível
72
+ e no título do formulário. Manter nomes específicos em menus com vários tipos de criação.
71
73
  12. Evoluir um pattern compartilhado apenas quando a recorrência e o contrato estiverem claros.
72
74
  13. Tratar tokens de superfície como pares indivisíveis no mesmo fragmento de classes:
73
75
  `bg-card text-card-foreground` e `bg-popover text-popover-foreground`. Não depender da
@@ -4,13 +4,13 @@
4
4
  - Não dispara: “Ajuste o CSS de um e-mail estático.”
5
5
  - Execução: implementar uma lista com modal roteável e provar loading, erro, vazio e back.
6
6
  - Execução estrutural: montar uma página de relatório isolada com `Page > PageHeader + PageBody`,
7
- outra em `PageShell` com navegação contextual acima de `Page > PageHeader + PageBody`, garantindo
7
+ outra em `PageShell` com ações contextuais no `PageHeader`, garantindo
8
8
  exatamente um `PageTitle` no header, e uma coleção nomeada com
9
9
  `Surface > SurfaceHeader + SurfaceBody > ActionList`. Provar que criação fica em
10
- `SurfaceActions`, no extremo oposto ao título, como botão textual `default` sem ícone e com
11
- `Criar recurso`, enquanto uma operação dependente do recorte usa `toolbarActions`; incluir
10
+ `ActionList.toolbarActions`, junto das operações do recorte, como botão textual normal sem ícone
11
+ com `Criar`, nome acessível `Criar recurso` e formulário com título específico; incluir
12
12
  `ActionFilterBar` separado do renderer, `ItemGroup` para
13
- uma coleção secundária e um `ActionFormDialog`; provar teto padrão de `80rem`, hierarquia por
13
+ uma coleção secundária e um `ActionFormDialog`; provar ocupação de toda a largura e scroll somente no body, hierarquia por
14
14
  `level`, vazio estrutural sólido, `Empty` apenas para criação ou vínculo, números sem alinhamento
15
15
  inferido e footer modal com largura natural, alinhado à direita e cancelamento `ghost`; incluir
16
16
  separadamente um caso deliberadamente equivalente com distribuição 50/50 e cancelamento
@@ -22,7 +22,7 @@
22
22
  rejeita a mistura entre props abreviadas e headers explícitos.
23
23
  - Execução de forma: compor uma tabela dentro de `Surface` sem moldura duplicada, manter a moldura
24
24
  estrutural standalone em `rounded-lg` e os controles internos em `rounded-md`.
25
- - Execução de Surface seccionada: compor `Surface framed layout="sectioned"` com mídia e
25
+ - Execução de Surface: compor `Surface framed` com mídia e
26
26
  `DetailGroup` com divisores; provar header com divisor, `SurfaceBody flush` sem recuo e ausência
27
27
  de classes no consumidor que reconstruam essa anatomia. Provar `as="fieldset"` com `legend`
28
28
  nativa sem `frameVariant`.
@@ -79,8 +79,19 @@ da label, duplicação entre hint/help e perda de `aria-describedby` quando apar
79
79
 
80
80
  - Gatilho positivo: “Monte o detalhe de um recurso com menu da página separado do título.”
81
81
  - Gatilho negativo: “Explique por que a ADR antiga juntava título e ações”, sem pedido de UI.
82
- - Execução: montar o mesmo detalhe em Page, Dialog e Drawer com header de navegação/ações e intro
83
- abrindo o body; montar também a versão compacta sem intro. Provar título único, ações locais em
84
- IntroActions, nenhum header vazio na Page e transição de loading para conteúdo sem título duplicado.
85
- - Reprovar intro depois de texto, título simultâneo em header e intro, ações da superfície no título
86
- ou adaptação de Pane para que ele volte a possuir anatomia visual.
82
+ - Execução: montar o mesmo detalhe em Page, Dialog e Drawer com título e ações gerais no Header;
83
+ provar um único título, linha mínima de `2.25rem`, botões normais e transição de loading para
84
+ conteúdo sem duplicação. Page usa coluna aberta com padding de `2.5rem`, título de `1.875rem`,
85
+ largura independente e rolagem flow/fill; Drawer e Dialog preservam recuo de `1rem` e divisores. Conferir tabs nativas e operações
86
+ de coleção dentro do painel ativo, junto à listagem.
87
+ - Reprovar `*Intro`, `compact`, `sectioned`, `surfaceLayout`, `tabsActions`, layout visual e
88
+ PageNavigation. Conferir que o checker orienta a migração. Não recriar anatomia em Pane.
89
+
90
+ ## Metadados e contadores
91
+
92
+ - Execução: compor um detalhe com título, descrição e badges em PageMeta, DrawerMeta e DialogMeta;
93
+ conferir a faixa separada no Header. Em Surface, conferir que SurfaceCount
94
+ fica na linha do título e SurfaceMeta abaixo dela. Migrar um contador antigo e uma descrição
95
+ antiga sem alterar suas funções; provar a equivalência das formas curtas com `meta`.
96
+ - Reprova: contador em SurfaceMeta, metadados em Description, dois Meta, Meta sem título,
97
+ Meta em fieldset ou DialogMeta em alerta.
@@ -7,25 +7,21 @@
7
7
  - Uma `Presentation` que representa esse estado declara `route.path` e `route.surface`. Rotas com
8
8
  parâmetro podem declarar `route.record` para hidratar o item a partir de uma Presentation de
9
9
  lista; não reconstruir essa decisão com condicionais no componente da página.
10
- - `Page` fornece o `<main>` e o container centralizado com teto padrão de `80rem`. Sua forma
11
- explícita exige `PageBody`, exatamente um heading principal e aceita `PageHeader` e `PageFooter`.
12
- Declare esse heading com PageTitle na PageIntro ou no PageHeader compacto, Surface level={1}
13
- ou PageState ativo. A intro abre o body; ações locais usam PageIntroActions, enquanto PageActions
14
- pertence ao header. A forma curta coloca title/description na intro e actions no header.
15
- Não misturar as duas formas. Para ajustar a largura interna, usar contentClassName; className pertence ao container externo.
16
- - Na composição com intro, PageHeader reúne navegação contextual e ações da superfície na mesma
17
- linha. PageBack recebe o destino pai explícito; para vários ancestrais relevantes, usar Breadcrumb
18
- em PageNavigation. Não combinar retorno e breadcrumb, nem criar chrome paralelo. Na composição
19
- compacta, título e orientação permanecem no header. PageActionsTarget fica reservado a workspaces
20
- imersivos com chrome próprio.
21
- - Quando shell e rota conhecem partes diferentes da mesma página, use `PageShell` ao redor da rota.
22
- O shell entrega navegação e ações ao `PageHeader` descendente sem criar uma barra visual.
23
- Ações com texto no cabeçalho usam `sm`, e ações somente com ícone usam `icon-sm`.
24
- Ações de footer de Dialog e Drawer conservam `default`. O tamanho `sm` fica para ações operacionais em
25
- toolbar, seção ou coleção densa, e `xs`/`icon-xs` para ações internas de linha ou célula. Contexto
26
- e variante resolvem a hierarquia visual sem alterar essa medida. Em uma coleção, a ação de
27
- criar usa botão textual `sm`, sem ícone, no extremo oposto do `SurfaceHeader`. Não monte
28
- cabeçalho paralelo, portal ou seletor global para reconstruir essa composição.
10
+ - `Page` fornece o `<main>` e a coluna com largura `narrow`, `default` ou `wide` (padrão). Sua forma explícita usa
11
+ `PageHeader` com `PageTitle`, `PageBody` e `PageFooter` opcional. A forma curta produz a mesma
12
+ anatomia; não misturar props abreviadas com slots explícitos. PageState ativo assume o título.
13
+ - Page, Dialog, Drawer e Surface têm Header com título, descrição opcional, metadados e ações
14
+ gerais, Body com ritmo de `1rem` e Footer opcional. Não há `*Intro`, `compact`, `sectioned` ou
15
+ escolha visual de layout. `Page.layout` preserva somente a ocupação `flow`/`fill`.
16
+ - Page tem uma aparência aberta, padding de `2.5rem` e título de `1.875rem`, sem divisores.
17
+ `flow` rola a página inteira; `fill` entrega a altura restante à lista ou ao body.
18
+ PageBack usa o destino pai e aparece como
19
+ primeira ação de ícone. PageShell entrega ações sem criar barra paralela; breadcrumbs
20
+ não fazem parte da composição. PageActionsTarget atende workspaces com chrome próprio.
21
+ - Headers usam botões `default`/`icon` e linha mínima de `2.25rem`. Regiões delimitadas
22
+ de Drawer, Dialog e Surface conservam recuo de `1rem` e divisores automáticos. Surface sem moldura permanece
23
+ alinhada ao hospedeiro. A prop `tabs` integra a navegação ao Header; operações da coleção ficam
24
+ em `ActionList.toolbarActions` no painel ativo. Não reconstruir isso por portais ou seletores.
29
25
  - `PageState` substitui todo o conteúdo principal quando a página carrega, falha ou está vazia. Na
30
26
  forma curta ele pode ser escrito como filho direto de `Page`, que cria o `PageBody`; na forma
31
27
  explícita, fica sozinho dentro de `PageBody`. Nos três estados ativos, o cabeçalho da Page
@@ -36,18 +32,27 @@
36
32
  recuperação como botão `outline` textual. Estados de seção ou coleção continuam em `DataState`,
37
33
  `ActionView`, `ActionList` ou `Alert`; não elevar uma falha parcial a estado da página.
38
34
  - `Surface` delimita uma região e segue a anatomia `Surface > SurfaceHeader (SurfaceMedia,
39
- SurfaceTitle, SurfaceDescription, SurfaceMeta, SurfaceActions) + SurfaceBody + SurfaceFooter`.
40
- Os slots nunca ficam soltos. `title`, `description`, `count`, `media` e `actions` são a abreviação
41
- para o caso direto e não podem ser misturados ao header explícito. Em `level={1}`, ações da
42
- coleção ficam no extremo oposto ao título; a criação usa botão textual `sm`, sem ícone, com
43
- `Criar recurso`.
35
+ SurfaceTitle, SurfaceCount, SurfaceDescription, SurfaceMeta, SurfaceActions) + SurfaceBody + SurfaceFooter`.
36
+ Os slots nunca ficam soltos. `title`, `description`, `count`, `meta`, `media` e `actions` são a abreviação
37
+ para o caso direto e não podem ser misturados ao header explícito. Ações do header pertencem à
38
+ superfície como um todo; operações da coleção ficam em `ActionList.toolbarActions`.
44
39
  O diálogo de criação usa `Criar recurso` e o de edição, `Editar recurso`. `level` preserva a
45
- hierarquia semântica do heading. `Surface framed` aceita `layout="sectioned"`; declare
40
+ hierarquia semântica do heading. Declare
46
41
  `SurfaceBody flush` quando o filho direto já possui recuo interno, como `DetailGroup` com
47
42
  divisores, lista ou tabela. `as="fieldset"` cria a `legend` nativa sem variante de moldura.
48
- - Uma página centrada em coleção compõe `Surface + ActionList`. O `SurfaceHeader` recebe título e
49
- criação; o `ActionList` mantém busca, filtros, atualização, resultados, estados e paginação.
50
- `toolbarActions` fica reservado a operações que dependem do recorte atual.
43
+ - `PageMeta`, `DialogMeta` e `DrawerMeta` recebem badges, datas e autoria em faixa própria
44
+ após título, descrição e ações, dentro do Header da mesma família.
45
+ `SurfaceMeta` ocupa essa faixa em `SurfaceHeader`; `SurfaceCount` mantém o contador na linha
46
+ do título. Ao migrar, conferir manualmente os antigos `SurfaceMeta`: contadores passam a
47
+ `SurfaceCount`, frases de orientação a `SurfaceDescription`, e metadados permanecem em Meta.
48
+ Cada composição aceita um Meta e exige título. Page e Surface aceitam `meta` na forma curta;
49
+ fieldset e Dialog em `mode="alert"` não aceitam Meta. Informações necessárias à decisão
50
+ permanecem no corpo ou junto à ação correspondente.
51
+ - Uma página centrada em coleção compõe `Page + ActionList`; coleções subordinadas podem usar
52
+ `Surface`. Criação, importação e exportação ficam em `toolbarActions`, junto de busca e filtros.
53
+ O botão de criação é textual normal, sem ícone, com `Criar` e nome acessível `Criar recurso`.
54
+ Menus com vários tipos de criação preservam os nomes específicos. Operações de seleção usam
55
+ `batchActions`, e operações individuais usam `rowActions`.
51
56
  - `Surface` e Drawer nomeiam a região principal como `SurfaceBody` e `DrawerBody`. `Pane` é apenas
52
57
  uma unidade dimensionável de `Split` e não possui anatomia interna. `*Content` permanece
53
58
  reservado a raízes técnicas ou painéis cujo papel não é o corpo de uma estrutura, como