---
fonte: 01-canonicos/seed-componentes.md
versao_da_fonte: v1.43
secao: 49
titulo: "Detalhe de registro — painel e modal — `estável` (F7.3, 2026-08-16) · PD1–PD19 · gabarito A8 · fecha a pendência PN-P3"
sequencia: 51 de 98
bytes_do_corpo: 16755
md5_do_corpo: 2bf3e3688f14b16938b8addf07e8476c
gerado_por: 06-validacao/geradores/gen-camada-ia.py
nota: fatia GERADA — o corpo abaixo é byte a byte o trecho do canônico; edite o canônico, nunca esta fatia. Canônico inteiro em https://ds.seed.eng.br/01-canonicos/seed-componentes.md
---
## 49. Detalhe de registro — painel e modal — `estável` (F7.3, 2026-08-16) · PD1–PD19 · gabarito A8 · fecha a pendência PN-P3

> **O que esta seção resolve, e por que ela demorou.** A **PN-P3** está aberta desde a Fase 5: todo
> ERP é *lista → detalhe*, e o nosso caminho morria na lista. A seção não foi escrita antes por um
> motivo declarado — não havia LEITURA VISUAL do arquétipo, só extração de valores, e a F7.2 provou
> caro o que isso produz (números certos, composição errada). A leitura chegou em 2026-08-15, na
> §13.4 do `estudo-clickup-completo.md`, com sete dispositivos observados: **C50** modal que navega
> entre irmãos sem fechar, com posição no conjunto em cabeçalho fixo · **C51** propriedades em
> grade de duas colunas · **C52** campos customizados em lista com ícone de tipo e rótulo truncado ·
> **C53** ações do campo reveladas no hover · **C54** corpo truncado com "Expandir" · **C55** rail
> de seções DENTRO do modal · **C56** contadores de metadado como ícone+número.
>
> **Base (3 rodadas encadeadas + rodada zero, 2026-08-15).**
> **Rodada zero — leitura visual:** confirmada na §13.4 antes de qualquer spec. Regra da fase.
> **R1 canon:** **Carbon** dá a régua por VOLUME, não por gosto — modal quando um subconjunto
> pequeno é editável; se há campos suficientes para exigir rolagem, painel lateral ou página
> inteira; e o side panel existe para *manter o usuário em contexto com a página*. **Fiori** dá o
> object page (seções e subseções com navegação por âncora ou aba) e duas réguas operacionais que
> entraram inteiras: a paginação entre irmãos só aparece quando a lista de origem tem **ao menos
> dois itens**, e ao excluir o item corrente seleciona-se o anterior — se não sobra nada, fecha.
> Fiori também manda usar **o mesmo layout em exibição e edição**: o conteúdo não muda de lugar.
> **R2 mercado/stack:** Radix Dialog entrega os dois regimes (modal e não-modal) com controle fino
> de foco, o que barateia a decisão PD1 — é o mesmo primitivo. Nota de fronteira registrada: o
> Radix foi adquirido pela WorkOS e a cadência caiu em alguns componentes; o Base UI é hoje a
> camada de primitivos mais ativa, e o shadcn/ui é o consumo dominante. Não muda decisão aqui;
> muda o que vigiamos. **R3 normas / fora do circuito:** o cabeçalho fixo do C50 é risco de norma,
> não detalhe estético — a falha **F110** do WCAG 2.2 descreve exatamente o sticky que cobre o
> elemento focado, e o SC 2.4.11 é AA; o conserto canônico é `scroll-padding`. O hover do C53 é a
> armadilha maior: o Spectrum corrigiu este caso em novembro de 2025 trocando `visibility:hidden`
> por `opacity:0`, porque `visibility:hidden` e `display:none` **removem da árvore de
> acessibilidade** e o botão deixa de ser alcançável por Tab. O "Expandir" do C54 precisa de
> `aria-expanded` + `aria-controls` e de rótulo que nomeie o quê.
>
> **A LACUNA QUE A PESQUISA REVELOU, e é o achado da rodada:** **não existe canon para troca de
> conteúdo DENTRO de um diálogo já aberto.** Todo o corpus trata abertura e fechamento. O anúncio
> do nome do diálogo acontece quando o foco entra nele; trocar o texto do título com o foco parado
> **não produz anúncio nenhum**. Sem instrumento próprio, navegar entre irmãos é silencioso para
> leitor de tela. O PD3 é decisão nossa, declarada como tal.
>
> **Gate de 2026-08-15 (Rafael, sobre amostra A/B navegável):** *"modal A por padrão, e opção do
> modelo B caso o usuário prefira, então vamos ter as duas opções. no modelo A, deixa opção de
> expandir a janela depois que ela for aberta."* Daí saem o PD1 (dois regimes, modal como padrão),
> o PD17 (dois degraus da janela) e o PD18 (alternância a partir do próprio detalhe).

