feat: bronkaart die klopt, een taalafspraak, en een ratchet per klasse #557

Merged
brenno merged 4 commits from feat/structuurschuld into main 2026-07-22 13:45:51 +00:00
Owner

Summary

The three parts of #518 that do not move code across many files. #518 stays open for the two that do.

1. SOURCE_MAP claimed a line per file and did not deliver

Narrowed and filled: the promise is now a line per Dart library, with three exceptions that follow the grain of the code — a part inherits its library's line, a conditional-import half (_io/_web/_stub) falls under its façade, and a uniform family is described as a group written as a glob that must still match real files.

Not "just complete it", because two thirds of the missing entries were part files and platform halves: a separate line for each says the same thing three times and buys only maintenance. Everything that carries a subject of its own now has one — 43 lines added.

Two things surfaced while doing it:

  • 24 files appeared two or three times, each with a different description — parallel branches that both filled the same gap, merges that kept both copies. For marp_html_service.dart that was a real decision: one copy carried a correction dated 22-07 00:22 saying the HTML export is not self-contained, while the branch that made it self-contained landed at 09:16 the same morning. The correction was itself out of date.
  • One entry sat under lib/models/ for a file that lives in lib/services/.

test/source_map_coverage_test.dart guards four rules and was verified negatively with a planted file.

2. Comment language — measured, not decided

The rule is Dutch or English, never both inside one comment, because that is what the code already does: of ~3,100 comment blocks of three or more lines in lib/, 71% are Dutch, 28% English, and 1% mix the two within a block.

So the rule records what exists rather than imposing a preference, and it says explicitly: do not rewrite existing comments for language. No behaviour change, thousands of lines of noise, and your name in git blame under someone else's reasoning. Not one existing comment was touched.

3. A ratchet per class

maxClassLines = 1000, counted per class across all part files of its library, including every extension … on hanging off it. Same number as the file limit on purpose: the promise was "no unit over a thousand lines", and the part split had quieted the gate while the class kept growing.

Fifteen classes sit above it today. The largest is not TabsNotifier (2,403) but _SettingsDialogState at 7,295 lines across 22 part files.

Verified both ways: green on current code; red on a planted 1,204-line class split across two 605- and 604-line parts — where the file ratchet stays green. That is exactly the gap the issue describes. Also red on growth of an existing baselined class.

