---
fonte: 01-canonicos/seed-componentes.md
versao_da_fonte: v1.43
secao: 67
titulo: "Edição em linha — **`estável`** (F7.5, 2026-08-16 · **promovido em 2026-08-17 pelo gate visual do Rafael — verbatim: 'aprovado'**) · IL1–IL14 · fecha **C15** · bancada `banco-edicao-linha.html` **v0.2** · `suite-edicao-linha.mjs` **23 · 0 · 0**"
sequencia: 73 de 98
bytes_do_corpo: 21340
md5_do_corpo: 9871169745b7ed5aaccd1604f094b76c
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
---
## 67. Edição em linha — **`estável`** (F7.5, 2026-08-16 · **promovido em 2026-08-17 pelo gate visual do Rafael — verbatim: "aprovado"**) · IL1–IL14 · fecha **C15** · bancada `banco-edicao-linha.html` **v0.2** · `suite-edicao-linha.mjs` **23 · 0 · 0**

> **O que é.** O valor exibido vira campo **no próprio lugar**, sem abrir formulário nem modal. O
> §48 (painel) já tinha o caso da **grade** — **PN37**: *"a lente Tabela é planilha: setas navegam
> entre células, Enter abre editor na própria célula"*. O que faltava era **o componente que a grade
> consome**, e que também serve fora dela: título de registro, campo do painel de detalhe (§49), item
> de lista (§43).
>
> **Fronteira declarada, e ela é o coração do §67:** esta seção especifica **a passagem** do estado
> de leitura para o de edição e de volta. **Qual controle aparece na edição não é decisão dela**
> (IL13) — monetário abre o §59, data abre o §61, escolha abre o §60, pessoa abre o §62, prioridade
> abre o §63. A bancada exercita **texto** e **número**, e **declara** os demais em vez de
> simulá-los: simular componente que já existe cria uma segunda verdade que ninguém mantém.

### 67.0 Rodada zero — SEM INSTÂNCIA ISOLÁVEL, e a diferença importa

O veredito aqui **não** é "não visitado". A edição direta **foi** lida no produto de referência — ela
é a origem do bloco **PN22–PN41** do §48, que nasceu de leitura visual em 2026-08-13. O que **não**
existe é uma instância do componente **isolado**: no produto ele aparece sempre dentro da grade, e
nunca como controle avulso com estados próprios.

**Veredito: SEM INSTÂNCIA ISOLÁVEL.** *Consequência de método:* o insumo de leitura visual desta
seção **já está gasto** no §48 — e é por isso que o §67 nasce citando PN27, PN28, PN29 e PN37 em vez
de reabrir a leitura. **Herdar decisão medida é mais barato e mais honesto que remedir mal.**

### 67.1 RITO — três rodadas encadeadas

| Rodada | Fonte | O que trouxe |
|---|---|---|
| **R1 — canon** | ARIA APG — *Button Pattern* · SC 2.1.1 *Keyboard* | Não existe padrão APG de "inline edit". O que existe é o padrão do **botão**: o valor em repouso precisa ser um controle de verdade para ser alcançável e anunciável (IL1). E o SC 2.1.1 é o que **elimina o duplo clique como caminho único** — duplo clique não tem equivalente de teclado |
| **R2 — mercado/stack** | PatternFly — *Inline edit* · Semrush Intergalactic — *InlineEdit a11y* | PatternFly: existe um **action-group explícito** de salvar/cancelar que só aparece na edição, e o padrão pede preservar o contexto visual — *"within the context of their current view"*, sem modal, sem transição destrutiva. Semrush: **Enter** abre e, dentro da edição, salva; **Esc** volta descartando; `aria-describedby` liga a mensagem de erro. **Nenhum dos dois define o comportamento do `blur`** — a decisão ficou em aberto no mercado, e por isso ela é decidida aqui com o porquê escrito |
| **R3 — normas** | WCAG 2.2: SC 3.2.2 *On Input* · SC 2.5.8 *Target Size* · SC 4.1.3 *Status Messages* | O SC 3.2.2 responde a pergunta que mais confunde: **salvar ao sair do campo NÃO é mudança de contexto** — contexto é viewport, foco ou conteúdo que muda o significado da página; *"uma mudança de conteúdo nem sempre é uma mudança de contexto"*. O que o critério proíbe é a mudança **automática** sem aviso prévio: submeter o formulário ao preencher um campo, ou **mover o foco sozinho** sem estar descrito antes. Daí o IL11 |

