feat(uiterlijk): meet de leesbaarheid van een eigen app-thema (#750) #755

Merged
brenno merged 1 commit from feat/leesbaarheid-profielbewerker-750 into main 2026-07-23 15:43:43 +00:00
Owner

Closes #750.

De reparaties in #744 en #746/#749 zetten toetsen op de drie ingebouwde
profielen. Uiterlijk → App-thema laat je een eigen profiel maken met acht
vrije kleuren, en daar kon je precies dezelfde fout terugbouwen — een donker
accent in een donker profiel — zonder dat iets er iets over zei.

Twee dingen, en het tweede is het belangrijkste

Een leesbaarheidsmeting onder het voorbeeld. Negen paren, met de gemeten
verhouding, de lat ernaast en de twee kleuren die vergeleken zijn als stippen
ervoor. Geen zin eromheen: label plus twee getallen leest sneller, en het
scheelt een geïnterpoleerde bronstring in 31 talen. Waarschuwen, niet
tegenhouden — het is de app van de gebruiker en de weg terug is één kleur;
blokkeren past bij een export, die de deur uit gaat.

Het voorbeeld is eerlijk gemaakt, en dat is de eigenlijke reparatie. Het
schilderde zijn eigen kleuren met een _contrastColor()-hulpje dat zwart of wit
koos op luminantie. De app doet dat niet — die gebruikt panelTextColor voor de
baltitel en een berekende voorgrond voor de knop. Het voorbeeld liet dus een
leesbare balk zien boven een profiel dat de app onleesbaar rendert: het vleide
precies het profiel dat een waarschuwing verdiende. Het bouwt nu het échte
ThemeData en rendert daarbinnen.

En het toont sinds nu een selectievakje, een schakelaar en een tekstknop. Het
oude voorbeeld liet uitsluitend de rollen zien die in #744 goed waren (balk,
zijbalk, kaart, gevulde knop) en precies niet de rollen die stukgingen. Wie
zorgvuldig naar het voorbeeld keek voordat hij opsloeg, zag het probleem niet.

Met een accent van #1B2537 in een donker profiel verdwijnen het vinkje en de
schakelaar nu zichtbaar in het voorbeeld, mét de melding eronder:

⚠ Leesbaarheid van dit profiel
   Selectievakjes, schakelaars en de tekstcursor    1.1 : 1   ≥ 3.0 : 1
   Het vinkje in een aangevinkt vakje               1.2 : 1   ≥ 4.5 : 1

Het meet het thema, niet de velden

lib/theme/appearance_contrast.dart bouwt het profiel door
AppTheme.fromProfile en leest de opgeloste waarden. Dat is geen netheid maar
de kern: vier van de negen paren bestaan niet als veld. De voorgrond van een
tekstknop, de interactiekleur, het vinkje daarop en het label van de primaire
knop worden afgeleid — en dáár zat #744. Een controle die de acht kleurkiezers
naast elkaar legt, had het profiel dat #744 veroorzaakte goedgekeurd.

Dezelfde functie voedt test/app_theme_contrast_test.dart. Twee rekensommen
over dezelfde vraag lopen uit elkaar, en dan bewaakt de toets niet meer wat het
scherm belooft.

Wat de meting meteen zelf vond

Bij de eerste run over de ingebouwde profielen: het label op de primaire knop
stond in Donker op 2,54:1.
De voorgrond werd gekozen met
brightness == light && luminance > 0.6 ? zwart : wit, wat in donkere modus
altijd wit afdwingt — ook op het lichte accent #60A5FA. Dat is de
opslaan-knop. De helderheid van het thema doet er niet toe, alleen die van de
knop zelf: zwart óf wit, net wat wint. 2,54 → 8,26:1, en beide lichte profielen
houden exact de kleur die ze hadden.

Dat is precies waar zo'n meting voor is, en het staat als aparte regel in de
CHANGELOG.

Bouw

AppearancePreview en AppearanceLegibility zijn losse widgets in
settings/, geen methodes op _SettingsDialogState. Ze zijn pure functies van
het profiel, en de klasse-ratchet duwde hier de goede kant op — de eerste opzet
liep er 157 regels overheen.

Twaalf nieuwe interfaceteksten, alle 31 talen via make add-l10n. De negen
paarnamen hergebruiken Gedempte tekst, dat al bestond.

Toetsen

