Files
atomic-design-poc/docs/project/readable-codebase/RD-02-max-lines-guard.md
T
ehoandClaude Sonnet 5 b9d572cfdc feat: add max-lines guard with self-cleaning exemptions (RD-02)
The dashboard refactor proved a page can be 42 lines. This rule holds
the rest of the app to that budget, before the split work in RD-20
through RD-26 begins.

Add max-lines at 250 (skipBlankLines, skipComments), scoped to
{apps,libs}/**/*.{page,component,section,step}.ts. The glob includes
section and step, the file kinds the dashboard refactor invented, so
new files from this arc do not escape the guard.

Add linterOptions.reportUnusedDisableDirectives: 'error' repo-wide.
ESLint 9 only warns on an unused disable by default, so a stale
exemption would not fail the build. At 'error', every later file
split must delete its own exemption or the build goes red.

Add a dated /* eslint-disable max-lines */ header to each of the
seven files that exceed the budget today, each with a reason and the
ticket that removes it. letter-canvas keeps its disable after RD-26,
because most of its lines are CSS and the rest is one letter.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 16:18:10 +02:00

6.3 KiB

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 */ // <why> — 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:

npm run lint                              # exits 0
npm run ci                                # exits 0

Then prove the guard actually bites, and that the glob covers section files:

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