Two real bugs in the counter were caught by its own tests: a multi-line class header was missed entirely (including _ChartPreviewState, 2,667 lines), and a ''' string with a brace at column 0 confused it.

The ratchet earned its keep on day one

During the rebase, the freshly landed #548 added 22 lines to FileService and the new ratchet fired immediately on work that was not this branch's. The baseline was re-measured (2882 → 2904) and the commit amended, so the baseline belongs to the tree it lands on.

Left deliberately

Git orchestration out of lib/state/ and splitting app_shell. Both move code across many files while other branches are in flight — four commits landed on main during this session alone, one of them inside FileService. Every rebase conflict would look like a semantic conflict while being nothing of the sort.

There is now a better reason to wait than timing: the class ratchet makes that debt visible and boundedTabsNotifier is pinned at 2,403 and may only shrink — so it can no longer grow quietly while the tree is busy. And the measurement suggests the target is wrong anyway: _SettingsDialogState (7,295) deserves attention before app_shell.

Verification

Built by a subagent in an isolated worktree; rebased and make check re-run by me: exit 0, 5,536 tests, 86.2% coverage.

## Summary The three parts of #518 that do not move code across many files. **`#518` stays open** for the two that do. ## 1. SOURCE_MAP claimed a line per file and did not deliver Narrowed *and* filled: the promise is now **a line per Dart library**, with three exceptions that follow the grain of the code — a `part` inherits its library's line, a conditional-import half (`_io`/`_web`/`_stub`) falls under its façade, and a uniform family is described as a group written as a glob that must still match real files. Not "just complete it", because two thirds of the missing entries were `part` files and platform halves: a separate line for each says the same thing three times and buys only maintenance. Everything that carries a subject of its own now has one — **43 lines added**. Two things surfaced while doing it: - **24 files appeared two or three times**, each with a different description — parallel branches that both filled the same gap, merges that kept both copies. For `marp_html_service.dart` that was a real decision: one copy carried a correction dated 22-07 00:22 saying the HTML export is *not* self-contained, while the branch that made it self-contained landed at 09:16 the same morning. The correction was itself out of date. - One entry sat under `lib/models/` for a file that lives in `lib/services/`. `test/source_map_coverage_test.dart` guards four rules and was verified negatively with a planted file. ## 2. Comment language — measured, not decided The rule is **Dutch or English, never both inside one comment**, because that is what the code already does: of ~3,100 comment blocks of three or more lines in `lib/`, 71% are Dutch, 28% English, and **1%** mix the two within a block. So the rule records what exists rather than imposing a preference, and it says explicitly: **do not rewrite existing comments for language.** No behaviour change, thousands of lines of noise, and your name in `git blame` under someone else's reasoning. Not one existing comment was touched. ## 3. A ratchet per class `maxClassLines = 1000`, counted **per class across all `part` files of its library**, including every `extension … on` hanging off it. Same number as the file limit on purpose: the promise was "no unit over a thousand lines", and the `part` split had quieted the gate while the class kept growing. Fifteen classes sit above it today. The largest is **not** `TabsNotifier` (2,403) but **`_SettingsDialogState` at 7,295 lines across 22 `part` files**. Verified both ways: green on current code; **red** on a planted 1,204-line class split across two 605- and 604-line parts — where the *file* ratchet stays green. That is exactly the gap the issue describes. Also red on growth of an existing baselined class. Two real bugs in the counter were caught by its own tests: a multi-line class header was missed entirely (including `_ChartPreviewState`, 2,667 lines), and a `'''` string with a brace at column 0 confused it. ## The ratchet earned its keep on day one During the rebase, the freshly landed #548 added 22 lines to `FileService` and the new ratchet **fired immediately** on work that was not this branch's. The baseline was re-measured (2882 → 2904) and the commit amended, so the baseline belongs to the tree it lands on. ## Left deliberately **Git orchestration out of `lib/state/` and splitting `app_shell`.** Both move code across many files while other branches are in flight — four commits landed on `main` during this session alone, one of them inside `FileService`. Every rebase conflict would look like a semantic conflict while being nothing of the sort. There is now a better reason to wait than timing: the class ratchet makes that debt **visible and bounded** — `TabsNotifier` is pinned at 2,403 and may only shrink — so it can no longer grow quietly while the tree is busy. And the measurement suggests the target is wrong anyway: `_SettingsDialogState` (7,295) deserves attention before `app_shell`. ## Verification Built by a subagent in an isolated worktree; rebased and `make check` re-run by me: **exit 0**, 5,536 tests, 86.2% coverage.
Vierentwintig bestanden stonden twee of drie keer in SOURCE_MAP.md, met
telkens een andere beschrijving: het gevolg van takken die naast elkaar
dezelfde regel aanvulden en waarvan de merge beide kopieën liet staan.
Dat is erger dan een ontbrekende regel, want de lezer weet niet welke
van de twee nu geldt.

Per bestand blijft de regel over die de code van vandaag beschrijft. Bij
`marp_html_service.dart` was dat een keuze: één kopie noemde de export
uitdrukkelijk *niet* zelfstandig, met een correctienotitie van 22-07
00:22 — maar de tak die de export wél zelfstandig maakte landde diezelfde
ochtend om 09:16. De correctie was dus zelf achterhaald; `export_service`
geeft `embedImage` mee, dus de afbeeldingen gaan als `data:`-URI mee.

Onderdeel van #518.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SOURCE_MAP.md beloofde een regel per bestand onder lib/ en miste er 156.
Een kaart die stilzwijgend gedeeltelijk is, is erger dan een die zegt wat
hij bestrijkt: wie een bestand niet vindt, weet niet of het ontbreekt of
of het er bewust niet in staat.

Gekozen voor versmallen mét aanvullen, langs de korrel die de code zelf
heeft: een regel per *library*. Een `part`-bestand erft de regel van zijn
library (mag er zelf een hebben, maar dat is geen belofte), een
conditional-import-helft valt onder zijn façade, en een uniforme familie
wordt als groep beschreven met een glob die nog echte bestanden moet
raken. Alles wat daarbuiten valt is nu werkelijk beschreven: 43 regels
erbij (modellen, services, providers, thema's, dialogen, editors en de
documentatielezer).

test/source_map_coverage_test.dart houdt die vier regels tegen de boom,
plus twee die er stil naast lagen: geen bestand met twee beschrijvingen,
en geen regel die naar een verdwenen bestand wijst.

Onderdeel van #518.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CONTRIBUTING.md had een regel voor l10n maar geen voor de taal van
commentaar, terwijl het commentaar ruwweg half Nederlands en half Engels
is — soms binnen één bestand. Zonder afspraak is elke keuze te
verdedigen en elke review een smaakdiscussie.

Niet verzonnen maar gemeten over lib/: van de ~3.100 commentaarblokken
van drie regels of meer is 71% Nederlands, 28% Engels, en mengt 1% de
twee bínnen één blok. De regel die dat gedrag beschrijft is dus niet
"één taal voor het project" maar "één taal per commentaar".

Nadrukkelijk erbij: bestaand commentaar niet herschrijven om de taal.
Dat verandert niets aan het gedrag, is duizenden regels ruis, en zet je
naam in git blame onder redeneerwerk van iemand anders.

Onderdeel van #518.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(poort): tel de ratchet per klasse, niet per bestand
Some checks failed
CI / Gate (Linux) · Format · Analyze · Coverage (pull_request) Failing after 4s
CI / Web hardening (pull_request) Failing after 5s
CI / Docs links (pull_request) Failing after 5s
CI / Supply-chain (Trivy · advisory) (pull_request) Failing after 5s
CI / Gate (Linux) · Format · Analyze · Coverage (push) Failing after 4s
CI / Web hardening (push) Failing after 4s
CI / Docs links (push) Failing after 4s
CI / Supply-chain (Trivy · advisory) (push) Failing after 5s
CI / Test (macos-latest) (pull_request) Has been cancelled
CI / Test (windows-latest) (pull_request) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
0e6fb84beb
De bestandsgrootte-ratchet staat op 1000 regels, maar telt bestanden. Een
`part`-splitsing haalt die teller onderuit zonder dat er iets kleiner
wordt: `TabsNotifier` staat op ~2.400 regels over zeven `part`-bestanden
en `_SettingsDialogState` op ~7.300 over tweeëntwintig — elk bestand
netjes onder de duizend, de klasse allang niet meer. De poort lag stil op
precies de plek waar hij voor bedoeld was.

`maxClassLines` telt nu per klasse, opgeteld over álle `part`-bestanden
van haar library: de klasse plus elke `extension … on` die eraan hangt.
Hetzelfde getal als de bestandsgrens, met opzet — de belofte was "geen
eenheid boven de duizend regels", en dit herstelt hem voor de eenheid die
je in je hoofd moet houden om iets te wijzigen.

Vijftien klassen zitten er vandaag boven; die staan met hun huidige
omvang in `classSizeBaseline` en mogen alleen krimpen. De sleutel is
`<library>#<Naam>` en niet `<bestand>#<Naam>`, zodat twee private
`_FooState`s in verschillende schermen niet bij elkaar worden opgeteld.
Een basislijnregel zonder klasse faalt ook — een plafond zonder klasse
dekt de volgende klasse niet af.

De teller leest regels in plaats van een AST; dat mag omdat
`make format-check` de opmaak vastlegt. De bewijslast daarvoor ligt in
test/class_size_ratchet_test.dart: de part-splitsing, gelijknamige
private klassen, een kop over meer regels, een enum op één regel, een
mixin-toepassing zonder body, en een '''-tekst met een accolade op kolom
0. Die laatste twee waren echte fouten in de eerste versie.

Onderdeel van #518.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit bfa78c0f9e into main 2026-07-22 13:45:51 +00:00
brenno deleted branch feat/structuurschuld 2026-07-22 13:45:51 +00:00
Sign in to join this conversation.
No description provided.