From 3812d8126139350bbd0a7deb9e4523f41f9aa041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Ign=C3=A1cio=20Fontoura=20Solla?= Date: Sat, 25 Jul 2026 13:15:30 -0300 Subject: [PATCH] Pass 2: materializa tracker e planos por workstream (W0-W5, D1-D6) --- docs/IMPROVE-CODE-QUALITY-PLAN.md | 52 ++++++++++++++++++++++ docs/plans/decisao-arquitetura-dominios.md | 41 +++++++++++++++++ docs/plans/mapa-mental-arquitetura.md | 32 +++++++++++++ docs/plans/pinagem-superficies-lista.md | 37 +++++++++++++++ docs/plans/sistema-listas-campanha.md | 52 ++++++++++++++++++++++ docs/plans/split-election-insights.md | 26 +++++++++++ 6 files changed, 240 insertions(+) create mode 100644 docs/plans/decisao-arquitetura-dominios.md create mode 100644 docs/plans/mapa-mental-arquitetura.md create mode 100644 docs/plans/pinagem-superficies-lista.md create mode 100644 docs/plans/sistema-listas-campanha.md create mode 100644 docs/plans/split-election-insights.md diff --git a/docs/IMPROVE-CODE-QUALITY-PLAN.md b/docs/IMPROVE-CODE-QUALITY-PLAN.md index 9a35d7d6..d128fc2f 100644 --- a/docs/IMPROVE-CODE-QUALITY-PLAN.md +++ b/docs/IMPROVE-CODE-QUALITY-PLAN.md @@ -1,5 +1,7 @@ # Improve Code Quality Plan +Two passes so far: **Pass 1** (2026-07-23/24, phases 0–6 below, all done) and **Pass 2** (started 2026-07-25, see [Pass 2](#pass-2--engineering-consolidation-2026-07-25)). + ## Context - **Started:** 2026-07-23 @@ -49,3 +51,53 @@ Statuses: pending · in-progress · awaiting-evidence · done · deferred: \ · skipped: \ + +## Pass 2 Decisions (signed off 2026-07-25) + +| ID | Decision | Rationale | +| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| D1 | **No `src/domains/` reorg, no ports-and-adapters.** Conventions + mechanical boundary fixes + staged within-layer subfolders for `components/campaign/`; `utilities/` subfoldering deferred with trigger | Payload collections ARE the data model; a big-bang move freezes the repo mid-window and risks the importmap. Full evaluation in [decisao-arquitetura-dominios.md](plans/decisao-arquitetura-dominios.md) | +| D2 | **Delete** the ~52 consumer-less `electionInsights` exports (+ their tests) rather than quarantining them | git history + plans carry the formulas; E10 replaces the 35/20/10 threshold approach with relative classification — keeping dead math invites accidental reuse | +| D3 | `consent: 2` hardcode folded into the Onda 0 provisional-key pattern in-pass, as an isolated delivery with the migration SQL explicitly reviewed by the user before landing | Same fail-closed pattern as `lideranca-autopreenchimento`; leaving the hardcode makes the legal batch harder to verify | +| D4 | users-roles migration stays ledgered/deferred | Schema migration; only blocks widening `/admin`, which isn't scheduled | +| D5 | W1 scope boundary: planos stays cards (adopts toolbar/URL state only), `TerritoryOverviewTable` stays a documented client-sort exception, LeaderContacts/candidate-comparison/import-preview stay out | Anti-classitis: the system earns its migrations; small static tables don't pay for the abstraction | +| D6 | Praça sweep = user-visible copy + admin labels; provisional Onda 0 consent texts untouched (legal batch) | Consent text versions are counsel-owned; code shouldn't edit them cosmetically | + +## Next Actions + +- [x] Materialize Pass 2 tracker section + per-workstream plans (this commit) +- [ ] W0 → W1-D1 → W1-D2 (window 1), W4a/W4e/W4f interleaved +- [ ] W2 → W4c/W4d → W3 (window 2) +- [ ] W4b (last code delivery) → W5 (docs close-out) +- [ ] D3 isolated delivery — migration SQL reviewed by the user before landing diff --git a/docs/plans/decisao-arquitetura-dominios.md b/docs/plans/decisao-arquitetura-dominios.md new file mode 100644 index 00000000..95167d41 --- /dev/null +++ b/docs/plans/decisao-arquitetura-dominios.md @@ -0,0 +1,41 @@ +# Decisão de arquitetura: domínios vs camadas (Pass 2 — W2) + +Status: aprovado (Pass 2, 2026-07-25) — veredito D1 assinado: NO-GO no reorg, GO em convenções + correções de fronteira +Atualizado em: 2026-07-25 +Item pai: [IMPROVE-CODE-QUALITY-PLAN.md](../IMPROVE-CODE-QUALITY-PLAN.md) — Pass 2, W2 +Appetite: ~1,5 dia + +## Pergunta + +O layout layer-first (`collections/ → utilities/ → components/ → app/`) deve virar domain-first (`src/domains/{municipality,supporter,...}/`), possivelmente com ports-and-adapters? Avaliação corrida contra `domain-driven-design`, `clean-architecture` e `software-design-philosophy`. + +## Veredito (D1, assinado 2026-07-25) + +**NO-GO no `src/domains/` reorg e no ports-and-adapters. GO em: (a) convenções documentadas e endurecidas; (b) correções mecânicas de fronteira; (c) subpastas por domínio DENTRO da camada `components/campaign/` (por etapas); (d) subpastas em `utilities/` adiadas com gatilho.** + +Racional registrado (alternativas rejeitadas): + +- **`src/domains/` rejeitado:** Payload collections JÁ SÃO o modelo de domínio — a "camada de domínio" real vive em `src/collections/` + `src/utilities/access/` e não pode sair de lá (importmap do admin, convenções do framework). Um big-bang move de ~230 arquivos congela o repo no meio da janela 1–2 do calendário eleitoral e arrisca o importmap gerado. O ganho (navegação) é obtido com subpastas within-layer por fração do risco. +- **Ports-and-adapters rejeitado:** só há um adapter de verdade (Payload/Postgres) e um framework (Next). Interfaces na frente do Payload Local API duplicariam a assinatura de cada query sem segundo backend plausível — abstração especulativa (SDP: shallow module). +- **Aceito do DDD:** mapa de bounded contexts + linguagem ubíqua (glossário) — vão para `docs/ARCHITECTURE.md` (W3) independentemente do veredito; os prefixos de domínio (`municipality*`, `supporter*`, …) são a expressão within-layer disso. + +## Correções de fronteira (parte mecânica desta entrega) + +1. **9 inversões `lib/`→`utilities/`:** mover os helpers puros (`phone`, `slug`, `wordStartFilter`, validadores de `voteEstimate`, chave de consent do invite) de `utilities/` para `lib/` — `lib/` = puro/client-safe, `utilities/` = acoplado a Payload/Next (regra já escrita, agora cumprida). +2. **21 loaders sem `server-only`:** marcar (`actionPlanPageData`, `campaignDashboardData`, os `*Data.ts` de lista, etc.). +3. **Sidebar client importando o barrel de access:** `nav.ts` para de importar `campaignAccess` (85 exports, incl. helpers Payload); predicados de role client-safe num módulo de contrato (mesmo padrão de `municipalityMapContract.ts`). +4. **Ciclo de tipos `supporterListFilters.ts` ↔ `supporterUi.ts`:** quebrar movendo o tipo compartilhado para um dos lados (ou módulo de tipos). +5. **`votePledgeData.ts`:** separar views puras dos loaders Payload (padrão contract-module), destravando importação client-safe dos tipos de cenário. + +## Subpastas por domínio em `components/campaign/` (por etapas) + +106 arquivos flat é a pior dor de navegação do repo. Mover por domínio (`municipality/`, `actionPlan/`, `supporter/`, `leadership/`, `advisor/`, `demand/`, `map/`, `shared/`…), **uma entrega `git mv` por domínio em momento calmo** (branch curta, conflito improvável), imports atualizados por codemod + `tsc`. `utilities/` (92 arquivos) fica adiada: gatilho = quando um domínio ganhar o 3º módulo novo num mesmo mês, subpasta desse domínio junto. + +## Impacto no roadmap + +Nenhum item bloqueado (moves per-domain em branches curtas); navegação de agente melhora para todo item futuro. Sem migration, sem mudança de comportamento — apenas `git mv` + imports + marcações `server-only` (podem revelar violações latentes de client boundary; cada uma é corrigida ou documentada, nunca silenciada). + +## Referências + +- `.cursor/rules/engineering-standards.mdc` (regra lib/utilities), AGENTS.md (estrutura), auditoria de fronteiras 2026-07-25 +- `src/utilities/campaignAccess.ts` (precedente de split within-layer com re-export surface) diff --git a/docs/plans/mapa-mental-arquitetura.md b/docs/plans/mapa-mental-arquitetura.md new file mode 100644 index 00000000..016fc78b --- /dev/null +++ b/docs/plans/mapa-mental-arquitetura.md @@ -0,0 +1,32 @@ +# Mapa mental da arquitetura (Pass 2 — W3) + +Status: aprovado (Pass 2, 2026-07-25) +Atualizado em: 2026-07-25 +Item pai: [IMPROVE-CODE-QUALITY-PLAN.md](../IMPROVE-CODE-QUALITY-PLAN.md) — Pass 2, W3 (depois de W1/W2) +Appetite: ~1 dia; docs-only + +## Objetivo + +Um humano ou agente novo se orienta no repo em minutos, sem arqueologia de git/AGENTS.md. Dois artefatos: + +**1. `docs/ARCHITECTURE.md`** — o documento de arquitetura canônico: + +- Contexto de sistema: um app Next + Payload, três route groups (`(frontend)` público, `(payload)` admin, `(campaign)` interno), duas barreiras de auth independentes (cookie `payload-token` vs `campaign-token`). +- Mapa de camadas + dependency rule como aplicada: `lib/` (puro, client-safe) ← `utilities/` (Payload/Next, `server-only` quando server-bound) ← `components/` ← `app/`; módulos de contrato para atravessar a fronteira client (padrão `municipalityMapContract`). +- Fluxo de dados: collections → access helpers (`utilities/access/*`) → loaders (`*Data.ts`, `select`ed view models) → RSC → ilhas client. +- Escada de caching (React `cache()` → `unstable_cache`+tag → artefato commitado) com os exemplos reais. +- Modelo RBAC da campanha (coordinator/candidate/advisor/leader; assimetria de pledge; lockdown do leader). +- Bounded contexts + glossário de linguagem ubíqua (W2): Município (unidade operacional ≠ município IBGE), Liderança, pledge `declaredVotes` vs `estimatedVotes`, Demanda, Plano, Apoiador, Dobradinha, termos da conta-da-cadeira (meta, cobertura, teto do campo, captura), contexts do site público (posts/tags/petições/consent). +- Decision log: todas as decisões do Pass 2 (D1–D6) + as decisões estruturais herdadas (transações multi-collection, consent fail-closed, migrations only). +- Cross-links para PRODUCT/DESIGN/CUSTOMER/research para o "porquê" — sem duplicar conteúdo. + +**2. `.cursor/rules/codebase-map.mdc`** — mapa navegacional compacto (~150 linhas), always-applied, criado com o skill `create-rule`: onde vive cada coisa, como achar o dono de uma feature, convenção de manutenção ("atualizado no mesmo PR que move coisas"). + +## Regras + +- ARCHITECTURE.md descreve o estado **pós-W1/W2** (por isso roda depois). +- AGENTS.md continua sendo o guia operacional (setup, seeds, deploy); ARCHITECTURE.md é o mapa conceitual — sem sobreposição de conteúdo, com links cruzados. + +## Impacto no roadmap + +Nenhum item bloqueado. Todo item futuro (e todo agente) se beneficia. diff --git a/docs/plans/pinagem-superficies-lista.md b/docs/plans/pinagem-superficies-lista.md new file mode 100644 index 00000000..50ea838f --- /dev/null +++ b/docs/plans/pinagem-superficies-lista.md @@ -0,0 +1,37 @@ +# Pinagem das superfícies de lista (Pass 2 — W0) + +Status: aprovado (Pass 2, 2026-07-25) +Atualizado em: 2026-07-25 +Item pai: [IMPROVE-CODE-QUALITY-PLAN.md](../IMPROVE-CODE-QUALITY-PLAN.md) — Pass 2, W0 (GATE de W1) +Appetite: ~1 dia; sem migration, sem UI + +## Contexto + +W1 vai generalizar o sistema de listas e migrar todas as superfícies. A auditoria de safety net (2026-07-25) mostrou que só a lista de municípios está fortemente pinada (`municipalityUi` unit + `municipalityPageData` int). Os parsers de URL e loaders de apoiadores, planos, assessores, lideranças, organizações, dobradinhas e demandas estão fracos ou sem pin — refatorá-los sem pin é mudar comportamento sem rede. + +## Escopo (conjunto mínimo de pinagem) + +**Unit (parsers/href — contrato de URL byte-idêntico):** + +- `campaignListUrl.ts` — canonicalização, param desconhecido ignorado, clamp de página, geração de href. +- `supporterUi.ts` — parse de URL (intenção de voto, território, busca) + hrefs. +- `actionPlanUi.ts` — default de tab, matriz tab→where, parse de filtros (kind/municipality). +- Um spec compartilhado para os 5 parsers simples de lista (advisor / leadership / organization / stateDeputy / demand) — hoje cada página tem um parser local de `first`/`page`/`q`. + +**Int (5 smokes finos de loader — escopo, `q` vazia, shape da paginação):** + +- `advisorData.ts`, `leadershipData.ts`, `organizationData.ts`, `stateDeputyData.ts`, `campaignDemandData.ts`. + +**Truth-up de documentação (mesma entrega):** + +- `TESTING.md`: contagens reais (41 unit / 46 int / 7 e2e), item do backlog do artefato eleitoral (entregue no Pass 1 fase 2) marcado, novo mapa de pin das listas. +- `TECH-DEBT.md`: "Praça" ~96 hits em 53 arquivos (não ~15); e2e 7 specs (não 5); resolver o conflito de status do C8 F4 (ledger diz "partially done", plano diz remainder aberto — o remainder É o W4d). + +## Regras + +- Bug achado durante a pinagem é **ledgerado, nunca corrigido silenciosamente** (o pin registra o comportamento atual). +- Sem refactor nesta entrega — só testes + docs. + +## Impacto no roadmap + +Nenhum item bloqueado; é o gate de W1 (que precisa terminar antes da janela 3, ~16/08). diff --git a/docs/plans/sistema-listas-campanha.md b/docs/plans/sistema-listas-campanha.md new file mode 100644 index 00000000..a3af3881 --- /dev/null +++ b/docs/plans/sistema-listas-campanha.md @@ -0,0 +1,52 @@ +# Sistema de listas da campanha (Pass 2 — W1) + +Status: aprovado (Pass 2, 2026-07-25) +Atualizado em: 2026-07-25 +Item pai: [IMPROVE-CODE-QUALITY-PLAN.md](../IMPROVE-CODE-QUALITY-PLAN.md) — Pass 2, W1 (2 entregas) +Appetite: ~4 dias; sem migration, sem mudança de comportamento visível +Gate: W0 ([pinagem-superficies-lista.md](pinagem-superficies-lista.md)) verde + +## Contexto + +12 superfícies `ui/Table` no `/campanha`, cada uma com parser de URL, header, paginação e filtros re-implementados por feature. Uma espinha compartilhada já existe pela metade (`campaignListUrl.ts` 6 importers, `CampaignListPagination` 9, `CampaignListPendingBoundary` 8, `CampaignSearchForm` 4) — mas a lista de municípios (a mais rica: sort, filtros no header, células editáveis B9, overview E8/E9) é um monolito (`MunicipalityList.tsx` 496 + `MunicipalityFilters` 354 + `MunicipalityHeaderFilter` 311 + `municipalityUi.ts` 765/59 exports), e as listas de lideranças/organizações/dobradinhas são clones de página com 5 parsers locais duplicados. B17 (seletor de colunas) e B18 (filtros salvos) pousariam em cima disso multiplicado. + +## Objetivos + +- Definições de coluna como dado: `id`, header, cell renderer, `sortKey?`, `filterDef?`, `mandatory?`, `defaultVisible?`, tratamento mobile. +- Estado de URL genérico (parse/canonicalize/serialize) estendendo `campaignListUrl.ts` — contrato de URL existente **byte-idêntico** (pins de W0 verdes). +- Tabela server-first + ilhas client generalizadas de `MunicipalitySortableHead`/`MunicipalityHeaderFilter`. +- Slots de toolbar (busca, barra de filtros, botões futuros de B17/B18); empty state que mantém o chrome montado (lição de B16); slot de card-stack mobile; paginação/pending pelos shells existentes. +- Superfície migrada = código antigo deletado na mesma entrega (knip limpo). + +## Entregas + +**D1 — núcleo + lista de municípios.** O sistema nasce extraído da lista mais rica, não inventado: column defs, URL state, ilhas, toolbar. Absorve o **B16+** ([escala-dry-pos-b16.md](escala-dry-pos-b16.md)): hook `useOptimistic` de navegação compartilhado, hrefs de opção a partir de uma base `URLSearchParams`, payload de facet por slugs. **Divide `municipalityUi.ts`** nas costuras naturais (url/filters/sort/labels/freshness). Células de edição inline (B9) viram cell-renderer islands. + +**D2 — migra o resto + deleta o superado.** Tripé de entidades (lideranças/organizações/dobradinhas) numa composição de página compartilhada + parsers de `campaignListUrl` (fecha o débito de parser do C8 F3); demandas (`CampaignFilterChips` → filter def); apoiadores (mantém cards mobile); assessores (tabela client adota estado/toolbar/paginação; inline-create continua bespoke). + +## Fora do escopo (D5 do sign-off — anti-classitis, documentado) + +- `ActionPlanList` — domínio de cards, não de tabela; adota só toolbar/URL state. +- `TerritoryOverviewTable` — exceção documentada de sort no cliente (dataset ≤27 linhas, já carregado). +- `MunicipalityCandidateComparisonTable`, `LeaderContactsPanel`, preview do wizard de import — pequenas/estáticas demais. +- B17/B18 **não são implementados** — o sistema deixa as costuras (visibilidade por column id, serializador de filtros) e para aí. + +## Impacto no roadmap + +- **Quase de graça depois:** B17 (seletor de colunas = toggle de visibilidade sobre column ids + localStorage), B18 (filtros salvos = serializador + slot na toolbar), "Cenário junto aos filtros" (slot na barra; cenário nunca entra na serialização de URL), ícone de prioridade (accessory slot na célula de nome). +- **Costuras para:** E10/E14 (nova coluna = nova column def + ilha inline opcional), E12 (ponto de extensão de group-mode documentado, não construído). +- **Absorve:** B16+ (plano marcado como absorvido). +- **Conflitos:** precisa terminar antes do trabalho de colunas da janela 3 (~16/08) e antes de B17/B18 entrarem em desenvolvimento. +- **Supersede premissas em:** [seletor-colunas-lista-municipios.md](seletor-colunas-lista-municipios.md), [filtros-salvos-municipios.md](filtros-salvos-municipios.md), [cenario-junto-filtros-municipios.md](cenario-junto-filtros-municipios.md), [escala-dry-pos-b16.md](escala-dry-pos-b16.md) (seções marcadas em W5). + +## Rabbit holes + +- **Generalizar `MunicipalityHeaderFilter` inteiro no D1.** As facetas de município (435 opções + busca) são as mais complexas; a generalização é a *interface* (filterDef + slot), a implementação rica continua específica até um segundo consumidor pedir. +- **DataTable do shadcn como base.** Consultar composição, mas a casa já tem `ui/Table` + DESIGN.md; introduzir TanStack Table trocaria o modelo server-first por client state — não. +- **Mexer na semântica de URL "aproveitando".** Congelada (B18 depende dela). + +## Referências + +- `src/utilities/campaignListUrl.ts`, `src/utilities/municipalityUi.ts`, `src/components/campaign/MunicipalityList.tsx`, `MunicipalitySortableHead.tsx`, `MunicipalityHeaderFilter.tsx`, `MunicipalityFilters.tsx` +- `src/app/(campaign)/campanha/(app)/{liderancas,organizacoes,dobradinhas,demandas,apoiadores,assessores}/page.tsx` +- [escala-dry-pos-c6.md](escala-dry-pos-c6.md) (C8 F3/F4), [filtros-no-header-lista-municipios.md](filtros-no-header-lista-municipios.md) (B16), DESIGN.md diff --git a/docs/plans/split-election-insights.md b/docs/plans/split-election-insights.md new file mode 100644 index 00000000..c51ce951 --- /dev/null +++ b/docs/plans/split-election-insights.md @@ -0,0 +1,26 @@ +# Split de electionInsights.ts (Pass 2 — W4a) + +Status: aprovado (Pass 2, 2026-07-25) — decisão D2 assinada: DELETAR os clusters mortos +Atualizado em: 2026-07-25 +Item pai: [IMPROVE-CODE-QUALITY-PLAN.md](../IMPROVE-CODE-QUALITY-PLAN.md) — Pass 2, W4a +Appetite: ~0,5 dia; precisa terminar antes de E10 começar (~16/08) + +## Contexto + +`src/lib/electionInsights.ts`: 941 linhas, 71 exports, **2 usados em produção** — `formatElectionNumber` (13 importers) e `computeVoteTrend` (+ tipos, 1 importer: `MunicipalityBaselineCard`). O resto é a matemática de insights da era núcleos (A4: gap vs 2022, limiares 35/20/10, rankings por núcleo) cuja UI morreu no remodel de municípios; os testes que restam testam código sem consumidor. + +## Decisão (D2, assinada 2026-07-25) + +**Deletar** os ~52 exports sem consumidor + seus testes, com ponteiro para E10 no commit e neste plano. Rejeitado: módulo-quarentena (`electionInsightsLegacy.ts`) — manter matemática morta convida reuso acidental da abordagem de limiares absolutos que E10 explicitamente substitui por classificação relativa (quantis/LQ — ver `docs/research/` e [classificacao-territorial-relativa.md](classificacao-territorial-relativa.md)). As fórmulas ficam no git history e nos planos. + +## Escopo + +1. Extrair `formatElectionNumber` (+ helpers de formatação vivos) para `src/lib/electionFormat.ts`; atualizar os 13 importers. +2. Manter o cluster `computeVoteTrend` (tipos + função) — em `electionFormat.ts` ou módulo próprio pequeno, o que a costura natural pedir. +3. Deletar `electionInsights.ts` restante + specs correspondentes; knip limpo. +4. Marcar em [classificacao-territorial-relativa.md](classificacao-territorial-relativa.md) que a premissa "editar electionInsights in place" foi superada — E10 nasce em módulo novo. + +## Impacto no roadmap + +- **E10** (classificação relativa): parte de terreno limpo em vez de herdar 900 linhas de abordagem rejeitada. Precisa desta entrega antes de começar (~16/08). +- Derruba o maior ofensor do knip `exports` (25 findings) — pré-requisito prático de W4b (warn→error).