Code in een alinea is weer te lezen, een tabelbewerking gooit je niet naar de bron, en een afbeelding laat het schrijfvlak niet omvallen #1604

Merged
brenno merged 4 commits from fix/visuele-code-en-tabelmodus into main 2026-08-20 07:51:32 +00:00
Owner

Drie klachten uit één document, plus een vierde die eronder vandaan kwam. Ze hebben dezelfde vorm: iets wat er in de toets goed uitzag, maar op het scherm stukging.

1. code midden in een zin was niet te lezen

Sinds #1567 kreeg een inline code-stuk het vlak van een codeblok — in elk ingebouwd profiel bijna zwart — terwijl de letter de kleur van de alinea hield. In LibreKAT is dat #003399 op #111827: 1,63:1, in Vigilis 1,05:1. Een zwart blokje waarin het woord verdween.

De vergissing was de aanname dat "hetzelfde als het codeblok" hetzelfde is als "één renderwereld". Wat gelijk moet lopen zijn de weergaven onderling — schrijfvlak, documentweergave en export — niet blok en inline-run. Bovendien liep de app hierdoor juist uit de pas met de HTML-uitvoer, die inline code helemaal geen vlak gaf.

Het vlakje is nu een tint van de tekstkleur (AppTheme.inlineCodeBackground, 12%). Die ligt per definitie tussen papier en letter, dus de letter houdt vrijwel het volle contrast van de alinea — in elk profiel, in beide modi, en ook onder een eigen kleurenschema dat niemand hier heeft voorzien. De drie weergaven rekenen met dezelfde som.

2. Een tabelbewerking wierp het document terug in de brontekst

Twee gewone handelingen deden het al: een regeleinde in een cel (Shift+Enter, precies zoals de cel het zelf aanbiedt) en een backslash typen. encodeMarkdownTableCell schrijft die als <br> en als \\, en markdownVisualLimitations las dat als rauwe HTML en als een ontsnapping van de rijke-tekstlaag.

Maar een tabel reist als één x-embed-table-blok — dat is juist de reden dat hij niet in die opsomming staat. Zijn regels kunnen in de conversie dus helemaal niet stukgaan, en worden nu overgeslagen; dezelfde uitzondering die al gold voor de pentest-envelop en de tijdlijn. opensAtomicMarkdownTable is de toelatingsregel, bewust een spiegel van EmbeddableTableSyntax (kop en scheidingsrij met evenveel kolommen, geteld zoals de embed telt) en bewust strenger dan GFM: te streng laat hooguit een hele tabel langs de scanners lopen, te ruim verklaart een tabel veilig die de omzetting juist stukmaakt.

Andersom levert een tabelregel die niet bij zo'n blok hoort nu looseTableLine op. _normalizeQuillOutput laat de escapes op elke pijpregel bewust staan (daar betekent \| structuur), dus buiten een echt tabelblok blijven Quills \- en \. in de tekst van de gebruiker staan. Zichtbaar terugvallen is dan het enige eerlijke antwoord; stil doorgaan maalde de tabel tot één regel escaped tekst — wat er bij een kop en een ongelijk brede scheidingsrij gebeurde.

3. Een afbeelding liet het schrijfvlak omvallen

markdown_quill maakt van ![alt](bron) een image-embed die alléén de bron draagt. Dat kostte twee dingen tegelijk:

  • Voor image stond hier geen bouwer, dus Quill wierp UnimplementedError: Embeddable type "image" is not supported tijdens het tekenen van de regel. Dat is geen stukgelopen alinea maar een foutscherm in plaats van het document.
  • De terugweg schreef ![](bron): de alt-tekst was weg. Eén bewerking elders in het document gooide overal de toegankelijke omschrijving weg, zonder melding en zonder weg terug.

De afbeelding reist nu als x-embed-image met haar hele markdown erin, en verschijnt als merkteken met de alt-tekst (of de bestandsnaam) op zijn plek in de zin. Niet de afbeelding zelf, en dat is een bewuste grens: een mem:/asset:/deck-relatief pad uitzoeken hoort bij ImageService met de deckmap erbij, en de documentlezer tekent afbeeldingen vandaag óók niet — wél de HTML- en PDF-uitvoer. Zolang die twee uit elkaar lopen is het eerlijker om te tonen dát er een afbeelding staat en welke, dan om er in één weergave een te verzinnen. (Dat gat is werk voor een eigen issue.)