test/appearance_contrast_test.dart: dat de ingebouwde profielen hun eigen norm
halen, dat een donker accent in een donker profiel gevonden wórdt, dat de
gemelde verhouding de verhouding tussen de twee gedragen kleuren is, dat de vier
afgeleide paren identiek zijn aan wat het gebouwde thema teruggeeft, en twee
widgettests op de bewerker zelf.

Twee mutaties rood gezien:

Mutatie Wat rood werd
knoplabel terug op de oude helderheidsregel "het profiel Donker zakt op zijn eigen controle"
meet profile.primaryColor i.p.v. scheme.primary idem — dat is de 1,21:1 uit #744 terug

Poorten

make check exit 0 (6.030 tests, dekking 86,6%, per-bestand-vloer 0),
make check-secrets 0, make sast 0 bevindingen over 690 bestanden. DAST niet
gedraaid — advisory, geen geserveerd oppervlak. Geen afhankelijkheid, geen
SBOM-gevolg. CHANGELOG, docs/ACCESSIBILITY.md en docs/SOURCE_MAP.md bij.

Met eigen ogen

flutter run -d macos, donkere modus: het voorbeeld met de echte kleuren, de
schone melding op een ingebouwd profiel, en een gedupliceerd profiel met een
donker accent dat de twee regels hierboven oplevert.

Eén ding dat ik onderweg tegenkwam en niet heb aangeraakt: de +-knop
schrijft een gedupliceerd profiel meteen naar de instellingen, niet pas bij
Opslaan. Annuleren laat het profiel dus staan (alleen de kleurwijzigingen
vervallen). Bestaand gedrag, buiten de reikwijdte van deze PR — maar het
verraste me, en het verrast een gebruiker waarschijnlijk ook.

