Pass 2 W5: close-out de documentacao
- AGENTS.md: Known Gap #2 resolvido (D3), arvore de estrutura (+AllocationDecision, PrivacyPolicy, CampaignGoals; components por dominio), URLs novas (/campanha/assessores|conceitos|perfil), entrada 'Recently resolved' do Pass 2 - tracker: todos os workstreams done; secao de achados de drift de docs de produto (PRODUCT/DESIGN/CUSTOMER - reportados, nao aplicados) - TESTING.md: 46/48/7 + linhas do safety net (formActions wrapper, voteTrend, submitWhatsapp) - TECH-DEBT: smells fechados (municipalityUi split, detail RSCs, formActions, components subpastas, electionInsights) - planos: status entregue nos 5 do Pass 2; escala-dry-pos-b16 absorvido; C8 F3/F4 fechados em escala-dry-pos-c6; notas de costura em B17/B18/cenario-junto; nota de conflito E4R em mapa-projecao - roadmap: entrada Pass 2 no 'Ja entregue', B17/B18 marcados como triviais, B16+ riscado, header atualizado - notebook do projeto: entrada Pass 2 - DESIGN.md: secao do sistema de listas (unico drift aplicado, conforme plano)
This commit is contained in:
parent
304eb34b64
commit
e8a440b837
18 changed files with 64 additions and 46 deletions
|
|
@ -7,6 +7,7 @@ alwaysApply: false
|
|||
|
||||
## Status
|
||||
|
||||
- **Pass 2 — Consolidação de engenharia entregue (2026-07-25, W0–W5 + D3):** tudo que foi one-shotted desde o Pass 1 virou infraestrutura compartilhada e arquitetura documentada. **W1 sistema de listas:** colunas como dado em `shared/CampaignTable.tsx` (seams `mandatory`/`defaultVisible` deixam **B17 trivial**; serialização canônica congelada deixa **B18 trivial**), o god module `municipalityUi.ts` (765 linhas/59 exports) dividido em `municipalityListUrl`/`municipalityListFilters`/`municipalityLabels`/`municipalitySignal`, 8 superfícies migradas com URLs byte-idênticas (pins do W0 verdes) e **B16+ absorvido** (`useOptimistic` no popover, facet por slugs, hrefs via serializador canônico com pin de byte-igualdade, facets no mesmo `Promise.all`); fora por decisão D5: planos (cards), `TerritoryOverviewTable`, comparação, LeaderContacts, preview de import. **W2 fronteiras:** `slug`/`phone`/`wordStartFilter`/`voteEstimate` → `lib/`, chaves de consent em `lib/campaignConsentKeys.ts`, 21 loaders `server-only`, `lib/campaignRoles.ts` client-safe (sidebar sem o barrel), `votePledgeViews` (contract) vs `votePledgeData` (loaders), ciclo supporter quebrado, `components/campaign` em **15 subpastas de domínio**; decisão D1: **sem `src/domains/`**, sem ports-and-adapters (`utilities/` adiado com gatilho: 3º módulo novo de um domínio num mês). **W4a:** `electionInsights.ts` (941 linhas, 2 exports vivos) **deletado** — sobreviventes em `electionFormat.ts`/`voteTrend.ts`; **E10 nasce em módulo novo**. **W4b:** knip `exports`/`types`/`enumMembers` em **ERROR** no CI, 0 findings (kit ui sem peças mortas; `duplicates` off — únicos hits eram os 6 aliases deliberados `canUpdateX = canReadX`). **W4c:** tabs de detalhe extraídas + factory `detailTabUi`. **W4d:** `runCampaignFormAction` fecha o C8 F4 (13 ações em 7 arquivos; exceções comentadas). **W4e:** tipos de fonte única (cenários, trend, strategy VM nomeado). **W4f:** copy Praça→Município varrida (~70 strings; apoiadores desambiguado: cidade do contato = "Cidade"). **D3:** `submitWhatsapp` resolve `Consent.key='whatsapp-inscricao'` fail-closed; migration `20260725_170000` tagueia o doc vivo — **revisar o SQL antes de push/deploy**. Artefatos novos: `docs/ARCHITECTURE.md` + `.cursor/rules/codebase-map.mdc` (atualizados no mesmo PR que mover coisas). Suites: 46 unit / 48 int / 7 e2e; gates verdes por entrega. Tracker: [`IMPROVE-CODE-QUALITY-PLAN.md`](../../../docs/IMPROVE-CODE-QUALITY-PLAN.md) § Pass 2.
|
||||
- **Correções e débitos pós-B16 (2026-07-25):** a revisão paralela achou três defeitos funcionais, **corrigidos na própria entrega**: as facetas omitiam só o param multi, então "Prioritária" escondia os não-prioritários do popover de Município e "Sem assessor" esvaziava o de Assessores (cada faceta passou a omitir o grupo do seu popover — `slugs`+`priority`, `advisors`+`coverage`, com int test); "todas as tendências" tinha duas codificações (as três marcadas deixavam `trend=` inútil na URL com o funil inativo — agora `parseMunicipalityListParams` canonicaliza o conjunto completo para ausente e `narrowingMunicipalityTrends` foi deletado); e o Território havia sumido do mobile ao virar multi (voltou como `MobileMultiFilterField` alimentado pela faceta, via novo prop `regionFilterOptions`). O `/simplify` da entrega (quality / performance / reuse em paralelo) aplicou o cleanup barato no próprio branch — setter único sobre params crus em `municipalityUi.ts` (fim de três casts e da escada de `if`), resumo de filtros de três funções para uma, remoção do array `municipalitySlugFilterOptions` (435 objetos que iam no bundle sem leitor), linha de opção única no Popover, facetas reusando o escopo já carregado e `getEligibleAdvisorOptions` de volta a coordenador-only. O que sobrou é maior que cleanup e virou o fill-in **B16+**: `useOptimistic` no lugar do par `{baseKey,next}` manual, payload dos rótulos de município (~20 KB por navegação) e hrefs do Popover a partir de uma base `URLSearchParams` em vez de duas serializações por opção — [`escala-dry-pos-b16.md`](../../../docs/plans/escala-dry-pos-b16.md). Adiados com gatilho no plano: gêmeo `MunicipalityFilterHead` (3º header com filtro), `ui/Command` no Popover (2º popover de busca), predicado de busca unificado, multi-seleção mobile honesta. Fora do `/campanha`: o `next build` inteiro morre quando o global `metadata` vem vazio (`/abaixo-assinado/[id]` chama `stripTrailingSlash` no `undefined`) — registrado em "Site público" no roadmap.
|
||||
- **B16 entregue em código (2026-07-25):** filtros no header das colunas em `/campanha/municipios` — funil + Popover (`MunicipalityHeaderFilter`) ao lado do sort do B15, com multi-seleção OR em Território (`region`), Município (`slug`, com busca acento-insensível e o check "Prioritária" no mesmo popover), Assessores (`advisor`, que também hospeda o toggle com/sem assessor) e Tendência (`trend`); "Limpar" nos multi grandes; mobile mantém os selects empilhados. Header sticky resolvendo contra o scroller do `<main>` (sem scroller interno na tabela) e empty state que troca só as linhas de conteúdo — visão geral e linha de filtros continuam montadas, senão o usuário fica preso em zero resultados. Facetas cross-filtradas (as opções de um filtro respeitam os demais, mantendo o que já está selecionado visível) reaproveitando o escopo já carregado em `loadMunicipalityListFilterFacets`. A pedido do usuário, a coluna "Assessoria" saiu: a ordenação `coverage` passou para "Assessores", o filtro com/sem assessor virou opção do popover, e o alerta "sem responsável" do E9 (prioridade alta sem ninguém) migrou para a célula de assessores, onde antes só haveria um "Sem assessor" apagado. Plano: [`filtros-no-header-lista-municipios.md`](../../../docs/plans/filtros-no-header-lista-municipios.md).
|
||||
- **E16 entregue em código (2026-07-25):** Dossiê do município (pré-agenda) — aba `?tab=dossie` no detalhe (`MunicipalityDossier.tsx`, server component em Suspense) compondo os loaders existentes via `municipalityDossierData.ts` (baseline TSE + rank A11, conta da cadeira E8, pledges, rede de lideranças com frescor `updatedAt`, agenda `actionPlan` `overrideAccess: false`, sinais C12, perfil IBGE A8 com caveat para zonas — reentrada do artefato órfão); caps 8 lideranças / 5 sinais / 3+2 planos com "ver tudo" por seção; novo campo staff-only `municipality.budgetNotes` ("Emendas aportadas", G11 manual-first) via migration `20260725_022155_add_municipality_budget_notes` (o `.json` da migration também sara a cadeia de snapshots pós-`campaign_goals`); form de estratégia + zod + view model estendidos; **print CSS** (chrome `print:hidden`, shells `print:h-auto print:overflow-visible`, tipografia A4 em `styles.css`) — dossiê imprime 1–2 páginas com capa autocontida ("Gerado em"). Int test `municipalityDossierData.int.spec.ts` (mock do `unstable_cache` do baseline). Gate verde (tsc/lint/knip/363 tests/build); Aikido bloqueado por sessão expirada (re-login navegador pendente). Plano: [`dossie-municipio.md`](../../../docs/plans/dossie-municipio.md).
|
||||
|
|
|
|||
14
AGENTS.md
14
AGENTS.md
|
|
@ -39,7 +39,7 @@ Teqo is the digital platform for deputado federal Jorge Solla (PT-BA): public si
|
|||
Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap.md`](docs/roadmap.md).
|
||||
|
||||
1. **Payload admin roles are still absent (access lockdown shipped 2026-07-23).** Every collection now declares explicit `access` — `users`, `signature`, `subscription`, `consent` are Payload-admin-only, and CMS writes (`post`/`tag`/`media`/`petition`) are admin-only — so campaign JWTs can no longer act on them via `/api`. But the `users` collection still has no `roles` field: every admin user remains omnipotent inside `/admin`. Add admin roles (schema migration) before opening `/admin` to a broader team.
|
||||
2. **Legacy public consent flows still hardcode document IDs** (e.g. `consent: 2` in `submitWhatsapp.ts`). The campaign leadership flow does not: it requires `Consent.key = 'lideranca-autopreenchimento'` and fails closed when missing. Migrating the older public flows remains separate work.
|
||||
2. **RESOLVED 2026-07-25 (Pass 2 D3):** the last hardcoded consent document id is gone — `submitWhatsapp.ts` now resolves `Consent.key = 'whatsapp-inscricao'` fail-closed (migration `20260725_170000_whatsapp_subscription_consent_key` tags the live document; keys live in `src/lib/campaignConsentKeys.ts`).
|
||||
3. **`Post`/`Tag` ship the news system, but there is still no `Pages` collection for institutional content.** Partly resolved: the `post`/`tag` collections back the news/publications system, and the home "Últimas notícias" list plus the `/[type]`, `/[type]/[category]`, and article routes are live (see "Posts & Tags" below). Still hardcoded/pending: the home hero heading + subtitle copy (`HomePage` global still exposes only a single `image` field — the text lives in `src/app/(frontend)/(home)/page.tsx`), and there is no `Pages` collection yet for institutional content such as the bio and propostas.
|
||||
4. **Campaign LGPD consent texts are not yet configured in production.** Invites and self-service leadership flows fail closed until an admin creates `Consent.key = 'lideranca-autopreenchimento'` with counsel-approved copy. The C2 supporter registry (merged to `main`, production-blocked) additionally requires `Consent.key = 'apoiador-cadastro'` and `Consent.key = 'apoiador-intencao-voto'` (vote intention is sensitive data) — same fail-closed behavior. All four keys are bundled in the single legal batch of Onda 0 (see deploy checklist).
|
||||
|
||||
|
|
@ -65,6 +65,8 @@ Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap.
|
|||
|
||||
**Recently resolved (2026-07-24):** **E4R** Import único da planilha de projeção — `pnpm db:seed:projecao` (`scripts/import-projecao.mjs`) lê o xlsx canônico em `docs/sheets/`, faz match via `canonicalizeMunicipalityName` + `municipalityCatalog`, e **sempre sobrescreve** `municipality.expectedVotes` + `priority` (re-seed quando a mesa manda planilha nova); Salvador pulado; `--dry-run` + guard `ALLOW_REMOTE_DB`; parser puro em `src/lib/projectionSheetParse.ts` (Bom→`optimistic`, Regular→`central`, Mínimo→`pessimistic`). **Fase 2 (mesmo dia, decisão do produto — não é mais zero PII):** o seed também importa as colunas de estratégia, com PRIORITÁRIAS sobrepondo MAPA GERAL quando não-vazia — SITUAÇÃO→`politicalTrend` (QUEDA/MANTÉM/AUMENTO→desfavorável/neutra/favorável, com nota de proveniência), DOBRADINHAS→`stateDeputy`+`stateDeputies` (união; célula crua em `dobradinhaNotes`), LIDERANÇAS→`contact` name-only **sem telefone** + `leadership` (dedup estadual por slug do nome; `Contact.phone` virou opcional via migration `20260724_175500_contact_phone_optional`, UI continua exigindo), ASSESSOR RESPONSÁVEL→`campaignUser`+`advisors` (união; Edizio=coordinator, Solla=candidate — `candidate` agora é elegível como assessor em `eligibleCampaignStaffWhere`; novos usuários com email `<slug>@planilha.invalid` + senha aleatória, sem login até um admin trocar), ENCAMINHAMENTOS→`nextSteps`, OBSERVAÇÃO→`strengths`/`risks` (classificação curada por slug). Relações são união (nunca removem vínculos da UI); re-run idempotente; nomes de atores políticos entraram sem trilha de consentimento — regularizar com a assessoria jurídica. No mesmo dia, o grupo duplicado `municipality.voteGoals` ("Meta Bom/Regular/Mínimo") foi **removido** — collection, zod, view models, form/card e utilitário `voteGoals.ts` — via migration `20260724_133600_drop_municipality_vote_goals` (backfill metas→estimativas onde a estimativa estava vazia): `expectedVotes` (pessimista/média/otimista) é a única série por cenário do município. Plano: [`docs/plans/import-planilha-projecao.md`](docs/plans/import-planilha-projecao.md).
|
||||
|
||||
**Recently resolved (2026-07-25): Engineering Consolidation Pass 2** (tracker: [`docs/IMPROVE-CODE-QUALITY-PLAN.md`](docs/IMPROVE-CODE-QUALITY-PLAN.md) § Pass 2; architecture: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); agent map: `.cursor/rules/codebase-map.mdc`). Highlights: **W1 sistema de listas** — colunas como dado em `CampaignTable` (seams B17/B18), URL state compartilhado (`campaignListUrl` + módulos por domínio; o god module `municipalityUi.ts` virou `municipalityListUrl`/`municipalityListFilters`/`municipalityLabels`/`municipalitySignal`), shells (`CampaignListFooter`, `CampaignListEmptyState`), 8 superfícies migradas (municípios, lideranças, organizações, dobradinhas, demandas, apoiadores, assessores, planos-shells), B16+ absorvido (`useOptimistic`, hrefs por serializador canônico, facet por slugs, facets no mesmo `Promise.all`); **W2** — fronteiras: `slug`/`phone`/`wordStartFilter`/`voteEstimate` movidos para `lib/`, 21 loaders `server-only`, `campaignRoles.ts` client-safe (sidebar sem o barrel de access), `votePledgeViews` (contract) vs `votePledgeData` (loaders), ciclo supporter quebrado, `components/campaign` em 15 subpastas de domínio; decisão D1: sem `src/domains/`, sem ports-and-adapters; **W4a** — `electionInsights.ts` (941 linhas, 2 exports vivos) deletado; sobreviventes em `electionFormat.ts`/`voteTrend.ts`; E10 nasce limpo; **W4b** — knip `exports`/`types`/`enumMembers` em **ERROR** no CI (0 findings; kit ui sem peças mortas); **W4c** — tabs das páginas de detalhe extraídas (`MunicipalityDetailTabs`, `ActionPlanOverviewTab`) + factory `detailTabUi`; **W4d** — `runCampaignFormAction` fecha o C8 F4 (13 ações em 7 arquivos; exceções documentadas); **W4e** — tipos de fonte única; **W4f** — copy "Praça"→"Município" varrida (apoiadores desambiguado: cidade do contato = "Cidade"); **D3** — consent do WhatsApp por chave estável (`whatsapp-inscricao`, fail-closed). Suites: 46 unit / 48 int / 7 e2e.
|
||||
|
||||
**Recently resolved (2026-07-25):** **E16** Dossiê do município (pré-agenda, field request O6) — staff-only `dossie` tab on the municipality detail page (6th value in `municipalityDetailTabs`, same query-param pattern): `src/utilities/municipalityDossierData.ts` composes the existing loaders in one pass (TSE baseline + A11 rank via `municipalityElectionGeographyForSlug`, E8 goal account, pledges, leaderships sorted by a new `LeadershipRowViewModel.updatedAt` freshness field, updates feed, two scoped `actionPlan` reads with `overrideAccess: false`, and A8 demographics — the previously consumer-less `bahiaMunicipalityDemographics.ts` re-enters through the "Perfil" section, with an explicit whole-city caveat for Salvador zona municipalities). Section caps 8 lideranças / 5 sinais / 3 upcoming + 2 realizado plans, each with a "ver tudo" link to its tab. New staff-only `municipality.budgetNotes` textarea ("Emendas aportadas", G11 manual-first) via migration `20260725_022155_add_municipality_budget_notes` — its committed `.json` snapshot also heals the drizzle snapshot chain after the snapshot-less `campaign_goals` migration; strategy form/zod/action extended; the section renders only when filled. **Print view is CSS-only:** app chrome gets `print:hidden`, the `h-svh`/`overflow-hidden` shells get `print:h-auto print:overflow-visible` (otherwise only page 1 prints), and `styles.css` gains A4 print typography; the dossier cover is self-contained (name, TI, badges, "Gerado em") so the printed page keeps its identity. Int test mocks `loadMunicipalityElectoralBaseline` (its `unstable_cache` wrapper needs the Next runtime). Plano: [`docs/plans/dossie-municipio.md`](docs/plans/dossie-municipio.md).
|
||||
|
||||
**Recently resolved (2026-07-24):** **E8** Conta da cadeira (metas derivadas, potencial por município, cobertura) — committed TSE artifact extended to v2 (`pnpm build:election-aggregates` now writes `campoFederalVotesByYear`, `federalTallyByYear`, and 2022 `majoritarian2022` presidente/governador T1 per municipality, alongside the existing `votesByYear`/`validVotesByYear`; byte budget raised 400→700 KB in the unit test); `src/lib/campoParties.ts` (curated field-party siglas per election year, unit-tested against `electionPartySpectrum.ts`'s `esquerda` bucket — not a second party classifier); global `campaignGoals` (`src/globals/CampaignGoals.ts`, migration `20260724_180000_add_campaign_goals_global`, `stateGoal` default 150,000 + `margin`/`baseYear`/`note`, `read` = admin/staff, `update` = admin/`isCampaignUnrestricted`, **no** `afterChange` hook — `/campanha` is dynamic with auth so nothing caches it); pure derived utilities `src/utilities/municipalityPotential.ts` (projected valid votes, field ceiling, capture rate, intra-field share, 2022-only roll-off, per-scenario suggested goal + TI sanity check) and `src/utilities/goalCoverage.ts` — **locked semantics:** `meta = expectedVotes[scenario] ?? suggestedGoal[scenario]`, `comprometido = aggregate.effectiveByScenario[scenario]` (pledges only — the mesa's own expectation never substitutes for the pledge sum in the coverage denominator, unlike `resolveMunicipalityStaffVoteTotal`); orchestration in `src/utilities/municipalityGoalAccount.ts` (`cache()`-deduplicated across dashboard/list/detail). UI (Impeccable class B): "Cobertura da meta" row on the dashboard and the list overview `CampaignMetricStrip`, a new list column (renaming the old "Cobertura" column to "Assessoria" to remove ambiguity) via a shared `MunicipalityListGoalCoverageCell` (compact/default layouts), and a new "Conta da cadeira" card on the municipality detail page (fixed to the `central` scenario, replacing the "Votos estimados" block that used to live in `MunicipalityStrategyCard`). Unblocks roadmap items **E9/E10/E12/E13**. Plano: [`docs/plans/conta-da-cadeira.md`](docs/plans/conta-da-cadeira.md). **As-built correction (same day, during E9):** the suggested goal originally split the state goal proportionally to the field ceiling alone, which ignored the candidate's own vote and produced absurd targets (2,911 for Vitória da Conquista, which gave him 5,005 votes in 2022; 813 for Campo Formoso, which gave him 47). `decomposeStateGoal` was deleted in favour of `deriveSuggestedGoalsByScenario`, anchored on the municipality's own 2022 vote — pessimistic = base × (1 − `margin`), central = base, optimistic = base × (`stateGoal` ÷ Σ base), clamped at `max(central, optimistic)` — so `stateGoal` now defines the optimistic scenario and `margin` the pessimistic haircut (10% fallback). Descriptions only, **no migration**.
|
||||
|
|
@ -135,13 +137,13 @@ The internal `/campanha` area is gated by its own authentication, deliberately k
|
|||
`/campanha` is organized around `municipality` ("Município") — the campaign's PREDEFINED operational territory. Deployed to production on 2026-07-23 (destructive remodel migrations applied via the Vercel build). Master plan: `docs/plans/remodelagem-municipios.md` (supersedes `docs/plans/remodelagem-pracas.md`).
|
||||
|
||||
- **Município definition:** 435 seeded rows — one per Bahia municipality (416), except Salvador (19 zone municipalities, ZE 1–19); Camaçari is a single whole municipality (ZE 170/171 aggregated). Canonical catalog: `src/lib/municipalityCatalog.ts` (`tests/fixtures/municipality-catalog.snapshot.json`). Seeded by migration `20260723_200000_remodel_municipalities` (+ reconcile `20260723_202000_reconcile_municipality_remodel`). Geography is admin-only; staff edit strategy fields and the coordinator assigns `advisors`.
|
||||
- **Collections (admin group `Campanha`):** `municipality`, `leadership`, `votePledge`, `organization`, `stateDeputy` (dobradinhas), `campaignDemand`, `municipalityUpdate`, `campaignInvite`, `supporter`, `actionPlan`. **Global** (same admin group): `campaignGoals` (E8 "conta da cadeira" — `stateGoal` sets the optimistic suggested-goal scenario, `margin` the pessimistic haircut; `read` = admin/staff, `update` = admin/`isCampaignUnrestricted`; no `afterChange` hook — nothing caches it).
|
||||
- **Collections (admin group `Campanha`):** `municipality`, `leadership`, `votePledge`, `organization`, `stateDeputy` (dobradinhas), `campaignDemand`, `allocationDecision`, `municipalityUpdate`, `campaignInvite`, `supporter`, `actionPlan`. **Global** (same admin group): `campaignGoals` (E8 "conta da cadeira" — `stateGoal` sets the optimistic suggested-goal scenario, `margin` the pessimistic haircut; `read` = admin/staff, `update` = admin/`isCampaignUnrestricted`; no `afterChange` hook — nothing caches it).
|
||||
- **Roles:** `coordinator` ("Coordenador Geral") and `candidate` ("Candidato", unrestricted visibility via `isCampaignUnrestricted`) see/manage everything staff sees; `advisor` ("Assessor") is scoped to `municipality.advisors`; `leader` ("Liderança") is **lockdown** — home is the supporter contact tool only (`LeaderContactsPanel`, `source: lideranca`), reads only supporters they created (`createdBy`), no municipalities/leaderships/pledges/demands/plans/election data.
|
||||
- **Vote pledge asymmetry:** staff declare `declaredVotes` and record `estimatedVotes` (pessimistic/central/optimistic); leaders never see estimates. Aggregates use `estimated[S] ?? declared`, default `central` (`src/utilities/votePledgeData.ts`).
|
||||
- **Demandas:** staff-only create/read (`canCreateCampaignDemand`); optional `leadership` provenance selected by staff. Escalated decisions: coordinator **or** candidate.
|
||||
- **Dobradinhas:** `stateDeputy` entity + `municipality.stateDeputies` / `leadership.stateDeputies` hasMany; UI at `/campanha/dobradinhas`.
|
||||
- **Map:** staff dashboard (`/campanha`) loads `MunicipalityMapPanel`; `/campanha/municipios` is list/overview only.
|
||||
- **URLs:** `/campanha/municipios[/slug]`, `/campanha/dobradinhas`, `/campanha/liderancas[/id]`, `/campanha/organizacoes[/slug]`, `/campanha/demandas[/slug]`, `/campanha/planos`, `/campanha/apoiadores`.
|
||||
- **URLs:** `/campanha/municipios[/slug]`, `/campanha/dobradinhas`, `/campanha/liderancas[/id]`, `/campanha/organizacoes[/slug]`, `/campanha/demandas[/slug]`, `/campanha/planos`, `/campanha/apoiadores`, `/campanha/assessores` (coordinator/candidate), `/campanha/conceitos` (staff glossary), `/campanha/perfil`.
|
||||
|
||||
## Campaign supporters (C2)
|
||||
|
||||
|
|
@ -214,9 +216,9 @@ src/
|
|||
│ ├── (frontend)/ # Frontend routes + server actions (src/app/(frontend)/actions/*.ts)
|
||||
│ ├── (payload)/ # Payload admin routes
|
||||
│ └── (campaign)/ # Internal campaign-management area (auth barrier, see "Campaign auth")
|
||||
├── collections/ # Users, CampaignUser, CampaignInvite, Municipality, Leadership, VotePledge, MunicipalityUpdate, Organization, StateDeputy, CampaignDemand, Supporter, SupporterImportBatch, ActionPlan, ElectionTally, ElectionCandidateVote, ElectionCandidate, Media, Petition, Signature, Consent, Contact, Subscription, Post, Tag
|
||||
├── globals/ # Global configs (SiteSettings, HomePage, Metadata)
|
||||
├── components/ # Custom React components (campaign/*, ui from shadcn)
|
||||
├── collections/ # Users, CampaignUser, CampaignInvite, Municipality, Leadership, VotePledge, MunicipalityUpdate, Organization, StateDeputy, CampaignDemand, AllocationDecision, Supporter, SupporterImportBatch, ActionPlan, ElectionTally, ElectionCandidateVote, ElectionCandidate, Media, Petition, Signature, Consent, Contact, Subscription, Post, Tag
|
||||
├── globals/ # Global configs (SiteSettings, HomePage, Metadata, PrivacyPolicy, CampaignGoals)
|
||||
├── components/ # campaign/<domínio>/ (municipality, actionPlan, supporter, …, shared, shell — Pass 2 W2), ui from shadcn
|
||||
├── lib/ # cities.ts, bahiaTerritories.ts, bahiaTseZones.ts, bahiaMunicipalityCodes.ts, bahiaGeometries.ts, geometries/*.topo.json, municipalityCatalog.ts, electionAggregates/ (committed TSE artifact) + bahiaElectionAggregates.ts, formData.ts, zod schemas (lib/schemas/*)
|
||||
├── utilities/ # campaign*, access/* (per-domain RBAC; campaignAccess.ts re-exports), municipality*, posts, documents, globals, locks, etc.
|
||||
└── payload.config.ts # Main config
|
||||
|
|
|
|||
|
|
@ -256,6 +256,10 @@ Controls feel **confident and compact**: short primary buttons, dense toolbars,
|
|||
|
||||
Operational meaning for leadership/support/estimates. Always use `Badge` variants tied to CSS tokens — never hardcode greens/ambers that drift from `--support-*` / `--estimate-*`.
|
||||
|
||||
### Campaign list system (Pass 2 W1 — canonical, do not fork)
|
||||
|
||||
Every `/campanha` list surface composes the shared pieces in `src/components/campaign/shared/`: `CampaignTable` (columns as data over house `ui/Table`; `rounded-xl border` container), `CampaignSearchForm` / `CampaignFilterChips` as the toolbar, `CampaignListFooter` (count + pagination), `CampaignListEmptyState` (`Empty` with icon + CTA, `min-h-72 border`), all inside `CampaignListPendingBoundary` so navigation dims the RESULTS region ("Feel the action"). Rich headers (sort + filter popovers) are the municipality islands (`MunicipalitySortableHead`/`MunicipalityHeaderFilter`) — the reference implementation, not yet generalized. New list = new column definitions + these shells; a bespoke table needs a documented exception (precedents: planos cards, `TerritoryOverviewTable`).
|
||||
|
||||
## 6. Do's and Don'ts
|
||||
|
||||
### Do:
|
||||
|
|
|
|||
|
|
@ -68,18 +68,18 @@ Statuses: pending · in-progress · awaiting-evidence · done · deferred: \<rea
|
|||
|
||||
| WS | Content | Plan file | Status | Date |
|
||||
| --- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------- | ---- |
|
||||
| W0 | Safety net: pin list URL parsers + list loaders (unit + int smokes); TESTING/TECH-DEBT truth-up | [pinagem-superficies-lista.md](plans/pinagem-superficies-lista.md) | pending | |
|
||||
| W1 | Generalized campaign list system (D1 core + municipality; D2 migrate entity triplet/demandas/apoiadores/assessores + delete superseded) | [sistema-listas-campanha.md](plans/sistema-listas-campanha.md) | pending | |
|
||||
| W2 | Architecture decision (DDD/clean-arch/SDP evaluation → conventions + boundary fixes) + staged `components/campaign` subfolders | [decisao-arquitetura-dominios.md](plans/decisao-arquitetura-dominios.md) | pending | |
|
||||
| W3 | `docs/ARCHITECTURE.md` + `.cursor/rules/codebase-map.mdc` | [mapa-mental-arquitetura.md](plans/mapa-mental-arquitetura.md) | pending | |
|
||||
| W4a | `electionInsights.ts` gut/split: extract `electionFormat`, keep voteTrend, delete dead clusters (before E10) | [split-election-insights.md](plans/split-election-insights.md) | pending | |
|
||||
| W4b | knip unused-exports warn→error: delete zero-ref exports, un-export in-file symbols, config, CI flip | (tracker only) | pending | |
|
||||
| W4c | Detail-page RSC extraction (`municipios/[slug]`, `planos/[slug]`) + dedupe detail-tab helper twins | (tracker only) | pending | |
|
||||
| W4d | formActions finish: `runCampaignFormAction` (stay-on-page) + migrate 9 hand-rolled ladders (closes C8 F4) | (tracker only) | pending | |
|
||||
| W4e | Single-source types: scenario triple, strategy VM, trend enum, duplicated constants/helpers, `DynamicFind` doc | (tracker only) | pending | |
|
||||
| W4f | "Praça"→"Município" user-visible copy sweep (~55 visible hits + admin labels; consent texts stay for the legal batch) | (tracker only) | pending | |
|
||||
| W5 | Documentation close-out: AGENTS.md refresh, ledger/TESTING reconcile, plan-status patches, roadmap fold-in, product-doc drift findings | (tracker only) | pending | |
|
||||
| D3 | `consent: 2` hardcode → Onda 0 provisional-key pattern (data migration + fail-closed switch; **migration SQL reviewed before landing**) | (tracker only) | pending | |
|
||||
| W0 | Safety net: pin list URL parsers + list loaders (unit + int smokes); TESTING/TECH-DEBT truth-up | [pinagem-superficies-lista.md](plans/pinagem-superficies-lista.md) | done | 2026-07-25 |
|
||||
| W1 | Generalized campaign list system (D1 core + municipality; D2 migrate entity triplet/demandas/apoiadores/assessores + delete superseded) | [sistema-listas-campanha.md](plans/sistema-listas-campanha.md) | done | 2026-07-25 |
|
||||
| W2 | Architecture decision (DDD/clean-arch/SDP evaluation → conventions + boundary fixes) + staged `components/campaign` subfolders | [decisao-arquitetura-dominios.md](plans/decisao-arquitetura-dominios.md) | done | 2026-07-25 |
|
||||
| W3 | `docs/ARCHITECTURE.md` + `.cursor/rules/codebase-map.mdc` | [mapa-mental-arquitetura.md](plans/mapa-mental-arquitetura.md) | done | 2026-07-25 |
|
||||
| W4a | `electionInsights.ts` gut/split: extract `electionFormat`, keep voteTrend, delete dead clusters (before E10) | [split-election-insights.md](plans/split-election-insights.md) | done | 2026-07-25 |
|
||||
| W4b | knip unused-exports warn→error: delete zero-ref exports, un-export in-file symbols, config, CI flip | (tracker only) | done | 2026-07-25 |
|
||||
| W4c | Detail-page RSC extraction (`municipios/[slug]`, `planos/[slug]`) + dedupe detail-tab helper twins | (tracker only) | done | 2026-07-25 |
|
||||
| W4d | formActions finish: `runCampaignFormAction` (stay-on-page) + migrate 9 hand-rolled ladders (closes C8 F4) | (tracker only) | done | 2026-07-25 |
|
||||
| W4e | Single-source types: scenario triple, strategy VM, trend enum, duplicated constants/helpers, `DynamicFind` doc | (tracker only) | done | 2026-07-25 |
|
||||
| W4f | "Praça"→"Município" user-visible copy sweep (~55 visible hits + admin labels; consent texts stay for the legal batch) | (tracker only) | done | 2026-07-25 |
|
||||
| W5 | Documentation close-out: AGENTS.md refresh, ledger/TESTING reconcile, plan-status patches, roadmap fold-in, product-doc drift findings | (tracker only) | done | 2026-07-25 |
|
||||
| D3 | `consent: 2` hardcode → Onda 0 provisional-key pattern (data migration + fail-closed switch; **migration SQL reviewed before landing**) | (tracker only) | done | 2026-07-25 |
|
||||
|
||||
Statuses: pending · in-progress · awaiting-evidence · done · deferred: \<reason\> · skipped: \<reason\>
|
||||
|
||||
|
|
@ -96,8 +96,15 @@ Statuses: pending · in-progress · awaiting-evidence · done · deferred: \<rea
|
|||
|
||||
## 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
|
||||
- [x] Materialize Pass 2 tracker section + per-workstream plans
|
||||
- [x] W0 → W1-D1 → W1-D2, W4a/W4e/W4f interleaved (all 2026-07-25)
|
||||
- [x] W2 → W4c/W4d → W3
|
||||
- [x] W4b (last code delivery) → W5 (docs close-out)
|
||||
- [x] D3 isolated delivery — **migration `20260725_170000_whatsapp_subscription_consent_key` must be SQL-reviewed by the user before pushing/deploying** (applies to prod on the Vercel build)
|
||||
- [ ] Post-pass follow-ups live in [TECH-DEBT.md](TECH-DEBT.md) (utilities/ subfolder trigger, `MunicipalityHeaderFilter` generalization trigger, e2e depth, users-roles migration) and the roadmap fill-ins
|
||||
|
||||
## Product-doc drift findings (reported, deliberately NOT applied — product owner's call)
|
||||
|
||||
- **PRODUCT.md** still describes surfaces with nuclei-era wording in places (e.g. references to the pre-remodel operational unit); the design principles and job statement remain accurate.
|
||||
- **DESIGN.md** component inventory misses the canonical shared components consolidated in Pass 2 (`CampaignTable` column system, `CampaignListFooter`, `CampaignListEmptyState`, `CampaignListPendingBoundary` family) — worth a section when DESIGN.md is next revised.
|
||||
- **docs/CUSTOMER.md** maps field request O6 to E13, but the shipped answer was **E16** (dossiê) — the mapping note should say "atendido por E16; E13 segue como evolução".
|
||||
|
|
|
|||
|
|
@ -28,16 +28,16 @@ Ledger of known engineering debt. Items large enough to be scheduled work become
|
|||
| Smell | Location | Refactoring | Status |
|
||||
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| God module: RBAC for every collection in one file (964 lines, ~80 exports) | `src/utilities/campaignAccess.ts` | split per domain under `src/utilities/access/` | done 2026-07-23 (14 domain modules; `campaignAccess.ts` kept as re-export surface) |
|
||||
| God module: ~71 exports of electoral math | `src/lib/electionInsights.ts` | split by concern when next touched | ledgered |
|
||||
| God module: ~71 exports of electoral math | `src/lib/electionInsights.ts` | split by concern when next touched | done 2026-07-25 (Pass 2 W4a): module DELETED; survivors in `electionFormat.ts` + `voteTrend.ts` |
|
||||
| Multi-feature action bag: CRUD + CSV import pipeline + LGPD in one file (722 lines) | `src/app/(campaign)/campanha/actions/supporter.ts` | split CRUD vs import | done 2026-07-23 (`supporterImport.ts`) |
|
||||
| Clone twins: create/update/staff-assert/unique-conflict skeleton | `actions/organization.ts` ↔ `actions/stateDeputy.ts` | extract shared factory | done 2026-07-23 (`runStaffEntityMutation` in `campaignEntityActions.ts`) |
|
||||
| 14 near-identical `formActions.ts` try/catch→mapError→redirect/return ladders | `src/app/(campaign)/campanha/**/formActions.ts` | shared form-action wrapper | partially done — C8 F4 (2026-07-21/23) unified the municipality staff wrappers + 4 `*/nova` ladders on `runCampaignRedirectFormAction`; **10 ladders written since remain hand-rolled** (844 lines: assessores, planos, apoiadores ×2, demandas/[slug], liderancas/[id], organizacoes/[slug], dobradinhas/[slug], municipios editar, convite) — scheduled Pass 2 W4d (status conflict with [escala-dry-pos-c6.md](plans/escala-dry-pos-c6.md) "F4 entregue" resolved 2026-07-25: F4 was complete for the surfaces that existed then) |
|
||||
| 14 near-identical `formActions.ts` try/catch→mapError→redirect/return ladders | `src/app/(campaign)/campanha/**/formActions.ts` | shared form-action wrapper | partially done — C8 F4 (2026-07-21/23) unified the municipality staff wrappers + 4 `*/nova` ladders on `runCampaignRedirectFormAction`; **10 ladders written since remain hand-rolled** (844 lines: assessores, planos, apoiadores ×2, demandas/[slug], liderancas/[id], organizacoes/[slug], dobradinhas/[slug], municipios editar, convite) — done 2026-07-25 (Pass 2 W4d): `runCampaignFormAction` + 13 ações migradas; exceções documentadas em comentário (planos, apoiadores/[id], convite login) |
|
||||
| Flat 80+-file `src/utilities/` junk drawer; `lib/` vs `utilities/` boundary undocumented | `src/utilities/`, `src/lib/` | document convention + move misfiled modules | done 2026-07-23 (convention in `.cursor/rules/engineering-standards.mdc`; `RefreshRouteOnSave` moved to `components/`) |
|
||||
| Praça→Município rename leftovers in identifiers (`PLAZA_MAP_YEARS`, `ACCESSIBLE_PLAZA_IDS_CONTEXT_KEY`, …) | map/list utilities | finish rename | done 2026-07-23 |
|
||||
| Props→state `useEffect` mirrors | `MunicipalityListExpectedVotesControl`, comboboxes | key-remount or fully controlled | closed 2026-07-23 (accepted as correct — see Debt Ledger row) |
|
||||
| Detail-page RSCs mixing loading + tab UI + forms (300+ lines) | `municipios/[slug]/page.tsx`, `planos/[slug]/page.tsx` | extract tab loaders/sections | scheduled Pass 2 W4c (tabs stream via Suspense since Phase 3; extraction still open) |
|
||||
| God module: list URL + filters + sort + labels + freshness in one file (765 lines, 59 exports) | `src/utilities/municipalityUi.ts` | split along url/filters/sort/labels/freshness seams | scheduled Pass 2 W1-D1 |
|
||||
| `components/campaign/` flat with 106 files; `utilities/` flat with 92 | `src/components/campaign/`, `src/utilities/` | within-layer domain subfolders | components scheduled Pass 2 W2 (staged per-domain `git mv`); `utilities/` deferred with trigger: a domain gaining its 3rd new module in a month gets its subfolder |
|
||||
| Detail-page RSCs mixing loading + tab UI + forms (300+ lines) | `municipios/[slug]/page.tsx`, `planos/[slug]/page.tsx` | extract tab loaders/sections | done 2026-07-25 (Pass 2 W4c): tabs em `MunicipalityDetailTabs.tsx` / `ActionPlanOverviewTab.tsx`; gêmeos de tab dedupados em `detailTabUi.ts` |
|
||||
| God module: list URL + filters + sort + labels + freshness in one file (765 lines, 59 exports) | `src/utilities/municipalityUi.ts` | split along url/filters/sort/labels/freshness seams | done 2026-07-25 (Pass 2 W1-D1): `municipalityListUrl` / `municipalityListFilters` / `municipalityLabels` / `municipalitySignal` |
|
||||
| `components/campaign/` flat with 106 files; `utilities/` flat with 92 | `src/components/campaign/`, `src/utilities/` | within-layer domain subfolders | components done 2026-07-25 (Pass 2 W2, 15 domínios); `utilities/` deferred with trigger: a domain gaining its 3rd new module in a month gets its subfolder |
|
||||
|
||||
## Sprout / Wrap Register
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
Three layers, all local-database-only (see `local-database` skill; tests refuse any database not ending in `_test`):
|
||||
|
||||
- **Unit** (`tests/unit/**/*.unit.spec.ts`, 45 specs as of 2026-07-25): pure logic; runs with a deliberately invalid `DATABASE_URL` so nothing can touch a database.
|
||||
- **Integration** (`tests/int/**/*.int.spec.ts`, 47 specs): real Payload + Postgres (`teqo_test`); campaign fixtures allocate seeded municipalities via a Postgres sequence so parallel spec files never share one, and purge residue on claim. Shared consent rows with stable keys are leased (`tests/helpers/testDatabaseLease.ts`), never owned.
|
||||
- **Unit** (`tests/unit/**/*.unit.spec.ts`, 46 specs as of 2026-07-25 (Pass 2)): pure logic; runs with a deliberately invalid `DATABASE_URL` so nothing can touch a database.
|
||||
- **Integration** (`tests/int/**/*.int.spec.ts`, 48 specs): real Payload + Postgres (`teqo_test`); campaign fixtures allocate seeded municipalities via a Postgres sequence so parallel spec files never share one, and purge residue on claim. Shared consent rows with stable keys are leased (`tests/helpers/testDatabaseLease.ts`), never owned.
|
||||
- **E2E** (`tests/e2e`, Playwright, 7 specs): smoke-level; most campaign behavior is pinned at the int layer.
|
||||
|
||||
CI (`.github/workflows/ci.yml`) runs lint (zero warnings), typecheck, knip, unit, migrate, int against a Postgres 17 service.
|
||||
|
|
@ -24,6 +24,9 @@ Modules the 2026-07 hardening phases touch, and where their behavior is pinned:
|
|||
| Leader supporter create | linked-municipality scope; out-of-scope rejection; staff rejection | `collectionAccessLockdown.int.spec.ts` | |
|
||||
| Posts visibility/caching | `isPostVisible` fail-closed; cached listing | (indirect via routes) | no dedicated `posts` spec — backlog |
|
||||
| List URL contracts (Pass 2 W0, pinned 2026-07-25) | `campaignListUrl` canonicalize/clamp/redirect; supporter + actionPlan parse/serialize/href; 5 entity parsers | `campaignListUrl`, `supporterUi`, `actionPlanUi`, `campaignEntityListParsers` (unit) | municipality list URL pinned separately in `municipalityUi.unit.spec.ts` |
|
||||
| Form-action ladders (Pass 2 W4d) | `runCampaignFormAction`: success spread, boundary/zod/safe/generic mapping, values+revision echo | `campaignFormActions.unit.spec.ts` | per-route ladders are thin shells; behavior pinned at the wrapper |
|
||||
| WhatsApp subscription consent (Pass 2 D3) | fail-closed without `whatsapp-inscricao`; happy path links subscription → keyed consent | `submitWhatsapp.int.spec.ts` | |
|
||||
| Vote trend + election number format (Pass 2 W4a survivors) | `computeVoteTrend` pair priority/±10% band; `formatElectionNumber` E18 fraction defect | `voteTrend.unit.spec.ts` | |
|
||||
| Entity list loaders (Pass 2 W0) | scope (leader empty/denied), marker-narrowed q, kind/status filters, pagination shape | `campaignEntityListData.int.spec.ts` | advisor loader runs post-gate with `overrideAccess: true` (gate asserted upstream) |
|
||||
|
||||
## Characterization Backlog
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Seletor de Cenário junto aos filtros de Municípios
|
||||
|
||||
Status: rascunho
|
||||
Status: rascunho — **nota Pass 2 W1 (2026-07-25):** o cenário continua client-side e NUNCA entra na serialização de URL da lista; mover para a URL é exatamente o escopo deste fill-in
|
||||
Atualizado em: 2026-07-24
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Fill-ins)
|
||||
Impeccable: B — encaixe em `MunicipalityFilters` + `MunicipalityListOverview` (`/campanha/municipios`); sem rota nova
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 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
|
||||
Status: **entregue** (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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Escala e DRY pós-B16 (filtros no header da lista)
|
||||
|
||||
Status: rascunho
|
||||
Status: **absorvido pelo Pass 2 W1-D1** (2026-07-25) — Fase 1 (`useOptimistic` no `MunicipalityHeaderFilter`, `onNavigate` dentro da transition), Fase 2 (facet de município como slugs rotulados no cliente), Fase 3 (hrefs de opção via serializador canônico confiável, 1 serialização por opção com pin de byte-igualdade) e Fase 4 (facets no mesmo `Promise.all`, catálogo DRY, `clearMunicipalityListFilters`) entregues em [sistema-listas-campanha.md](sistema-listas-campanha.md); selects mobile otimistas continuam adiados com gatilho
|
||||
Atualizado em: 2026-07-25
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (B16+, fill-in de engenharia)
|
||||
Impeccable: A — N/A (sem superfície UI nova; as fases preservam o comportamento visível de B16)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Escala e DRY pós-C6 (apoiadores / import / listas)
|
||||
|
||||
Status: F4 (DRY de forms) **entregue** — twin praça 2026-07-21 + hardening 2026-07-23; F1–F2 abertos com gatilho (volume real de import/base nominal pós-Onda 0); F3 parcialmente absorvido
|
||||
Status: F4 (DRY de forms) **entregue** — twin praça 2026-07-21 + hardening 2026-07-23; **o resto do F4 (10 escadas hand-rolled escritas depois) fechou em 2026-07-25 no Pass 2 W4d** (`runCampaignFormAction`, exceções documentadas em comentário); **F3 (parsers das listas de entidade) fechou no Pass 2 W1-D2**; F1–F2 abertos com gatilho (volume real de import/base nominal pós-Onda 0)
|
||||
Atualizado em: 2026-07-24 (refs sincronizadas pós-remodelagem Municípios + hardening)
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Fill-ins abertos, item C8)
|
||||
Responsável: —
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# B18 — Filtros salvos na lista de Municípios (+ acesso rápido no sidebar)
|
||||
|
||||
Status: rascunho
|
||||
Status: rascunho — **costuras prontas desde o Pass 2 W1 (2026-07-25):** o estado de URL é canônico e serializável (`municipalityListUrl.ts`, contrato congelado); salvar filtro = nomear a serialização atual
|
||||
Atualizado em: 2026-07-24
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Demais itens abertos, B18; superfície de coordenação)
|
||||
Impeccable: B — encaixe em `MunicipalityFilters` / barra slim + `CampaignSidebar` (2º nível sob Municípios); sem rota nova
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Mapa mental da arquitetura (Pass 2 — W3)
|
||||
|
||||
Status: aprovado (Pass 2, 2026-07-25)
|
||||
Status: **entregue** (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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Mapa de projeção de votos por município (equiparar e superar as planilhas 2026)
|
||||
|
||||
Status: E1, E2 e E3 implementados e mesclados em `main` (2026-07-19); **E4 cortado** (2026-07-19) e **reaberto/entregue como E4R em 2026-07-24** ([import-planilha-projecao.md](import-planilha-projecao.md) — `pnpm db:seed:projecao`); E5 pendente
|
||||
Status: E1, E2 e E3 implementados e mesclados em `main` (2026-07-19); **E4 cortado** (2026-07-19) e **reaberto/entregue como E4R em 2026-07-24** — **atenção (nota 2026-07-25):** o E4R fase 2 importa TAMBÉM as colunas de estratégia (SITUAÇÃO/DOBRADINHAS/LIDERANÇAS/ASSESSOR/…), superando o que este plano descrevia como import zero-PII ([import-planilha-projecao.md](import-planilha-projecao.md) — `pnpm db:seed:projecao`); E5 pendente
|
||||
Atualizado em: 2026-07-24 (E4R entregue — seed overwrite-always)
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha E — itens E1–E5, "Ciclo 3")
|
||||
Responsável: —
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Pinagem das superfícies de lista (Pass 2 — W0)
|
||||
|
||||
Status: aprovado (Pass 2, 2026-07-25)
|
||||
Status: **entregue** (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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# B17 — Seletor de colunas na lista de Municípios
|
||||
|
||||
Status: rascunho
|
||||
Status: rascunho — **costuras prontas desde o Pass 2 W1 (2026-07-25):** as colunas da lista são dado (`CampaignTable`, `CampaignTableColumn.id`/`mandatory`/`defaultVisible`); o seletor vira um toggle de visibilidade sobre esses ids + `localStorage`
|
||||
Atualizado em: 2026-07-24
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Demais itens abertos, B17; superfície de coordenação)
|
||||
Impeccable: B — encaixe em `MunicipalityList` / barra slim de `/campanha/municipios`; sem rota nova
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Sistema de listas da campanha (Pass 2 — W1)
|
||||
|
||||
Status: aprovado (Pass 2, 2026-07-25)
|
||||
Status: **entregue** (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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Split de electionInsights.ts (Pass 2 — W4a)
|
||||
|
||||
Status: aprovado (Pass 2, 2026-07-25) — decisão D2 assinada: DELETAR os clusters mortos
|
||||
Status: **entregue** (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)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Roadmap — Teqo
|
||||
|
||||
Atualizado em: 2026-07-25 (**B16+** débitos de escala/DRY pós-simplify do B16 registrados como fill-in; resiliência do build ao global `metadata` ausente no site público; **B16 ✓** filtros no header da lista; **E16 ✓** dossiê do município — aba + print + `budgetNotes` G11; **E9 ✓** fila de alocação — desbloqueia E11, e revisou a fórmula da meta sugerida do E8; **E18 ✓** documentação de conceitos (`/campanha/conceitos`); **B19 ✓** gerenciar assessores; **E8 ✓** conta da cadeira — desbloqueou E9/E10/E12/E13; C12 ✓; C11 absorve escala plano↔demandas; B18; B17; reorder DnD fora de escopo; A11 ✓; B15 ✓; E4R ✓; E17 ✓; B14; janela 1: smoke, R6)
|
||||
Atualizado em: 2026-07-25 (**Pass 2 ✓** consolidação de engenharia — listas genéricas/B16+ absorvido/B17-B18 triviais/E10 limpo/C8 F4 ✓/D3 consent WhatsApp; **B16+** débitos de escala/DRY pós-simplify do B16 registrados como fill-in; resiliência do build ao global `metadata` ausente no site público; **B16 ✓** filtros no header da lista; **E16 ✓** dossiê do município — aba + print + `budgetNotes` G11; **E9 ✓** fila de alocação — desbloqueia E11, e revisou a fórmula da meta sugerida do E8; **E18 ✓** documentação de conceitos (`/campanha/conceitos`); **B19 ✓** gerenciar assessores; **E8 ✓** conta da cadeira — desbloqueou E9/E10/E12/E13; C12 ✓; C11 absorve escala plano↔demandas; B18; B17; reorder DnD fora de escopo; A11 ✓; B15 ✓; E4R ✓; E17 ✓; B14; janela 1: smoke, R6)
|
||||
Registro canônico dos **próximos** planos e débitos. Histórico de entregas: resumo abaixo + planos em [`docs/plans/`](plans/) + notebook [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc).
|
||||
|
||||
## Âncoras do calendário eleitoral 2026 (Res. TSE 23.760/2026)
|
||||
|
|
@ -44,6 +44,7 @@ Textos provisórios de Consent + `/privacidade` auto-provisionados ([onda-0.md](
|
|||
- **A10 (2026-07-23)** — cenários pessimista/média/otimista em `votePledge.estimatedVotes` e `municipality.expectedVotes`; agregação por cenário (default média); seletor no mapa/overview; liderança segue com um `declaredVotes` ([plano](plans/cenarios-estimativa-votos.md)). Desbloqueou **E8**.
|
||||
- **Admin Payload (2026-07-23)** — export CSV de assinaturas e contatos ([plano](plans/exportar-csv-assinaturas.md)).
|
||||
- **Hardening de engenharia (2026-07-23, Fases 0–6)** — access lockdown (PII/CMS admin-only + gates de rota do leader), tooling gate (knip no CI, `--max-warnings=0`, ban `as never`), artefato TSE commitado + cache (`bahiaElectionAggregates`, `election-tse`, `loadMunicipalityScope`), pending honesto + Suspense streaming, state scoping, splits (`src/utilities/access/*`, `supporterImport.ts`, shells DRY). Tracker: [IMPROVE-CODE-QUALITY-PLAN.md](IMPROVE-CODE-QUALITY-PLAN.md) · ledger: [TECH-DEBT.md](TECH-DEBT.md) · mapa de testes: [TESTING.md](TESTING.md).
|
||||
- **Consolidação de engenharia Pass 2 (2026-07-25, W0–W5 + D3)** — sistema de listas da campanha (colunas como dado em `CampaignTable`, URL state compartilhado, 8 superfícies migradas, B16+ absorvido; **B17/B18 ficaram triviais** — só falta a UI de toggle/salvar), split do `municipalityUi`, fronteiras lib/utilities zeradas + `server-only` em 21 loaders + `components/campaign` em 15 domínios (decisão: sem `src/domains/`), `electionInsights` deletado (**E10 nasce limpo**), knip `exports` em ERROR no CI (0 mortos), tabs de detalhe extraídas, `runCampaignFormAction` fecha C8 F4, copy Praça→Município varrida, consent do WhatsApp por chave estável (D3). Tracker: [IMPROVE-CODE-QUALITY-PLAN.md](IMPROVE-CODE-QUALITY-PLAN.md) § Pass 2 · arquitetura: [ARCHITECTURE.md](ARCHITECTURE.md).
|
||||
- **E4R (2026-07-24)** — import único da planilha de projeção → `municipality.expectedVotes` (Bom→otimista, Regular→média, Mínimo→pessimista) + `priority` (`pnpm db:seed:projecao`, always-overwrite, dry-run + runbook; Salvador pulado; zero PII). No mesmo dia o grupo duplicado `voteGoals` ("Meta Bom/Regular/Mínimo") foi **removido do app** (migration `20260724_133600` com backfill metas→estimativas) — a única série por cenário é `expectedVotes`. Plano: [import-planilha-projecao.md](plans/import-planilha-projecao.md). Seed local verificado (189 estimativas / 50 alta); produção após smoke.
|
||||
- **E17 (2026-07-24)** — tabela comparativa dos 27 Territórios de Identidade no Início staff (`/campanha`): `territoryOverview.ts` (rollup puro client-safe: `computeTerritoryRollup` + `sortTerritoryRows`) + `loadTerritoryOverview.ts` (loader server-only, `overrideAccess: true`) + `TerritoryOverviewTable.tsx` (tabela densa, ordenação client-side default `% da própria votação desc`, Metropolitano decomposto em Salvador 19 zonas × Demais RMS, linha→`/campanha/municipios?region=<TI>`). Somas/razões apenas (salvaguarda MAUP); sem migration/collection. Primeira fatia de E12. Plano: [tabela-ti-inicio.md](plans/tabela-ti-inicio.md).
|
||||
- **C12 (2026-07-24)** — registro-fundação: versions nativas em `votePledge` com baseline; sinais tipados em `municipalityUpdate`; origem do `actionPlan`; demandas vinculadas com custo derivado e criação múltipla atômica; `allocationDecision` ex-ante imutável. Migration `20260724_180000_add_campaign_foundation_records`; testes int + E2E. Plano: [registro-fundacao.md](plans/registro-fundacao.md).
|
||||
|
|
@ -88,8 +89,8 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
|
|||
- **B14** município mais próximo (geolocalização → atalho no Início staff) · pede permissão **1× por sessão** se ainda não concedida; matching client-side sobre centroides IBGE; Salvador multi-zona → lista filtrada até B8 F2 · ~1d · Janela 1–2 · sem deps duras · cortável · [plano](plans/municipio-mais-proximo.md)
|
||||
- ~~**B15** ordenar lista de municípios pelo header da coluna~~ — **entregue 2026-07-24** (`?sort=`/`?dir=` na URL + clique no header (desktop) / select compacto (mobile); ordenação global no filtrado; **A11** consome o contrato (key `votos`)) · [plano](plans/ordenacao-colunas-lista-municipios.md)
|
||||
- ~~**B16** filtros no header das colunas da lista de municípios~~ — **entregue 2026-07-24** (funil+Popover no `TableHead` ao lado do sort B15; barra slim busca+resumo+Limpar; mobile selects empilhados; optimistic + busca no Popover de TI) · [plano](plans/filtros-no-header-lista-municipios.md)
|
||||
- **B17** seletor de colunas (mostrar/ocultar) na lista de municípios — Popover “Colunas” + `localStorage`; desktop only; `name` obrigatória; soft: B15 ✓ (ids) / B16 (barra slim); prepara viewport para **E9** · ~0,5–1d · Janela 1–2 · cortável · [plano](plans/seletor-colunas-lista-municipios.md)
|
||||
- **B18** filtros salvos na lista de municípios — nomear o estado URL atual (`localStorage`); acesso rápido de 2º nível sob Municípios no sidebar (hover desktop / expand mobile; sticky só no filtro salvo ativo) · ~1–1,5d · Janela 1–2 · soft: B16/B17 (barra) · cortável · [plano](plans/filtros-salvos-municipios.md)
|
||||
- **B17** seletor de colunas (mostrar/ocultar) na lista de municípios — Popover “Colunas” + `localStorage`; desktop only; `name` obrigatória; **Pass 2 W1 deixou trivial:** colunas já são dado (`CampaignTableColumn.id`/`mandatory`/`defaultVisible`); soft: B15 ✓ (ids) / B16 ✓ (barra slim); prepara viewport para **E9** · ~0,5–1d · Janela 1–2 · cortável · [plano](plans/seletor-colunas-lista-municipios.md)
|
||||
- **B18** filtros salvos na lista de municípios — nomear o estado URL atual (`localStorage`); **Pass 2 W1 deixou trivial:** serialização canônica pronta e congelada em `municipalityListUrl.ts`; acesso rápido de 2º nível sob Municípios no sidebar (hover desktop / expand mobile; sticky só no filtro salvo ativo) · ~1–1,5d · Janela 1–2 · soft: B16/B17 (barra) · cortável · [plano](plans/filtros-salvos-municipios.md)
|
||||
- ~~**B19** gerenciar assessores — `/campanha/assessores` (lista + novo + detalhe): criar/editar contas `advisor`, ver/atribuir municípios, reenviar link de senha; **só Coordenador Geral e Candidato** (`isCampaignUnrestricted`); alinha access de update/phone do `candidate`~~ — **entregue 2026-07-24** (também alinha atribuição `municipality.advisors` a unrestricted; e-mail na lista via leitura privilegiada; auto-save da carteira) · [plano](plans/gerenciar-assessores.md)
|
||||
- ~~**E18** documentação de conceitos de inteligência de campanha~~ — **entregue 2026-07-24** (`/campanha/conceitos` staff-only; 7 conceitos de E8 em `src/lib/campaignIntelligenceConcepts.ts` agrupados em base/diagnóstico/meta; "Saiba mais" por métrica nos tooltips + link de teclado no Popover do card; `formatElectionNumber` passou a arredondar votos) · cada fatia futura (E9/E10/B13/E11/E12/E13/E14) acrescenta sua seção ao array como parte da própria entrega · [plano](plans/documentacao-conceitos-campanha.md)
|
||||
|
||||
|
|
@ -97,11 +98,11 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
|
|||
|
||||
- **Ícone de prioridade na lista** em `/campanha/municipios` — trocar Badge “Prioritária” por ícone Flag + tooltip “Município prioritário” (hover) na coluna do nome · ~0,25d · Impeccable B · [plano](plans/icone-prioridade-lista-municipios.md)
|
||||
- **Cenário junto aos filtros** em `/campanha/municipios` — mover `VoteEstimateScenarioField` para a fileira/barra slim de `MunicipalityFilters` (com **B16**, a barra deixa de carregar os selects de coluna); overview só consome o contexto · ~0,25–0,5d · Impeccable B · soft: B16 · [plano](plans/cenario-junto-filtros-municipios.md)
|
||||
- **B16+** escala/DRY pós-B16 — `useOptimistic` no Popover do header (hoje há um par `{baseKey,next}` manual com janela de reversão em cliques encadeados), payload dos rótulos de município (~20 KB por navegação) e hrefs do Popover a partir de uma base `URLSearchParams` (435 opções × 2 serializações hoje) · ~0,5–1d · sem migration · soft: antes de **B17**/**B18** reusarem a barra · [plano](plans/escala-dry-pos-b16.md)
|
||||
- ~~**B16+** escala/DRY pós-B16~~ — **absorvido pelo Pass 2 W1-D1 (2026-07-25)**: `useOptimistic`, facet por slugs, hrefs via serializador canônico, facets no mesmo `Promise.all` · [plano](plans/escala-dry-pos-b16.md)
|
||||
- **O0+** escala/DRY pós-Onda 0 · [plano](plans/escala-dry-pos-onda0.md)
|
||||
- **RS+** auth read leve + shells de senha · [plano](plans/escala-dry-pos-reset-senha-perfil.md)
|
||||
- **C10 / C11** escala apoiadores/planos · C11 absorveu a extensão pós-C12 para picker de planos e paginação das demandas vinculadas (Fase 6 condicional; ≥100 planos ou ≥50 demandas/plano) · gatilhos: base nominal crescendo ([plano](plans/escala-dry-pos-c9.md)) / volume de planos medido ([plano](plans/escala-dry-pos-c7.md))
|
||||
- **C8 F1–F3 restantes** perf de import/lista + DRY de helpers (F4 DRY de forms entregue; parte de F2/F3 absorvida pelo split de access) · gatilho: import em volume real pós-Onda 0 · [plano](plans/escala-dry-pos-c6.md)
|
||||
- **C8 F1–F2 restantes** perf de import/lista (F4 DRY de forms fechou de vez no Pass 2 W4d; F3 parsers fechou no Pass 2 W1-D2) · gatilho: import em volume real pós-Onda 0 · [plano](plans/escala-dry-pos-c6.md)
|
||||
- Higiene PascalCase de componentes legados
|
||||
|
||||
### A validar (assumptions)
|
||||
|
|
|
|||
Loading…
Reference in a new issue