Daaronder ligt nu een vangnet: unknownEmbedBuilder maakt van een embed-type zonder bouwer een zichtbaar rafeltje in plaats van een dichtgeslagen deur. Deze klasse fout mag nooit meer het hele document meenemen.

4. De melding "je bewerkt de bron" was zelf onleesbaar

Vierde plek met dezelfde vergissing als 1: de balk lag op theme.codeBackground met de alineakleur erop. In Vigilis zijn dat allebei #111318. De enige melding die vertelt wáár je bent stond er dus wel en zei niets — precies het gat dat #1565 wilde dichten. Hij draagt nu dezelfde tint als een code-woord, en het icoon de inkt van de alinea in plaats van het accent (een accent kán geel zijn, en dat haalt op een licht vlak de 3:1 voor een grafisch onderdeel niet).

Afweging (bewaker)

Raakt dit het bestandsformaat? Nee, en dat is de kern. Er komt geen token, geen sidecar en geen markering bij; wat er verandert is dat de rijke-tekstlaag mínder aan de tekst van de gebruiker doet. De afbeelding-embed leeft alleen ín het Quill-document en schrijft er letterlijk uit wat erin ging — dat is één stap meer byte-getrouwheid, niet minder uitwisselbaarheid.

Eén botsing is expliciet gemaakt: looseTableLine maakt de poort op één punt strenger, dus een bestaand document met een losse pijpregel of een ongelijk brede scheidingsrij opent voortaan in de bron waar het eerder visueel opende. Ik laat "de tekst van de gebruiker blijft heel" voorgaan op "de visuele stand blijft staan", omdat dat visueel openen de tabel juist sloopte. Ik zou van gedachten veranderen als blijkt dat gewone documenten pijpregels dragen die géén tabel zijn — dan hoort de regel nauwer, niet ruimer.

Geen nieuwe afhankelijkheid, geen netwerk, geen uitgaand verkeer, geen publieke belofte verruimd.

Toetsen

  • make check groen (10.091 tests, dekking 87,3%, per-bestand-vloer 0).
  • Nieuwe poort test/inline_code_contrast_test.dart meet het paar dat niemand als paar zag — de alineakleur op de inline-code-achtergrond, per ingebouwd profiel plus een donker papier, én dezelfde som voor de bronmodus-melding. Eerst rood gemaakt tegen de ónherstelde kleur: LibreKAT 1,63:1, Vigilis 1,05:1, Standaard 1,12:1.
  • test/document_visual_table_edit_test.dart: een regeleinde en een backslash in een cel houden het schrijfvlak overeind (eerst rood: QuillEditor verdween).
  • test/document_visual_image_test.dart: geen uitzondering, merkteken met alt-tekst, alt-tekst en titel overleven de heen-en-terugweg, en het vangnet los getoetst.
  • test/markdown_visual_compatibility_test.dart uitgebreid met de tabelregels die wél en niet meereizen.
  • Met eigen ogen in de draaiende app: het rapport waar de klacht vandaan kwam (code leest nu op zijn tint), Shift+Enter in een cel houdt je in Visueel en levert | Aap<br>Noot | in de bron, de afbeelding rendert als merkteken, en de meldingsbalk is leesbaar.
  • make check-secrets (gitleaks + trufflehog, werkboom én historie): geen bevindingen. make sast (semgrep, lokale regels): 0 findings op 1.136 bestanden.
  • DAST (ZAP) niet gedraaid: deze wijziging raakt het geserveerde weboppervlak niet.

Documentatie

USER_GUIDE (en + nl) beschrijft wanneer Visueel terugvalt op de bron en waarom, en hoe een afbeelding in de visuele stand verschijnt. SOURCE_MAP draagt de twee nieuwe bestanden en de gewijzigde regels van markdown_table_lines en markdown_visual_compatibility. CHANGELOG heeft een Development-log-ingang.

