teqo/docs/TECH-DEBT.md
Francisco Ignácio Fontoura Solla 0e4e6855dc Add the tooling gate: knip, CI, zero-warning lint, as-never ban
- 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)
2026-07-23 23:06:12 -03:00

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 lint runs with --max-warnings=0; deliberate placeholders use a _ prefix (argsIgnorePattern).
  • as never is banned (ESLint no-restricted-syntax). Production code uses typed helpers (e.g. hookFilledCreateData for hook-completed create data); tests use stub<T>(partial) which keeps property-level checking. A justified eslint-disable-next-line is the only escape hatch.
  • Dead code is deleted, not commented. knip runs in CI (files/dependencies at error level). After any change: pnpm exec knip and remove what died.
  • Collections always declare access explicitly. Payload's default ("any authenticated user") is treated as a bug — see payloadAdminOnly.
  • 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 /simplify registers surviving debts here or in docs/plans/escala-dry-pos-*.md (existing convention), whichever the item's size warrants.