- submitWhatsapp resolve Consent.key = 'whatsapp-inscricao' via requireConsentByKey e FALHA FECHADO com mensagem clara enquanto o documento nao existir - migration 20260725_170000 (data-only, idempotente, guardada): tagueia o documento vivo id 2 (key vazia/'') com a chave estavel; no-op se ja houver documento com a chave ou se id 2 tiver outra chave; NAO inventa texto juridico (politica Onda 0) - chave em lib/campaignConsentKeys + lease key nas fixtures; int spec pina fail-closed + caminho feliz (contact + subscription -> consent.id) - ATENCAO DEPLOY: revisar o SQL antes do push (aplica em producao no build da Vercel)
19 KiB
19 KiB
Tech Debt
Ledger of known engineering debt. Items large enough to be scheduled work become entries in roadmap.md (linked in the Ref column); this file tracks the full inventory so nothing is lost. Sourced initially from the 2026-07-23 engineering-standards audits.
Debt Ledger
| Item | Location | Type | Risk | Effort | Priority | Status | Ref |
|---|---|---|---|---|---|---|---|
| 164 unused exports + 60 unused types reported by knip (warn-level; 158 at Pass 1) | god modules, ui kit families | dead code | low | M | P2 | closed 2026-07-25 (Pass 2 W4b): 0 findings; exports/types/enumMembers at error in CI; tests entered the knip graph; duplicates off (only hits were the 6 deliberate access aliases canUpdateX = canReadX) |
Pass 2 W4b |
knip loader noise: Error loading src/payload.config.ts (server-only guard) |
knip run output | tooling noise | none (exit 0, static graph resolves) | S | P3 | open | |
| Missing-consent window fixture releases its exclusive lease during the observation window — readers can interleave (rare flake). Full fix: session-level advisory lock spanning delete→op→restore + updated fault-path pins | tests/helpers/testDatabaseLease.ts (withMissingInviteConsentFixture) |
test-infra race | low (ms-wide window; shared leases added 2026-07-23 narrow it) | M | P2 | open | |
users collection has no roles (all admins omnipotent) |
src/collections/Users.ts |
RBAC | medium (small trusted admin set) | M (schema migration) | P1 before widening /admin |
open | AGENTS Known Gaps #1 |
Any campaign user can enumerate staff names/roles (canReadCampaignUsers) |
src/utilities/access/campaignUsers.ts |
info disclosure | low | S | P2 | open | |
removeSupporterData deletes via overrideAccess: true while canDeleteSupporter is admin-only |
src/app/(campaign)/campanha/actions/supporter.ts |
access asymmetry (likely intentional LGPD path) | low | S | P3 | open — document or align | |
Legacy public consent flows hardcode document IDs (consent: 2) |
src/app/(frontend)/actions/submitWhatsapp.ts |
correctness | medium | S | P1 | closed 2026-07-25 (Pass 2 D3): resolves Consent.key = 'whatsapp-inscricao' fail-closed; migration 20260725_170000 tags the live doc (id 2, key vazio) com a chave |
AGENTS Known Gaps #2 |
| E2E suite thin (7 specs; most campaign CRUD int-only) | tests/e2e/ |
coverage | medium | L | P2 | open | |
| CI runs on GitHub mirror only (no Codeberg/Woodpecker pipeline) | .github/workflows/ci.yml |
process | low | M | P3 | open | |
| E2E campaign journeys latency-flake on loaded local machines (5s expect / 30s test budgets vs cold dev compiles; suite green with CI retries) | tests/e2e/campaignMunicipalities.e2e.spec.ts |
test-infra | low | S | P3 | open — consider per-step budgets or a warmed dist dir | |
| Post listing cache holds all published posts, filters in memory | src/utilities/posts.ts |
scale (fine at ~tens of posts) | low | M | P3 | open — watch corpus growth | |
Petition generateStaticParams uses uncached getDocuments without select/depth: 0 |
src/utilities/documents.ts consumers |
over-fetch (build-time) | low | S | P2 | closed 2026-07-23 (Phase 2: getPetitionIds with depth: 0 + empty select) |
|
Compare mode (?compare=) still queries per candidate uncached cross-request |
src/utilities/municipalityMapData.ts |
perf | low | M | P3 | open — React cache() dedups in-request; add unstable_cache if profiling warrants |
|
| Map bundle serializes all years × scenarios (~100KB raw) | municipalityMapContract.ts consumers |
payload size | low — gzip crushes repeating keys to ~15-25KB on the wire; instant year/scenario switching is a core analysis flow worth the bytes | M | P3 | deferred 2026-07-23 with rationale (revisit only if RSC payload profiling says otherwise) | |
Props→state useEffect mirrors in list controls/comboboxes |
MunicipalityListExpectedVotesControl, AsyncSearchCombobox, StrictCombobox |
pattern | none — reviewed 2026-07-23: guarded-equality sync is the correct "server echo vs local draft" reconciliation (A10 pattern), not derived state | — | — | closed (accepted as correct) | |
User-visible "Praça" copy survived the M1 rename (map panel title "Mapa das Praças", admin field descriptions, provisional Onda 0 consent texts, SupportStatusBadge summary, fallback/error strings, code comments) |
rg -n 'Praça' src (~96 hits in 53 files, ~55 user-visible — 2026-07-25 audit) |
copy drift | low (cosmetic; consent texts are provisional Onda 0 copies) | S | P2 | open — scheduled Pass 2 W4f (consent texts stay for the legal batch) | Pass 2 W4f |
Smell Inventory
| 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 |
| 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 "F4 entregue" resolved 2026-07-25: F4 was complete for the surfaces that existed then) |
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 |
Sprout / Wrap Register
| Host | Sprouted/Wrapped | Notes |
|---|---|---|
| (none yet) |
Adopted Conventions
- Zero lint warnings.
pnpm lintruns with--max-warnings=0; deliberate placeholders use a_prefix (argsIgnorePattern). as neveris banned (ESLintno-restricted-syntax). Production code uses typed helpers (e.g.hookFilledCreateDatafor hook-completed create data); tests usestub<T>(partial)which keeps property-level checking. A justifiedeslint-disable-next-lineis the only escape hatch.- Dead code is deleted, not commented. knip runs in CI (files/dependencies at error level). After any change:
pnpm exec knipand remove what died. - Collections always declare
accessexplicitly. Payload's default ("any authenticated user") is treated as a bug — seepayloadAdminOnly. - Generated migration signatures are exempt from unused-args linting (
src/migrations/**override); never hand-edit migration content for lint cosmetics. - Shared consent fixture rows are leased, never owned in integration tests (
tests/helpers/testDatabaseLease.ts); deleting them steals from parallel spec files.
Debt Budget & Broken-Windows Policy
- New warnings/
as never/unused exports fail or get ledgered the same day — no untracked// TODOs. - Each delivery that runs
/simplifyregisters surviving debts here or indocs/plans/escala-dry-pos-*.md(existing convention), whichever the item's size warrants.