- knip (files/deps at error level; unused exports ledgered as warn) with config for Payload/Next entries; deleted the dead modules it confirmed: RecentlyVisited + UpcomingActionPlansCard + actionPlanUpcomingPreview, lazy-dialog helpers, tseZone, federalCandidateTotalsAggregate (test-only), unused ui kit files, one-shot rename script; pruned unused radix deps and typescript-eslint; added pg/@types/pg (pinned to drizzle's 8.10.2) - GitHub Actions CI on the mirror: lint, typecheck, knip, unit, migrate, int against a Postgres 17 service - eslint: unused-vars with ^_ ignore patterns (migrations args exempt), repo-wide ban on `as never` (no-restricted-syntax), lint gate at --max-warnings=0; fixed all 54 pre-existing warnings (dead imports, hooks deps, tracking-pixel img disable with impeccable config exception) - tsconfig: include consolidated from 219 lines to 5 (wildcard covers all .next dist variants), noUnusedLocals on - typed replacements for every as-never: hookFilledCreateData (actions create data completed by collection hooks), drizzle scopeColumn narrowing, tests' stub<T> helper (property-level checking kept, completeness waived) - test infra: leased-consent generalization used by onda0 spec (shared leases over the stable consent keys; window race ledgered), purge fix - docs: IMPROVE-CODE-QUALITY-PLAN.md tracker, TECH-DEBT.md ledger, TESTING.md safety-net map (package.json also gains the Phase 2 build:election-aggregates script entry; the script lands next commit)
5.8 KiB
5.8 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 |
|---|---|---|---|---|---|---|---|
| 158 unused exports reported by knip (warn-level) | god modules, ui kit families | dead code | low | M | P2 | open — drive exports rule to error after splits |
Phase 5 |
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/campaignAccess.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 (5 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 | |
| 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 | planned (Phase 2) | |
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 |
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/ |
planned (Phase 5) |
| 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 | planned (Phase 5) |
| Clone twins: create/update/staff-assert/unique-conflict skeleton | actions/organization.ts ↔ actions/stateDeputy.ts |
extract shared factory | planned (Phase 5) |
13 near-identical formActions.ts try/catch→mapError→redirect ladders |
src/app/(campaign)/campanha/(app)/*/formActions.ts |
shared form-action wrapper | planned (Phase 5) |
Flat 80+-file src/utilities/ junk drawer; lib/ vs utilities/ boundary undocumented |
src/utilities/, src/lib/ |
document convention + move misfiled modules | planned (Phase 5/6) |
Praça→Município rename leftovers in identifiers (PLAZA_MAP_YEARS, ACCESSIBLE_PLAZA_IDS_CONTEXT_KEY, …) |
map/list utilities | finish rename | planned (Phase 5) |
Props→state useEffect mirrors |
MunicipalityListExpectedVotesControl, comboboxes |
key-remount or fully controlled | planned (Phase 4) |
| Detail-page RSCs mixing loading + tab UI + forms (300+ lines) | municipios/[slug]/page.tsx, planos/[slug]/page.tsx |
extract tab loaders/sections | ledgered |
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.