diff --git a/docs/plans/react-19-form-reset-campanha.md b/docs/plans/react-19-form-reset-campanha.md new file mode 100644 index 00000000..30838de6 --- /dev/null +++ b/docs/plans/react-19-form-reset-campanha.md @@ -0,0 +1,86 @@ +# C140 — Formulários de campanha apagam o digitado em erro de validação (React 19 form reset) + +Status: rascunho +Atualizado em: 2026-08-13 +Issue: #740 +Priority: P2 +Model: composer-2.5 +Impeccable: B — UX (perda de dado digitado em erro); sem fluxo novo +Canvas UI: N/A — não muda layout +Appetite: ~1 dia eng; um outcome verificável +Responsável: — + +## Intenção + +Formulários de campanha com `useActionState` + `
` (padrão +`action={submitAction}`) sofrem o reset automático do React 19: **após QUALQUER action +assentada (sucesso ou erro), campos não controlados voltam ao defaultValue**. Quando o +formulário fica aberto em erro de validação/conflito, o usuário vê o alerta mas **todo o +digitado some** — silenciosamente. + +**Provado empiricamente nesta sessão (C139):** create row desktop e sheets mobile de +contatos — mesmo nó DOM, valor restaurado ao default após submit que retornou erro +(validation e conflito de nome). O fix do C139 trocou para campos controlados + dispatch +manual (`preventDefault` + `startTransition(() => submitAction(new FormData(...)))`), que +mantém `isPending` correto sem reentrar no plumbing de form action. + +Restam **~21 formulários** com o padrão antigo na árvore campaign +(`grep 'action={submitAction}' src/components/campaign`): votePledge (2), municipality +(wizard steps, updates, advisors), tour, advisor, leadership (3), organization, stateDeputy, +demand (3), shared wizard. Todos com o mesmo defeito de UX em erro. + +## Persona e fluxo + +- **Persona:** staff da campanha (coordenador/assessor) preenchendo formulários longos + (liderança, demanda, organização, dobradinha, declaração de votos, wizard de giro). +- **Job principal:** errar um campo, ver o erro, corrigir SÓ o campo errado e salvar. +- **Fluxo desejado:** o erro aparece, os demais campos **permanecem digitados**, o usuário + corrige o mínimo. +- **Anti-goal:** nenhuma mudança de comportamento de sucesso; nenhuma mudança de schemas + de dados; não virar epic de refactor global. + +## Objetivo e aceite + +- Nenhum form com `useActionState` em `/campanha` apaga o digitado quando a action retorna + erro (fieldErrors ou message). +- O padrão de fix segue o precedente C139: campos controlados **ou** dispatch manual + (`onSubmit` + `preventDefault` + `startTransition`), decidido por formulário conforme o + que o C139 já fez (sheets controladas; create row com dispatch manual + uncontrolled). +- Cobertura por spec e2e ou unit do form mais representativo por família (ex.: um form + longo de criação em erro mantém valores). +- Regressão: os specs existentes das famílias tocadas seguem verdes sem alteração. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — o "dado" é o comportamento de formulário já provado na + sessão C139 (mesmo nó, valor resetado, 3 formas distintas). + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** os ~21 forms listados acima em `src/components/campaign/*` + (`grep -rn 'action={submitAction}' src/components/campaign`). +- **Precedente a olhar:** `src/components/campaign/contacts/ContactFormFields.tsx` + (controlado + manual dispatch, comentário documentando o porquê) e + `ContactCreateRow.tsx` (dispatch manual); `ActivityOverlay.tsx` (controlado + manual — + precedente anterior ao C139). +- **Risco de acoplamento:** cada form é independente; o fix é mecânico por arquivo. + Forms com telefones/arrays (`PhonesFieldEditor` FORM mode) precisam conferir se o editor + interno é controlado (não é resetado pelo dispatch manual). + +## Já resolvido no simplify/critique (não reabrir) + +- Forms do domínio `contacts` (create row, create/edit sheets) — fix C139 nesta entrega, + coberto pelo e2e `campaignContacts`. + +## Explicitamente fora + +- Forms fora de `/campanha` (frontend público) — não verificados nesta colheita; reavaliar + se o padrão aparecer lá. +- Mudança de comportamento de sucesso ou de schemas — fora de escopo. +- Padronizar TODOS os forms num único helper compartilhado — decisão de engenharia do + item (pode ficar com os dois padrões C139, ou extrair helper se o lote mostrar DRY). + +## Dependências + +- `C139 (#728)` — a entrega que provou o defeito e fixou o domínio contacts (via `depends` + na Issue).