Drie klachten uit één document, plus een vierde die eronder vandaan kwam. Ze hebben dezelfde vorm: iets wat er in de toets goed uitzag, maar op het scherm stukging. ## 1. `code` midden in een zin was niet te lezen Sinds #1567 kreeg een inline `code`-stuk het vlak van een **codeblok** — in elk ingebouwd profiel bijna zwart — terwijl de letter de kleur van de alinea hield. In LibreKAT is dat `#003399` op `#111827`: **1,63:1**, in Vigilis **1,05:1**. Een zwart blokje waarin het woord verdween. De vergissing was de aanname dat "hetzelfde als het codeblok" hetzelfde is als "één renderwereld". Wat gelijk moet lopen zijn de **weergaven onderling** — schrijfvlak, documentweergave en export — niet blok en inline-run. Bovendien liep de app hierdoor juist uit de pas met de HTML-uitvoer, die inline code helemaal geen vlak gaf. Het vlakje is nu een tint van de **tekstkleur** (`AppTheme.inlineCodeBackground`, 12%). Die ligt per definitie tussen papier en letter, dus de letter houdt vrijwel het volle contrast van de alinea — in elk profiel, in beide modi, en ook onder een eigen kleurenschema dat niemand hier heeft voorzien. De drie weergaven rekenen met dezelfde som. ## 2. Een tabelbewerking wierp het document terug in de brontekst Twee gewone handelingen deden het al: een **regeleinde in een cel** (`Shift+Enter`, precies zoals de cel het zelf aanbiedt) en een **backslash typen**. `encodeMarkdownTableCell` schrijft die als `<br>` en als `\\`, en `markdownVisualLimitations` las dat als rauwe HTML en als een ontsnapping van de rijke-tekstlaag. Maar een tabel reist als één `x-embed-table`-blok — dat is juist de reden dat hij niet in die opsomming staat. Zijn regels kunnen in de conversie dus helemaal niet stukgaan, en worden nu overgeslagen; dezelfde uitzondering die al gold voor de pentest-envelop en de tijdlijn. `opensAtomicMarkdownTable` is de toelatingsregel, bewust een spiegel van `EmbeddableTableSyntax` (kop en scheidingsrij met evenveel kolommen, geteld zoals de embed telt) en bewust strenger dan GFM: te streng laat hooguit een hele tabel langs de scanners lopen, te ruim verklaart een tabel veilig die de omzetting juist stukmaakt. Andersom levert een tabelregel die **niet** bij zo'n blok hoort nu `looseTableLine` op. `_normalizeQuillOutput` laat de escapes op elke pijpregel bewust staan (daar betekent `\|` structuur), dus buiten een echt tabelblok blijven Quills `\-` en `\.` in de tekst van de gebruiker staan. Zichtbaar terugvallen is dan het enige eerlijke antwoord; stil doorgaan maalde de tabel tot één regel escaped tekst — wat er bij een kop en een ongelijk brede scheidingsrij gebeurde. ## 3. Een afbeelding liet het schrijfvlak omvallen `markdown_quill` maakt van `![alt](bron)` een `image`-embed die alléén de bron draagt. Dat kostte twee dingen tegelijk: - Voor `image` stond hier geen bouwer, dus Quill wierp `UnimplementedError: Embeddable type "image" is not supported` **tijdens het tekenen van de regel**. Dat is geen stukgelopen alinea maar een foutscherm in plaats van het document. - De terugweg schreef `![](bron)`: de **alt-tekst was weg**. Eén bewerking elders in het document gooide overal de toegankelijke omschrijving weg, zonder melding en zonder weg terug. De afbeelding reist nu als `x-embed-image` met haar hele markdown erin, en verschijnt als merkteken met de alt-tekst (of de bestandsnaam) op zijn plek in de zin. **Niet de afbeelding zelf, en dat is een bewuste grens:** een `mem:`/`asset:`/deck-relatief pad uitzoeken hoort bij `ImageService` met de deckmap erbij, en de documentlezer tekent afbeeldingen vandaag óók niet — wél de HTML- en PDF-uitvoer. Zolang die twee uit elkaar lopen is het eerlijker om te tonen *dát* er een afbeelding staat en welke, dan om er in één weergave een te verzinnen. (Dat gat is werk voor een eigen issue.) Daaronder ligt nu een **vangnet**: `unknownEmbedBuilder` maakt van een embed-type zonder bouwer een zichtbaar rafeltje in plaats van een dichtgeslagen deur. Deze klasse fout mag nooit meer het hele document meenemen. ## 4. De melding "je bewerkt de bron" was zelf onleesbaar Vierde plek met dezelfde vergissing als 1: de balk lag op `theme.codeBackground` met de alineakleur erop. In Vigilis zijn dat allebei `#111318`. De enige melding die vertelt wáár je bent stond er dus wel en zei niets — precies het gat dat #1565 wilde dichten. Hij draagt nu dezelfde tint als een `code`-woord, en het icoon de inkt van de alinea in plaats van het accent (een accent kán geel zijn, en dat haalt op een licht vlak de 3:1 voor een grafisch onderdeel niet). ## Afweging (bewaker) Raakt dit het bestandsformaat? **Nee, en dat is de kern.** Er komt geen token, geen sidecar en geen markering bij; wat er verandert is dat de rijke-tekstlaag mínder aan de tekst van de gebruiker doet. De afbeelding-embed leeft alleen ín het Quill-document en schrijft er letterlijk uit wat erin ging — dat is één stap meer byte-getrouwheid, niet minder uitwisselbaarheid. Eén botsing is expliciet gemaakt: `looseTableLine` maakt de poort op één punt **strenger**, dus een bestaand document met een losse pijpregel of een ongelijk brede scheidingsrij opent voortaan in de bron waar het eerder visueel opende. Ik laat "de tekst van de gebruiker blijft heel" voorgaan op "de visuele stand blijft staan", omdat dat visueel openen de tabel juist sloopte. Ik zou van gedachten veranderen als blijkt dat gewone documenten pijpregels dragen die géén tabel zijn — dan hoort de regel nauwer, niet ruimer. Geen nieuwe afhankelijkheid, geen netwerk, geen uitgaand verkeer, geen publieke belofte verruimd. ## Toetsen - `make check` groen (10.091 tests, dekking 87,3%, per-bestand-vloer 0). - Nieuwe poort `test/inline_code_contrast_test.dart` meet het paar dat niemand als paar zag — de alineakleur op de inline-code-achtergrond, per ingebouwd profiel plus een donker papier, én dezelfde som voor de bronmodus-melding. Eerst rood gemaakt tegen de ónherstelde kleur: LibreKAT 1,63:1, Vigilis 1,05:1, Standaard 1,12:1. - `test/document_visual_table_edit_test.dart`: een regeleinde en een backslash in een cel houden het schrijfvlak overeind (eerst rood: `QuillEditor` verdween). - `test/document_visual_image_test.dart`: geen uitzondering, merkteken met alt-tekst, alt-tekst en titel overleven de heen-en-terugweg, en het vangnet los getoetst. - `test/markdown_visual_compatibility_test.dart` uitgebreid met de tabelregels die wél en niet meereizen. - **Met eigen ogen in de draaiende app**: het rapport waar de klacht vandaan kwam (`code` leest nu op zijn tint), `Shift+Enter` in een cel houdt je in Visueel en levert `| Aap<br>Noot |` in de bron, de afbeelding rendert als merkteken, en de meldingsbalk is leesbaar. - `make check-secrets` (gitleaks + trufflehog, werkboom én historie): geen bevindingen. `make sast` (semgrep, lokale regels): 0 findings op 1.136 bestanden. - DAST (ZAP) niet gedraaid: deze wijziging raakt het geserveerde weboppervlak niet. ## Documentatie USER_GUIDE (en + nl) beschrijft wanneer Visueel terugvalt op de bron en waarom, en hoe een afbeelding in de visuele stand verschijnt. SOURCE_MAP draagt de twee nieuwe bestanden en de gewijzigde regels van `markdown_table_lines` en `markdown_visual_compatibility`. CHANGELOG heeft een Development-log-ingang.
Drie klachten uit één document, met dezelfde vorm: iets wat er goed uitzag
in de toets, maar op het scherm stukging.

