- unit: campaignListUrl (canonicalize/clamp/redirect), supporterUi, actionPlanUi (matriz tab->where), 5 parsers de entidade - int: campaignEntityListData smokes (escopo por role, busca por marker, shape de paginacao) - fixtures: suporte a stateDeputy (create + cleanup) - docs: contagens reais (45/47/7), Praca ~96 hits, conflito C8 F4 resolvido (10 ladders restantes -> W4d)
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 | open — Pass 2 W4b flips exports to error after W4a kills the biggest offender |
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 | open | 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.