docs(13-01): complete lint gate foundation plan — SUMMARY + STATE + ROADMAP

Plan 13-01 complete: ESLint flat config + Prettier installed and wired,
SC-1 gate proven (exit 1 on deliberate violations), 119 real violations
inventoried for Plan 02 (api:42, pwa:77). STATE advanced to Plan 2 of 3.
This commit is contained in:
Lucas Berger
2026-06-11 19:53:55 -04:00
parent 7a255eacaa
commit 39e26561cf
3 changed files with 161 additions and 12 deletions
+1 -1
View File
@@ -222,7 +222,7 @@ Make FamilySync configurable, administrable, and maintainable for real multi-mem
**Plans**: 3 plans
**Wave 1**
- [ ] 13-01-PLAN.md — Install ESLint/Prettier deps + flat config + package scripts + prove the gate fails (SC-1)
- [x] 13-01-PLAN.md — Install ESLint/Prettier deps + flat config + package scripts + prove the gate fails (SC-1)
**Wave 2** *(blocked on Wave 1 completion)*
+14 -11
View File
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
milestone: v1.1
milestone_name: Operability & Polish
status: executing
stopped_at: Phase 13 context gathered
last_updated: "2026-06-11T23:27:49.541Z"
last_activity: "2026-06-11 -- Quick task 260611-ozt: split publish into standalone push-only publish.yml (kills orphaned CI / publish (pull_request) pending status, WR-01); release model documented in README + publish.yml. Branch-protection contexts unchanged."
stopped_at: Phase 13, Plan 1 complete
last_updated: "2026-06-11T23:51:00Z"
last_activity: 2026-06-11 -- Phase 13 Plan 01 complete (lint gate foundation)
progress:
total_phases: 17
completed_phases: 2
total_plans: 8
completed_plans: 8
percent: 12
total_plans: 11
completed_plans: 9
percent: 13
---
# Project State
@@ -21,14 +21,14 @@ progress:
See: .planning/PROJECT.md (updated 2026-06-10)
**Core value:** One color-coded family calendar (shared + personal) and shared lists from a single low-friction PWA — cross-ecosystem, no app store
**Current focus:** Phase 08gitea-ci
**Current focus:** Phase 13real-lint-gate-eslint
## Current Position
Phase: 08 (gitea-ci) — COMPLETE
Plan: 4 of 4 (08-04 complete)
Status: Ready to execute
Last activity: 2026-06-11 -- Quick task 260611-ozt: split publish into standalone push-only publish.yml (kills orphaned CI / publish (pull_request) pending status, WR-01); release model documented in README + publish.yml. Branch-protection contexts unchanged.
Phase: 13 (real-lint-gate-eslint) — EXECUTING
Plan: 2 of 3
Status: Executing Phase 13 — Plan 01 complete
Last activity: 2026-06-11 -- Phase 13 Plan 01 complete (lint gate foundation)
## Performance Metrics
@@ -82,6 +82,7 @@ Last activity: 2026-06-11 -- Quick task 260611-ozt: split publish into standalon
| Phase 07 P02 | 196 | 2 tasks | 4 files |
| Phase 07-mobile-test-harness P03 | 480 | 2 tasks | 2 files |
| Phase 07-mobile-test-harness P04 | 22 | 2 tasks | 2 files |
| Phase 13 P01 | 8 | 2 tasks | 7 files |
## Accumulated Context
@@ -90,6 +91,8 @@ Last activity: 2026-06-11 -- Quick task 260611-ozt: split publish into standalon
Decisions are logged in PROJECT.md Key Decisions table.
Recent decisions affecting current work:
- D-13-ESLint-PIN (2026-06-11, 13-01): eslint pinned to 9.39.4 — ESLint 10 breaks eslint-plugin-react@7.37.5 at runtime ("getFilename is not a function", jsx-eslint#3977). Unpin when plugin releases ESLint 10 support.
- D-13-JSX-SCOPE (2026-06-11, 13-01): react/react-in-jsx-scope disabled explicitly — flat.recommended enables it at error; PWA uses jsx:react-jsx (React 19 automatic transform), React import not required in JSX files.
- D-PROBE-01 (2026-06-11, 08-01): runs-on must be ubuntu-latest — runner has no self-hosted label; all downstream ci.yml workflows use ubuntu-latest.
- D-PROBE-02 (2026-06-11, 08-01): Docker-executor confirmed (/.dockerenv present); services: works; DB_HOST=mariadb in all downstream jobs.
- D-PROBE-03 (2026-06-11, 08-01): No mysql CLI in runner image — DB readiness uses healthcheck.sh --connect --innodb_initialized or Node mysql2 wait; no mysql shell-out.
@@ -0,0 +1,146 @@
---
phase: 13-real-lint-gate-eslint
plan: "01"
subsystem: dev-tooling
tags: [eslint, prettier, lint-gate, typescript-eslint, react-hooks, flat-config]
dependency_graph:
requires: []
provides: [eslint-flat-config, prettier-config, lint-scripts, format-scripts]
affects: [ci-lint-slot, apps/api, apps/pwa]
tech_stack:
added:
- eslint@9.39.4 (root devDep — pinned, ESLint 10 breaks eslint-plugin-react)
- "@eslint/js@9.39.4"
- typescript-eslint@8.61.0
- eslint-plugin-react@7.37.5
- eslint-plugin-react-hooks@7.1.1
- eslint-config-prettier@10.1.8
- prettier@3.8.4
patterns:
- ESLint flat config with tseslint.config() helper
- projectService:true for multi-tsconfig monorepo (auto-discovers all tsconfigs)
- disableTypeChecked override block for config files + API test dir
- eslint-config-prettier/flat LAST to disable formatting rule conflicts
key_files:
created:
- eslint.config.js
- .prettierrc
- .prettierignore
modified:
- package.json (type:module + format/format:check scripts)
- apps/api/package.json (lint script --max-warnings 0)
- apps/pwa/package.json (lint script --max-warnings 0)
- pnpm-lock.yaml
decisions:
- "ESLint pinned to 9.39.4 (non-negotiable): eslint-plugin-react@7.37.5 declares peer ^9.7 and hits TypeError at runtime on ESLint 10 (getFilename removed)"
- "react/react-in-jsx-scope explicitly disabled: flat.recommended enables it at error; codebase uses jsx:react-jsx (React 17+ transform) so React import is not required"
- "apps/api/tests/** in disableTypeChecked block: excluded from apps/api/tsconfig.json so projectService cannot type-check them"
- "eslint.config.js itself added to disableTypeChecked block as precaution (JS file, not in any TS project)"
metrics:
duration_minutes: 8
completed_date: "2026-06-11"
tasks_completed: 2
files_changed: 7
---
# Phase 13 Plan 01: ESLint + Prettier Lint Gate Foundation Summary
Real ESLint flat config (type-aware via projectService) + Prettier installed and wired; both gates proven non-zero on deliberate violations (SC-1).
## What Was Built
### Task 1: Install devDependencies + author flat config + Prettier config
Installed 7 packages at pinned versions as root devDependencies. Authored `eslint.config.js` with the required 5-block structure per RESEARCH Pattern 1:
1. **Global ignores**`**/dist/**`, `**/node_modules/**`, `apps/api/src/db/migrations/**`, `pnpm-lock.yaml`
2. **Base block**`apps/**/*.{ts,tsx}`, extends `js.configs.recommended` + `tseslint.configs.recommendedTypeChecked`, `parserOptions.projectService: true` + `tsconfigRootDir: import.meta.dirname`, `@typescript-eslint/no-unused-vars` rule with `^_` ignore patterns
3. **PWA-React block**`apps/pwa/**/*.{ts,tsx}` only, extends `reactPlugin.configs.flat.recommended` + `reactHooks.configs.flat.recommended`, `react.version: 'detect'`, `react/prop-types: 'off'`, `react/react-in-jsx-scope: 'off'`
4. **disableTypeChecked override** — config files (drizzle.config, vitest.config, vite.config, playwright.config) + `apps/api/tests/**/*.ts` + `eslint.config.js` itself
5. **`eslint-config-prettier/flat` LAST** — disables ESLint formatting rules that conflict with Prettier
Added `"type": "module"` to root `package.json` (required for `eslint.config.js` to parse as ESM). Added `format`/`format:check` scripts. Added `lint` script with `--max-warnings 0` to both `apps/api/package.json` and `apps/pwa/package.json`.
Authored `.prettierrc` (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100) and `.prettierignore`.
**Commit:** `df62d33`
### Task 2: SC-1 smoke proof — deliberate violations, both gates, throwaway cleanup
**Lint gate (SC-1 lint half):**
- Created `apps/api/src/_lint-gate-test.ts` with an unhandled `Promise.resolve(1)` expression
- Ran `pnpm --filter @familysync/api lint`**exit code 1**, `@typescript-eslint/no-floating-promises` reported on the deliberate file
- Deleted `apps/api/src/_lint-gate-test.ts` — never staged or committed
**Format gate (SC-1 format half):**
- Created `_format-gate-test.ts` at repo root with double quotes + irregular indentation (opposite of .prettierrc settings)
- Ran `pnpm format:check`**exit code 1**, `_format-gate-test.ts` listed in warnings
- Deleted `_format-gate-test.ts` — never staged or committed
**Commit:** `7a255ea`
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] `react/react-in-jsx-scope` unexpectedly enabled in `reactPlugin.configs.flat.recommended`**
- **Found during:** Task 2 (SC-1 smoke) — 404 `react/react-in-jsx-scope` errors in the PWA
- **Issue:** RESEARCH.md (Assumption A1, Pitfall 8) stated that `reactPlugin.configs.flat.recommended` disables `react/react-in-jsx-scope` for React 17+ JSX transform. In practice, `eslint-plugin-react@7.37.5` flat.recommended sets this rule to `2` (error). The PWA uses `jsx: "react-jsx"` (React 19 automatic transform) — `React` does not need to be in scope.
- **Fix:** Added `'react/react-in-jsx-scope': 'off'` alongside `'react/prop-types': 'off'` in the pwa-react config block
- **Files modified:** `eslint.config.js`
- **Commit:** `7a255ea`
## SC-1 Evidence
| Gate | Deliberate Violation | Exit Code | Rule Reported |
|------|---------------------|-----------|---------------|
| `pnpm --filter @familysync/api lint` | `Promise.resolve(1)` — unhandled promise | **1 (non-zero)** | `@typescript-eslint/no-floating-promises` |
| `pnpm format:check` | double quotes + irregular indent (opposite of .prettierrc) | **1 (non-zero)** | `_format-gate-test.ts` listed as unformatted |
Both throwaway files confirmed absent and untracked (`THROWAWAYS_ABSENT_AND_UNTRACKED`).
## First-Run Violation Inventory (for Plan 02)
Plan 02 consumes this inventory to scope fixes. No violations were fixed in this plan.
### apps/api — 42 errors total
| Rule | Count | Description |
|------|-------|-------------|
| `@typescript-eslint/no-unnecessary-type-assertion` | 24 | Redundant `as` casts (mainly in outboxWorker.ts dispatcher blocks) |
| `@typescript-eslint/no-unsafe-assignment` | 3 | `any` assignment from ical.js property access |
| `@typescript-eslint/no-unsafe-argument` | 4 | `any` args from ical.js property values |
| `@typescript-eslint/no-unused-vars` | 7 | Test file variables assigned but unused |
| `@typescript-eslint/no-misused-promises` | 1 | `sse.ts:96` — async callback in void context |
| `@typescript-eslint/no-base-to-string` | 1 | Template literal with possible `[object Object]` |
| `@typescript-eslint/restrict-template-expressions` | 1 | `Record<string, unknown>` in template literal |
| `prefer-const` | 1 | `lists.ts:604``let` that is never reassigned |
### apps/pwa — 77 errors total
| Rule | Count | Description |
|------|-------|-------------|
| `@typescript-eslint/require-await` | 23 | Async functions with no `await` expression |
| `@typescript-eslint/no-unnecessary-type-assertion` | 14 | Redundant `as` casts |
| `@typescript-eslint/no-floating-promises` | 11 | Unhandled promises |
| `@typescript-eslint/no-unused-vars` | 4 | Unused variables |
| `@typescript-eslint/no-unsafe-assignment` | 4 | `any` assignments |
| `@typescript-eslint/no-unsafe-member-access` | 3 | Member access on `any` |
| `@typescript-eslint/no-misused-promises` | 3 | Async callbacks in void contexts |
| `react/no-unescaped-entities` | 1 | Unescaped `'` in JSX |
**Total:** 119 errors across both apps (0 warnings — `--max-warnings 0` working).
## Self-Check: PASSED
Created files exist:
- `eslint.config.js` — FOUND
- `.prettierrc` — FOUND
- `.prettierignore` — FOUND
Commits exist:
- `df62d33` — FOUND (chore(13-01): install ESLint+Prettier, author flat config, wire lint/format scripts)
- `7a255ea` — FOUND (fix(13-01): disable react/react-in-jsx-scope for React 19 JSX transform)
Throwaway files absent: THROWAWAYS_ABSENT_AND_UNTRACKED confirmed.