**`code` midden in een zin.** Sinds #1567 kreeg een inline `code`-stuk het
vlak van een codeblok — in elk ingebouwd profiel bijna zwart — terwijl de
letter de kleur van de alinea hield. In LibreKAT is dat `#003399` op
`#111827`: 1,6:1, een zwart blokje waarin het woord niet meer te lezen was.
De vergissing was de aanname dat "hetzelfde als het codeblok" hetzelfde is
als "één renderwereld"; wat gelijk moet lopen zijn de weergaven onderling.
Het vlakje is nu een tint van de tékstkleur, en die ligt per definitie
tussen papier en letter — schrijfvlak, documentweergave en HTML-uitvoer
rekenen met dezelfde som.

**Een tabelbewerking wierp het document terug in de brontekst.** Een
regeleinde in een cel (Shift+Enter, precies zoals de cel het aanbiedt) of
een backslash typen was genoeg: `encodeMarkdownTable` schrijft die als
`<br>` en als `\\`, en de visuele poort las dat als rauwe HTML en als een
ontsnapping. De tabel reist juist als één `x-embed-table`-blok, dus zijn
regels kunnen daar helemaal niet stukgaan — ze worden nu overgeslagen,
zoals dat al gold voor de pentest-envelop en de tijdlijn. Andersom levert
een tabelregel die *niet* bij zo'n blok hoort nu `looseTableLine` op, want
`_normalizeQuillOutput` laat de escapes op elke pijpregel bewust staan;
zichtbaar terugvallen is dan beter dan de tabel stilletjes tot één regel
escaped tekst malen (wat er bij een ongelijk brede scheidingsrij gebeurde).

