fix(import): voer presentatievalidatie en parsing buiten de UI-isolate uit (#875) #902

Merged
brenno merged 5 commits from fix/875-import-off-isolate into main 2026-07-26 16:46:18 +00:00
Owner

Wat

Haalt de presentatie-import (#875, P0) van de UI-isolate af. Formaatvalidatie,
ZIP-decodering, XML/IWA/Snappy-parsen, reconstructie en classificatie draaiden
synchroon op de isolate die de UI tekent — bij een middelgroot, complex of
vijandig bestand bevroor het venster daardoor secondenlang, zonder betrouwbaar
annuleren en zonder verschil tussen "lang bezig" en "vastgelopen".

Hoe

  • Serialiseerbaar taakcontract (pipeline/import_task.dart): ImportRequest
    → sealed ImportTaskResult, met ImportProgress en de coöperatief
    annuleerbare gedeelde kern parseAndClassify.
  • Platform-gesplitste runner (pipeline/import_runner*.dart): op desktop een
    worker-isolate (Isolate.spawn, kill bij annuleren, één uitkomst achter een
    latch); op web in-process met yields. Alleen de lichte deckbouw blijft op de
    hoofd-isolate — die raakt WebAssetStore en de l10n-vertaler, die geen van
    beide over een isolategrens reizen.
  • Annuleren: coöperatief tussen begrensde eenheden en, op de worker, een
    directe kill. Stopt binnen een gedocumenteerde tijd en laat niets half af.
    Nieuw annuleerbaar voortgangsvenster voor de enkelvoudige import; de wachtrij
    stopt nu ook midden in een bestand.
  • Budget: ImportBudget krijgt de maxDuration-deadline die #874 al had
    voorzien (overschrijding → tooLarge).

Acceptatiecriteria (#875)

  • UI aantoonbaar responsief — een test bewijst dat de hoofd-isolate blijft
    tikken tijdens het parsen van een groot deck.
  • Voortgang geordend, eindigt precies één keer (succes/fout/annulering).
  • Annuleren binnen een gedocumenteerde tijd, geen gedeeltelijke uitvoer
    (service- én bulk-niveau, ook midden in een bestand).
  • Bestaande ImportFailure-redenen en per-dia-resultaten behouden.
  • Desktop en web hebben een expliciet en getest uitvoeringspad.

Toetsing

make check volledig groen: 6720 tests, dekking 86,6%, per-file-floor 0 onder
20%, alle statische poorten. Nieuwe tests dekken het isolate-, web-, annuleer-,
deadline- en foutpad; de twee bestaande widget-tests draaien via de
debugImportTaskRunner-haak in-process (fake-async kan geen echte isolate
aansturen).

Sluit #875.

## Wat Haalt de presentatie-import (#875, P0) van de UI-isolate af. Formaatvalidatie, ZIP-decodering, XML/IWA/Snappy-parsen, reconstructie en classificatie draaiden synchroon op de isolate die de UI tekent — bij een middelgroot, complex of vijandig bestand bevroor het venster daardoor secondenlang, zonder betrouwbaar annuleren en zonder verschil tussen "lang bezig" en "vastgelopen". ## Hoe - **Serialiseerbaar taakcontract** (`pipeline/import_task.dart`): `ImportRequest` → sealed `ImportTaskResult`, met `ImportProgress` en de coöperatief annuleerbare gedeelde kern `parseAndClassify`. - **Platform-gesplitste runner** (`pipeline/import_runner*.dart`): op desktop een worker-isolate (`Isolate.spawn`, kill bij annuleren, één uitkomst achter een latch); op web in-process met yields. Alleen de lichte deckbouw blijft op de hoofd-isolate — die raakt `WebAssetStore` en de l10n-vertaler, die geen van beide over een isolategrens reizen. - **Annuleren**: coöperatief tussen begrensde eenheden en, op de worker, een directe kill. Stopt binnen een gedocumenteerde tijd en laat niets half af. Nieuw annuleerbaar voortgangsvenster voor de enkelvoudige import; de wachtrij stopt nu ook midden in een bestand. - **Budget**: `ImportBudget` krijgt de `maxDuration`-deadline die #874 al had voorzien (overschrijding → `tooLarge`). ## Acceptatiecriteria (#875) - ✅ UI aantoonbaar responsief — een test bewijst dat de hoofd-isolate blijft tikken tijdens het parsen van een groot deck. - ✅ Voortgang geordend, eindigt precies één keer (succes/fout/annulering). - ✅ Annuleren binnen een gedocumenteerde tijd, geen gedeeltelijke uitvoer (service- én bulk-niveau, ook midden in een bestand). - ✅ Bestaande `ImportFailure`-redenen en per-dia-resultaten behouden. - ✅ Desktop en web hebben een expliciet en getest uitvoeringspad. ## Toetsing `make check` volledig groen: 6720 tests, dekking 86,6%, per-file-floor 0 onder 20%, alle statische poorten. Nieuwe tests dekken het isolate-, web-, annuleer-, deadline- en foutpad; de twee bestaande widget-tests draaien via de `debugImportTaskRunner`-haak in-process (fake-async kan geen echte isolate aansturen). Sluit #875.
Legt de basis om de presentatie-import van de UI-isolate te halen: een
serialiseerbaar opdracht-/resultaat-/voortgangscontract, een gedeelde parse-kern
en een platform-gesplitste runner.

- `ImportBudget` krijgt de `maxDuration`-deadline die #874 al had voorzien; de
  worker toetst hem zelf per werkeenheid. De annuleertoken blijft bewust buiten
  dit const, over de isolategrens gekopieerde object.
- `import_task.dart` — `ImportRequest`/`ImportProgress`/`ParsedPresentation`,
  een sealed `ImportTaskResult`, de coöperatief annuleerbare `parseAndClassify`
  (de uitgetilde `prepare`-stappen) en `runImportTaskInline`. `ImportFailure`
  wordt voor transport tot een verzendbare vorm gesaneerd.
- `import_runner*.dart` — conditional-import gevel: op desktop een worker-isolate
  (`Isolate.spawn`, kill bij annuleren, één uitkomst achter een latch), op web
  in-process met yields. `debugImportTaskRunner` is de testhaak voor widget-tests
  die onder fake-async geen echte isolate kunnen aansturen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`PresentationImportService.prepare` stuurt het zware werk nu via `runImportTask`
naar het worker-uitvoeringspad en houdt alleen de lichte deckbouw op de
hoofd-isolate — die raakt de procesglobale `WebAssetStore` en de l10n-vertaler,
en geen van beide reist over een isolategrens. Injecteert een test een eigen
registry (nep-importers, niet gegarandeerd verzendbaar), dan draait dezelfde
kern in-process.

`prepare`/`importBytes` nemen een optionele `ImportCancelToken`; een gestopte
lezing geeft een `cancelled`-uitkomst terug, zonder deck en zonder uitvoer. De
bulk-rij voedt die token door, zodat annuleren nu óók midden in een bestand
grijpt — het onderbroken bestand telt als niet-bereikt, niet als mislukt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
De enkelvoudige import las vroeger zonder venster: de UI wachtte af en bevroor
bij een groot of vijandig bestand, zonder dat de gebruiker kon zien of stoppen.
Nu draait het lezen op een worker-isolate en toont een klein voortgangsvenster
de stappen met een *Stoppen*-knop die de import netjes afbreekt — niets half af,
niets weggeschreven. De wachtrijdialoog geeft zijn Stop-knop dezelfde token,
zodat ook daar het lopende bestand midden in het lezen stopt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- `import_isolate_test` — de gedeelde kern (web-pad), de echte worker-isolate,
  geordende voortgang, een vrije hoofd-isolate tijdens het parsen, coöperatieve
  en pre-annulering, de tijddeadline en een foutreis over de grens.
- `import_cancellation_test` — geen gedeeltelijke uitvoer bij annuleren, op
  service- en bulk-niveau, ook midden in een bestand.
- `presentation_import_progress_dialog_test` — het venster verwerkt *Stoppen*
  terwijl de import loopt en sluit met de uitkomst.
- De twee bestaande widget-tests draaien de import in-process via
  `debugImportTaskRunner`: onder fake-async kan een widget-test geen echte
  isolate aansturen. `import_note_l10n_test` bewaakt nu ook
  `ImportProgress(x, '…')`. De web-helft van de runner staat in
  `uncoveredBaseline` (nooit geladen onder `flutter test`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(import): documenteer de off-isolate import en annulering (#875)
All checks were successful
scans / scans (pull_request) Successful in 3m22s
eec6207486
CHANGELOG, SOURCE_MAP (de nieuwe contract-/runner-/dialoogbestanden en de
gewijzigde service/bulk/budget) en USER_GUIDE (het venster blijft responsief en
is te stoppen; de wachtrij stopt nu ook midden in een bestand).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit b897eef9d4 into main 2026-07-26 16:46:18 +00:00
Sign in to join this conversation.
No description provided.