Closes #750. De reparaties in #744 en #746/#749 zetten toetsen op de **drie ingebouwde** profielen. *Uiterlijk → App-thema* laat je een eigen profiel maken met acht vrije kleuren, en daar kon je precies dezelfde fout terugbouwen — een donker accent in een donker profiel — zonder dat iets er iets over zei. ## Twee dingen, en het tweede is het belangrijkste **Een leesbaarheidsmeting onder het voorbeeld.** Negen paren, met de gemeten verhouding, de lat ernaast en de twee kleuren die vergeleken zijn als stippen ervoor. Geen zin eromheen: label plus twee getallen leest sneller, en het scheelt een geïnterpoleerde bronstring in 31 talen. Waarschuwen, niet tegenhouden — het is de app van de gebruiker en de weg terug is één kleur; blokkeren past bij een export, die de deur uit gaat. **Het voorbeeld is eerlijk gemaakt, en dat is de eigenlijke reparatie.** Het schilderde zijn eigen kleuren met een `_contrastColor()`-hulpje dat zwart of wit koos op luminantie. De app doet dat niet — die gebruikt `panelTextColor` voor de baltitel en een berekende voorgrond voor de knop. Het voorbeeld liet dus een leesbare balk zien boven een profiel dat de app onleesbaar rendert: het vleide precies het profiel dat een waarschuwing verdiende. Het bouwt nu het échte `ThemeData` en rendert daarbinnen. En het toont sinds nu een selectievakje, een schakelaar en een tekstknop. Het oude voorbeeld liet uitsluitend de rollen zien die in #744 goed waren (balk, zijbalk, kaart, gevulde knop) en precies niet de rollen die stukgingen. Wie zorgvuldig naar het voorbeeld keek voordat hij opsloeg, zag het probleem niet. Met een accent van `#1B2537` in een donker profiel verdwijnen het vinkje en de schakelaar nu zichtbaar in het voorbeeld, mét de melding eronder: ``` ⚠ Leesbaarheid van dit profiel Selectievakjes, schakelaars en de tekstcursor 1.1 : 1 ≥ 3.0 : 1 Het vinkje in een aangevinkt vakje 1.2 : 1 ≥ 4.5 : 1 ``` ## Het meet het thema, niet de velden `lib/theme/appearance_contrast.dart` bouwt het profiel door `AppTheme.fromProfile` en leest de opgeloste waarden. Dat is geen netheid maar de kern: **vier van de negen paren bestaan niet als veld.** De voorgrond van een tekstknop, de interactiekleur, het vinkje daarop en het label van de primaire knop worden afgeleid — en dáár zat #744. Een controle die de acht kleurkiezers naast elkaar legt, had het profiel dat #744 veroorzaakte goedgekeurd. Dezelfde functie voedt `test/app_theme_contrast_test.dart`. Twee rekensommen over dezelfde vraag lopen uit elkaar, en dan bewaakt de toets niet meer wat het scherm belooft. ## Wat de meting meteen zelf vond Bij de eerste run over de ingebouwde profielen: **het label op de primaire knop stond in *Donker* op 2,54:1.** De voorgrond werd gekozen met `brightness == light && luminance > 0.6 ? zwart : wit`, wat in donkere modus altijd wit afdwingt — ook op het lichte accent `#60A5FA`. Dat is de opslaan-knop. De helderheid van het thema doet er niet toe, alleen die van de knop zelf: zwart óf wit, net wat wint. 2,54 → 8,26:1, en beide lichte profielen houden exact de kleur die ze hadden. Dat is precies waar zo'n meting voor is, en het staat als aparte regel in de CHANGELOG. ## Bouw `AppearancePreview` en `AppearanceLegibility` zijn losse widgets in `settings/`, geen methodes op `_SettingsDialogState`. Ze zijn pure functies van het profiel, en de klasse-ratchet duwde hier de goede kant op — de eerste opzet liep er 157 regels overheen. Twaalf nieuwe interfaceteksten, alle 31 talen via `make add-l10n`. De negen paarnamen hergebruiken `Gedempte tekst`, dat al bestond. ## Toetsen `test/appearance_contrast_test.dart`: dat de ingebouwde profielen hun eigen norm halen, dat een donker accent in een donker profiel gevonden wórdt, dat de gemelde verhouding de verhouding tussen de twee gedragen kleuren is, dat de vier afgeleide paren identiek zijn aan wat het gebouwde thema teruggeeft, en twee widgettests op de bewerker zelf. Twee mutaties rood gezien: | Mutatie | Wat rood werd | |---|---| | knoplabel terug op de oude helderheidsregel | "het profiel *Donker* zakt op zijn eigen controle" | | meet `profile.primaryColor` i.p.v. `scheme.primary` | idem — dat is de 1,21:1 uit #744 terug | ## Poorten `make check` exit 0 (6.030 tests, dekking 86,6%, per-bestand-vloer 0), `make check-secrets` 0, `make sast` 0 bevindingen over 690 bestanden. DAST niet gedraaid — advisory, geen geserveerd oppervlak. Geen afhankelijkheid, geen SBOM-gevolg. CHANGELOG, `docs/ACCESSIBILITY.md` en `docs/SOURCE_MAP.md` bij. ## Met eigen ogen `flutter run -d macos`, donkere modus: het voorbeeld met de echte kleuren, de schone melding op een ingebouwd profiel, en een gedupliceerd profiel met een donker accent dat de twee regels hierboven oplevert. Eén ding dat ik onderweg tegenkwam en **niet** heb aangeraakt: de `+`-knop schrijft een gedupliceerd profiel meteen naar de instellingen, niet pas bij Opslaan. *Annuleren* laat het profiel dus staan (alleen de kleurwijzigingen vervallen). Bestaand gedrag, buiten de reikwijdte van deze PR — maar het verraste me, en het verrast een gebruiker waarschijnlijk ook.
De toetsen uit #744 dekken de drie ingebouwde profielen. Wie zelf
kleuren koos kon dezelfde fout terugbouwen zonder dat iets er iets over
zei. Onder het voorbeeld staat nu een meting van negen paren, met de
gemeten verhouding, de lat en de twee vergeleken kleuren. Waarschuwen,
niet tegenhouden: het is de app van de gebruiker.

De rekensom staat in lib/theme/appearance_contrast.dart en meet het
gebouwde thema, niet de acht kleurvelden — vier van de negen paren
bestaan niet als veld maar worden in fromProfile afgeleid, en dat was
precies waar #744 zat. Dezelfde functie voedt de contrasttoets; twee
sommen over dezelfde vraag lopen uit elkaar.

Het voorbeeld is eerlijk gemaakt. Het schilderde zijn eigen kleuren met
een zwart-of-wit-op-luminantie-hulpje, terwijl de app panelTextColor en
een berekende knopvoorgrond gebruikt: het toonde een leesbare balk waar
de app een onleesbare rendert. Het bouwt nu het echte thema en toont een
selectievakje, een schakelaar en een tekstknop — de rollen die in #744
stukgingen en die het oude voorbeeld wegliet.

Voorbeeld en meting zijn losse widgets in settings/, niet nog twee
methodes op _SettingsDialogState: ze zijn pure functies van het profiel,
en de klasse-ratchet duwt hier de goede kant op.

Twaalf nieuwe interfaceteksten, alle 31 talen.

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