Files
2026-06-18 22:21:38 -04:00

7.7 KiB

Phase 13: Real Lint Gate (ESLint) - Context

Gathered: 2026-06-11 Status: Ready for planning

## Phase Boundary

Stand up a real ESLint flat config across both workspaces (apps/api — NodeNext ESM Node/TS; apps/pwa — React 19 + Bundler ESM), plus package-level lint scripts, so the existing CI step pnpm lint actually fails on violations instead of exiting 0 as a no-op.

Scope expanded during discussion (flag for planning): This phase now also adds Prettier with a standalone prettier --check CI format gate. The original ROADMAP Phase 13 one-liner scopes to "ESLint only" — planning MUST update the ROADMAP entry and success criteria to include the format gate. Treated as an in-domain expansion (still a CI quality gate; CLAUDE.md's stack table lists "ESLint + Prettier"), not a separate phase.

In scope:

  • ESLint flat config (eslint.config.js) — typescript-eslint + React + react-hooks plugins
  • Package-level lint scripts in apps/api and apps/pwa (so root pnpm -r --if-present lint runs a real linter)
  • Prettier config + root format / format:check scripts
  • A new format:check CI step in the fast-checks job
  • Fixing all first-run violations so pnpm lint AND pnpm format:check are green across both apps

Out of scope:

  • CI lint-step plumbing — .gitea/workflows/ci.yml already runs pnpm lint; the slot auto-activates once package lint scripts exist. Only the NEW format:check step is added.
  • Desktop E2E coverage (Phase 14), any non-lint CI changes.
## Implementation Decisions

Ruleset & strictness

  • D-13-01: Use typescript-eslint recommendedTypeChecked (type-aware), not the non-type-aware recommended. Rationale: this is an async-heavy backend (outbox drain, push dispatch, CalDAV/reminder schedulers) — type-aware rules catch floating promises, no-misused-promises, and unsafe any that syntactic linting misses. Enable via projectService: true (resolves all tsconfigs automatically).
  • D-13-02: Add React + react-hooks plugins for apps/pwa (per ROADMAP goal). apps/api is Node/TS only (no React config).
  • D-13-03: Do NOT adopt strict/strictTypeChecked presets — too much churn on the existing 91-file codebase; CLAUDE.md warns against bikeshedding.

Gate threshold

  • D-13-04: Run with --max-warnings 0 — any warning fails CI. Every rule must be a real decision: either error-worthy or off. No non-blocking warnings (they rot into ignored noise).

First-run violation strategy

  • D-13-05: Fix all violations now. The phase is not done until pnpm lint and pnpm format:check are green across both apps. Real bugs (floating promises, misused promises) get genuinely fixed.
  • D-13-06 (HARD CONSTRAINT — for executors): Fixes must address the violation, not mask it. No blanket eslint-disable and no void promise to silence a floating-promise that should actually be awaited. Any suppression (eslint-disable-next-line) requires a justifying inline comment explaining why the rule is wrong here. A type-aware lint finding is a candidate bug — review before suppressing.

Prettier

  • D-13-07: Add Prettier + standalone prettier --check as its own CI step (separate from lint), AND add eslint-config-prettier to the flat config to disable ESLint's formatting rules. Clean separation: ESLint finds bugs, Prettier owns formatting, no double-reporting. (Rejected: eslint-plugin-prettier — slower, noisier, discouraged by Prettier docs.)
  • D-13-08: All files get reformatted in this phase — accepted as one large mechanical diff. Planning should consider isolating the reformat commit from logic fixes for reviewability.

File coverage

  • D-13-09: Lint all TS/TSX: app src/, vitest tests, Playwright e2e specs (apps/pwa uses tsconfig.e2e.json), and config files (vite.config, drizzle.config, playwright.config).
  • D-13-10: Config files / non-project files that projectService can't type-check need a dedicated override block (non-type-checked rules, or disableTypeChecked for those globs) so type-aware linting doesn't error on them.

Claude's Discretion

  • Flat-config file layout (single root eslint.config.js vs per-app configs) — planner/researcher decides; root config with per-package overrides is the common pattern for a small 2-app pnpm workspace.
  • Exact Prettier options (.prettierrc) — standard defaults; no bikeshedding.
  • CI step ordering within fast-checks (lint → format:check → typecheck → tests).

<canonical_refs>

Canonical References

Downstream agents MUST read these before planning or implementing.

CI integration (the slot this phase fills)

  • .gitea/workflows/ci.yml — the fast-checks job runs pnpm lint (currently a no-op; see the inline comment at the Lint step). This phase makes it real and adds a format:check step. No other CI plumbing change.
  • package.json (root) — lint: pnpm -r --if-present lint, typecheck: pnpm -r typecheck. Add root format / format:check.

Workspace / TS config (constrains the flat config)

  • apps/api/tsconfig.json — NodeNext ESM, target ES2023, strict, excludes tests. type: module.
  • apps/pwa/tsconfig.json — ESNext / Bundler resolution, jsx: react-jsx, strict, noEmit. type: module.
  • apps/pwa/tsconfig.e2e.json — separate project for Playwright specs; must be in the lint projectService set for type-aware linting of e2e tests.
  • pnpm-workspace.yaml — packages: apps/*.

Stack guidance

  • CLAUDE.md (Development Tools table) — "ESLint + Prettier | Standard config; no bikeshedding needed." TypeScript 5.x strict: true.

No external ADRs/specs specific to linting — requirements fully captured in decisions above.

</canonical_refs>

<code_context>

Existing Code Insights

Reusable Assets

  • None to reuse — greenfield lint config. No ESLint or Prettier anywhere in the repo today (confirmed: no eslint dep in any package.json, no config files).

Established Patterns

  • Both apps are type: module ESM → flat config file must be eslint.config.js (ESM) or .mjs.
  • API and PWA each already have a typecheck script (tsc --noEmit) wired into the root pnpm -r typecheck — mirror that wiring style for the new package-level lint scripts.
  • Type-aware linting needs every linted file resolvable by a tsconfig project. PWA has TWO tsconfigs (tsconfig.json + tsconfig.e2e.json) — projectService: true handles multi-project resolution.

Integration Points

  • .gitea/workflows/ci.yml fast-checks job — pnpm lint step already present (activates automatically); ADD a pnpm format:check step.
  • File volume for the first-run fix pass: apps/api/src = 30 .ts files; apps/pwa/src = 61 .ts/.tsx files, plus tests, e2e specs, and config files.

</code_context>

## Specific Ideas
  • Type-aware ruleset is specifically motivated by the async surfaces in this codebase: apps/api/src/broker/ (outbox worker, poller, reminder scheduler — all recently bug-prone per STATE.md: node-cron→setInterval, reminder catch-up). Floating-promise / misused-promise detection here has concrete bug-catching value, not just style.
## Deferred Ideas

None — discussion stayed within phase scope (Prettier was folded IN as an accepted scope expansion, not deferred).

Reviewed Todos (not folded)

  • Gitea CI — full regression on PR to main + build/publish Docker image (2026-06-10-gitea-ci-regression-and-docker-publish.md, score 0.6) — reviewed but NOT folded: this is the Phase 8 CI work, already complete and merged to main. Matched only on shared keywords (CI/apps/api). Not in Phase 13 scope.

Phase: 13-real-lint-gate-eslint Context gathered: 2026-06-11