### 49.1 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **PD1** | **Três regimes, escolhidos por VOLUME de conteúdo, com o MODAL como padrão.** Modal centrado (registro que cabe sem rolagem longa) · painel lateral **não-modal** (o contexto da lista importa durante a leitura) · página inteira (objeto com muitas seções e edição global). O painel é **preferência do usuário**, persistida por usuário e não por sessão | Copiar o modal do produto de referência como regime único. Carbon é explícito: campos suficientes para exigir rolagem já pedem painel ou página. E descarta manter **dois desenhos** do detalhe: o markup é UM só, movido de hospedeiro (ver PD16) |
| **PD2** | **A paginação entre irmãos só existe com conjunto ≥ 2**, exibe a posição (`9/10`) e nas pontas fica **desabilitada** | Seta sempre presente e inerte: com um registro só, um controle que não faz nada é botão morto. Régua literal do Fiori |
| **PD3** | **Trocar de irmão NÃO devolve o foco ao topo**; o foco permanece no controle acionado e a troca é anunciada por live region `polite` no formato "Registro N de M — título" | Reabrir o diálogo a cada irmão (perde rolagem e re-anuncia tudo) e confiar no `aria-labelledby`, que só é lido na abertura. **Sem canon — decisão declarada** |
| **PD4** | **Registro excluído → seleciona o anterior; conjunto vazio → fecha** | Manter o detalhe aberto sobre um registro que não existe mais |
| **PD5** | **Cabeçalho do detalhe é FIXO**, e o contêiner de rolagem declara `scroll-padding-top` **maior** que a altura dele | Sticky sem compensação: é a falha F110 do SC 2.4.11. Medido no gabarito: cabeçalho 68px, `scroll-padding-top` 76px — 64px reprovava por 4px, e "quase" não passa numa norma cujo teste é o foco sumir atrás da barra |
| **PD6** | **Ausência de valor é SEMPRE travessão**, na grade e na lista de campos | A palavra "Vazio". O produto de referência usa os dois tratamentos no mesmo registro (C52) — divergência registrada e **não copiada**; o DT7 já escolheu o travessão |
| **PD7** | **A grade de propriedades responde ao CONTÊINER** (CP27): 1 coluna até 520px, 2 acima, 3 a partir de 900px | Breakpoint de viewport. Consequência que vale a lei: expandir a janela faz **caber mais**, nunca deixa o dado **maior** |
| **PD8** | **Altura de linha do campo é FIXA; quem cede por falta de largura é o RÓTULO**, com o texto completo em `title` e equivalente por foco (X2 do §23) | Truncar o valor primeiro. O valor é o dado; o rótulo é a etiqueta dele, e etiqueta curta ainda identifica |
| **PD9** | **Ações do campo reveladas no hover E no `:focus-within`, com `opacity:0` — nunca `visibility:hidden` nem `display:none` — e FORA do fluxo** (`position:absolute`) | Os dois primeiros removem da árvore de acessibilidade (SC 2.1.1). E manter as ações no fluxo reserva largura permanente para o que está invisível: medido, 88px que faziam o valor truncar sempre no painel de 460px |
| **PD10** | **O corte do corpo é disclosure de verdade**: `<button>` com `aria-expanded` + `aria-controls` resolvendo em elemento real, rótulo que nomeia o quê ("Expandir descrição") e muda com o estado; corte por `line-clamp` | Link, `<details>`/`<summary>` (perde o controle do clamp por número de linhas) e o rótulo "Expandir" solto, que fora do contexto visual não informa nada |
| **PD11** | **O rail de seções é navegação por ÂNCORA**, não abas: documento único rolável, `aria-current` em exatamente uma seção, nome acessível em cada botão | Abas: fragmentariam o objeto e quebrariam a rolagem única que o PD10 pressupõe. Fiori admite âncora **ou** aba; âncora é o caso quando as seções são partes do mesmo objeto |
| **PD12** | **Contador de metadado leva nome textual** ("3 anexos"); o ícone não é rótulo | O número nu: não diz de quê. Herda o §21 |
| **PD13** | **O detalhe tem URL própria** e, ao fechar, **o foco volta à linha de origem** — se ela saiu do DOM, ao lugar mais próximo dela | Estado só em memória: impede compartilhar o registro e devolve o foco ao topo do documento, fazendo perder o lugar na fila |
| **PD14** | **Regime modal:** `showModal()` nativo, `aria-modal`, fundo inerte, Esc fecha. **Regime painel: NÃO-MODAL** — sem trap de foco, com a lista viva ao lado | Painel lateral com trap: contradiz a razão de ele existir. Para diálogo não-modal a norma pede atalho global que mova o foco entre o diálogo e a página |
| **PD15** | **Campos editam por edição direta**, herdando PN22–PN41 do §48 sob o SC 2.5.7 | Modo de edição global na v1. Fiori: o conteúdo não muda de lugar entre exibição e edição |
| **PD16** | **UM só markup de detalhe no documento**, movido entre os dois hospedeiros | Clonar o conteúdo por regime. Dois desenhos vivos é como um deles envelhece sozinho — e a troca de regime passaria a perder o estado do registro |
| **PD17** | **A janela tem DOIS DEGRAUS DECLARADOS** (padrão e expandido), operados por **botão de dois estados** com `aria-pressed`, rótulo e ícone acompanhando. O degrau é preferência do usuário e **sobrevive à troca de registro**. No degrau estreito o botão **some** | Alça de arraste como caminho único: cai sob o SC 2.5.7 e exigiria alternativa sem arraste de qualquer forma — o mesmo raciocínio que transformou o resizer de coluna do §51 em slider. Fiori admite arraste **com tamanhos predefinidos e encaixe**; adotamos os tamanhos e dispensamos o arraste. Descarta também a janela que cresce até o conteúdo: com registro curto o botão ficaria inerte |
| **PD19** | **A lista de origem só ACOMPANHA quando está viva.** No regime painel o realce segue a navegação entre irmãos — a lista está visível ao lado e a sincronia é o serviço. No regime **modal** ela **não acompanha**; a marcação é aplicada no **fechamento**, junto com o foco, e aponta **onde a leitura parou** | Sincronizar sempre. Achado do gate de 2026-08-16, verbatim: *"quando navego com a seta pra cima ou para baixo, ele está mexendo as duas janelas… era pra mexer só a que abriu sobreposta"*. Sob o véu do modal o fundo está **inerte**: movimento ali não informa nada e compete com o conteúdo que tem o foco. Descarta também devolver o foco à linha que **abriu** a leitura — quem navegou do 3 ao 7 espera voltar ao 7 |
| **PD18** | **A alternância de regime vive DENTRO do cabeçalho do detalhe** | Um controle de preferência fora do detalhe. Com o diálogo modal aberto o fundo é **inerte**, e o controle ficaria inalcançável exatamente quando seria usado — medido nesta produção |