### 67.2 A decisão que mais custa: **clicar fora CONFIRMA**

O mercado não decide isso (R2), então a decisão é da casa, e ela tem lado.

Clicar fora é **o acidente mais comum** num campo em linha. As duas escolhas erram de formas
assimétricas:

| Escolha | O que o acidente causa | Recuperável? |
|---|---|---|
| `blur` **cancela** | **destrói** o que a pessoa digitou | **Não.** O texto nunca existiu em lugar nenhum |
| `blur` **confirma** | **grava** um valor que a pessoa talvez não quisesse | **Sim** — desfazer de 8 segundos, já contratado no **PN28** |

> **Entre um erro reversível e um irreversível, o sistema escolhe o reversível.** E o `Esc` continua
> existindo para quem quer descartar **de propósito** — a diferença entre descartar por decisão e
> descartar por acidente é a decisão.

**E há um efeito colateral que só aparece implementando:** o `blur` do campo dispara **antes** do
clique no botão ✕. Sem tratamento, cancelar seria precedido de um salvamento — *a ação faria o oposto
do que promete*. O contrato IL5 tem guarda própria para isso, e ela clica no ✕ de verdade.

### 67.3 Os contratos IL1–IL14

| # | Contrato | Por quê | Descartado |
|---|---|---|---|
| **IL1** | O valor em repouso é um **botão**, e o nome diz a **ação e o valor** — “Editar Descrição da ordem: Inspeção termográfica do QGBT” | Texto com `onclick` não é alcançável por Tab nem anunciado como acionável. E “Inspeção termográfica…” sozinho não diz que dá para editar | `<div onclick>` (invisível ao teclado); `contenteditable` no próprio texto (estado de edição sem fronteira visível, e sem lugar para confirmar/cancelar) |
| **IL2** | Entra por **clique simples** e por **Enter/Espaço** | SC 2.1.1. O botão nativo já dispara `click` nas duas teclas — não se acrescenta `keydown` paralelo, que abriria duas vezes | **Duplo clique como caminho único** (sem equivalente de teclado); ícone de lápis separado como único gatilho (alvo minúsculo ao lado de um alvo grande inerte) |
| **IL3** | Ao entrar, o foco vai ao campo e o texto fica **selecionado** | Substituir é o caso comum em campo de lista. Cursor no fim cobra uma seleção extra a cada edição | Cursor no fim (mais teclas para o caso comum) |
| **IL4** | **Enter** confirma · **Esc** cancela e restaura · **clicar fora CONFIRMA** | Ver 67.2 | `blur` cancela (perda irreversível); pedir confirmação em diálogo (mata a vantagem de editar em linha) |
| **IL5** | Confirmar e cancelar **também** existem como botões visíveis durante a edição | **Toque não tem Esc.** Teclado tem Enter/Esc; ponteiro e toque têm ✓ e ✕. Dois caminhos, **a mesma mutação** — a mesma lei do CP30 | Só teclado (exclui toque); botões sempre visíveis mesmo em repouso (numa lista de 20 linhas, 40 botões de ruído) |
| **IL6** | **Sem salto de layout**: a caixa de edição mantém a altura da linha (tolerância **2px**, declarada) | Numa lista longa, abrir a edição empurraria tudo o que está abaixo e a pessoa perde o lugar na tela. **Medido: 49,0 → 49,0px, deslocamento 0,0px** | Campo maior que o texto “para caber melhor” (empurra a lista) |
| **IL7** | Confirmar devolve o **foco ao gatilho** e anuncia o **resultado** | Componente que troca o próprio DOM sem devolver o foco larga o usuário no `body` — defeito clássico. E o anúncio é *"Responsável alterada para X"*: **resultado, não percurso** (PN28) | Foco no próximo campo (decide pelo usuário); nenhum anúncio |
| **IL8** | Erro de validação **não sai** do modo de edição | Fechar a edição no erro **descarta o que a pessoa digitou** — o mesmo dano que o `blur cancela`, agora causado pelo sistema. A mensagem se liga por `aria-describedby` (R2/Semrush) | Fechar e mostrar toast (o texto se perde) |
| **IL9** | **“Salvando” e “salvo” declarados**, e o valor **não pisca** de volta ao antigo | Mutação otimista, herdada do **PN27**: o gesto aplica na hora, na tela. Ver o valor voltar e avançar de novo lê como falha | Esperar o servidor para desenhar (o campo trava); nenhum estado (a pessoa não sabe se gravou) |
| **IL10** | A affordance aparece no **hover E no foco** — nunca só no hover | Quem navega por teclado **não passa o mouse**. Medido: borda `#788F9D`, **3,38** no claro e **4,50** no escuro contra o fundo | Só hover (exclui teclado); borda permanente (a lista vira grade de caixas) |
| **IL11** | Digitar **não** salva a cada tecla e **não** move o foco sozinho | SC 3.2.2: mudança automática exige **aviso prévio descrito**. Auto-avanço sem aviso é o mesmo defeito das caixinhas de OTP do §66 | Salvar a cada tecla (grava lixo intermediário e enche o histórico) |
| **IL12** | Alvo do gatilho **≥ 44px** de altura | Piso do projeto desde o §1, acima do SC 2.5.8 (24px). Medido: **784×44px** | 32px “por densidade” (o alvo é o que se erra, não o que se vê) |
| **IL13** | O controle da edição é **o do domínio** — §59 · §60 · §61 · §62 · §63 | Sem isso o componente vira **campo de texto universal**, e a data volta a ser string livre. A tabela de delegação está **impressa na bancada** | Texto livre para tudo (perde máscara, teclado e validação de cada domínio) |
| **IL14** | Campo que **não se edita não vira gatilho** — e **diz por quê** | Herda o **PN29**: alça que existe e não obedece é botão morto; trava invisível vira bug aos olhos de quem usa. Sem `button`, sem `tabindex`, com o motivo ao lado | `disabled` mudo (a pessoa clica e nada acontece, e ela não sabe se é bug) |

