# RD-02 — `max-lines` guard with a self-cleaning exemption list Status: done Source: PLAN.md section 3a ## Why The dashboard refactor proved a page can be 42 lines. Nothing stops the next one being 340 again, and nothing stops this arc itself adding a new oversized file. The guard therefore lands **before** the work it protects, not after. Seven files exceed the budget today. Each becomes a visible, dated to-do rather than a silent one. ## Read first - `eslint.config.mjs` — a flat array of 4 config objects. You add a 5th. - `docs/project/readable-codebase/README.md` — the session protocol and GREEN. - CLAUDE.md, "Enforced, not just hoped-for". ## Decisions (pre-made, don't relitigate) 1. **The rule.** `max-lines` at 250, `skipBlankLines: true`, `skipComments: true`, scoped to `['{apps,libs}/**/*.{page,component,section,step}.ts']`. 2. **The glob must include `section` and `step`.** `*.{page,component}.ts` does **not** match `*.section.ts` — the file kind the dashboard refactor invented, and the kind RD-22 through RD-26 create most of. Omitting them lets every new file this arc produces escape the guard. This is the single most important line of the ticket. 3. **Add `linterOptions: { reportUnusedDisableDirectives: 'error' }`, in this same commit.** ESLint 9 only _warns_ by default, and `npm run lint` does not fail on warnings. At `error`, a disable that is no longer needed becomes a lint failure — so **every later split commit is forced to delete its own exemption or go red.** The exemption list cannot rot into permanent debt. Verified clean on the current tree, so it lands green. 4. **250 is chosen because the dashboard proves it is reachable**, not from a style guide. 5. **Seven files, not eight or nine.** The original plan counted with `wc -l`; this rule counts without blank lines or comments. `behandel-scherm.component.ts` (232) and `stamdata-table-editor.component.ts` (236) were on the old list and **already pass** — leave them alone. 6. **Each disable names its reason and the ticket that removes it**, per CLAUDE.md's standing rule. Use the exact mapping in Files below. Note `letter-canvas` is the one file whose disable is **expected to stay** — RD-26 rewrites its reason rather than deleting it, because 77 of its lines are CSS and the rest is one letter (PLAN.md 3d). ## Files Add the config block to `eslint.config.mjs`, then a `/* eslint-disable max-lines */` header with a one-line reason to each of these seven. Verified counts under the rule: | Rule lines | File | Removed by | | ---------- | --------------------------------------------------------------------------------------------- | ----------------------- | | 574 | `apps/ssp/src/app/registratie/ui/registratie-wizard/registratie-wizard.component.ts` | RD-23 | | 472 | `apps/ssp/src/app/showcase/concepts.page.ts` | RD-24 | | 414 | `apps/ssp/src/app/brief/ui/letter-canvas/letter-canvas.component.ts` | RD-26 (rewrites, keeps) | | 368 | `apps/ssp/src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts` | RD-22 | | 329 | `apps/ssp/src/app/brief/ui/org-template-editor/org-template-editor.component.ts` | RD-25 | | 253 | `libs/shared/src/ui/rich-text-editor/rich-text-editor.component.ts` | RD-21 | | 252 | `apps/ssp/src/app/herregistratie/ui/herregistratie-wizard/herregistratie-wizard.component.ts` | RD-20 | ## Steps 1. Add the 5th config object to `eslint.config.mjs` with the rule from decision 1, and a short comment saying why 250 and that the exemptions are the to-do list. 2. Add `linterOptions: { reportUnusedDisableDirectives: 'error' }` so it applies repo-wide. 3. Add the seven disable headers, each of the form `/* eslint-disable max-lines */ // — removed by RD-NN`. 4. Update this ticket's `Status:` to `done` and the README's RD-02 row to `done`. 5. Commit all of it together. ## Acceptance criteria Commands, not judgements: ```bash npm run lint # exits 0 npm run ci # exits 0 ``` Then prove the guard actually bites, and that the glob covers section files: ```bash # 1. A new oversized section file must FAIL. Expect a max-lines error, then delete the file. printf '/* x */\nexport class X {\n%s\n}\n' "$(for i in $(seq 260); do echo " p$i = $i;"; done)" \ > apps/ssp/src/app/registratie/ui/dashboard/tmp-probe.section.ts npx eslint apps/ssp/src/app/registratie/ui/dashboard/tmp-probe.section.ts # must report max-lines rm apps/ssp/src/app/registratie/ui/dashboard/tmp-probe.section.ts # 2. Removing any one disable must FAIL (proves all 7 are load-bearing, none is decoration). ``` ## Verification `npm run ci`. This ticket touches no story, no `.mdx` and no `libs/shared/src/ui/**` _content_, so `--full` is not required — but `rich-text-editor.component.ts` does live under `libs/shared/src/ui/`, and adding a comment line to it is harmless. Skip `--full`. ## Out of scope - Splitting any of the seven files. That is RD-20 through RD-26. - `behandel-scherm.component.ts` and `stamdata-table-editor.component.ts` — already compliant. - The 7 non-component files over 250 lines (`brief.adapter.ts` 408, `upload.machine.spec.ts` 364, `brief.store.spec.ts` 357, `registratie-wizard.machine.spec.ts` 287, `registratie-wizard.machine.ts` 285, `upload.machine.ts` 256). The glob deliberately does not reach specs, adapters or machines — this budget is about components. ## Risks - **The inline-template processor can make `max-lines` fire twice per file** (once for the `.ts`, once for the extracted virtual `.html`). Measured: it does **not** with this glob, because the virtual path ends in `.html` and the glob ends in `.ts`. If a doubled report appears, the glob is wrong — do not "fix" it by raising the limit. - **`reportUnusedDisableDirectives` is repo-wide**, so it also polices the generated `api-client.ts` header. That file is already in `ignores`, so it is out of reach. Confirmed clean before this change.