fix(a11y): merkkleuren als interfacetekst worden mode-afhankelijk (#606) #712

Merged
brenno merged 1 commit from fix/606-contrast-audit into main 2026-07-22 23:36:00 +00:00
Owner

Werkt de audit uit #606 af voor de drie merkkleuren. Een eerdere ronde deed de meting (app_theme_contrast_test.dart) en drie call sites; wat restte was het per-gebruik nalopen van de tokens.

Wat er misging

accent (#2563EB), navy (#1C2B47) en teal (#2E7D64) werden op zeventig plekken als tekst- en icoonkleur gebruikt in dialogen, editors, panelen en de schil. In de donkere modus haalt het blauw daar 3,3:1 (de eis is 4,5:1) en verdwijnt het marineblauw zo goed als helemaal. Dat is precies de "physician, heal thyself" die de issue noemt: de app meet de contrast van jóuw dia's, maar hield zich er zelf niet aan.

De scheidslijn: chroom tegen inhoud

De kern van de ingreep is dat de merkkleuren niet mogen meebewegen. Ze vullen vlakken, tekenen randen, en een deel rendert ín een dia — en een dia moet in de preview identiek zijn aan een headless export, waar de app-modus niet bestaat. Een kleur die het thema volgt zou daar twee verschillende PDF's van één deck opleveren (PENTEST_MIAUW §11).

Dus niet licht-tegen-donker maar chroom tegen inhoud: tekst die je in de app leest volgt de app (accentFg/brandFg/tealFg), inkt die op een dia landt blijft de const-merkkleur. Waar mijn omzetting een vlak of rand raakte in plaats van tekst, heb ik het teruggedraaid — een vlak hoeft de tekstlat niet te halen.

De ratchet

app_theme_contrast_test.dart krijgt een tweede toets: de mode-afhankelijke tokens moeten de lat wél halen, in beide modi. Anders is een Fg-token invoeren dat net zo onleesbaar is als de merkkleur die het verving een stille no-op. Donker zakt van 17 naar 14 tokens onder de lat — die drie verdwenen niet door recolour maar doordat hun tekstgebruik verhuisde.

De contrastverhoudingen zijn vooraf uitgerekend, niet gegokt: de donkere varianten boven 7:1, de lichte boven 6:1.

Getoetst

  • make check groen, make test-golden groen — de dia's renderen byte-identiek, wat hier de kern is: de merkkleuren daar mochten niet bewegen.
  • make check-secrets en make sast schoon.
  • Mutatie in twee richtingen: een Fg-token onder de lat maakt de nieuwe toets rood, en een verhuisd token terugzetten in de baseline ook.
  • Visueel gekeurd (beeldkeurder, met eigen ogen en schermafdrukken): donkere modus leesbaar op alle omgezette plekken (instellingen, scorecard-/timeline-/hulp-editors, slidelijst, "Slide toevoegen"); lichte modus nog herkenbaar OciDeck, geen lelijk kleurverschil tussen mode-afhankelijke tekst en een merkkleurvlak; de dia's ongewijzigd in preview én PDF-export. Eén gat: de HTML-export kon hij niet openen (Chrome-toegang geweigerd) — buiten deze wijziging, want die rendert via Marp/CSS, niet via deze Flutter-tokens.

Wat van #606 overblijft

De ernst-, checklist- en scopepaletten (de 14 resterende donkere tokens). Díé renderen werkelijk in dia's, dus recolour kan niet en een overal-gebruikte tegenhanger ook niet; ze vragen dezelfde lezing per gebruik. ACCESSIBILITY.md draagt die stand en de tweedeling.

Werkt aan #606; die blijft open voor die paletten.

Werkt de audit uit #606 af voor de drie merkkleuren. Een eerdere ronde deed de meting (`app_theme_contrast_test.dart`) en drie call sites; wat restte was het per-gebruik nalopen van de tokens. ## Wat er misging `accent` (#2563EB), `navy` (#1C2B47) en `teal` (#2E7D64) werden op zeventig plekken als **tekst- en icoonkleur** gebruikt in dialogen, editors, panelen en de schil. In de donkere modus haalt het blauw daar 3,3:1 (de eis is 4,5:1) en verdwijnt het marineblauw zo goed als helemaal. Dat is precies de "physician, heal thyself" die de issue noemt: de app meet de contrast van jóuw dia's, maar hield zich er zelf niet aan. ## De scheidslijn: chroom tegen inhoud De kern van de ingreep is dat de merkkleuren **niet** mogen meebewegen. Ze vullen vlakken, tekenen randen, en een deel rendert ín een dia — en een dia moet in de preview identiek zijn aan een headless export, waar de app-modus niet bestaat. Een kleur die het thema volgt zou daar twee verschillende PDF's van één deck opleveren (PENTEST_MIAUW §11). Dus niet licht-tegen-donker maar **chroom tegen inhoud**: tekst die je *in* de app leest volgt de app (`accentFg`/`brandFg`/`tealFg`), inkt die *op een dia* landt blijft de const-merkkleur. Waar mijn omzetting een vlak of rand raakte in plaats van tekst, heb ik het teruggedraaid — een vlak hoeft de tekstlat niet te halen. ## De ratchet `app_theme_contrast_test.dart` krijgt een tweede toets: de mode-afhankelijke tokens moeten de lat wél halen, in beide modi. Anders is een `Fg`-token invoeren dat net zo onleesbaar is als de merkkleur die het verving een stille no-op. Donker zakt van 17 naar 14 tokens onder de lat — die drie verdwenen niet door recolour maar doordat hun tekstgebruik verhuisde. De contrastverhoudingen zijn vooraf uitgerekend, niet gegokt: de donkere varianten boven 7:1, de lichte boven 6:1. ## Getoetst - `make check` groen, `make test-golden` groen — **de dia's renderen byte-identiek**, wat hier de kern is: de merkkleuren daar mochten niet bewegen. - `make check-secrets` en `make sast` schoon. - Mutatie in twee richtingen: een `Fg`-token onder de lat maakt de nieuwe toets rood, en een verhuisd token terugzetten in de baseline ook. - **Visueel gekeurd** (beeldkeurder, met eigen ogen en schermafdrukken): donkere modus leesbaar op alle omgezette plekken (instellingen, scorecard-/timeline-/hulp-editors, slidelijst, "Slide toevoegen"); lichte modus nog herkenbaar OciDeck, geen lelijk kleurverschil tussen mode-afhankelijke tekst en een merkkleurvlak; de dia's ongewijzigd in preview én PDF-export. Eén gat: de HTML-export kon hij niet openen (Chrome-toegang geweigerd) — buiten deze wijziging, want die rendert via Marp/CSS, niet via deze Flutter-tokens. ## Wat van #606 overblijft De ernst-, checklist- en scopepaletten (de 14 resterende donkere tokens). Díé renderen werkelijk in dia's, dus recolour kan niet en een overal-gebruikte tegenhanger ook niet; ze vragen dezelfde lezing per gebruik. `ACCESSIBILITY.md` draagt die stand en de tweedeling. Werkt aan #606; die blijft open voor die paletten.
De audit die #606 openliet, voor de merkkleuren. `accent`, `navy` en `teal`
werden op zeventig plekken als tekst- of icoonkleur gebruikt in dialogen,
editors, panelen en de schil. In de donkere modus haalt het blauw daar 3,3:1 en
verdwijnt het marineblauw zo goed als helemaal.

Ze hebben nu mode-afhankelijke tegenhangers — `accentFg`, `brandFg`, `tealFg` —
en de interface gebruikt die. De merkkleuren zelf blijven const, en dat is de
kern van de ingreep: ze vullen nog vlakken, tekenen randen, en een deel rendert
ín een dia. Een dia moet in de preview identiek zijn aan een headless export,
waar de app-modus niet bestaat; een kleur die met het thema meebeweegt zou daar
twee verschillende PDF's van één deck opleveren (PENTEST_MIAUW §11).

De scheidslijn is dus niet licht-tegen-donker maar chroom-tegen-inhoud: tekst
die je ín de app leest volgt de app, inkt die óp een dia landt niet. Waar mijn
omzetting een vlak of een rand raakte in plaats van tekst, heb ik het
teruggedraaid naar de merkkleur — een vlak hoeft de tekstlat niet te halen.

De ratchet in `app_theme_contrast_test.dart` krijgt er een tweede toets bij: de
mode-afhankelijke tokens moeten de lat wél halen, in beide modi. Anders is een
Fg-token invoeren dat net zo onleesbaar is als de merkkleur die het verving een
stille no-op. Donker zakt van 17 naar 14 tokens onder de lat; die drie
verdwenen niet door een andere kleur maar doordat hun tekstgebruik verhuisde.

Wat overblijft van #606 zijn de ernst-, checklist- en scopepaletten. Díé
renderen werkelijk in dia's, dus recolour kan niet en een overal-gebruikte
tegenhanger ook niet; ze vragen dezelfde lezing per gebruik. ACCESSIBILITY.md
draagt die stand.

De contrastverhoudingen zijn vooraf uitgerekend, niet gegokt: de donkere
varianten zitten boven 7:1, de lichte boven 6:1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit 829de950dd into main 2026-07-22 23:36:00 +00:00
Sign in to join this conversation.
No description provided.