@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
@@ -89,20 +89,15 @@ fundo sustenta o modal, mas não é uma etapa para onde voltar. Formulários mod
89
89
  rodapé pelo conteúdo, com `Cancelar` em `ghost` e a ação principal sólida.
90
90
 
91
91
  Dentro de [`PageShell`](/ui/page), a superfície `page` usa a anatomia real da aplicação. O
92
- `PageHeader` reúne navegação e ações globais; o título abre o corpo na intro, e `PageFooter`
93
- recebe ações de rodapé. Comandos textuais nos headers de todas as superfícies usam `sm`; retorno e assistente
94
- usam `icon-sm`. O rodapé preserva `default`.
92
+ `PageHeader` reúne título, retorno e ações gerais, e `PageFooter`
93
+ recebe ações de rodapé. Comandos textuais nos headers de todas as superfícies usam `default`; retorno e assistente
94
+ usam `icon`. O rodapé preserva `default`.
95
95
 
96
- Uma Presentation de listagem exibida como página muda essa distribuição para manter a criação perto
97
- da coleção. O `PageBody` recebe um [`Surface`](/ui/surface) com a
98
- [`ActionList`](/ui/action-list), e o cabeçalho desse `Surface` recebe o título e os comandos
99
- declarados com `placement: "header"`, no extremo oposto ao título. Os comandos de cabeçalho usam o
100
- tamanho `sm` e permanecem textuais, mesmo quando a action declara um ícone: o que abre outra
101
- Presentation é um `Button` sólido, e uma action `simple` vira um
102
- [`ActionTrigger`](/ui/action-trigger) `ghost`. O rótulo vem do `label` da action, como `Criar workspace`.
103
-
104
- Não envolva a Presentation em um card para simular a página. Valide proporção, rolagem e ações no
105
- shell que efetivamente hospeda a rota.
96
+ Em uma Presentation de listagem, comandos da coleção usam `placement: "toolbar"`
97
+ e são enviados a `ActionList.toolbarActions`, depois dos controles da lista. Use `label: "Criar"`
98
+ para abreviar apenas o botão; o contrato e a Presentation do formulário preservam o nome
99
+ específico. Comandos gerais continuam em `header`; ações de conclusão permanecem em `footer`.
100
+ A validação rejeita `toolbar` se o body não declarar uma action list.
106
101
 
107
102
  ## Assistência contextual
108
103
 
