---
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.