### 67.4 A galeria de estados, e por que ela é contrato

O catálogo operante mostra **um** estado por vez — é assim que o componente funciona. Mas a lei
**CP31** diz que bancada imprime os estados **lado a lado**, e ela existe por um motivo prático:
*avaliar o estado de erro não pode exigir que o avaliador reproduza o erro* — gate que depende de
reprodução não acontece.

Os **seis** estados — repouso · hover/foco · em edição · erro · salvando · bloqueado — são impressos
juntos, e são **cópias estáticas**. Isso não é detalhe de implementação: um `<button>` de verdade
impresso ali seria um **botão morto**, a classe de defeito que o projeto persegue desde a v1.4. A
galeria não tem `button`, não tem `input` e não tem `tabindex` — **e a suíte mede essa ausência**
(guarda `CP31`), assim como o gerador aborta se a marcação da galeria contiver qualquer um dos três.

### 67.5 A suíte

**`suite-edicao-linha.mjs` — 20 PASS · 0 FAIL · 1 medida.** Doze das vinte guardas **agem**:

| Guarda | O que ela faz de fato |
|---|---|
| `IL6` | mede o retângulo da linha **antes e depois** de abrir, e o deslocamento da linha seguinte |
| `IL3` | confere que o foco está no `input` **e que o texto está selecionado** (`selectionStart/End`) |
| `IL4-esc` | digita, aperta **Esc** e confere valor restaurado, foco de volta e anúncio |
| `IL4-enter` | digita, aperta **Enter** e confere valor, foco, **nome acessível atualizado** e anúncio de resultado |
| `IL4-blur` | digita e **clica fora**: prova que grava |
| `IL5` | digita e **clica no ✕**: prova que cancela mesmo com o `blur` disparando antes |
| `IL5-b` | digita e **clica no ✓**: prova que confirma |
| `IL8` | esvazia e confirma: a edição **continua aberta**, com `aria-invalid` e `aria-describedby` |
| `IL9` | espera as duas fases e confere **salvando → salvo** sem o valor piscar |
| `IL11` | digita e confere que o gatilho **ainda mostra o valor antigo** e o foco não fugiu |
| `IL2` | foca e aperta **Enter** no gatilho |
| `CP31` | conta os estados impressos e **procura controles mortos** entre eles |