**Een afbeelding liet het schrijfvlak omvallen.** `markdown_quill` maakt
van `![alt](bron)` een `image`-embed die alleen de bron draagt: geen bouwer
voor dat type, dus `UnimplementedError` tijdens het tekenen van de regel en
een foutscherm in plaats van het document — én `![](bron)` op de terugweg,
dus de alt-tekst was weg. De afbeelding reist nu als `x-embed-image` met
haar hele markdown erin, en er staat een vangnet onder de bouwers: een
onbekend embed-type wordt een zichtbaar rafeltje, geen dichtgeslagen deur.

De contrastpoort meet voortaan het paar dat niemand als paar zag: de
alineakleur op de inline-code-achtergrond, per profiel. Op de oude kleur
staat hij rood (LibreKAT 1,63:1, Vigilis 1,05:1).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vierde plek met dezelfde vergissing als de inline code: de balk lag op
`theme.codeBackground` — het paneel van een codeblok — met de gewone
alineakleur erop. In Vigilis zijn dat allebei `#111318` en in LibreKAT
donkerblauw op bijna zwart. De enige melding die vertelt wáár je bent stond
er dus wel, en zei niets; precies het gat dat #1565 juist wilde dichten.

Hij draagt nu dezelfde tint als een `code`-woord, en het icoon de inkt van
de alinea in plaats van het accent — een accent kán geel zijn (Vigilis), en
dat haalt op een licht vlak de 3:1 voor een grafisch onderdeel niet. De
contrastpoort meet nu ook dit paar, per profiel.

Verder: SOURCE_MAP en gebruikersgids bijgewerkt (wanneer Visueel terugvalt
op de bron en waarom, en hoe een afbeelding in de visuele stand verschijnt),
plus een regel in het ontwikkellogboek.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`translate_docs --check` eist ze in dezelfde wijziging; er is bewust geen
basislijn om op te schuiven.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chore(macos): Podfile.lock houdt zijn gepinde CocoaPods-regel
All checks were successful
scans / scans (pull_request) Successful in 2m48s
static-gate / static-gate (pull_request) Successful in 5m57s
92f4ecb7a2
Ruis van het lokaal draaien van de app: `pod install` schreef 1.16.2 terug
over de 1.17.0 die fc9ded8b er bewust in zette. Hoort niet bij deze wijziging.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
brenno merged commit f8a5aa0bf0 into main 2026-08-20 07:51:31 +00:00
Sign in to join this conversation.
No description provided.