Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.7 KiB
Phase 13: Real Lint Gate (ESLint) - Context
Gathered: 2026-06-11 Status: Ready for planning
## Phase BoundaryStand 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
lintscripts inapps/apiandapps/pwa(so rootpnpm -r --if-present lintruns a real linter) - Prettier config + root
format/format:checkscripts - A new
format:checkCI step in thefast-checksjob - Fixing all first-run violations so
pnpm lintANDpnpm format:checkare green across both apps
Out of scope:
- CI lint-step plumbing —
.gitea/workflows/ci.ymlalready runspnpm lint; the slot auto-activates once packagelintscripts exist. Only the NEWformat:checkstep is added. - Desktop E2E coverage (Phase 14), any non-lint CI changes.
Ruleset & strictness
- D-13-01: Use typescript-eslint
recommendedTypeChecked(type-aware), not the non-type-awarerecommended. Rationale: this is an async-heavy backend (outbox drain, push dispatch, CalDAV/reminder schedulers) — type-aware rules catch floating promises,no-misused-promises, and unsafeanythat syntactic linting misses. Enable viaprojectService: true(resolves all tsconfigs automatically). - D-13-02: Add React + react-hooks plugins for
apps/pwa(per ROADMAP goal).apps/apiis Node/TS only (no React config). - D-13-03: Do NOT adopt
strict/strictTypeCheckedpresets — 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 lintandpnpm format:checkare 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-disableand novoid promiseto silence a floating-promise that should actually beawaited. 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 --checkas its own CI step (separate from lint), AND addeslint-config-prettierto 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/pwausestsconfig.e2e.json), and config files (vite.config,drizzle.config,playwright.config). - D-13-10: Config files / non-project files that
projectServicecan't type-check need a dedicated override block (non-type-checked rules, ordisableTypeCheckedfor those globs) so type-aware linting doesn't error on them.
Claude's Discretion
- Flat-config file layout (single root
eslint.config.jsvs 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— thefast-checksjob runspnpm lint(currently a no-op; see the inline comment at the Lint step). This phase makes it real and adds aformat:checkstep. No other CI plumbing change.package.json(root) —lint: pnpm -r --if-present lint,typecheck: pnpm -r typecheck. Add rootformat/format:check.
Workspace / TS config (constrains the flat config)
apps/api/tsconfig.json— NodeNext ESM,target ES2023,strict, excludestests.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.xstrict: 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: moduleESM → flat config file must beeslint.config.js(ESM) or.mjs. - API and PWA each already have a
typecheckscript (tsc --noEmit) wired into the rootpnpm -r typecheck— mirror that wiring style for the new package-levellintscripts. - Type-aware linting needs every linted file resolvable by a tsconfig project. PWA has TWO tsconfigs (
tsconfig.json+tsconfig.e2e.json) —projectService: truehandles multi-project resolution.
Integration Points
.gitea/workflows/ci.ymlfast-checksjob —pnpm lintstep already present (activates automatically); ADD apnpm format:checkstep.- File volume for the first-run fix pass:
apps/api/src= 30.tsfiles;apps/pwa/src= 61.ts/.tsxfiles, 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.
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 tomain. Matched only on shared keywords (CI/apps/api). Not in Phase 13 scope.
Phase: 13-real-lint-gate-eslint Context gathered: 2026-06-11