### 67.6 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **IL-P1** | ✅ **FECHADA — gate visual APROVADO em 2026-08-17**, sem achado próprio. A seção é promovida a `estável` |
| **IL-P2** | ✅ **FECHADA — EXECUTADA em 2026-08-19.** E os defeitos eram **SETE**, não três: o pior — **a grade era INALCANÇÁVEL POR TECLADO** (56 células em `tabindex="-1"`, nenhuma em `"0"`; 40 Tabs sem entrar) — **não estava escrito na pendência**. A grade passa a cumprir o padrão `grid` da ARIA e o §67; o contrato **IL15** e a guarda que o mede por COMPORTAMENTO estão no **§67.7** |
| **IL-P3** | O **desfazer de 8 segundos** que sustenta a decisão do `blur` **não existe nesta bancada** | Ele é contrato do §48 (PN28) e vive no painel. A decisão do 67.2 **depende** dele: sem desfazer, `blur confirma` deixa de ser o lado seguro. *Isso está dito aqui para não virar suposição* — quem consumir o §67 fora do painel precisa levar o desfazer junto |
| **IL-P4** | Edição em linha de **valor de domínio** não foi exercida | O IL13 delega, e delegação declarada não é delegação medida. Exercitar exigiria montar §59/§60/§61/§62 dentro da bancada — trabalho de composição, não de spec |


### 67.7 IL15 — a grade do §48 em USO: o padrão `grid` da ARIA, e o defeito que ninguém tinha medido

> **Escrito em 2026-08-19, ao executar a IL-P2.** Seção autossuficiente: presume um leitor que nunca viu
> a conversa que a gerou.

#### 67.7.1 O que é a grade, e o que estava errado

O **§48** (`tela-painel`, dispositivo **PN37**) desenha uma **planilha**: tabela com célula editável,
setas que navegam e alça que preenche para baixo. O **§67** já especifica como se edita um valor em
linha — e a grade tinha **editor próprio**. Essa é a **IL-P2**, decidida pelo Rafael em 2026-08-17 junto
com a SI-P4 (*"unificar é melhor… precisamos manter o mesmo padrão, isso é inegociável"*).

**A pendência listava TRÊS divergências. Medido em 2026-08-19, eram SETE — e a mais grave não estava
escrita:**

| # | Defeito medido | Norma |
|---|---|---|
| **1** | **A GRADE ERA INALCANÇÁVEL POR TECLADO.** As **56** células tinham `tabindex="-1"` e **nenhuma** tinha `"0"`. Partindo do seletor "selecionar todas" da própria tabela e teclando **Tab 40 vezes**, o foco **nunca** entrava numa célula. Só o clique abria a grade | **SC 2.1.1** |
| 2 | a célula era `<td>` sem papel nem nome, e não `<button>` com o nome do **IL1** | SC 4.1.2 |
| 3 | sem nome acessível na forma `"Editar <rótulo>: <valor>"` | SC 4.1.2 · 2.4.6 |
| 4 | sem a affordance de lápis do **IL10** | IL10 |
| 5 | sem `role="grid"` — `tabindex` em `<td>` solto **não é** o padrão ARIA | SC 1.3.1 |
| 6 | o `title` da alça de preencher **entrava no nome acessível** da célula. Medido: *"Ana Lima arraste para copiar para baixo"* | SC 4.1.2 |
| 7 | faltavam **Home/End**, **Ctrl+Home/Ctrl+End** e **F2** | padrão `grid` |

