diff --git a/.cursor/rules/projects/nucleos-eleitorais.mdc b/.cursor/rules/projects/nucleos-eleitorais.mdc index 1f7fffd3..ffa644f1 100644 --- a/.cursor/rules/projects/nucleos-eleitorais.mdc +++ b/.cursor/rules/projects/nucleos-eleitorais.mdc @@ -7,6 +7,7 @@ alwaysApply: false ## Status +- **B32+ entregue em código (2026-07-28):** escala e DRY pós-B32 (+564 / −802 linhas em 15 arquivos, mais quatro novos; sem migration e sem mudança de contrato de rede). Toda rota JSON de `/campanha` agora nasce de **`campaignJsonMutationRoute(config, handler)`** — origem verificada, corpo parseado e `catch` mapeado por construção, com o `bodySchema.parse` **dentro do mesmo `try`** para não trocar a mensagem de corpo inválido; `parseCampaignJsonRequestBody`/`campaignJsonMutationErrorResponse` deixaram de ser exportados e um **guard de convenção** em `codebaseConventions.unit.spec.ts` recusa um `POST` que não passe pelo wrapper. Isso existe porque a política de mesma origem estava copiada em cinco rotas: a que esquecesse a linha aceitaria POST cross-origin em silêncio. `engagement-level` mantém o 409 dentro do handler. No cliente, `postCampaignJson` (transporte) e **`useCampaignCellAutosave`** (`shared/`) absorvem os três controles de auto-save. Três achados da auditoria que valem mais que o refactor: `display`/`draft` **nunca divergiram** (eram setados juntos em todos os caminhos — o hook tem um valor só, e o `noteDraft` do Trend fica no chamador porque texto cru e nota normalizada são coisas diferentes); `reportFailure` **não** existia nos quatro controles como o plano afirmava (o de assessores não importava `sonner`); e `flush()` idempotente elimina na raiz o `inFlightRef` que o B24 tinha como contorno de dois caminhos para o mesmo save. O hook é dono do `open` — fechar É o commit —, e `valueRef` avança sincronamente para não reler o render que agendou a mudança (bug que B33+ e B34 já pagaram). Casca: a região `aria-live` mudou para **fora** do Popover/Drawer (prop `statusMessage`), já que o fechamento que dispara a gravação desmontava justamente o nó que anunciaria "Salvando…"; e `variant="sheet"` ganhou slot `footer`, que tirou "Registrar movimento" de baixo da dobra no controle de nível (E14) e permitiu ao Sinal (B26) largar o Popover/Drawer próprios — `formId` na casca acabou desnecessário, porque `