Kwaliteitspaneel: 'Fix alle problemen' laat problemen staan — belofte breder dan de auto-fix dekt #1280

Closed
opened 2026-08-05 19:01:53 +00:00 by brenno · 2 comments
Owner

Symptoom

Het slidekwaliteitspaneel heeft een knop "Fix alle problemen". Na het indrukken
blijven er nog kwaliteitsproblemen in het paneel staan. Dat botst met wat de knop
belooft: als hij zegt alle problemen op te lossen, hoort het paneel daarna schoon
te zijn — of moet duidelijk zijn welke meldingen principieel niet automatisch
fixbaar zijn, en dan hoort de knop dat niet te beloven.

Reproductie: open Proefpresentatie.md (of elk deck met contrast-, privacy- of
alt-tekstmeldingen), open het kwaliteitspaneel, druk op "Fix alle problemen". De
structurele problemen (te volle tekst) worden opgelost; contrast-, privacy-,
alt-tekst- en media-meldingen blijven staan.

Oorzaak — twee lagen

Er zitten twee losse oorzaken onder hetzelfde symptoom.

Laag 1 (hoofdoorzaak, altijd zichtbaar): de belofte is breder dan de motor

De auto-fixmotor pakt bewust maar een deelverzameling aan. isStructurallyAutofixable
in lib/services/quality_autofix.dart:20 dekt
8 van de 43 SlideQualityIssueKind-waarden
(lib/models/slide_quality.dart:15):
bulletMultiSentence, splitRunDragged, textDensityWarning/Critical,
bulletCountHigh/Critical, bulletWordCountHigh/Critical. De motor _applyNextFix
(quality_autofix.dart:126) heeft precies drie
takken: opknippen, splitsen, reeks losmaken.

De overige 35 categorieën hebben geen fix-tak en worden alleen gemeld: alle
contrast- (6), alt-/media- (5), privacy- (10), lege-dia-, en de niet-splitsbare
dichtheidscategorieën (tableDensityMinimum, codeDensityHigh,
freeMarkdownDensityHigh, titleDensityHigh, quoteDensityHigh,
bulletAverageLengthHigh, bulletNestingDeep, bulletColumnImbalance).