> **A lição, e ela é a mesma da SI-P4 vista de outro ângulo: PENDÊNCIA ESCRITA É HIPÓTESE, NÃO CENSO.**
> A IL-P2 estava escrita desde 2026-08-17 com três itens de SEMÂNTICA. O defeito que realmente impedia
> uma pessoa de usar a grade era de **TECLADO**, e ele só apareceu quando alguém teclou Tab de verdade.
> *Guarda que pergunta "tem este atributo?" nunca teria achado: foi preciso PRESSIONAR a tecla.*

#### 67.7.2 RITO — três rodadas encadeadas

- **R1 — canon (padrão `grid` da ARIA / APG).** O padrão admite **duas** formas: *"a célula contém UM
  widget cuja operação não exige as setas, e as teclas de navegação da grade põem o foco NESSE widget"*
  ou *"a célula contém texto ou um único gráfico e as teclas de navegação põem o foco na CÉLULA"*.
  **As duas exigem os papéis `grid`/`row`/`gridcell`.** Teclado nomeado pelo padrão: setas · Home/End ·
  Ctrl+Home/Ctrl+End · **Enter ou F2** para editar · Esc para cancelar · `tabindex` rotativo.
- **R2 — mercado/stack.** Partir de uma `<table>` semântica e acrescentar `role="grid"` preserva as
  relações cabeçalho↔célula pelos `<th scope>`; quando a célula tem **um** controle, o `tabindex`
  rotativo mora **no controle**, não na célula; e o editor precisa de **nome acessível próprio**, porque
  o cabeçalho de coluna deixa de bastar no instante em que o texto da célula vira um campo nu.
- **R3 — normas.** SC 2.1.1 (teclado) · 1.3.1 (relações) · 4.1.2 (nome, papel, valor) · 2.4.6 (rótulos)
  · 3.2.2 (sem mudança automática) · 2.5.8 (tamanho de alvo).

**VEREDITO: o IL1 do §67 é exatamente a forma (a) do padrão, e fica CONFIRMADO.** O RITO **não** revogou
o que estava escrito — corrigiu o peso: a pendência tratava `role="grid"` como *"agravante"*, e ele é a
**PRECONDIÇÃO**; sem o papel, nenhuma das duas formas existe.

**ALTERNATIVA DESCARTADA:** a **forma (b)** — célula focável com texto dentro. É legítima na ARIA e seria
menos código, mas contradiz o **IL1**, que o Rafael já aprovou, e deixaria a célula **sem nome de ação**:
*"Ana Lima"* não diz que dá para editar.

#### 67.7.3 O contrato IL15

| # | Cláusula | O que a guarda faz de fato |
|---|---|---|
| **IL15-a** | a tabela declara `role="grid"` e tem cabeçalhos de coluna | lê o papel e conta `th[scope="col"]` |
| **IL15-b** | toda célula editável carrega **UM** gatilho (forma (a)) | conta gatilhos contra células |
| **IL15-c** | `tabindex` rotativo: **exatamente um** gatilho na sequência de Tab | conta os `tabindex="0"` |
| **IL15-d** | **a grade é ALCANÇÁVEL por Tab** | **pressiona Tab** até 25 vezes a partir do seletor "selecionar todas" e vê se o foco entra |
| **IL15-e/f** | seta direita anda uma coluna · seta baixo anda uma linha | **tecla** e lê a coordenada do foco |
| **IL15-g/h** | End e Home vão às pontas da **LINHA** | **tecla** e lê |
| **IL15-i/j** | Ctrl+End e Ctrl+Home vão às pontas da **GRADE** | **tecla** e lê |
| **IL15-k** | o `tabindex="0"` **acompanha** o foco | conta depois de navegar |
| **IL15-l** | nome do gatilho na forma `"Editar <rótulo>: <valor>"` | lê o nome acessível |
| **IL15-m/o** | **F2** e **Enter** entram na edição | **tecla** e confere que o foco caiu num campo |
| **IL15-n** | **Esc** cancela e devolve o foco ao **gatilho** (IL7) | **tecla** e lê onde o foco parou |
| **IL15-p** | a alça de preencher é `aria-hidden` — o `title` dela não entra no nome | lê o atributo |
| **IL15-q** | a affordance de lápis aparece **no foco**, não só no hover (IL10) | foca e lê a opacidade computada |
| **IL15-r** | célula travada **não** tem gatilho e **diz o motivo** (IL14) | conta gatilhos e motivos nas travadas |

