Skip to content

Rapport: voet met legenda op elke pagina (#113, nabespreking) - #135

Merged
Nozzit merged 7 commits into
mainfrom
claude/report-footer-per-page
Sep 14, 2026
Merged

Nozzit merged 7 commits into
mainfrom
claude/report-footer-per-page

Conversation

@Nozzit

@Nozzit Nozzit commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

What and why

Vervolg op #132 (issue #113) — gestapeld op die branch (bevat de laatste #132-commit 1cf581b via merge); de base kan na de merge van #132 naar main worden gezet. Refs #113. Let op: ci.yml triggert alleen op PR's naar main, dus op deze PR draait geen CI zolang de base de #132-branch is; tot de retarget zijn de lokale suites hieronder de poort.

De Gantt-afdruk tekent één voetstrook (projectnaam, afdrukdatum, legenda, merk) onder de laatste rij, en de pagineerders herhaalden alleen de kop: bij elke meerpagina-afdruk stond de legenda dus alleen op de laatste pagina. Dat bestond al, maar "een blad per persoon" (#132) maakt het onhoudbaar — een uitgedeeld vel met rode en blauwe balken zonder legenda (bevinding 5 van de hyperkritische review op #132).

De voet is nu, net als de kop, een herhaalbaar blok:

  • RenderReportResult.footerHeight (verplicht, zoals headerHeight): de render meldt zijn voethoogte (schaalt mee met de rapport-lettergrootte; tabelrenders en de lege staat melden 0).
  • tileLayout.computeTileLayout kent repeatFooterHeightPx: de body loopt van onder de kop tot boven de voet (repeatFooterSrcY); elke pagina houdt onderaan het printgebied footerTopPt vrij (boven de paginanummer-marge, nu PAGE_NUMBER_PT). De voet wordt uit één vast bronvenster getekend, footerWindow (x vanaf 0, één paginabreedte, op de linkermarge) — níét per kolomvenster gesneden — en de render legt de voetinhoud binnen PrintOptions.footerLayoutWidth (= footerLayoutWidthFor(layout): één paginabreedte zodra de voet herhaald wordt, anders undefined = de oude render), zodat naam, legenda en merk op élk vel compleet zijn, ook met de tijdlijn over meerdere pagina's. Herhaald wordt er alleen als er meer dan één vel is: meer dan één rij, of meer dan één kolom, of een gedwongen paginaovergang (blad per resource); past alles op één vel, dan blijft de voet onder de laatste rij. Zonder de optie is de tegeling én de render byte-identiek (op het weggehaalde nepnummer na, zie onder); dezelfde degeneratievangnetten als de kop.
  • Raster-pagineerder (paginate.ts), vector-pagineerder (paginateVector.ts) én de preview (renderPrintPreviewPage) tekenen de strook één keer per pagina uit dat venster, alle drie via dezelfde footerLayoutWidthFor. De vector-tak doet bij meer dan één kolom één extra render-pass met de voetbreedte (zoals de CJK-pass; alleen als die breedte écht kleiner is dan het canvas, met herhaalde coverage-poort); raster-export en preview kennen de layout al vóór hun render. De preview rendert per pagina één volledige pass extra voor de strook, net als voor de kop (staat in CLAUDE.md; een offscreen-cache voor kop én voet is een aparte klus).
  • Instelling Voet op elke pagina herhalen (repeatFooter, standaard aan zoals de kop) in ops-reportSettings, checkbox naast "Kop op elke pagina herhalen"; 14 locales; gids nl+en (optiebullet met de kosten, het éénpagina-geval en het paginanummer; in de resourcediagram-sectie vervangt "elk uitgedeeld vel zijn eigen legenda" de zin dat de voet op het laatste vel staat).
  • Het vaste "Pagina 1 van 1" in de voet is weg (het echte "n / totaal" drukken de pagineerders in de ondermarge) — ook met de optie uit; de daarmee dode labels.page/labels.of zijn uit PrintOptions, ReportPanel, vijf testfixtures en de 14 report.json-locales.

Schermbewijs: artifacts/resourcediagram/03-voet-op-elke-pagina-pagina-2.png — pagina 2 van het resourcediagram met blad per resource, mét voet.

Hyperkritische reviews (subagent, Opus), drie rondes. Ronde 1 ("nee, nog niet", verwerkt in 59ab7a1): voet per kolom gesneden ⇒ legenda in één kolom; éénpagina-afdruk kreeg de voet aan de onderrand; footerHeight optioneel; dode labels + weessleutels; browsertest telde vensters i.p.v. inhoud; kosten ongedocumenteerd; fixture op de cachegrens; moduledoc/naam. Ronde 2 ("nee", verwerkt in 99d6107): één rij × N kolommen kreeg geen herhaling én een in kolom 1 geperste, gesneden voet; voetbreedte ging onvoorwaardelijk mee ⇒ UIT-stand ≠ oude render, drie paden uiteen; browsertest bewees geometrie, geen inhoud; hoogtefilter aan lettergrootte 100 % geklonken; twee bovengrenzen voor gedwongen posities; één fixture over het hoofd gezien. Ronde 3 ("ja — mag door", restpunten verwerkt in de laatste commit): loze extra vector-pass door float-< (~5 % van de breedtes); coverage-poort na die pass; testparser met q/Q-stack en hoogte t.o.v. de papierhoogte; gids over het paginanummer; previewkosten in CLAUDE.md. De reviewer mat de PDF-vorm zelf na met een eigen headless vector-export in acht configuraties en vond geen tekstlek tussen body en voet.

How it was verified

  • npm run verify green — lokaal op de Rapport: voet met legenda op elke pagina (#113, nabespreking) #135-branch: typecheck, lint, cycles, verify:docs (34 × 14), verify:i18n, de rapportbatterijen (check-print-report, check-reports, beide settings-checks, plus check-today-label, check-split-bar-render, check-print-working-exceptions, check-print-raster-export-streaming, check-dependency-style voor de labelverwijdering), de volledige planningssuite (exit 0, incl. tijdzone-matrix) en de volledige browsersuite (142/144 — de twee bekende sandbox-fouten in just-updated-dialog.spec.ts op de proxy-CA, niet op CI; zie Rapport: resourcediagram — de Gantt-afdruk per resource, met een blad per persoon (#113) #132). Na de laatste commit opnieuw: typecheck, lint, docs, check-print-report en report-footer.spec.ts (2/2).
  • Nieuwe regressie: tests/planning/check-print-report.ts (voethoogte gemeld en geschaald; voet = precies het stuk onder de laatste rijgrens; tegeling zonder optie byte-identiek en de voet hangt dan aan de laatste tegel; met optie eindigt de laatste body-tegel boven de voet, pagina's eindigen nog op rijgrenzen, kop + body + voet passen in het printgebied; te hoge voet ⇒ niet herhaald; zonder kop en in actual; twee kolommen: footerWindow = één paginabreedte vanaf x=0, en met footerLayoutWidth staat alle voettekst incl. het merk binnen die breedte zonder dat de canvasmaten veranderen; éénpagina ⇒ voet niet herhaald, tegeling identiek; één rij × twee kolommen ⇒ wél herhaald, ook in actual; blad-per-resource ⇒ wél; gedwongen positie in de voetstrook zet de herhaling niet aan; footerLayoutWidthFor undefined zonder herhaling; geen nep-paginanummer), nieuw tests/planning/check-report-footer-setting.ts (default aan, opslag van vóór de optie, rommel, round-trip; bedraad in run.sh), tests/browser/report-footer.spec.ts (parseert per PDF-pagina de tegelvensters met een q/Q-stack: precies één voetvenster op de onderrand van het printgebied, vanaf x = 24, één volle printbreedte breed en met ≥ 4 tekstoperatoren — mét voet op elke pagina, óók met "Tijdlijn over 2 pagina's"; zonder voet nergens; tweede scenario: 6 taken ⇒ één vel zonder los voetvenster, met de tijdlijn over 2 pagina's ⇒ twee vellen die elk de volle voet mét tekst dragen).
  • tests/browser/report-preview-performance.spec.ts: fixture 260 → 200 rijen (6 van maxPages 7, met marge). Die test materialiseert bewust de hele preview; met 260 rijen stond het paginatotaal precies op de grens en kost de voet per pagina één strook body.

Does this touch

  • Project data — n.v.t. (app-voorkeur in ops-reportSettings).
  • Scheduling logic — n.v.t.
  • User-visible textrepeatFooter via t(...) in alle 14 report.json-locales (+ page/of verwijderd); verify:i18n groen.
  • @tauri-apps/* — n.v.t.

Documentation

  • public/docs/{nl,en}/gids-rapporten-printen.md: bullet Voet op elke pagina herhalen (incl. meerdere kolommen, éénpagina-geval, de kosten, de botsing met blad-per-resource en het paginanummer in de ondermarge); resourcediagram-sectie aangepast.
  • CLAUDE.md: rapportparagraaf (voet als herhaalbaar blok, de keten footerHeightrepeatFooterHeightPx/repeatFooter, de previewkosten).
  • docs/CHANGELOG.md bewust niet (alleen bij een release).

🤖 Generated with Claude Code

https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq

De Gantt-afdruk tekent één voetstrook (projectnaam, afdrukdatum, legenda, merk) onder de laatste
rij; de pagineerder herhaalde alleen de kop, dus bij een meerpagina-afdruk stond de legenda
uitsluitend op de laatste pagina. Met "een blad per persoon" (#132) is dat geen randgeval meer:
een uitgedeeld vel zonder legenda is onleesbaar (hyperkritische review op #132, bevinding 5).

De voet is nu, net als de kop, een herhaalbaar blok:
- `RenderReportResult.footerHeight` (de render meldt zijn voethoogte, schaalt mee met de
  rapport-lettergrootte; tabelrenders en de lege staat hebben er geen);
- `tileLayout.computeTileLayout` kent `repeatFooterHeightPx`: de body loopt van onder de kop tot
  boven de voet (`repeatFooterSrcY`), elke pagina houdt onderaan het printgebied `footerTopPt`
  vrij; zonder de optie byte-identiek, met dezelfde degeneratievangnetten als de kop;
- raster-pagineerder, vector-pagineerder én preview tekenen de strook per pagina uit hetzelfde
  bronvenster (in de vector-tak komt de voettekst daardoor per pagina mee, zoals de koptekst);
- instelling `repeatFooter` (standaard aan, zoals de kop) in ops-reportSettings, checkbox naast
  "Kop op elke pagina herhalen", 14 locales, gids nl+en.

Het vaste "Pagina 1 van 1" in de voet is weg: de render kent het paginatotaal niet en het zou nu
op elke pagina zo staan; het echte "n / totaal" drukken de pagineerders al in de ondermarge.

Regressie: check-print-report.ts (voethoogte, tegeling met/zonder voet byte-identiek, laatste
body-tegel eindigt boven de voet, rijgrenzen blijven, degeneratie, zonder kop en in 'actual',
samen met blad-per-resource, geen nep-paginanummer), nieuw check-report-footer-setting.ts,
browserspec report-footer.spec.ts (telt per PDF-pagina de XObject-tekeningen: 3 mét voet, 2 zonder;
preview rastert opnieuw). report-preview-performance.spec.ts: fixture 260 → 240 rijen, want het
paginatotaal stond precies op de previewcachegrens en de voet kost per pagina één strook body.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq
Nozzit pushed a commit that referenced this pull request Sep 14, 2026
…ollator, één relatiepredicaat (review ronde 2)

Tweede hyperkritische review op #132 ("ja, mits"), verwerkt:

N1  De bandvolgorde hing aan de host-locale (`localeCompare(b, undefined, …)`): dezelfde
    planning gaf op een Zweedse machine een andere bladnummering dan op een Nederlandse.
    `computeResourceGanttRows` krijgt nu `opts.locale` (de app-taal, `i18n.language`); één
    `Intl.Collator` voor sortering én gelijknaamdetectie. Test: en ⇒ Älg vóór Zorg, sv ⇒ erna.
N5  `Jan`/`jan`/` Jan ` kregen geen volgnummer maar sorteerden wél als gelijk. Duplicaten worden
    nu met dezelfde collator (sensitivity base) gegroepeerd ⇒ `Jan #1`, `jan #2`, `Jan #3`.
N4  Het surrogaat `#n` voor een naamloze resource deelt de naamruimte met een resource die
    letterlijk "#2" heet; surrogaten gaan nu door dezelfde duplicaatpas (`#2 #1`, `#2 #2`).
N6  `showDeps`-forcering en verborgen vinkje hangen aan één predicaat,
    `reportTypeDrawsRelations()` in reportSettings, headless bewaakt (plus: showDeps staat
    standaard aan, dus de forcering is dragend).
N8  `counts.assignments` telt records i.p.v. rijen — dezelfde telling als Resourcetoewijzingen.
N9  `startOf` weg; `taskStart` uit reportCommon.
N7  `RESOURCE_GANTT_NONE_KEY` niet meer geëxporteerd; `resourceBandLabels` blijft publiek en
    wordt nu rechtstreeks getest.
N2  Gids (14 talen): "verzameltaken worden nooit toegewezen" was onwaar — MSPDI/MPP-importeurs
    maken zulke toewijzingen wél; nu: "een toewijzing op een verzameltaak (uit een import) wordt
    hier niet getoond". Module-commentaar idem.
N10 ja/ko: dubbele zin over "rijen niet van het scherm" weg.
N11 nl/en: de reden voor "geen relaties" eerlijker geformuleerd (kan onder meerdere banden
    staan; pijlen zouden bij blad per resource van het vel lopen).

N3 (het vaste "Pagina 1 van 1" in de voet) zit in #135, samen met de voet op elke pagina.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq
…gina's (review op #135)

Hyperkritische review op #135 ("nee, nog niet"), verwerkt:

1. De voet werd per KOLOMvenster gesneden: met de tijdlijn over N pagina's (of een horizontaal
   getegelde 'actual'-export) stond de legenda in één kolom en kregen de andere pagina's een
   lege grijze strook — terwijl de gids "elk vel zijn eigen legenda" beloofde. Nu: de render
   legt de voetinhoud binnen `PrintOptions.footerLayoutWidth` (= `TileLayout.footerLayoutWidthPx`,
   één paginabreedte; achtergrondstrook blijft canvasbreed), en raster-pagineerder, vector-
   pagineerder én preview tekenen de voet één keer per pagina uit het vaste `footerWindow`
   (x vanaf 0, één paginabreedte, op de linkermarge). De vector-tak doet daarvoor bij meer dan
   één kolom één extra render-pass met die breedte (zoals de CJK-pass); raster en preview kennen
   de layout al vóór hun render.
4. Eénpagina-afdruk: de voet sprong van onder de laatste rij naar de onderrand van het papier
   (≈20 cm wit). Past de bron (kop meegerekend) op één pagina en dwingt geen gedwongen
   breekpositie een tweede af, dan wordt de voet niet herhaald — byte-identiek aan zonder optie.
5. `RenderReportResult.footerHeight` is verplicht (zoals `headerHeight`): 0 in pdfTable (2×) en
   in de lege render; de `?? 0`-terugvallen zijn weg.
6. `labels.page`/`labels.of` waren dood sinds het nepnummer uit de voet is: uit `PrintOptions`,
   `ReportPanel`, vier testfixtures en de 14 `report.json`-locales (28 weessleutels).
7. Browsertest bewaakt nu inhoud i.p.v. venstertelling: per PDF-pagina één clip-rechthoek op de
   onderrand van het printgebied, laag, vanaf x = 24 en één volle printbreedte breed — ook met
   de tijdlijn over 2 pagina's (kolom 2 krijgt dus niet de kolomsnede); zonder optie op geen
   enkele pagina; vacuüm-`every` uitgesloten.
3. Gids nl/en: de kosten (±2 rijen minder per vel, soms een pagina meer, een resource die net
   op één vel paste kan bij "blad per resource" over twee gaan) en het éénpagina-geval.
8. report-preview-performance: fixture 240 → 200 rijen (6 pagina's bij maxPages 7, met marge).
9. tileLayout-moduledoc bijgewerkt (drie assen, "oude gedrag"-defaults, TileBodyRow);
   `FOOTER_PT` → `PAGE_NUMBER_PT` zodat het niet met `repeatFooter*` verwart.

Headless: footerWindow = één paginabreedte vanaf x=0 bij twee kolommen; met `footerLayoutWidth`
staat alle voettekst (merk incl.) binnen die breedte en veranderen de canvasmaten niet;
éénpagina ⇒ voet niet herhaald, tegeling identiek; blad-per-resource (gedwongen posities) ⇒ wél.

Niet gedaan (bevinding 2): de preview rendert per pagina een derde volledige pass voor de
voetstrook, zoals dat voor de kop al gold — een offscreen-cache voor kop én voet is een aparte
klus. De previewkosten zijn niet gemeten.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq
…een bij herhaling (review #135 ronde 2)

B1  `multiPage` keek alleen omlaag: één rij hoog maar horizontaal over N vellen getegeld gold als
    "past op één pagina" ⇒ voet niet herhaald, en — omdat de voetbreedte tóch meeging — in kolom 1
    geperst terwijl hij per kolom gesneden werd: lege grijze strook op kolom 2..N (omslagpunt
    ±36 taken op A3 liggend). Nu: `cols > 1` telt als meerdere vellen.
B2  `footerLayoutWidth` ging in preview en raster-export onvoorwaardelijk mee (vector niet) ⇒ de
    UIT-stand week af van vóór de optie en de drie paden gaven drie uitkomsten. Eén helper
    `footerLayoutWidthFor(layout)` in tileLayout (undefined zonder herhaling) voor alle drie.
B3  Browsertest bewaakt nu inhoud: per pagina het voetvenster (`q … re … Q`) met ≥ 4
    tekstoperatoren (naam, datum, merk, legenda), x = marge, volle printbreedte — plus een
    tweede scenario: 6 taken × "tijdlijn over 2 pagina's" (één rij hoog, twee vellen breed), waar
    de voet op beide vellen compleet moet zijn en zonder kolommen juist aan de body blijft.
B4  Hoogtefilter in de browsertest ruimer (tot 80 pt) zodat lettergrootte 125 % ook past; een
    body-tegel op de onderrand is een volle pagina en dus honderden punten hoog.
B5  `forcesSecondPage` toetst gedwongen posities tegen `ch - voet` (zelfde bovengrens als
    `inBody` ná de beslissing), met commentaar over de circulariteit; headless bewaakt.
B6  Laatste fixture met `page`/`of` opgeschoond.

Headless: één rij × twee kolommen ⇒ voet herhaald en body-tegel eindigt boven de voet; 'actual'
met meerdere kolommen idem; `footerLayoutWidthFor` undefined zonder herhaling.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq
… testparser met q/Q-stack (review #135 ronde 3)

F1  De extra render-pass voor de voetbreedte draaide bij een kale float-`<` in ~5 % van de
    breedtes voor niets (col0Bodypx en cw analytisch gelijk, als float 1e-13 uit elkaar): nu
    alleen bij `footerLayoutWidth < dims.width - 0.5`, met commentaar dat zegt wat er getoetst wordt.
F2  Na die pass wordt de coverage-poort herhaald: een smallere voet kan alleen legenda-items
    weglaten, maar dat is nu geen ongeschreven aanname meer.
F3  Browsertestparser: tegelvensters met een stack voor geneste q/Q (halfdoorzichtige tekst)
    i.p.v. "tot de eerste Q"; de voethoogte getoetst tegen de halve papierhoogte i.p.v. een vaste
    band, zodat een smal rapport (schaal > 0,75) de UIT-assertie niet vals groen maakt.
F4  Gids nl/en: het paginanummer staat altijd in de ondermarge, de voet draagt er geen meer —
    ook met de optie uit is dat anders dan vóór deze PR.
F5  CLAUDE.md: de preview rendert per pagina één volledige pass extra voor de voetstrook.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq
@Nozzit
Nozzit changed the base branch from claude/newest-issue-5kqmzu to main September 14, 2026 12:41
@Nozzit
Nozzit merged commit 6cabb16 into main Sep 14, 2026
8 checks passed
Nozzit pushed a commit that referenced this pull request Sep 14, 2026
, rapportvoet #135)

Conflicten in `reportSettings.ts` (periode-sleutels naast de resourcediagram-opties) en de
import van `check-reports.ts` (periodehulpen naast `computeResourceGanttRows`); beide additief
samengevoegd. Alle poorten groen op het resultaat.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JoVB3BSFPZoKLvL6NmWRKy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants