Componentes
49. Detalhe de registro — painel e modal
seed-componentes.md v2.00 · §49seção 99 de 18449-detalhe-de-registro-painel-e-modal-estavel-f7-3.md · MD5 d39903c8Título completo no canon: Detalhe de registro — painel e modal — estável (F7.3, 2026-08-16) · PD1–PD19 · PD20–PD24 decididos em 2026-10-10 (leva 30) · gabarito A8 · fecha a pendência PN-P3
Em uma frase: Três regimes por VOLUME — modal (padrão), painel lateral e página inteira —, cabeçalho fixo, ausência de valor sempre em travessão, e URL própria.
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.(decisão registrada — ver Histórico da seção)
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 — container query peca declarada na PEÇA (o nó do detalhe), não no corpo |
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 |
| PD20 (PD-P5 a) | A zona de ações do REGISTRO é uma barra no TOPO DO CORPO, entre os contadores (PD12) e a grade (PD7), com um primário por vista (§1) — "Dar ganho", "Nova tarefa", "Solicitar contrato". A ação indisponível MOSTRA, com o motivo em texto visível logo abaixo (a regra do SH6), ligada à ação por aria-describedby; é aria-disabled, nunca disabled nativo (que sairia da ordem de foco), e acioná-la explica o motivo em vez de calar. Motivo 7,75 / 8,11 |
Primário no cabeçalho fixo + menu "⋯" (o cabeçalho já carrega navegação, status, título, tamanho e fechar; no painel estreito não cabe; o motivo ficaria escondido no menu) e barra fixa no pé (mais uma faixa fixa encolhe o corpo útil; o PD5 já calibrou o cabeçalho fixo contra o foco escondido, SC 2.4.11). Custo conhecido: a barra rola com o corpo e some da vista em registro longo |
| PD21 (PD-P6 b) | O PD9 vale também para a GRADE de propriedades (PD7): a ação de uma propriedade ("Trocar responsável") aparece no sobrevoo e no :focus-within da linha, com opacity:0, FORA do fluxo (position:absolute) — não reserva largura (o PD9 mediu 88 px que faziam o valor truncar). No toque (hover: none) é sempre visível, como a HV-P13 fez na lista de campos. O marcador de estado ("trabalha agora") é ESTADO, não ação: fica visível e dentro do fluxo |
Ação sempre visível (a grade vira lista de botões e o valor trunca) e ação só no menu de ações (a troca fica longe do valor que ela muda). Custo conhecido: quem não passa o mouse não vê a ação até focar a linha |
| PD22 (PD-P7 a) | O seletor de pessoa (§62) aberto dentro do detalhe é SOBREPOSIÇÃO presa ao campo — o padrão de combobox do WAI-ARIA: a lista flutua ancorada ao campo, na camada do popover (75), e não empurra o conteúdo. No detalhe modal a lista nasce dentro do próprio diálogo [inferência da folha]. As medidas (largura, altura máxima, deslocamento e camada da lista aberta) foram medidas no gabarito A8 em 2026-10-11 (leva 34, PD-P10) e estão no §62.4 |
Em fluxo, empurrando o resto (o conteúdo muda de lugar enquanto a pessoa escolhe — o que o mockup do ERP imprimiu) e diálogo modal (modal sobre modal quando o detalhe já é modal, §49 regime padrão). Exercido no gabarito desde a leva 34 (PD-P10): o "Trocar responsável" do A8 abre a lista de verdade (suite-detalhe, casos SOB-01…18) |
| PD23 (PD-P8 a) | O campo exigido só para uma ação ("exigido para dar ganho") é dito UMA vez, na AJUDA da seção (uma linha sob o título), e o motivo da ação diz o que falta na hora (PD20). Sem marcador por campo | Marcador em cada campo (o rótulo, que já é quem cede na largura, perde espaço, e vários marcadores repetem a mesma frase) e só na hora da ação (a pessoa só descobre ao tentar). Custo conhecido: a pessoa lê a lista longe do campo |
| PD24 (PD-P9 a) | O rótulo truncado (PD8) tem equivalente por FOCO: a linha de rótulo cortado entra na ordem de foco (tabindex="0" só nela) e mostra o balão do §23 (role="tooltip", aria-describedby, o par surface-inverse / text-inverse que o rail já usa) com o texto completo, o mesmo do title. Esc fecha o balão sem fechar o diálogo e sem tirar o foco (WCAG 1.4.13, X3). Rótulo que cabe não ganha parada de Tab |
Rótulo em duas linhas (supersede do PD8: a altura de linha deixa de ser fixa e a grade perde o ritmo medido) e só o title (descumpre o próprio PD8 para quem usa teclado: o title o teclado não mostra). Custo conhecido: mais paradas de Tab, só nas linhas truncadas |
49.2 Anatomia#
┌─ cabeçalho FIXO (PD5) ──────────────────────────────────────────────┐
│ [↑][↓] 9/10 «chip de status» Título do registro … [⤢][×] │
├──────────────────────────────────────────────────┬──────────────────┤
│ contadores de metadado (PD12) │ rail de seções │
│ AÇÕES DO REGISTRO — 1 primário (PD20) │ (PD11, âncora) │
│ grade de propriedades — 1 · 2 · 3 col (PD7) │ │
│ 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-labelledbyapontando 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-topmaior 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 usavaborder-defaultna 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 porborder-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 |
| Piso do rótulo que cede (PD8) — o valor só trunca com o rótulo aqui | 64px |
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); 91 · 0 em 2026-10-10 (eram 83 · 0; +8 da leva 30: ACR · GRD · AJU · BAL) |
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 comrole=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 SIGA |
| 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 |
49.8 Decisões da folha de 2026-10-10 (PD20–PD24) — o que cada uma fechou#
A folha de perguntas de 2026-10-10 (P12, "a, a, a" = "aplicar a recomendação da sessão nas três folhas v3") trouxe cinco perguntas do detalhe, todas respondidas pela opção recomendada. Fecham: PD-P5 (PD20) · PD-P6 (PD21) · PD-P7 (PD22, a forma; as medidas seguem a medir) · PD-P8 (PD23) · PD-P9 (PD24). O gabarito tela-detalhe v0.6 exerce o PD20, o PD21, o PD23 e o PD24 (gerador gen-tela-detalhe.py; seletores .acoes-reg, .prop .acoes, .marca-estado, .secao-ajuda, .rot--trunca + .balao); o PD22 fica só no canon.
49.9 O regime painel segue o §31.4 — SH-P62 e SH-P63 (leva 45, folha 11)#
O painel lateral do PD1 deixa de ter largura fixa (460 px; 400 px com contêiner ≤ 1100 — os números da §49.5 e do PD9 ficam como registro da época) e deixa de empurrar sem piso: segue a regra do painel lateral à direita do §31.4 (PL1–PL9). Medido em 2026-10-24 (Chrome, dois temas): a 1440 px a lista ia de 1062 a 590 px e agora vai a 640 (painel de 410, empurrando); a 1100 px ia a 310 e agora fica com 722 (o painel de 420 sobrepõe de cara); a 390 px o painel substitui a lista, como antes. O painel ganha a borda de arraste (PL5) e o botão Ampliar (PL6), que só aparece no regime painel — no modal seguem os dois degraus do PD17. Fecha a SH-P63.
Também cita o §49: tela-painel.