@@ -148,7 +143,8 @@ const workspaceList = definePresentation({
148
143
  actions: [
149
144
  {
150
145
  action: "workspace.create",
151
- placement: "header",
146
+ placement: "toolbar",
147
+ label: "Criar",
152
148
  target: { presentation: "workspace.create", surface: "dialog" },
153
149
  },
154
150
  ],
@@ -185,15 +181,15 @@ O identificador de `component` vai para o manifest; o JSX, não. Uma Presentatio
185
181
  componente exige `children`, e uma com body de action os recusa: nos dois casos, a renderização
186
182
  lança um erro que nomeia a Presentation.
187
183
 
188
- Com `heading: "shell"`, o padrão, a casca emite o título da definição. Quando o conteúdo React já
189
- declara o heading principal da página, como um `Surface level={1}`, use `heading: "content"`: dentro
190
- de `PageShell`, a casca deixa de emitir o `PageHeader` com título e o documento continua com um único `h1`. Em
191
- Dialog e Drawer, o título continua no cabeçalho da superfície.
184
+ Com `heading: "shell"`, o padrão, a casca emite o título da definição. Com
185
+ `heading: "content"`, o consumidor fornece uma Page completa, com título no PageHeader
186
+ ou pela forma curta `Page title`. A Presentation contribui com ações e rodapé sem criar
187
+ outra Page. O runtime reprova conteúdo sem Page e rodapés concorrentes. Em Dialog e
188
+ Drawer, o título continua no cabeçalho da superfície.
192
189
 
193
- Se o conteúdo precisa ocupar a altura da página e controlar a própria rolagem, como uma lista com
194
- cabeçalho fixo, passe `layout="fill"` ao `Presentation`. Com o padrão `flow`, a página rola como um
195
- documento. Uma [`ActionList`](/ui/action-list) filha direta do conteúdo React recebe essa altura e
196
- assume `fill` sozinha.
190
+ Para uma lista ocupar a altura disponível, declare `layout="fill"`. Quando o conteúdo
191
+ fornece a própria Page, declare fill também nela. Em flow, o body da Page rola e o header
192
+ permanece fixo. ActionList recebe fill pela cadeia estrutural sem classes adicionais.
197
193
 
198
194
  ## Registrar as declarações
199
195
 
@@ -249,7 +245,10 @@ no manifest e concentre a inspeção na Lens em vez de criar um launcher flutuan
249
245
  | `className` | `string` | | Classes adicionais da superfície. |
250
246
  | `bodyClassName` | `string` | | Classes adicionais do body, aplicadas uma única vez. |
251
247
  | `layout` | `'flow' \| 'fill'` | `'flow'` | Em `page`, `fill` limita a altura ao shell e entrega a rolagem ao conteúdo do body; a lista de um body `list` ocupa essa altura e rola só os resultados. Dialog e Drawer ignoram. |
252
- | `children` | `ReactNode` | | Conteúdo React do body declarado com `body.component`. Obrigatório nesse caso e recusado com `body.action`. |
248
+ | `children` | `ReactNode` | | Conteúdo React do body declarado com `body.component`. Obrigatório nesse caso e recusado nos demais tipos de body. |
249
+ | `compositionRuntime` | `PresentationCompositionRuntime` | | Slots, fontes, células, ícones, Presentations de abas e seleção controlada. |
250
+ | `can` | `(permission: string) => boolean` | | Resolve permissões declaradas em `requires`. Ausência nega permissões explícitas. |
251
+ | `embedded` | `boolean` | `false` | Renderiza conteúdo e ações sem título nem retorno de uma página hospedada em outra composição. |
253
252
 
254
253
  ## Propriedades de PresentationInspector
255
254
 
@@ -262,3 +261,352 @@ no manifest e concentre a inspeção na Lens em vez de criar um launcher flutuan
262
261
  | `triggerLabel` | `string` | `Ver JSON da Presentation` | Nome acessível e tooltip do botão. |
263
262
  | `title` | `string` | `JSON da Presentation` | Título do dialog de inspeção. |
264
263
  | `className` | `string` | | Classes adicionais do gatilho. |
264
+
265
+
266
+ A Page usa uma única aparência aberta. `page.width` limita sua coluna, inclusive quando
267
+ o conteúdo declara a Page. Uma Page completa pode substituir o limite com a prop `width`.
268
+ `layout="flow"` rola título, corpo e rodapé juntos; `layout="fill"` entrega a altura restante
269
+ ao conteúdo. Essas opções não alteram Dialog ou Drawer.
270
+
271
+ ## Compor uma página por seções
272
+
273
+ Para descrever a estrutura interna de uma página, use `body.sections`. São aceitas
274
+ seções de itens (`kind: "items"`), listas (`kind: "list"`) e controles específicos
275
+ (`kind: "content"`). A ordem do array é a ordem da tela. Seções e itens possuem ids estáveis e únicos
276
+ no seu grupo. Os mesmos Surface, ItemGroup e ActionList do catálogo fazem a renderização.
277
+
278
+ ```tsx
279
+ const securityPresentation = definePresentation({
280
+ schemaVersion: 1,
281
+ id: "settings.security",
282
+ title: "Segurança",
283
+ page: { width: "default" },
284
+ body: {
285
+ sections: [
286
+ {
287
+ id: "access",
288
+ kind: "items",
289
+ title: "Acesso",
290
+ actions: "password.edit",
291
+ items: [{ id: "password", title: "Senha", content: "password.summary" }],
292
+ },
293
+ {
294
+ id: "devices",
295
+ kind: "list",
296
+ title: "Dispositivos conectados",
297
+ source: "account.sessions",
298
+ item: { title: "device", description: "since" },
299
+ emptyMessage: "Nenhuma sessão ativa.",
300
+ },
301
+ ],
302
+ overlays: ["password.dialog"],
303
+ },
304
+ });
305
+ ```
306
+
307
+ A definição contém somente JSON. `page.width` escolhe `narrow`, `default` ou `wide`.
308
+ A largura da página não altera Dialog ou Drawer.
309
+
310
+ `compositionRuntime` registra as extensões que a definição nomeia:
311
+
312
+ - `slots` fornece ReactNode para ações de seção, conteúdo de item, feedback e overlays.
313
+ `null` representa uma extensão disponível, mas sem conteúdo naquele estado. Uma referência
314
+ sem registro gera erro antes de iniciar as consultas da composição.
315
+ - `lists` associa cada `source` ao resultado de `bindPresentationList(source, actions)`.
316
+ O helper conserva a tipagem dos itens e aceita `toolbarActions` e `rowActions`, com o
317
+ contexto de consulta do ActionList. A definição escolhe título, campos e mensagens.
318
+
319
+ Os adaptadores continuam em código confiável da aplicação. Não persistir callbacks, tokens,
320
+ URLs de execução ou nomes de módulos no artefato. O JSON pode ser inspecionado e transportado,
321
+ mas cada hospedeiro precisa registrar as extensões referenciadas. Formulários, views e listas
322
+ baseados em actions continuam usando `body.action`; componentes específicos podem continuar
323
+ usando `body.component`. Abas têm uma composição própria, descrita a seguir.
324
+
325
+ ### Cabeçalho, controles e painel lateral
326
+
327
+ `page.layout` declara `flow` ou `fill`; a prop `layout` pode substituí-lo na invocação.
328
+ `header.title`, `header.meta` e `header.actions` referenciam slots de dados dinâmicos.
329
+ Sem `header.title`, o título continua vindo de `title`.
330
+
331
+ Uma seção `content` usa um slot para um controle específico, como uma matriz de preferências.
332
+ O título, as ações e a posição desse controle pertencem à declaração. Não esconder uma página
333
+ inteira nesse slot: grupos estáticos devem usar `items`, e coleções devem usar `list`.
334
+
335
+ Nos itens, `labelId` permite associar o controle ao título acessível; `placement: "actions"`
336
+ posiciona switches e seletores em ItemActions. O padrão é ItemBody com dois terços da largura;
337
+ `placement: "below"` coloca conteúdo extenso abaixo do título, ocupando a linha. `intro` e `feedback`
338
+ referenciam conteúdos antes e depois do grupo. O título de seção é opcional.
339
+
340
+ `bindPresentationActionList(contract, props)` registra uma lista de action, conservando filtros,
341
+ células, callbacks e toolbar do ActionList. A declaração pode escolher `item` para linhas de
342
+ itens; sem ele, vale a tabela do contrato. `bindPresentationList` continua atendendo fontes locais.
343
+
344
+ `body.aside: { content: "test", label: "Teste do agente", width: "default" }` declara uma
345
+ coluna lateral. As larguras são `narrow` (20rem), `default` (24rem) e `wide` (28rem).
346
+ Em uma página `fill`, o conteúdo principal rola dentro da altura disponível e o painel conserva
347
+ seu lugar. `compositionRuntime.asideOpen` controla sua visibilidade sem perder o estado montado.
348
+ `compositionRuntime.state` substitui temporariamente o conteúdo durante carga, falha ou ausência;
349
+ os slots que dependem dos dados só são exigidos quando esse estado termina.
350
+
351
+ ## Declarar um CRUD com ações por linha
352
+
353
+ Uma lista convencional continua usando `body.action`, com rotas para as Presentations de
354
+ criação e edição. A criação usa `placement: "toolbar"`. Para editar ou excluir um item,
355
+ use `placement: "row"` e bindings `source: "item"`:
356
+
357
+ ```tsx
358
+ const storesPresentation = definePresentation({
359
+ schemaVersion: 1,
360
+ id: "settings.stores",
361
+ title: "Lojas",
362
+ page: { width: "default" },
363
+ body: { action: "store.list", cells: "stores.cells", emptyMessage: "Nenhuma loja." },
364
+ actions: [{
365
+ action: "store.delete",
366
+ placement: "row",
367
+ label: "Excluir",
368
+ icon: "trash",
369
+ itemLabel: "name",
370
+ input: { id: { source: "item", field: "id" } },
371
+ }],
372
+ });
373
+ ```
374
+
375
+ Uma action `simple` executa por ActionTrigger, incluindo confirmação, mensagens e
376
+ invalidação de consultas. Actions de formulário ou view declaram `target` para abrir outra
377
+ Presentation. `itemLabel` identifica o campo usado no nome acessível e na confirmação;
378
+ `icon` identifica um ReactNode em `compositionRuntime.icons`. Ambos são próprios do
379
+ placement `row`. O bloqueio de linha é `action` e cada gatilho mantém sua execução isolada.
380
+
381
+ `body.cells` referencia uma entrada de `compositionRuntime.cells`. Registre-a com
382
+ `bindPresentationCells(listContract, cells)`: os callbacks recebem o item tipado, e o
383
+ renderer recusa o adaptador de outra action. Essa extensão atende ícones e controles
384
+ específicos sem mover funções para o JSON. O ActionList mantém consulta, estados, busca,
385
+ ordenação e paginação. `body.emptyMessage` pode particularizar o vazio da coleção.
386
+
387
+
388
+ ## Abas declaradas
389
+
390
+ Use `body.tabs` quando a estrutura da navegação precisa estar na definição. A página
391
+ mantém título, abas e conteúdo na mesma coluna, com rolagem definida por `layout`.
392
+ O renderer usa os headers e as abas nativas, inclusive em Dialog e Drawer.
393
+
394
+ ```ts
395
+ const emails = definePresentation({
396
+ schemaVersion: 1,
397
+ id: 'settings.emails',
398
+ title: 'E-mails',
399
+ page: { width: 'default' },
400
+ body: {
401
+ tabLabel: 'Configurações de e-mail',
402
+ defaultTab: 'deliveries',
403
+ tabs: [
404
+ { id: 'deliveries', label: 'Envios', icon: 'mail', content: 'emails.deliveries', route: '/settings/emails' },
405
+ { id: 'senders', label: 'Remetentes', content: 'emails.senders', route: '/settings/emails/senders' },
406
+ ],
407
+ },
408
+ })
409
+ ```
410
+
411
+ Cada `content` referencia um slot de `compositionRuntime.slots`; cada `icon`, uma entrada
412
+ em `compositionRuntime.icons`. As referências são conferidas antes da montagem e somente
413
+ o conteúdo da aba ativa monta. Os ids devem ser únicos e `defaultTab` precisa existir.
414
+ Rotas são opcionais, absolutas dentro da aplicação, sem parâmetros e únicas na composição;
415
+ não representam URLs externas. O JSON não executa imports nem callbacks.
416
+
417
+ Sem controle externo, a seleção usa estado local. Para acompanhar a URL, forneça
418
+ `compositionRuntime.tabs: { value, onValueChange }`. O roteador da aplicação resolve a
419
+ aba pelo endereço e usa `tab.route` para navegar; recargas e voltar do navegador atualizam
420
+ `value`. A Presentation não presume como áreas, filtros ou subrotas de formulários funcionam.
421
+ Os conteúdos registrados continuam responsáveis pelas listas, permissões e operações específicas.
422
+
423
+
424
+ ## Páginas sem componente específico
425
+
426
+ Uma família de Presentations pode descrever a página e seus diálogos sem um arquivo React
427
+ por recurso. `body.tabs[].presentation` referencia uma Presentation de action. O hospedeiro
428
+ resolve essa referência e fornece `compositionRuntime.presentations`; `embedded` permite
429
+ renderizar o conteúdo na aba sem outro cabeçalho de página. Declare apenas `content` ou
430
+ `presentation` em cada aba. A aplicação ainda conecta o roteador, os contratos e os ícones.
431
+
432
+ Listas podem declarar `body.columns`: `text` aceita `prefix`, `empty`, `labels`, `fallback` e
433
+ `icon`; `switch` declara action, bindings, `valueField`, `label` e `itemLabel`. O switch usa a
434
+ invalidação da action e bloqueia submissões concorrentes. `body.opens` oferece destinos em
435
+ ordem de prioridade, com condições `when` sobre campos da linha: `eq`, `neq`, `in`, `notIn`.
436
+ As comparações aceitam valores escalares JSON e todas as condições precisam ser atendidas.
437
+ Campo ausente não atende uma condição. `open` e `opens` são mutuamente exclusivos.
438
+
439
+ `requires`, na Presentation, no comando ou no switch, é resolvido pelo callback `can` do
440
+ hospedeiro. Sem resolvedor, uma permissão explícita é negada. Isso controla a interface;
441
+ a action continua responsável pela autorização do servidor. Comandos de linha podem usar
442
+ `when`; confirmações específicas usam `confirm: { title, body?, actionLabel? }`.
443
+
444
+ Formulários podem declarar `formFields` para selecionar campos, `lookups` com action list e
445
+ `paginate: false`, campos `label`/`value`, e `defaultsFromRecord` para aproveitar o registro obtido pelo
446
+ hospedeiro. Bindings explícitos prevalecem sobre os valores desse registro. Campos, validação,
447
+ submissão e efeitos continuam pertencendo ao ActionForm. Lookups exibem carga e falha com
448
+ retentativa usando os drivers existentes.
449
+
450
+ Views aceitam `fields[].format` como `text`, `pre` ou `html`. O HTML é exibido em iframe
451
+ com sandbox sem permissões, CSP que bloqueia scripts e recursos externos e sem referrer.
452
+ Use para prévia de conteúdo, não para documentos que precisem de navegação ou scripts.
453
+ `width: "wide"` amplia um diálogo para conteúdo extenso; `body.intro` acrescenta orientação
454
+ visível. `route.parent` identifica a página de retorno; parâmetros de rota aceitam camelCase.
455
+
456
+ O protocolo é serializável e validado contra o registro de actions e Presentations. Ele não
457
+ executa expressões de JavaScript recebidas no JSON. A resolução de registros e a sincronização
458
+ de URL continuam no hospedeiro; uma aplicação que consulte só a primeira página de uma lista
459
+ precisa tornar essa limitação visível ou fornecer uma consulta própria por identidade.
460
+
461
+ ## Arquivos JSON e permissões
462
+
463
+ Uma página pode ser mantida em `presentations/<recurso>-list.json` e importada pelo registro do
464
+ domínio. Valide o import com `presentationSchema.parse` e o conjunto com `validatePresentations`.
465
+ O JSON contém apenas dados: componentes e handlers continuam em registros confiáveis da aplicação.
466
+ `opus copy` inclui esses arquivos no inventário e aponta textos para a linha do JSON autoral.
467
+
468
+ O callback `can` resolve os requisitos da declaração e das actions. Quando ambos existem, ambos
469
+ precisam ser atendidos; uma lista em `requires` aceita qualquer uma das permissões indicadas.
470
+ Não fornecer `can` nega requisitos explícitos. Um roteador que carrega registros antes da renderização
471
+ deve chamar `canAccessPresentation(definition, actions, can)` antes de iniciar essas consultas.
472
+ Ocultar controles não substitui a autorização executada no servidor; restrições por registro podem
473
+ continuar declaradas em `when`, usando capacidades devolvidas pela própria action.
474
+
475
+ ## Detalhe especializado ao selecionar uma linha
476
+
477
+ Use `body.inspect: { component: "audit.detail" }` quando o detalhe é local e não muda a URL.
478
+ Registre `details["audit.detail"] = bindPresentationDetail(auditList, (item, onClose) =>
479
+ <AuditDetailDialog item={item} onClose={onClose} />)` no runtime. O adaptador recebe o item
480
+ tipado pelo contrato. A seleção e o fechamento ficam com a Presentation; o componente só é
481
+ montado após o clique. `requires` e `when` restringem a abertura e a permanência do detalhe.
482
+
483
+ `inspect` só vale para lists e não pode coexistir com `open` ou `opens`. Para destinos navegáveis,
484
+ continue usando esses últimos. Um adaptador ausente ou vinculado a outra action é recusado antes
485
+ de carregar a lista. As actions consultadas dentro do detalhe mantêm sua autorização no servidor.
486
+
487
+ ## Opções e filtros de coleção
488
+
489
+ `body.filterDefaults` passa os filtros iniciais ao ActionList; valores explícitos na URL continuam
490
+ prevalecendo. Nos formulários, cada lookup aceita `prepend: [{ value: "*", label: "Todas as unidades" }]`.
491
+ Essas opções aparecem antes das linhas retornadas pela action. Quando o rótulo precisa combinar
492
+ dados, declare `format: "units.options"` e registre o adaptador com
493
+ `bindPresentationOptions(unitList, unit => ({ value: unit.id, label: unit.name }))` em `runtime.options`.
494
+
495
+ O formatador apenas apresenta a opção. A consulta continua sendo a action declarada no lookup,
496
+ e seu `requires` é verificado antes da busca. O inventário de copy lê os rótulos de `prepend` no JSON.
497
+
498
+
499
+ ## Campos especializados e resumos de coleção
500
+
501
+ Quando um campo exige um editor próprio, declare `body.fieldComponents` com o nome do campo e
502
+ um identificador do registro. Por exemplo: `fieldComponents: { questions: "loss.questions" }`.
503
+ Registre `fields["loss.questions"] = bindPresentationField(questionnaireUpdate, "questions",
504
+ props => <QuestionnaireField {...props} />)` no runtime. O componente recebe `name` e `context`,
505
+ pode ler os dados do registro e usa `useActionFormContext` para atualizar o campo e mostrar seus
506
+ erros. ActionForm continua validando e enviando pelo contrato; o componente não cria outro form
507
+ nem outro botão Salvar. A declaração não pode combinar lookup e componente no mesmo campo.
508
+
509
+ Para cartões ou resumos antes da tabela, declare `body.beforeContent: "usage.summary"` e registre
510
+ `listContent["usage.summary"] = bindPresentationListContent(usageList, input =>
511
+ <UsageSummary input={input} />)`. O input vem do ActionList, com período, busca, filtros e escopo
512
+ materializados, inclusive quando a lista usa estado interno. O resumo pode consultar seus totais
513
+ por uma action própria, sem somar somente as linhas da página atual.
514
+
515
+ As duas extensões são código confiável registrado pelo consumidor, não código transportado no
516
+ JSON. O renderer verifica action e campo do adaptador antes da montagem, e não monta controles
517
+ nem resumos quando a pessoa não pode acessar a Presentation.
518
+
519
+ ## Configurações com leitura e edição
520
+
521
+ Use `source` quando seções ou formulários precisarem carregar um registro. Declare a action
522
+ `view` e seus bindings de input. Para uma action `list`, declare também `record.field` e
523
+ `record.param`: o renderer seleciona apenas entre os itens retornados nesse recorte.
524
+ Loading, erro e ausência pertencem ao renderer; requisitos da fonte e da apresentação se somam.
525
+
526
+ Itens de uma seção podem usar `value: { field, empty?, suffix? }`. Mensagens em `notices`
527
+ usam `text` e condições `when`. Comandos `placement: "section"` são referenciados por `id`
528
+ ou pelo nome da action em `section.actions`. `accessibleLabel` distingue botões com o mesmo
529
+ texto visível. Ações de seção usam `ghost`. Declare `icon` para exibir somente o ícone,
530
+ com tooltip e nome acessível; mantenha o texto quando ele for necessário para entender
531
+ o efeito. Ícones também podem acompanhar comandos do cabeçalho.
532
+
533
+ Formulários podem declarar `formGroups: [{ title, fields }]`. `initialValues` fornece o
534
+ rascunho inicial; esse rascunho não precisa passar na validação antes do preenchimento.
535
+ Os nomes dos campos devem existir no contrato, e ActionForm valida o envio. Bindings de
536
+ input explícitos têm precedência sobre o rascunho.
537
+
538
+ Registre controles e conteúdos específicos com `bindPresentationComponent(contract, render)`
539
+ em `runtime.components`. O retorno do render é conteúdo React; hooks pertencem aos componentes
540
+ retornados. O componente recebe contexto, input e controles de fechar a superfície e o aside.
541
+ Use esses vínculos em slots ou em `body: { component, contract, input }`. O vínculo verifica
542
+ acesso, mas não executa a action automaticamente. A estrutura da página permanece no JSON.
543
+
544
+ Rotas com `record` podem declarar `alternatives` com condições e outra apresentação de mesma
545
+ superfície, sem alternativas encadeadas. Isso permite preservar endereços legados que passam
546
+ a abrir uma consulta conforme o registro. O host deve verificar a permissão do destino antes
547
+ de montá-lo. `record.fallbackFields` identifica dados transitórios necessários a fluxos como
548
+ criar e abrir permissões: o host aceita somente o retorno em memória, nunca parâmetros da URL.
549
+
550
+ ### Conta externa e navegação com filtros
551
+
552
+ Use `externalSource` para ler dados de uma integração que já possua cliente próprio, como um
553
+ provedor de identidade. Registre seu `load(input, { signal })` em `recordSources`. O renderer
554
+ trata carga, falha e repetição, ignora respostas canceladas e verifica o acesso antes da leitura.
555
+ O JSON contém apenas o identificador do adaptador. Não combine esse modo com `source`.
556
+
557
+ Controles externos usam `bindPresentationContent`. Um formulário específico pode declarar
558
+ `body.footer: true`, renderizar suas ações em `footerTarget` e informar seu envio por
559
+ `onBusyChange`; a moldura continua pertencendo à apresentação. Actions Opus continuam usando
560
+ `ActionForm` e seus contratos. Para navegação sem execução, declare `links` com identificador,
561
+ rótulo, apresentação de destino, posição e bindings de input. Links de seção também usam
562
+ `ghost` e aceitam `icon` do registro runtime; nesse caso, `label` fica no tooltip e no
563
+ nome acessível. Declare `showLabel: true` para combinar ícone e texto visível.
564
+
565
+ Em detalhes abertos na mesma URL da lista, declare, por exemplo,
566
+ `route.query: { "param": "customer", "input": "id" }`. O host passa os parâmetros de busca a
567
+ `matchPresentationRoute` e combina o caminho gerado com os filtros existentes. Ao fechar,
568
+ remove o parâmetro de seleção. `body.rowKey` define a identidade da linha quando a chave pública
569
+ da coleção difere de `id`, como em listas de revisões.
570
+
571
+
572
+ ### Conteúdo próprio em listas e regiões de trabalho
573
+
574
+ Para apresentar os resultados em cards ou outra composição, declare
575
+ `body.collection: "catalog.cards"` e registre o adaptador em `runtime.collections`
576
+ com `bindPresentationCollection(contract, (items, controls) => ...)`.
577
+ O ActionList conserva busca, estados e paginação; `items` contém a página atual.
578
+ Use `controls.canOpen(item)` e `controls.onOpen(item)` para respeitar os destinos
579
+ autorizados do JSON, e `controls.actions(item)` para manter os comandos declarados.
580
+ `controls.refetch()` atualiza a consulta. O adaptador deve oferecer foco e nome
581
+ acessível aos controles que desenhar.
582
+
583
+ `body.rowContent` referencia `compositionRuntime.rowContent`, registrado com
584
+ `bindPresentationRowContent(listContract, render)`. Use para uma linha específica do domínio;
585
+ comandos declarados por linha continuam disponíveis. `body.toolbarContent` referencia uma
586
+ entrada de `listContent`, registrada com `bindPresentationListContent`. Ambos precisam pertencer
587
+ à mesma list action. Estados, busca e paginação continuam no ActionList.
588
+
589
+ Uma seção `kind: "content"` pode declarar `layout: "fill"` para um canvas ocupar a altura restante.
590
+ Use junto de `page.layout: "fill"`; `page.flush: true` deixa o conteúdo controlar seu recuo.
591
+ `header.description` referencia um slot quando a orientação depende do registro carregado.
592
+
593
+ `body.tabs` aceita `framed` e `distribution` (`content` ou `equal`), encaminhados ao Tabs nativo.
594
+ O modo embedded mantém o contexto das abas mesmo sem renderizar o heading da página.
595
+ `width: "wide"` também se aplica a Drawer; abrir seu assistente pode ampliar a superfície.
596
+
597
+ Comandos podem declarar `successMessage`, apresentada somente após a operação bem-sucedida.
598
+ Ela entra no inventário de copy como mensagem de sucesso; não substitui erro nem confirmação.
599
+
600
+ Em rotas, `params` limita valores de parâmetros do caminho. `query` pode ser um seletor ou uma
601
+ lista de seletores `{ param, input }`; todos precisam estar presentes para selecionar a rota.
602
+ Os nomes de busca e seus destinos de binding devem ser distintos, inclusive dos parâmetros do
603
+ caminho. A geração da URL também valida as restrições declaradas.
604
+
605
+ Ao fornecer uma Page completa por `heading: "content"`, mantenha a Page montada também
606
+ durante carregamento ou Suspense. Use PageState dentro dela para os estados integrais;
607
+ o renderer precisa dessa estrutura para integrar cabeçalho e ações.
608
+
609
+ No modo `embedded`, o formulário conserva seu botão de envio e ações declaradas ficam
610
+ junto ao conteúdo da aba. O título e o retorno pertencem à página que hospeda as abas.
611
+ `successMessage` substitui a notificação de comandos diretos; comandos com `target` usam
612
+ o resultado e a mensagem do formulário de destino.
@@ -14,7 +14,7 @@ com regiões. A orientação horizontal fica entre blocos empilhados e ocupa tod
14
14
 
15
15
  Não use `Separator` entre cabeçalho, corpo e rodapé de [`Surface`](/ui/surface), [`Dialog`](/ui/dialog) ou
16
16
  [`Drawer`](/ui/drawer). Nessas superfícies, os divisores são derivados das regiões presentes: use
17
- `layout="sectioned"` no componente raiz.
17
+ a composição das regiões no componente raiz.
18
18
 
19
19
  ## Vertical
20
20