De knop verschijnt zodra één melding structureel-fixbaar is
(lib/widgets/panels/slide_quality_panel.dart:314),
maar staat naast een lijst die óók contrast/privacy/alt bevat. Het label is
l10n.d('Fix alle problemen') → EN "Fix all issues"
(slide_quality_panel.dart:320). De
code klopt met de ontwerpfilosofie ("veilig = omkeerbaar, structureel, alle inhoud
zichtbaar", docstring quality_autofix.dart:38);
de belofte in het label klopt niet.

Laag 2 (secundaire bug): de knop-poort is ruimer dan de motor-poort

isStructurallyAutofixable (poort voor knopzichtbaarheid) toetst alleen de soort
melding, maar de motor gate't óók op de dia via _shouldSplitFor
(quality_autofix.dart:180). Voor een
woord-melding op een dia met weinig bullets:

  • bulletWordCountHigh vuurt bij totalWords > 90
    (lib/services/slide_quality/slide_quality_analyzer_density.dart:201),
    óók bij ≤8 bullets.
  • _shouldSplitFor eist visibleContentBulletCount > _splitThreshold (bijv. 7 > 8 =
    false) én rendersTooSmall (alleen waar voor textDensity*) → false → geen
    splitsing.
  • Gevolg: knop-poort = true (knop verschijnt en belooft), maar _applyNextFix geeft
    null, applied == 0, en de melding blijft staan. De snackbar zegt dan zelfs
    "Niets dat zich vanzelf laat oplossen" terwijl de knop wél verscheen.

Dezelfde divergentie treft richText-bulletdia's: de analyzer telt bullets uit
customMarkdown, maar visibleContentBulletCount telt slide.bullets (leeg bij
richText) → bulletCountHigh kan vuren terwijl _shouldSplitFor false geeft.

Voorgestelde oplossing (combinatie, zwaartepunt op eerlijk labelen)

De 35 resterende categorieën zijn principieel niet veilig auto-fixbaar binnen de
OciDeck-filosofie (contrast = thema-ontwerpkeuze; alt-tekst = tekst die een mens
schrijft; ontbrekende media = bestand zoeken; privacy = expliciet menselijk oordeel;
lege dia = niets te verzinnen; woord-/tabel-/codedichtheid = inhoud weghalen). De
motor uitbreiden naar "echt alles" is dus onwenselijk. Daarom:

A. Knop-poort motor-accuraat maken (lost Laag 2 op). Voeg in
quality_autofix.dart een goedkope top-level helper toe die de echte motorpoort
spiegelt:

bool hasApplicableStructuralFix(Deck deck,
    {SlideQualityAnalyzer analyzer = const SlideQualityAnalyzer()})
    => _applyNextFix(deck, analyzer) != null;

Vervang in slide_quality_panel.dart:314
de test result.issues.any((i) => isStructurallyAutofixable(i.kind)) door
hasApplicableStructuralFix(deck). Nu geldt: knop zichtbaar ⇔ applied > 0.
isStructurallyAutofixable blijft bestaan voor hasFixableIssue in tests.
(Top-level helper in dezelfde library → klasse-ratchet niet geraakt.)

B. Label eerlijk maken (lost Laag 1 op). Vervang l10n.d('Fix alle problemen')
(:320) door een nieuwe, dekkende
string, bijv. l10n.d('Los automatisch op wat kan') / EN "Fix what can be
automated". De bestaande snackbar is al eerlijk. Het paneel lijst de resterende
meldingen al met per-melding-acties, dus minimaal volstaat label + snackbar; het
apart groeperen van "vraagt om een keuze" is optioneel netjes.

Geen nieuwe fix-takken voor de 35 andere categorieën — dat is de bewuste
ontwerpgrens.

Regressietest (eerst-rood)

Sjabloon: test/quality_autofix_test.dart.
De bestaande fix-all-tests asserten niet dat het paneel schoon is in het
gemengde/woord-geval — precies het gat (panel-test toetst alleen slides.length > 1

  • snackbar).
  1. In quality_autofix_test.dart: bouw een dia met 7 bullets × ~13 woorden (> 90
    woorden → bulletWordCountHigh, maar ≤8 bullets → motor kan niet splitsen) en
    assert hasApplicableStructuralFix(deck) == (fixAllStructuralQualityIssues(deck).applied > 0).
    Vandaag rood (predicate = true, applied = 0), groen na fix A. Dekt meteen de
    coverage-poort voor de nieuwe helper.
  2. Widget-test in test/slide_quality_panel_test.dart:
    gemengd deck (te volle dia + contrastmelding) → na tik op de (hernoemde) knop is
    de contrastmelding er nog én de knop weg.

Geraakte poorten / ketens

  • l10n (31 talen): nieuwe knop-string = make add-l10n + 31 vertalingen. Oude key
    'Fix alle problemen' staat al in alle 31 lib/l10n/translations/*.dart; bij
    hernoemen opruimen (dode key is geen poortfout maar wel netjes).
  • file-size ratchet: slide_quality_panel.dart 710 regels, quality_autofix.dart
    228 — ruim onder 1000. Helper top-level → geen klasse-ratchet.
  • coverage-poort: nieuwe helper moet in een test voorkomen (test 1 dekt dat).

Open vragen voor de bouwer

  1. Scope: akkoord dat we het label eerlijk maken i.p.v. de auto-fix uitbreiden?
    Dit is een productkeuze (productowner/gebruiksgemak/bewaker); de filosofie "nooit
    inhoud weghalen" pleit sterk voor eerlijk labelen. bulletColumnImbalance
    (kolommen herverdelen) zou eventueel een veilige mechanische fix kunnen krijgen;
    de rest niet.
  2. Beide lagen in één PR? Laag 1 is het dagelijkse symptoom, Laag 2 een echte
    inconsistentie. Aanbevolen beide mee te nemen.
  3. richText-tellingdivergentie (_visibleBulletTexts vs visibleContentBulletCount)
    blijft onder de motorpoort bestaan ook na fix A (die verbergt alleen de knop) —
    mogelijk aparte bevinding waard.
## Symptoom Het slidekwaliteitspaneel heeft een knop **"Fix alle problemen"**. Na het indrukken blijven er nog kwaliteitsproblemen in het paneel staan. Dat botst met wat de knop belooft: als hij zegt *alle* problemen op te lossen, hoort het paneel daarna schoon te zijn — of moet duidelijk zijn welke meldingen principieel niet automatisch fixbaar zijn, en dan hoort de knop dat niet te beloven. **Reproductie:** open `Proefpresentatie.md` (of elk deck met contrast-, privacy- of alt-tekstmeldingen), open het kwaliteitspaneel, druk op "Fix alle problemen". De structurele problemen (te volle tekst) worden opgelost; contrast-, privacy-, alt-tekst- en media-meldingen blijven staan. ## Oorzaak — twee lagen Er zitten twee losse oorzaken onder hetzelfde symptoom. ### Laag 1 (hoofdoorzaak, altijd zichtbaar): de belofte is breder dan de motor De auto-fixmotor pakt **bewust** maar een deelverzameling aan. `isStructurallyAutofixable` in [lib/services/quality_autofix.dart:20](lib/services/quality_autofix.dart) dekt **8 van de 43** `SlideQualityIssueKind`-waarden ([lib/models/slide_quality.dart:15](lib/models/slide_quality.dart)): `bulletMultiSentence`, `splitRunDragged`, `textDensityWarning/Critical`, `bulletCountHigh/Critical`, `bulletWordCountHigh/Critical`. De motor `_applyNextFix` ([quality_autofix.dart:126](lib/services/quality_autofix.dart)) heeft precies drie takken: opknippen, splitsen, reeks losmaken. De overige **35** categorieën hebben geen fix-tak en worden alleen gemeld: alle contrast- (6), alt-/media- (5), privacy- (10), lege-dia-, en de niet-splitsbare dichtheidscategorieën (`tableDensityMinimum`, `codeDensityHigh`, `freeMarkdownDensityHigh`, `titleDensityHigh`, `quoteDensityHigh`, `bulletAverageLengthHigh`, `bulletNestingDeep`, `bulletColumnImbalance`). De knop verschijnt zodra **één** melding structureel-fixbaar is ([lib/widgets/panels/slide_quality_panel.dart:314](lib/widgets/panels/slide_quality_panel.dart)), maar staat naast een lijst die óók contrast/privacy/alt bevat. Het label is `l10n.d('Fix alle problemen')` → EN "Fix all issues" ([slide_quality_panel.dart:320](lib/widgets/panels/slide_quality_panel.dart)). De code klopt met de ontwerpfilosofie ("veilig = omkeerbaar, structureel, alle inhoud zichtbaar", docstring [quality_autofix.dart:38](lib/services/quality_autofix.dart)); **de belofte in het label klopt niet.** ### Laag 2 (secundaire bug): de knop-poort is ruimer dan de motor-poort `isStructurallyAutofixable` (poort voor knopzichtbaarheid) toetst alleen de *soort* melding, maar de motor gate't óók op de dia via `_shouldSplitFor` ([quality_autofix.dart:180](lib/services/quality_autofix.dart)). Voor een woord-melding op een dia met weinig bullets: - `bulletWordCountHigh` vuurt bij `totalWords > 90` ([lib/services/slide_quality/slide_quality_analyzer_density.dart:201](lib/services/slide_quality/slide_quality_analyzer_density.dart)), óók bij ≤8 bullets. - `_shouldSplitFor` eist `visibleContentBulletCount > _splitThreshold` (bijv. 7 > 8 = false) én `rendersTooSmall` (alleen waar voor `textDensity*`) → **false** → geen splitsing. - Gevolg: knop-poort = true (knop verschijnt en belooft), maar `_applyNextFix` geeft `null`, `applied == 0`, en de melding **blijft staan**. De snackbar zegt dan zelfs "Niets dat zich vanzelf laat oplossen" terwijl de knop wél verscheen. Dezelfde divergentie treft **richText-bulletdia's**: de analyzer telt bullets uit `customMarkdown`, maar `visibleContentBulletCount` telt `slide.bullets` (leeg bij richText) → `bulletCountHigh` kan vuren terwijl `_shouldSplitFor` false geeft. ## Voorgestelde oplossing (combinatie, zwaartepunt op eerlijk labelen) De 35 resterende categorieën zijn principieel niet veilig auto-fixbaar binnen de OciDeck-filosofie (contrast = thema-ontwerpkeuze; alt-tekst = tekst die een mens schrijft; ontbrekende media = bestand zoeken; privacy = expliciet menselijk oordeel; lege dia = niets te verzinnen; woord-/tabel-/codedichtheid = inhoud weghalen). De motor uitbreiden naar "echt alles" is dus onwenselijk. Daarom: **A. Knop-poort motor-accuraat maken (lost Laag 2 op).** Voeg in `quality_autofix.dart` een goedkope top-level helper toe die de echte motorpoort spiegelt: bool hasApplicableStructuralFix(Deck deck, {SlideQualityAnalyzer analyzer = const SlideQualityAnalyzer()}) => _applyNextFix(deck, analyzer) != null; Vervang in [slide_quality_panel.dart:314](lib/widgets/panels/slide_quality_panel.dart) de test `result.issues.any((i) => isStructurallyAutofixable(i.kind))` door `hasApplicableStructuralFix(deck)`. Nu geldt: **knop zichtbaar ⇔ `applied > 0`**. `isStructurallyAutofixable` blijft bestaan voor `hasFixableIssue` in tests. (Top-level helper in dezelfde library → klasse-ratchet niet geraakt.) **B. Label eerlijk maken (lost Laag 1 op).** Vervang `l10n.d('Fix alle problemen')` ([:320](lib/widgets/panels/slide_quality_panel.dart)) door een nieuwe, dekkende string, bijv. `l10n.d('Los automatisch op wat kan')` / EN "Fix what can be automated". De bestaande snackbar is al eerlijk. Het paneel lijst de resterende meldingen al met per-melding-acties, dus minimaal volstaat label + snackbar; het apart groeperen van "vraagt om een keuze" is optioneel netjes. **Geen nieuwe fix-takken** voor de 35 andere categorieën — dat is de bewuste ontwerpgrens. ## Regressietest (eerst-rood) Sjabloon: [test/quality_autofix_test.dart](test/quality_autofix_test.dart). De bestaande fix-all-tests asserten **niet** dat het paneel schoon is in het gemengde/woord-geval — precies het gat (panel-test toetst alleen `slides.length > 1` + snackbar). 1. In `quality_autofix_test.dart`: bouw een dia met 7 bullets × ~13 woorden (> 90 woorden → `bulletWordCountHigh`, maar ≤8 bullets → motor kan niet splitsen) en assert `hasApplicableStructuralFix(deck) == (fixAllStructuralQualityIssues(deck).applied > 0)`. Vandaag rood (predicate = true, applied = 0), groen na fix A. Dekt meteen de coverage-poort voor de nieuwe helper. 2. Widget-test in [test/slide_quality_panel_test.dart](test/slide_quality_panel_test.dart): gemengd deck (te volle dia + contrastmelding) → na tik op de (hernoemde) knop is de contrastmelding er nog én de knop weg. ## Geraakte poorten / ketens - **l10n (31 talen):** nieuwe knop-string = `make add-l10n` + 31 vertalingen. Oude key `'Fix alle problemen'` staat al in alle 31 `lib/l10n/translations/*.dart`; bij hernoemen opruimen (dode key is geen poortfout maar wel netjes). - **file-size ratchet:** `slide_quality_panel.dart` 710 regels, `quality_autofix.dart` 228 — ruim onder 1000. Helper top-level → geen klasse-ratchet. - **coverage-poort:** nieuwe helper moet in een test voorkomen (test 1 dekt dat). ## Open vragen voor de bouwer 1. **Scope:** akkoord dat we het label eerlijk maken i.p.v. de auto-fix uitbreiden? Dit is een productkeuze (productowner/gebruiksgemak/bewaker); de filosofie "nooit inhoud weghalen" pleit sterk voor eerlijk labelen. `bulletColumnImbalance` (kolommen herverdelen) zou eventueel een veilige mechanische fix kunnen krijgen; de rest niet. 2. **Beide lagen in één PR?** Laag 1 is het dagelijkse symptoom, Laag 2 een echte inconsistentie. Aanbevolen beide mee te nemen. 3. **richText-tellingdivergentie** (`_visibleBulletTexts` vs `visibleContentBulletCount`) blijft onder de motorpoort bestaan ook na fix A (die verbergt alleen de knop) — mogelijk aparte bevinding waard.
Author
Owner

Opgepakt. Tak: fix/1280-fixall-quality. Reikwijdte: lib/services/quality_autofix.dart (top-level hasApplicableStructuralFix), lib/widgets/panels/slide_quality_panel.dart (knop-poort motor-accuraat + eerlijk label), nieuwe l10n-string, test/quality_autofix_test.dart + test/slide_quality_panel_test.dart.

Opgepakt. Tak: fix/1280-fixall-quality. Reikwijdte: lib/services/quality_autofix.dart (top-level hasApplicableStructuralFix), lib/widgets/panels/slide_quality_panel.dart (knop-poort motor-accuraat + eerlijk label), nieuwe l10n-string, test/quality_autofix_test.dart + test/slide_quality_panel_test.dart.
Author
Owner

Opgelost op main als 5d9f78cc (squash van PR #1286). Deel A: top-level hasApplicableStructuralFix spiegelt de motorpoort, dus de knop verschijnt alleen als er ook echt iets wordt opgelost. Deel B: label 'Fix alle problemen' → 'Los automatisch op wat kan' in alle 31 talen, oude sleutel opgeruimd. Eerst-rode regressietests (motor-spiegel + gemengd-deck widgettest). Volledige make check groen (8605 tests). NIET in deze wijziging: de richText-tellingdivergentie (aparte bevinding) en het bewuste ontwerp dat 35 categorieën menselijk oordeel vragen.

Opgelost op main als 5d9f78cc (squash van PR #1286). Deel A: top-level hasApplicableStructuralFix spiegelt de motorpoort, dus de knop verschijnt alleen als er ook echt iets wordt opgelost. Deel B: label 'Fix alle problemen' → 'Los automatisch op wat kan' in alle 31 talen, oude sleutel opgeruimd. Eerst-rode regressietests (motor-spiegel + gemengd-deck widgettest). Volledige make check groen (8605 tests). NIET in deze wijziging: de richText-tellingdivergentie (aparte bevinding) en het bewuste ontwerp dat 35 categorieën menselijk oordeel vragen.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#1280
No description provided.