fix(export): een deck zonder afbeeldingen liet de PDF-export omvallen (#714) #781

Merged
brenno merged 4 commits from fix/pdf-export-clamp-714 into main 2026-07-23 22:37:08 +00:00
Owner

Repareert #714 — en de melding was letterlijker dan hij leek.

De oorzaak

final number = (done + 1).clamp(1, total);   // export_dialog.dart

Dart's clamp weigert een bovengrens onder de ondergrens en gooit dan
ArgumentError(lowerLimit). Bij total == 0 is dat dus ArgumentError(1), en
de tekst daarvan is Invalid argument(s): 1. Wat de gebruiker zag was de
naam van de ondergrens.

total is nul wanneer het deck geen enkele afbeelding heeft: de rasteraar
meldt de precache-stap met het aantal te laden afbeeldingen. number werd
berekend vóór de switch, dus ook voor precache — juist de tak die total == 0
netjes afvangt.

Waarom de melding zo raadselachtig was

Waarneming in #714 Verklaring
Alleen een pentestrapport Bevindingen, checklists, scope-matrices, tabellen — geen enkele foto. Het schoolvoorbeeld van een deck zonder afbeeldingen.
HTML lukte op hetzelfde deck HTML rastert niet en komt hier nooit langs.
Videodia's weghalen hielp niet Het lag aan geen enkel diatype.
Geen enkele poort ving het Alle exporttoetsen roepen ExportService rechtstreeks aan en de rasteraar zonder onStage-callback. Dan draait deze functie nooit.

Die laatste rij is de eigenlijke bevinding: de fout zat in een laag waar geen
toets langskwam, en niet in de code die iedereen wél toetste.

De reparatie

Eén regel: bij total < 1 vervalt de bovengrens. De ondergrens 1 blijft — een
dia heet "1" en niet "0".

De borging, in twee lagen

  1. De exportknop door de héle dialoog (export_dialog_pdf_end_to_end_test),
    op het deck uit de melding, onbewerkt én gecomprimeerd. Die toets stond eerst
    rood met exact de gemelde tekst. Hij dekt meteen de zeven argumenten die de
    dialoog meegeeft en die in elke bestaande exporttoets hun standaardwaarde
    hadden: audience, themeProfile, tlp, enforcementPolicy, metadata,
    de doelmap en de voortgangs-callbacks.
  2. De grens zelf, los (export_progress_text_test, elf toetsen). Daarvoor
    is _stageText uit het venster getrokken naar exportProgressText — pure
    tekstkeuze, net als export_failure_text.dart al was. Dat is hier geen
    smaakkwestie: dát de functie privé op de dialoogstate zat, ís waarom #714 er
    ongezien in kon zitten.

Rood gezien: de clamp terugzetten laat beide lagen omvallen, met
ArgumentError: Invalid argument(s): 1.

Foutklasse afgelopen

grep op .clamp( in lib/: twee andere variabele-bovengrenzen zijn
aantoonbaar veilig (editor_provider guardt count <= 0, tabs_provider_tab_info
klemt de bovengrens zelf af). Eén plek — slide_layout_metrics.dart:319,
clamp(1.0, availW) — heeft hetzelfde patroon maar geen aantoonbaar bereikbaar
pad; apart uitgezet in plaats van hier stilzwijgend meegenomen.

Nog iets rechtgezet

Het klasseplafond viel (1002 van 1000) door het commentaar bij de reparatie.
Niet de uitleg ingekort en niet de basislijn opgehoogd: de methode hoorde daar
sowieso niet. Klasse nu 936.

Poort

make check groen, check-secrets en sast schoon, gerebased op main en
daarna opnieuw getoetst. CHANGELOG en SOURCE_MAP bij; geen nieuwe
l10n.d('…')-string, geen afhankelijkheidswijziging.

Closes #714.

Repareert #714 — en de melding was letterlijker dan hij leek. ## De oorzaak ```dart final number = (done + 1).clamp(1, total); // export_dialog.dart ``` Dart's `clamp` weigert een bovengrens onder de ondergrens en gooit dan `ArgumentError(lowerLimit)`. Bij `total == 0` is dat dus `ArgumentError(1)`, en de tekst daarvan is **`Invalid argument(s): 1`**. Wat de gebruiker zag was de naam van de ondergrens. `total` is nul wanneer het deck **geen enkele afbeelding** heeft: de rasteraar meldt de precache-stap met het aantal te laden afbeeldingen. `number` werd berekend vóór de `switch`, dus ook voor precache — juist de tak die `total == 0` netjes afvangt. ## Waarom de melding zo raadselachtig was | Waarneming in #714 | Verklaring | | --- | --- | | Alleen een pentestrapport | Bevindingen, checklists, scope-matrices, tabellen — geen enkele foto. Het schoolvoorbeeld van een deck zonder afbeeldingen. | | HTML lukte op hetzelfde deck | HTML rastert niet en komt hier nooit langs. | | Videodia's weghalen hielp niet | Het lag aan geen enkel diatype. | | Geen enkele poort ving het | Alle exporttoetsen roepen `ExportService` rechtstreeks aan en de rasteraar **zonder** `onStage`-callback. Dan draait deze functie nooit. | Die laatste rij is de eigenlijke bevinding: de fout zat in een laag waar geen toets langskwam, en niet in de code die iedereen wél toetste. ## De reparatie Eén regel: bij `total < 1` vervalt de bovengrens. De ondergrens 1 blijft — een dia heet "1" en niet "0". ## De borging, in twee lagen 1. **De exportknop door de héle dialoog** (`export_dialog_pdf_end_to_end_test`), op het deck uit de melding, onbewerkt én gecomprimeerd. Die toets stond eerst rood met exact de gemelde tekst. Hij dekt meteen de zeven argumenten die de dialoog meegeeft en die in elke bestaande exporttoets hun standaardwaarde hadden: `audience`, `themeProfile`, `tlp`, `enforcementPolicy`, `metadata`, de doelmap en de voortgangs-callbacks. 2. **De grens zelf, los** (`export_progress_text_test`, elf toetsen). Daarvoor is `_stageText` uit het venster getrokken naar `exportProgressText` — pure tekstkeuze, net als `export_failure_text.dart` al was. Dat is hier geen smaakkwestie: dát de functie privé op de dialoogstate zat, ís waarom #714 er ongezien in kon zitten. **Rood gezien:** de clamp terugzetten laat beide lagen omvallen, met `ArgumentError: Invalid argument(s): 1`. ## Foutklasse afgelopen `grep` op `.clamp(` in `lib/`: twee andere variabele-bovengrenzen zijn aantoonbaar veilig (`editor_provider` guardt `count <= 0`, `tabs_provider_tab_info` klemt de bovengrens zelf af). Eén plek — `slide_layout_metrics.dart:319`, `clamp(1.0, availW)` — heeft hetzelfde patroon maar geen aantoonbaar bereikbaar pad; apart uitgezet in plaats van hier stilzwijgend meegenomen. ## Nog iets rechtgezet Het klasseplafond viel (1002 van 1000) door het commentaar bij de reparatie. Niet de uitleg ingekort en niet de basislijn opgehoogd: de methode hoorde daar sowieso niet. Klasse nu 936. ## Poort `make check` groen, `check-secrets` en `sast` schoon, gerebased op main en daarna opnieuw getoetst. CHANGELOG en SOURCE_MAP bij; geen nieuwe `l10n.d('…')`-string, geen afhankelijkheidswijziging. Closes #714.
Alle bestaande exporttoetsen roepen ExportService.export rechtstreeks
aan. Dat slaat zeven argumenten over die de dialoog wél meegeeft —
audience, themeProfile, tlp, enforcementPolicy, metadata, doelmap en de
voortgangs-callbacks — en precies daar zat #714.

Deze toets drukt de knop zoals een gebruiker dat doet, op het deck uit
de melding. Hij staat nu rood met exact de gemelde tekst: 'Invalid
argument(s): 1'.

Twee valkuilen die dit bestand kostte, beide vastgelegd in het
commentaar: Directory.systemTemp.createTemp() hoort in setUp (in een
testWidgets-body komt een echte dart:io-future nooit terug uit de
FakeAsync-zone, en de toets loopt zonder melding in zijn timeout), en de
uitslag staat in een SelectableText en niet in een Text — daar kijken
levert een toets op die een mislukking niet ziet.

Dat eerste heeft me bij de vorige ronde een verkeerde conclusie
opgeleverd ('de dialoog hangt op een bevestigingsvenster'); de dialoog
is prima aanstuurbaar.
`(done + 1).clamp(1, total)` in _stageText gooit ArgumentError(1) zodra
total onder 1 zakt — Dart's clamp weigert een bovengrens onder de
ondergrens. De rasteraar meldt de precache-stap met total = het aantal
te laden afbeeldingen, en dat is nul voor een deck zonder ook maar één
afbeelding. De melding die de gebruiker zag was dus letterlijk de naam
van de ondergrens: 'Invalid argument(s): 1'.

Dat verklaart alles wat de melding raar maakte. Een pentestrapport is
het schoolvoorbeeld: bevindingen, checklists, scope-matrices, tabellen —
geen enkele foto. HTML lukte op hetzelfde deck omdat die niet rastert en
er dus nooit langskomt. En geen enkele toets ving het, omdat ze allemaal
de rasteraar zonder onStage-callback aanriepen; dan wordt _stageText
nooit aangeroepen.

De ondergrens 1 blijft staan — een dia heet '1' en niet '0'. Bij total
== 0 is er alleen geen bovengrens om tegen te knippen.
Het klasseplafond viel (1002 van 1000) door het commentaar bij de
reparatie. Niet de uitleg inkorten en niet de basislijn ophogen: deze
methode hoort daar sowieso niet.

exportProgressText is pure tekstkeuze — geen toestand, geen widgets —
precies zoals export_failure_text.dart dat al is, en om dezelfde reden
losgetrokken. Hier is dat geen smaakkwestie: dat de functie privé op de
dialoogstate zat, ís waarom #714 er ongezien in kon zitten. De fout was
alleen zichtbaar door de héle dialoog aan te sturen, en zo'n toets
schrijft niemand voor een regel tekst.

Nu staat de grens in elf goedkope toetsen. Rood gezien: de clamp
terugzetten laat ze omvallen met ArgumentError: Invalid argument(s): 1 —
letterlijk de gemelde tekst.

Klasse nu 936 regels.
docs(export): de reparatie en de blinde vlek vastgelegd (#714)
All checks were successful
ci / gate (pull_request) Successful in 51m6s
54dee803df
CHANGELOG met de oorzaak en waarom de melding zo raadselachtig leek, en
SOURCE_MAP met het nieuwe bestand — inclusief de valstrik dat `total`
tijdens precache afbeeldingen telt en dus nul mag zijn.
brenno merged commit cc59a2106c into main 2026-07-23 22:37:08 +00:00
Sign in to join this conversation.
No description provided.