`validacao/guarda-il15.mjs` — **ALVO DE PASTA**: varre todo `*.html` da raiz e mede todo artefato com
`table.planilha`; arquivo sem grade sai `[n/a]` **nomeado**. Ela troca a lente para "tabela" antes de
medir, porque a grade só existe nessa lente.

**PROVA DE REPROVAÇÃO:** rodada sobre a cópia anterior do `tela-painel.html`, reprova **6 cláusulas** —
`IL15-a` (sem papel), `IL15-b` (0 gatilhos em 56 células), `IL15-c` (0 de 0), **`IL15-d` (NUNCA em 25
pressionamentos)**, `IL15-p` (alça visível ao leitor de tela) e `IL15-q` (sem gatilho, logo sem lápis).

**PLACAR: 1 PASS · 0 FAIL · 50 `[n/a]`**, em **seis execuções idênticas**.

> **DEFEITO DE MEDIÇÃO CONSERTADO NA PRÓPRIA SESSÃO EM QUE A GUARDA NASCEU.** A primeira versão lia a
> opacidade do lápis **logo após** dar o foco, e a transição ainda estava correndo: o valor voltava
> `0.999599` e a guarda **reprovava um artefato correto em 3 de 6 execuções**. *Guarda intermitente é
> pior que guarda ausente: ela ensina a ignorar o placar.* A transição é desligada antes da leitura, e o
> que se mede passa a ser o valor de DESTINO.

#### 67.7.4 O que mudou no artefato, e o que ficou aberto

O `tela-painel` é **gerado**: a mudança foi no molde `validacao/painel-template.html` e o artefato foi
regerado. **A `suite-painel` reprovou o bloco novo no primeiro segundo** — `transition: opacity .12s`
tinha duração **solta**, e a guarda **PN21-11** exige token. Trocado por `var(--seed-dur-productive-fast)`.

**Placares depois:** `suite-painel` **146 PASS · 0 FAIL** · `guarda-il15` **1 · 0 · 50** ·
contraste **2 · 0** a 1440px e **2 · 0** a 390px · forced-colors FC1/FC2 **2 · 0** cada ·
reflow BT7 **3 · 0 · 1**.

| # | Pendência que nasce | Por que fica aberta |
|---|---|---|
| **PN-P6** | ✅ **FECHADA em 2026-08-19 pelo gate. Decisão do Rafael, verbatim: "sobe para 44".** A altura mínima da célula (`.planilha .cel-edit`) foi de **40px para 44px** no molde `validacao/painel-template.html`, e o painel foi regerado. **Medido depois:** a `guarda-il15` imprime o alvo em **234×44px** — a mesma régua que produziu a pendência. **Custo medido, e é o único:** no mesmo recorte de 360px de altura cabe **uma linha a menos** (recorte B2 da folha `render-audit/gate-v134/fechamento.html`). **ALTERNATIVA DESCARTADA:** manter 40px e declarar a planilha como exceção de densidade — descartada porque *toda exclusão declarada é uma dívida com juros*, e porque a linha inteira é o alvo, então os 4px não compram densidade real. **Aplicado à célula inteira, não só à coluna medida:** altura por coluna quebraria o alinhamento horizontal da planilha. *Placar depois: `suite-painel` **146 · 0**, `guarda-il15` **1 · 0 · 50**, contraste **2 · 0** nos dois temas.* |
| **CC-P7** | O contraste do `tela-painel` é medido na lente **padrão** (lista). **Nenhum instrumento troca a lente** antes de medir, então as outras **sete** lentes — inclusive a grade — seguem **não medidas** por contraste. *Ausência de medição não é aprovação* |


