fix(a11y): maak de contrastaudit af — chrome volgt het thema, de dia niet (#606) #715

Merged
brenno merged 2 commits from fix/contrast-audit-606 into main 2026-07-23 08:48:10 +00:00
Owner

Sluit #606. De volledige audit, met beeldcontrole in beide thema's.

De regel die eruit kwam

Niet licht-tegen-donker, maar chrome tegen inhoud. Tekst die je in de app leest volgt de app. Inkt die op een dia landt doet dat niet — dat kan niet, want een dia moet identiek renderen in de preview en in een headless export-isolate zonder appearance-instelling. Elk van de ~200 gebruiken is als het een of het ander gelezen.

Wat er veranderde

37 chrome-plekkenaccent, navy, teal, plus het rood en groen van de ernst- en statuspaletten — schilderden tekst en iconen in dialogen, editors, panelen en de schil, waar navy op donker zo goed als verdwijnt. Ze gebruiken nu accentFg, brandFg, tealFg, dangerFg, successFg.

43 plekken in previews/ gaan door nieuwe vaste dia-inkt. Dit was de grootste vondst en hij kwam uit de beeldkeuring: de previews schilderden hun grijzen met de mode-afhankelijke slate-schaal, op een canvas dat wit blijft. In donkere modus werd slate700 daarmee 1,3:1 — de tekst van een checklist, scope-matrix en bevindingenoverzicht verdween zo goed als helemaal. En het week af van de export: de HTML-export draait zonder thema en schrijft altijd de lichte waarden, terwijl het exportdialoog belooft dat de export exact de weergave uit de editor gebruikt.

Alle 33 goldens zijn byte-identiek, want de nieuwe vaste waarden zijn precies wat er in lichte modus al stond.

De meting klopte niet, en dat was van mij

De eerste ronde legde élk vast token naast AppTheme.paper — het interface-oppervlak — en noteerde zeventien tekortkomingen in donkere modus. Maar een ernstkleur wordt op een dia gelezen, en die is wit. Tegen de juiste achtergrond en met de lat die werkelijk geldt: veertien tokens zijn diatekst en halen 4,5:1 op wit; twee zijn nooit tekst (een 6%-tint, een randstreep, en de vulling van een badge met wit vet label van ~30px) en halen als grafisch object en grote tekst hun 3:1.

Er staat dus geen basislijn meer open — niet afgeschreven, maar grotendeels een categoriefout die ik zelf had gemaakt. Een basislijn die schuld opschrijft die er niet is, maakt de regels die er wél toe doen ongeloofwaardig.

Drie kleinere uit de keuring, twee door mij veroorzaakt

  • het exportdialoog was half omgezet: succes-tak successFg (9,6:1 donker), faaltak nog een vaste rode (3,1:1). "Geëxporteerd naar…" schreeuwde en "De export is mislukt" fluisterde — precies andersom;
  • de gebruikersnotities-kop bleef vast blauw terwijl icoon en knop ernaast meebewogen: kop 2,4:1, eigen ondertitel eronder 6,2:1. Omgekeerde hiërarchie in zijn eigen blok;
  • dangerFg was in licht blékér dan wat hij verving. Op de scorecard-chip, die zijn achtergrond met 12% van zichzelf vult, zakte hij naar 4,2:1 — onder AA, voor de kleur die alarm betekent.

Ik heb de getallen van de keuring zelf nagerekend voordat ik ze aannam; één ervan klopte niet, de bevindingen wel.

Wat dit vastzet

Vier rekentoetsen en twee bronwachten: geen vaste merk- of ernstkleur als tekst buiten de dia-renderende code, en geen mode-afhankelijk grijs binnen previews/. Beide lezen de bron, dus de volgende AppTheme.navy in een dialoog — of slate600 op een dia — faalt de bouw. Beide eerst rood gemaakt met een geplante overtreding.

Los hiervan gemeld

De keuring stuitte op een gebroken PDF-export (Invalid argument(s): 1, reproduceerbaar, niet de video). Staat als #714.

Poort

make check groen (niet door tail gepijpt), make test-golden groen (33 onveranderd).

Sluit #606. De volledige audit, met beeldcontrole in beide thema's. ## De regel die eruit kwam Niet licht-tegen-donker, maar **chrome tegen inhoud**. Tekst die je *in de app* leest volgt de app. Inkt die *op een dia* landt doet dat niet — dat kan niet, want een dia moet identiek renderen in de preview en in een headless export-isolate zonder appearance-instelling. Elk van de ~200 gebruiken is als het een of het ander gelezen. ## Wat er veranderde **37 chrome-plekken** — `accent`, `navy`, `teal`, plus het rood en groen van de ernst- en statuspaletten — schilderden tekst en iconen in dialogen, editors, panelen en de schil, waar navy op donker zo goed als verdwijnt. Ze gebruiken nu `accentFg`, `brandFg`, `tealFg`, `dangerFg`, `successFg`. **43 plekken in `previews/`** gaan door nieuwe vaste dia-inkt. Dit was de grootste vondst en hij kwam uit de beeldkeuring: de previews schilderden hun grijzen met de *mode-afhankelijke* slate-schaal, op een canvas dat wit blijft. In donkere modus werd `slate700` daarmee **1,3:1** — de tekst van een checklist, scope-matrix en bevindingenoverzicht verdween zo goed als helemaal. En het **week af van de export**: de HTML-export draait zonder thema en schrijft altijd de lichte waarden, terwijl het exportdialoog belooft dat de export exact de weergave uit de editor gebruikt. **Alle 33 goldens zijn byte-identiek**, want de nieuwe vaste waarden zijn precies wat er in lichte modus al stond. ## De meting klopte niet, en dat was van mij De eerste ronde legde élk vast token naast `AppTheme.paper` — het *interface*-oppervlak — en noteerde zeventien tekortkomingen in donkere modus. Maar een ernstkleur wordt op een **dia** gelezen, en die is wit. Tegen de juiste achtergrond en met de lat die werkelijk geldt: veertien tokens zijn diatekst en halen 4,5:1 op wit; twee zijn nooit tekst (een 6%-tint, een randstreep, en de vulling van een badge met wit vet label van ~30px) en halen als grafisch object en grote tekst hun 3:1. Er staat dus **geen basislijn meer open** — niet afgeschreven, maar grotendeels een categoriefout die ik zelf had gemaakt. Een basislijn die schuld opschrijft die er niet is, maakt de regels die er wél toe doen ongeloofwaardig. ## Drie kleinere uit de keuring, twee door mij veroorzaakt - het **exportdialoog** was half omgezet: succes-tak `successFg` (9,6:1 donker), faaltak nog een vaste rode (**3,1:1**). "Geëxporteerd naar…" schreeuwde en "De export is mislukt" fluisterde — precies andersom; - de **gebruikersnotities-kop** bleef vast blauw terwijl icoon en knop ernaast meebewogen: kop 2,4:1, eigen ondertitel eronder 6,2:1. Omgekeerde hiërarchie in zijn eigen blok; - **`dangerFg`** was in licht blékér dan wat hij verving. Op de scorecard-chip, die zijn achtergrond met 12% van zichzelf vult, zakte hij naar **4,2:1** — onder AA, voor de kleur die alarm betekent. Ik heb de getallen van de keuring zelf nagerekend voordat ik ze aannam; één ervan klopte niet, de bevindingen wel. ## Wat dit vastzet Vier rekentoetsen en **twee bronwachten**: geen vaste merk- of ernstkleur als tekst buiten de dia-renderende code, en geen mode-afhankelijk grijs binnen `previews/`. Beide lezen de bron, dus de volgende `AppTheme.navy` in een dialoog — of `slate600` op een dia — faalt de bouw. Beide eerst rood gemaakt met een geplante overtreding. ## Los hiervan gemeld De keuring stuitte op een gebroken PDF-export (`Invalid argument(s): 1`, reproduceerbaar, niet de video). Staat als #714. ## Poort `make check` groen (niet door `tail` gepijpt), `make test-golden` groen (33 onveranderd).
De vorige ronde zette de meting neer en repareerde drie plekken; dit is de
audit die het issue eigenlijk vroeg: elk van de ~200 gebruiken gelezen als
dia-inhoud of als chrome.

**37 chrome-plekken om.** `accent`, `navy`, `teal`, plus het rood en groen
van de ernst- en statuspaletten, schilderden tekst en iconen in dialogen,
editors, panelen en de schil — waar navy op een donker oppervlak zo goed
als verdwijnt en het accentblauw 3,3:1 haalt. Ze gebruiken nu `accentFg`,
`brandFg`, `tealFg`, `dangerFg` en `successFg`.

**De vaste kleuren zelf blijven**, en dat is het punt: vullingen, randen,
gradiënten en alles wat op een dia landt. Een dia moet in de preview en in
een headless export-isolate identiek renderen, dus een themavolgende kleur
zou daar twee verschillende PDF's van één deck opleveren. Alle 33 goldens
zijn onveranderd.

**En de meting klopte niet.** De eerste ronde legde élk vast token naast
`AppTheme.paper` — het interface-oppervlak — en noteerde zeventien
tekortkomingen in donkere modus. Maar een ernstkleur wordt op een dia
gelezen, en die is wit. Tegen de juiste achtergrond, en met de lat die
werkelijk geldt:

- veertien tokens zijn diatekst en halen 4,5:1 op wit;
- twee zijn nooit tekst. `severityHigh` en `severityMedium` zijn een
  6%-tint, een randstreep, en de vulling van een badge met een wit vet
  label van ~30px op een dia van 1280 — een grafisch object (1.4.11) en
  grote tekst (1.4.3), lat 3:1, gehaald met 3,6 en 3,2.

Er staat dus geen basislijn meer open. Niet omdat de schuld is
afgeschreven, maar omdat het merendeel een categoriefout was en de rest is
gerepareerd. Een basislijn die schuld opschrijft die er niet is, maakt de
regels die er wél toe doen ongeloofwaardig.

**De bronwacht is wat dit vastzet.** De test leest `lib/` en laat geen
vaste merk- of ernstkleur meer toe als tekstkleur buiten de
dia-renderende code. Vullingen en tinten vallen er bewust buiten — daar is
de merkkleur juist gewenst. Eerst rood laten worden met een geplante
`AppTheme.navy` in een dialoog.

Sluit #606.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
De beeldkeuring in beide thema's vond vier dingen. Eén ervan was groter dan
alles wat de audit tot dan toe had aangeraakt.

**De dia-previews schilderden met de mode-afhankelijke slate-schaal, op een
canvas dat wit blijft.** In donkere modus werd `slate700` daarmee 1,3:1 en
`slate500` 2,1:1: de tekst van een checklist, een scope-matrix en een
bevindingenoverzicht verdween zo goed als helemaal. Dat is de platste vorm
van wat dit issue aanwijst — de app die op het eigen werk van de gebruiker
zakt door de lat die ze hém oplegt.

En erger dan onleesbaar: het wéék af van de export. De HTML-export draait
zonder thema en schrijft altijd de lichte waarden, terwijl het
exportdialoog belooft dat de export exact de weergave uit de editor
gebruikt. In donkere modus was dat onwaar. 43 plekken in `previews/` gaan
nu door vaste dia-inkt — dezelfde waarden, alleen niet meer bewegend. Alle
33 goldens onveranderd, want die waarden zijn precies wat er in lichte
modus al stond.

**Drie kleinere, waarvan twee door mij veroorzaakt:**

- het exportdialoog was half omgezet: de succes-tak kreeg `successFg`, de
  faaltak bleef op een vaste rode staan. In donker schreeuwde "Geëxporteerd
  naar…" op 9,6:1 terwijl "De export is mislukt" op 3,1:1 stond — precies
  andersom dan je wilt. Ook de twee geblokkeerd-meldingen;
- de gebruikersnotities-kop bleef een vaste blauwe terwijl icoon en knop
  ernaast mode-afhankelijk werden. Op het donkere paneel haalde de kop
  2,4:1 en zijn eigen ondertitel eronder 6,2 — omgekeerde hiërarchie, in
  zijn eigen blok. Kop én tekst volgen nu het thema, net als de
  amber-tegenhanger al deed;
- `dangerFg` was in lichte modus bleker dan de kleur die hij verving. Op de
  scorecard-chip, die zijn achtergrond met 12% van zichzelf vult, zakte hij
  daardoor naar 4,2:1 — onder AA, voor de kleur die alarm betekent. De
  lichte kant is nu de diepe rode; donker blijft.

Twee bronwachten erbij: geen mode-afhankelijk grijs in `previews/`, en de
dia-inkt moet in beide modi dezelfde waarde hebben.

De keuring meldde ook een gebroken PDF-export ("Invalid argument(s): 1").
Dat heeft niets met kleur te maken en staat als #714.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit 7c55b7e80e into main 2026-07-23 08:48:10 +00:00
Sign in to join this conversation.
No description provided.