### 49.2 Anatomia

```
┌─ cabeçalho FIXO (PD5) ──────────────────────────────────────────────┐
│ [↑][↓]  9/10  «chip de status»  Título do registro …   [⤢][×]       │
├──────────────────────────────────────────────────┬──────────────────┤
│ contadores de metadado (PD12)                    │  rail de seções  │
│ grade de propriedades — 1 · 2 · 3 col (PD7)      │  (PD11, âncora)  │
│ DESCRIÇÃO + [Expandir descrição] (PD10)          │   ▤              │
│ CAMPOS DO DOMÍNIO — lista, ícone de tipo,        │   ✉ ②            │
│   rótulo truncável, ações no hover/foco (PD8/PD9)│   ∿              │
│ COMENTÁRIOS                                       │                  │
└──────────────────────────────────────────────────┴──────────────────┘
```

**Os dois hospedeiros, com a mesma anatomia:**

| | Modal (padrão) | Painel lateral |
|---|---|---|
| Elemento | `<dialog>` com `showModal()` | `<aside>` irmão da região de conteúdo |
| Fundo | inerte | vivo — a lista continua operável |
| Largura | 860px · expandido 1360×940 | largura de peça lateral |
| Grade de propriedades | 2 col · 3 col no expandido | 1 col |
| Botão de tamanho | presente | ausente (não há dois tamanhos a oferecer) |

### 49.3 Acessibilidade

- O diálogo é **nomeado pelo título do registro** (`aria-labelledby` apontando para o `<h2>` que
  exibe o nome). O nome muda a cada irmão; o **anúncio** não vem daí, vem da live region do PD3.
