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>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-lines */ // 77 lines are CSS, the rest is one letter — RD-26 rewrites this reason, keeps the disable
|
||||
import {
|
||||
Component,
|
||||
DestroyRef,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-lines */ // sample letter + labels + editor in one file — removed by RD-25
|
||||
import { Component, computed, input, output } from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { HeadingComponent } from '@shared/ui/heading/heading.component';
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-lines */ // single-step wizard shell — removed by RD-20
|
||||
import { Component, computed, inject, input } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormFieldComponent } from '@shared/ui/form-field/form-field.component';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-lines */ // one wizard shell for the intake steps — removed by RD-22
|
||||
import { Component, computed, effect, inject, input, untracked } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormFieldComponent } from '@shared/ui/form-field/form-field.component';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-lines */ // one wizard shell for 3 steps + upload — removed by RD-23
|
||||
import { Component, computed, effect, inject, input, untracked } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { FormFieldComponent } from '@shared/ui/form-field/form-field.component';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-lines */ // teaching page covering every concept — removed by RD-24
|
||||
import { Component, computed, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import type { Resource } from '@angular/core';
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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:
|
||||
|
||||
```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.
|
||||
@@ -96,7 +96,7 @@ two. Note that RD-15 exists because 22 abandoned agent worktrees are still on di
|
||||
| ID | Ticket | Deps | `--full`? | Status |
|
||||
| ----- | ---------------------------------------------------------------------------- | ---------- | --------- | ------ |
|
||||
| RD-01 | Scaffold this backlog: README, PLAN, ticket template | — | | done |
|
||||
| RD-02 | `max-lines` rule + `reportUnusedDisableDirectives` + 7 disables | 01 | | todo |
|
||||
| RD-02 | `max-lines` rule + `reportUnusedDisableDirectives` + 7 disables | 01 | | done |
|
||||
| RD-03 | `overzicht` context: page + 2 nav sections, boundary edge, admin-links token | 02 | yes | todo |
|
||||
| RD-04 | Story titles to `Domein/<Context>/<Name>`; add the missing stories | 03 | yes | todo |
|
||||
| RD-05 | `createStore` gains the effect map + specs | 02 | | todo |
|
||||
|
||||
@@ -14,6 +14,13 @@ import angular from 'angular-eslint';
|
||||
* `herregistratie` uncovered).
|
||||
*/
|
||||
export default [
|
||||
// A disable directive that no rule needs any more is a lint failure, repo-wide (no
|
||||
// `files` key, so this applies everywhere — ESLint 9 flat config rule). This forces
|
||||
// every later file split in the readable-codebase arc (RD-02) to delete its own
|
||||
// `max-lines` exemption instead of leaving it as permanent debt.
|
||||
{
|
||||
linterOptions: { reportUnusedDisableDirectives: 'error' },
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'dist/**',
|
||||
@@ -48,4 +55,16 @@ export default [
|
||||
...c,
|
||||
files: ['{apps,libs}/**/*.html'],
|
||||
})),
|
||||
|
||||
// The dashboard refactor proved a page can be 42 lines. This rule holds the rest of
|
||||
// the app to that budget: 250 is reachable, not a style-guide default. The glob must
|
||||
// include `section` and `step` — the dashboard refactor's own file kinds — or every
|
||||
// file this arc creates escapes the guard. Each exemption below names its reason and
|
||||
// the ticket that removes it, so the list is a dated to-do, not silent debt.
|
||||
{
|
||||
files: ['{apps,libs}/**/*.{page,component,section,step}.ts'],
|
||||
rules: {
|
||||
'max-lines': ['error', { max: 250, skipBlankLines: true, skipComments: true }],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-lines */ // toolbar + contenteditable logic in one component — removed by RD-21
|
||||
import { Component, ElementRef, computed, effect, input, output, viewChild } from '@angular/core';
|
||||
import { RichTextBlock, emptyBlock } from '@shared/kernel/rich-text';
|
||||
import { adjacentChip, createChip, readBlock, renderInto } from './rich-text-dom';
|
||||
|
||||
Reference in New Issue
Block a user