fix(callouts): een callout uit de interface overleeft nu het opslaan (#1848) #1858

Merged
brenno merged 3 commits from fix/1848-callout-overleeft-opslaan into main 2026-08-29 19:49:30 +00:00
Owner

Uit de functionele reis van #1848. De zwaarste bevinding van de hele acceptatieronde: alles wat je in de callout-dialoog maakte, was na één keer opslaan en heropenen weg.

Wat de app schreef

ocideck_callouts:
  :                                     # <- lege sleutel
    A: point 0.279 0.180 | Kop van de kat
---
...
- De regelaar zit hier                  # <- geen (A)

Geen dia-anker, geen (A) in de opsommingsregel. Twee helften van dezelfde fout, allebei op zichzelf blokkerend:

  1. De editor schreef de zichtbare (A) niet. Die letter is de koppelsleutel (§2.1) — de ziende lezer leest hem af, de editor koppelt de verwijzing eraan, en een oudere lezer houdt er tenminste nog iets aan over. Zonder hem hoorde de verwijzing bij geen enkele regel. Daardoor bleef bij élke regel "Toevoegen" staan (de lijst koppelt op die letter) en kon je er ongemerkt een tweede verwijzing bij zetten waar het formaat er één toelaat.
  2. De dia kreeg geen anker. Het front-matter-blok is gesleuteld op het anker (§2.2). Zonder anker belandde het onder een lege sleutel en was het bij heropenen aan geen dia te koppelen.

Handgeschreven decks werkten al — daarom viel het niet eerder op. De interface staat nu op gelijke hoogte.

Waar het anker wordt toegekend, en waarom daar

In updateSlide, niet in de editor. Alleen op die plek is het hele deck bekend, en een anker moet uniek zijn: twee naamloze dia's zouden allebei dia krijgen en elkaars blok overschrijven. De gebruiker ziet het anker nooit, dus het wordt toegekend en niet gevraagd — dezelfde lijn als de sprong-ankers van #1162.

Ook mee: de LaTeX-export compileerde niet

Zodra er een beeldverwijzing op een dia stond tekende de TikZ-code met fill=ocideckTableAccent, maar die kleur werd alleen in de article-preamble gedefinieerd. xcolor gaf "Undefined color" en het hele document weigerde — terwijl de exportdialoog belooft "compileer je met pdflatex of xelatex". De beamer-preamble definieert hem nu, met de accentkleur van het deck.

buildBeamerDocument bundelt preamble en body, zodat de twee helften niet meer los kunnen lopen: dát was de fout.

Test plan

  • make check groen
  • make check-secrets groen · make sast groen (0 bevindingen)
  • Nieuw: callout_survives_save_test.dart — de hele keten dia → markdown → terug, plus een toets die vastlegt wat er zónder anker gebeurt
  • Nieuw in callout_editor_test.dart: de letter komt in de regel, een tweede verwijzing landt op de tweede regel, en verwijderen haalt de letter weer weg
  • Nieuw in deck_provider_test.dart: anker toegekend, twee naamloze dia's krijgen elk een eigen anker, en een dia zónder verwijzingen krijgt er géén opgedrongen
  • Mutatieproef op beide helften: de (A) niet schrijven maakt twee toetsen rood, het anker niet toekennen ook twee

Twee ratchets, geen enkele opgehoogd

_withCalloutAnchor raakt geen notifier-stand en staat top-level. De switch over de vijf exportformaten is _encode geworden in het part-bestand dat de rasterbouwers al hield; alles ná die laag is voor alle formaten gelijk. Beide plafonds zakken: export van 148 naar 125 regels, het bestand van 525 naar 494.

Bewaker

Raakt het bestandsformaat — maar in de richting die het formaatcontract al voorschrijft: de (A) in de regel en het anker als sleutel staan allebei in IMAGE_CALLOUTS.md §2.1 en §2.2. Wat hier verandert is dat de app zich eraan houdt. Er komt geen nieuwe sleutel bij en er wordt niets afgeleids opgeslagen.

🤖 Generated with Claude Code

Uit de functionele reis van #1848. De zwaarste bevinding van de hele acceptatieronde: **alles wat je in de callout-dialoog maakte, was na één keer opslaan en heropenen weg.** ## Wat de app schreef ```yaml ocideck_callouts: : # <- lege sleutel A: point 0.279 0.180 | Kop van de kat --- ... - De regelaar zit hier # <- geen (A) ``` Geen dia-anker, geen `(A)` in de opsommingsregel. Twee helften van dezelfde fout, allebei op zichzelf blokkerend: 1. **De editor schreef de zichtbare `(A)` niet.** Die letter is de koppelsleutel (§2.1) — de ziende lezer leest hem af, de editor koppelt de verwijzing eraan, en een oudere lezer houdt er tenminste nog iets aan over. Zonder hem hoorde de verwijzing bij geen enkele regel. Daardoor bleef bij élke regel "Toevoegen" staan (de lijst koppelt op die letter) en kon je er ongemerkt een tweede verwijzing bij zetten waar het formaat er één toelaat. 2. **De dia kreeg geen anker.** Het front-matter-blok is gesleuteld op het anker (§2.2). Zonder anker belandde het onder een lege sleutel en was het bij heropenen aan geen dia te koppelen. Handgeschreven decks werkten al — daarom viel het niet eerder op. De interface staat nu op gelijke hoogte. ## Waar het anker wordt toegekend, en waarom daar In `updateSlide`, niet in de editor. Alleen op die plek is het hele deck bekend, en een anker moet uniek zijn: twee naamloze dia's zouden allebei `dia` krijgen en elkaars blok overschrijven. De gebruiker ziet het anker nooit, dus het wordt toegekend en niet gevraagd — dezelfde lijn als de sprong-ankers van #1162. ## Ook mee: de LaTeX-export compileerde niet Zodra er een beeldverwijzing op een dia stond tekende de TikZ-code met `fill=ocideckTableAccent`, maar die kleur werd alleen in de **article**-preamble gedefinieerd. xcolor gaf "Undefined color" en het hele document weigerde — terwijl de exportdialoog belooft "compileer je met pdflatex of xelatex". De beamer-preamble definieert hem nu, met de accentkleur van het deck. `buildBeamerDocument` bundelt preamble en body, zodat de twee helften niet meer los kunnen lopen: dát was de fout. ## Test plan - [x] `make check` groen - [x] `make check-secrets` groen · `make sast` groen (0 bevindingen) - [x] Nieuw: `callout_survives_save_test.dart` — de hele keten dia → markdown → terug, plus een toets die vastlegt wat er zónder anker gebeurt - [x] Nieuw in `callout_editor_test.dart`: de letter komt in de regel, een tweede verwijzing landt op de tweede regel, en verwijderen haalt de letter weer weg - [x] Nieuw in `deck_provider_test.dart`: anker toegekend, twee naamloze dia's krijgen elk een eigen anker, en een dia zónder verwijzingen krijgt er géén opgedrongen - [x] Mutatieproef op beide helften: de `(A)` niet schrijven maakt twee toetsen rood, het anker niet toekennen ook twee ## Twee ratchets, geen enkele opgehoogd `_withCalloutAnchor` raakt geen notifier-stand en staat top-level. De switch over de vijf exportformaten is `_encode` geworden in het part-bestand dat de rasterbouwers al hield; alles ná die laag is voor alle formaten gelijk. Beide plafonds **zakken**: `export` van 148 naar 125 regels, het bestand van 525 naar 494. ### Bewaker Raakt het bestandsformaat — maar in de richting die het formaatcontract al voorschrijft: de `(A)` in de regel en het anker als sleutel staan allebei in IMAGE_CALLOUTS.md §2.1 en §2.2. Wat hier verandert is dat de app zich eraan houdt. Er komt geen nieuwe sleutel bij en er wordt niets afgeleids opgeslagen. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Uit de functionele reis van #1848, en het is de zwaarste tot nu toe: alles wat
je in de callout-dialoog maakte was na één keer opslaan en heropenen weg.

Twee helften van dezelfde fout:

- **De editor schreef de zichtbare `(A)` niet in de opsommingsregel.** Die
  letter is de koppelsleutel (§2.1); zonder hem hoort de verwijzing bij geen
  enkele regel. Daardoor bleef ook bij élke regel "Toevoegen" staan en kon je
  er ongemerkt een tweede verwijzing bij zetten waar het formaat er één toelaat.
- **De dia kreeg geen anker.** Het front-matter-blok is gesleuteld op het anker
  (§2.2); zonder anker schreef de app het onder een lege sleutel weg en was het
  bij heropenen aan geen dia te koppelen.

Het anker wordt toegekend in `updateSlide` en niet in de editor: alleen daar is
het hele deck bekend, en het moet uniek zijn — twee naamloze dia's zouden
anders allebei `dia` krijgen en elkaars blok overschrijven.

Handgeschreven decks werkten al; dit brengt de interface op gelijke hoogte.
Beide reparaties zijn met een mutatie geproefd.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twee plafonds liepen om door de reparatie hierboven. Geen van beide is met een
baseline opgehoogd:

- `_withCalloutAnchor` raakt geen notifier-stand en staat nu top-level, net als
  `_exitSlideId` in de presenter;
- `_buildLatex` is `buildBeamerDocument` geworden in `latex/`. Daar hoort het:
  het is LaTeX-opbouw, niet bestands- of formaatkeuze — en preamble en body
  moeten dezelfde accentkleur kennen, wat precies de fout was die dit oploste.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
refactor(export): de formaatkeuze naar de laag die het formaat kent
All checks were successful
scans / scans (pull_request) Successful in 3m4s
static-gate / static-gate (pull_request) Successful in 7m11s
aed7ba5fc4
ExportService.export liep over het methodeplafond. De switch over de vijf
formaten is nu `_encode` in het part-bestand dat de rasterbouwers al hield;
alles ná die laag — naamgeving, atomair schrijven, het redactiemanifest — is
voor alle vijf gelijk en blijft waar het stond.

Beide ratchets zakken mee: 148→125 regels voor `export`, 525→494 voor het
bestand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
brenno merged commit 87e9edc4de into main 2026-08-29 19:49:30 +00:00
Sign in to join this conversation.
No description provided.