- **Foco entra** no detalhe ao abrir, **volta ao gatilho** ao fechar (PD13).
- **Nenhuma ação depende de hover** (PD9): tudo que o mouse revela, o teclado revela por
  `:focus-within`, e o alvo tem 44px **no eixo curto também** — regra que este projeto já reprovou
  quatro vezes por medir só um dos eixos.
- **Cabeçalho fixo × foco**: `scroll-padding-top` maior que o cabeçalho (PD5, SC 2.4.11).
- **Contraste**: 28 pares medidos nos dois temas, **56 · 0** (`contraste-f73.py`). Nenhum par novo:
  tudo é consumo de token canônico. **Um achado real da medição**: a primeira versão usava
  `border-default` na fronteira dos botões, que mede **1,49** sobre branco e **2,32** no escuro —
  reprova o SC 1.4.11 nos dois temas. Trocado por `border-interactive` (**3,38** e **4,50**), que
  nasceu na v6.9 exatamente para este papel.

### 49.4 Estados

| Estado | Tratamento |
|---|---|
| Valor ausente | travessão, nos dois blocos (PD6) |
| Rótulo longo | trunca com reticências, `title` com o texto completo, altura fixa (PD8) |
| Corpo longo | `line-clamp` + disclosure nomeado (PD10) |
| Primeiro/último do conjunto | seta da ponta **desabilitada**, nunca inerte sem sinal (PD2) |
| Conjunto vazio | o detalhe fecha (PD4); a lista mostra o zero-resultados do §25, com saída |
| Degrau estreito | o detalhe vira **página**: ocupa a tela, o botão de tamanho some (PD17) |

### 49.5 Números MEDIDOS na produção do gabarito

A spec nasce com **relações**; os números são medidos no artefato — método do §55.

| Medida | Valor |
|---|---:|
| Largura do modal, degrau padrão | **860px** |
| Largura × altura, degrau expandido | **1360 × 940px** |
| Colunas da grade: painel · modal · expandido | **1 · 2 · 3** |
| Tipografia do rótulo e do valor nos dois degraus | **12px e 14px — idênticas** |
| Altura do cabeçalho fixo · `scroll-padding-top` | **68px · 76px** |
| Altura da linha de campo do domínio | **36px** |
| Alvo dos botões do cabeçalho e do rail de seções | **44 × 44px** |
| Alvo das ações de campo | **44 × 36px** (a linha inteira é o alvo de leitura) |
| Fronteira de controle × superfície (claro · escuro) | **3,38 · 4,50** |

### 49.6 Validação

| Camada | Placar |
|---|---|
| `suite-detalhe.mjs` (render) — as guardas do §49 | **54 · 0** (47 na produção + 7 nascidas do gate de 2026-08-16) |
| `suite-composicao.mjs` (render, 2 temas) | **33 · 0** |
| `suite-container.mjs` — blocos CQ e RF, com alvo explícito | **15 · 0** |
| `contraste-f73.py` — 28 pares × 2 temas | **56 · 0** |
| Fidelidade do gerador | reprodução byte-perfeita conferida por MD5 |

> **Escopo declarado da `suite-container.mjs`:** ela foi escrita para os gabaritos de dado A2/A3 e
> cobra árvore com `role=tree` (ARV-01…03), que é estrutura do A2 e não do A8 — o escopo do detalhe
> é lista plana, e dar hierarquia à sidebar só para satisfazer um teste seria inventar estrutura
> para agradar instrumento. Os blocos que se aplicam (CQ container query, RF reflow) rodam com o A8
> passado como alvo explícito. **Isto é decisão registrada, não placar vermelho tolerado.**

### 49.7 Pendências desta seção

| # | Pendência | Por que fica aberta |
|---|---|---|
| **PD-P1** | O **§56 comentários** não é decidido aqui. A fronteira do C85 — comentário como cartão dentro do cartão do objeto — é **peça em peça**, que o CP2 proíbe no caso base | Há **um** dispositivo observado, e ele é justamente o caso difícil. A rodada zero exige leitura visual do arquétipo, e uma leitura de um caso não é leitura do arquétipo. **Navegar antes** |
| **PD-P2** | O regime **página inteira** (3º degrau do PD1) não tem gabarito | Nenhum registro do escopo atual exige. Nasce quando houver caso SEED que o peça, sob a mesma régua de volume |
| **PD-P3** | **Persistência** da preferência de regime e do degrau da janela | É contrato de produto, não de DS: o gabarito demonstra o estado; onde ele mora é decisão do ERP |
| **PD-P4** | **Deep link** do PD13 declarado, não exercitado no gabarito | O gabarito é arquivo único sem rota; provar URL própria exige aplicação |

---

