feat(documentmodus): voetnoten, breedte en zoom, een kop die niet alleen blijft, en pijltjes in een tabel #1554

Merged
brenno merged 14 commits from feat/documenteditor-schrijfcomfort into main 2026-08-18 15:17:44 +00:00
Owner

Wat er opviel tijdens het werken in een document

Vier waarnemingen, en wat elk ervan bleek te zijn.

1. De volle breedte gebruiken kon niet, zoomen helemaal niet

De instelling Schrijfbreedte editor had een optie "volledige breedte" die
niets leek te doen. Ze deed ook niets: de visuele stand trok de schrijfbreedte
naar die van het vel zodra de pagina-einden aanstonden, en die staan
standaard aan. Eén schakelaar stuurde twee losse dingen, en degene die de
gebruiker zag was de verkeerde.

Ze zijn nu uit elkaar. De werkbalk kiest de breedte — Paginabreedte,
Leeskolom, Volledige breedte — want dat is een keuze die je tijdens het
schrijven maakt: even het hele scherm voor een brede tabel, daarna terug naar
het vel. Ernaast staat een zoom (− / percentage / +) met Cmd/Ctrl +, en
0. Buiten Paginabreedte worden de streepjeslijnen niet meer getekend en zegt
de knop waarom: op een andere maat breekt het vel ergens anders dan de lijn
aanwijst.

De zoom is meetkundig eerlijk gemaakt: hij schaalt de tekst, de kolombreedte
én de paginahoogte waarmee de einden worden gerekend, alledrie met dezelfde
factor. Daardoor valt een pagina-einde op 250% op precies dezelfde plek in de
tekst als op 50%. In de Pagina's-stand schaalt hij het vel zelf — via
PagedDocumentView.scale, een parameter die al bestond maar die niemand
doorgaf — met een horizontale rol voor wat dan niet meer in het venster past.

2. Een kop bleef alleen onderaan een blad achter

De paginaverdeling kende alleen blokhoogtes, dus een kop kon prima het laatste
zijn wat op een vel paste. De regel die erbij komt: onder een kop moeten op
hetzelfde vel nog minstens twee regels tekst passen
, geteld over zoveel
volgende blokken als nodig — anders schuift de kop mee. Twee regels en niet één,
want een kop met één losse regel eronder leest niet beter dan een kop die
helemaal alleen staat; dat was ook precies hoe de melding luidde.

Twee koppen op elkaar reizen als groep, en een te hoge tabel neemt de kop
erboven mee in plaats van een vers vel te eisen en hem achter te laten. Geldt in
de Pagina's-stand, in de streepjeslijnen van de schrijfstand, en bij het
afdrukken van de HTML-export (break-after: avoid plus weduwen/wezen). LaTeX
deed dit al uit zichzelf.

3. Voetnoten

Op schijf de voetnootschrijfwijze van Pandoc, die GitHub en Obsidian ook lezen —
[^1] in de tekst, [^1]: … eronder. Het merkteken is het volgnummer in
leesvolgorde; het label blijft van de auteur en blijft onaangeroerd in het
bestand staan, dus een noot ertussen voegen hernummert niets met de hand. Een
[^abc] zonder definitie blijft letterlijke tekst.

Waar ze landen staat per document in de front matter, als
reference-location: document — een sleutel die Pandoc en Quarto zélf
uitvoeren. De standaard (onderaan de bladzijde) schrijft niets: een document
dat niets bijzonders wil, blijft een .md zonder front matter. Zie
FILE_FORMAT.md §14.9.

In de Pagina's-stand staan de noten echt onderaan het blad waar de verwijzing
valt. De ruimte ervoor hangt aan het blok dat de noot aanhaalt en niet aan de
pagina: schuift dat blok door, dan schuift de noot mee en komt de ruimte hier
vanzelf weer vrij. Dat is de reden dat dit in één doorloop kan in plaats van net
zo lang heen en weer te rekenen tot het stil ligt.

In de visuele editor reizen ze als twee embeds mee — de verwijzing inline,
de definitie als blok op zijn eigen plek. Bewust twee: met één embed die tekst
én merkteken droeg, zou de definitie bij de eerste bewerking naar de verwijzing
verhuizen, en dat is precies wat een byte-getrouwe documentmodus niet mag doen.
Daarmee vervalt de laatste reden waarom één voetnoot je terugwierp in de
brontekst.

4. Pijltjes in een tabelcel deden niets herkenbaars

Ze verplaatsten de cursor binnen de cel en liepen daarna dood (of schoten de
tekst buiten de tabel in). Nu doen ze wat een rekenblad doet: ←/→ lopen eerst
door de celtekst en springen aan de rand naar de buurcel, ↑/↓ gaan een rij op of
neer zodra de cursor de boven- of onderste regel heeft bereikt. Geen doorloop
voorbij de rand van de tabel — daarvoor is Tab, die de tabel ook laat groeien.
De regel staat op één plek en geldt voor de documenttabel én de diabouwer: twee
tabellen die anders op een pijltje reageren is precies wat een mens niet
begrijpt.

Afweging (bewaker)

Het formaat is de kern hier, en die houdt stand: alles wat er in het .md
belandt is Pandoc-vocabulaire dat andere gereedschappen uitvoeren, er komt geen
eigen sleutel bij, geen nieuwe afhankelijkheid en geen nieuwe partij om te
vertrouwen. Houdt OciDeck morgen op te bestaan, dan rendert Pandoc het document
inclusief de gekozen notenplaatsing.

Eén botsing, hardop: een voetnootdefinitie die in de bron over meerdere
ingesprongen regels stond, komt na een bewerking in de visuele editor als één
regel terug. De tekst is identiek, alleen de regelval in de bron niet. Dat
verzwakt de byte-getrouwheid van §14.3 in dat ene geval. Het alternatief was
voetnoten als bron-terugval houden, en dan is de visuele stand precies bij deze
functie onbruikbaar. Uitwisselbaarheid houdt stand (de bytes blijven geldige
Pandoc-voetnoten), dus die kant heeft voorrang gekregen — met de beperking in
FILE_FORMAT.md §14.9 in plaats van stilzwijgend. Ik verander van gedachten zodra
noten van meerdere alinea's gewoon worden: dan hoort de definitie-embed de
regelstructuur zelf te dragen.

Toetsing

Regressietests per onderdeel, elk één keer rood gezien tegen de onherstelde
code: de opmaakregels als pure tests op documentPageOffsets (kopregel én de
gereserveerde ruimte voor noten), de voetnoot-ontleding, de round-trip door de
rijke-tekstlaag, de celnavigatie, de werkbalk met breedte en zoom, en beide
exportwegen. De projectie document → deck → document is apart vastgepind, want
daar reist een noot doorheen op weg naar de export.

Niet met eigen ogen gekeurd. Dit is visueel werk en dat vraagt een
beeldkeuring in de draaiende app; de schermtoegang werd geweigerd, dus die ronde
staat nog open. De widget-tests bevestigen de waarden en de boom, niet of het er
goed uitziet.

Poorten

make check groen (9781 tests, dekking 87,0%, per-bestandsvloer 0),
make check-secrets groen, make sast groen (0 findings). DAST niet gedraaid —
deze wijziging raakt geen geserveerd oppervlak. pubspec.yaml ongewijzigd, dus
geen SBOM-ronde.

## Wat er opviel tijdens het werken in een document Vier waarnemingen, en wat elk ervan bleek te zijn. ### 1. De volle breedte gebruiken kon niet, zoomen helemaal niet De instelling *Schrijfbreedte editor* had een optie "volledige breedte" die niets leek te doen. Ze deed ook niets: de visuele stand trok de schrijfbreedte naar die van het vel **zodra de pagina-einden aanstonden**, en die staan standaard aan. Eén schakelaar stuurde twee losse dingen, en degene die de gebruiker zag was de verkeerde. Ze zijn nu uit elkaar. De werkbalk kiest de breedte — **Paginabreedte**, **Leeskolom**, **Volledige breedte** — want dat is een keuze die je tijdens het schrijven maakt: even het hele scherm voor een brede tabel, daarna terug naar het vel. Ernaast staat een zoom (− / percentage / +) met `Cmd/Ctrl +`, `−` en `0`. Buiten *Paginabreedte* worden de streepjeslijnen niet meer getekend en zegt de knop waarom: op een andere maat breekt het vel ergens anders dan de lijn aanwijst. De zoom is meetkundig eerlijk gemaakt: hij schaalt de tekst, de kolombreedte **én** de paginahoogte waarmee de einden worden gerekend, alledrie met dezelfde factor. Daardoor valt een pagina-einde op 250% op precies dezelfde plek in de tekst als op 50%. In de Pagina's-stand schaalt hij het vel zelf — via `PagedDocumentView.scale`, een parameter die al bestond maar die niemand doorgaf — met een horizontale rol voor wat dan niet meer in het venster past. ### 2. Een kop bleef alleen onderaan een blad achter De paginaverdeling kende alleen blokhoogtes, dus een kop kon prima het laatste zijn wat op een vel paste. De regel die erbij komt: **onder een kop moeten op hetzelfde vel nog minstens twee regels tekst passen**, geteld over zoveel volgende blokken als nodig — anders schuift de kop mee. Twee regels en niet één, want een kop met één losse regel eronder leest niet beter dan een kop die helemaal alleen staat; dat was ook precies hoe de melding luidde. Twee koppen op elkaar reizen als groep, en een te hoge tabel neemt de kop erboven mee in plaats van een vers vel te eisen en hem achter te laten. Geldt in de Pagina's-stand, in de streepjeslijnen van de schrijfstand, en bij het afdrukken van de HTML-export (`break-after: avoid` plus weduwen/wezen). LaTeX deed dit al uit zichzelf. ### 3. Voetnoten Op schijf de voetnootschrijfwijze van Pandoc, die GitHub en Obsidian ook lezen — `[^1]` in de tekst, `[^1]: …` eronder. Het merkteken is het volgnummer in leesvolgorde; het label blijft van de auteur en blijft onaangeroerd in het bestand staan, dus een noot ertussen voegen hernummert niets met de hand. Een `[^abc]` zonder definitie blijft letterlijke tekst. **Waar ze landen staat per document in de front matter**, als `reference-location: document` — een sleutel die Pandoc en Quarto zélf uitvoeren. De standaard (onderaan de bladzijde) schrijft *niets*: een document dat niets bijzonders wil, blijft een `.md` zonder front matter. Zie FILE_FORMAT.md §14.9. In de Pagina's-stand staan de noten echt onderaan het blad waar de verwijzing valt. De ruimte ervoor hangt aan het blok dat de noot aanhaalt en niet aan de pagina: schuift dat blok door, dan schuift de noot mee en komt de ruimte hier vanzelf weer vrij. Dat is de reden dat dit in één doorloop kan in plaats van net zo lang heen en weer te rekenen tot het stil ligt. In de visuele editor reizen ze als **twee** embeds mee — de verwijzing inline, de definitie als blok op zijn eigen plek. Bewust twee: met één embed die tekst én merkteken droeg, zou de definitie bij de eerste bewerking naar de verwijzing verhuizen, en dat is precies wat een byte-getrouwe documentmodus niet mag doen. Daarmee vervalt de laatste reden waarom één voetnoot je terugwierp in de brontekst. ### 4. Pijltjes in een tabelcel deden niets herkenbaars Ze verplaatsten de cursor binnen de cel en liepen daarna dood (of schoten de tekst buiten de tabel in). Nu doen ze wat een rekenblad doet: ←/→ lopen eerst door de celtekst en springen aan de rand naar de buurcel, ↑/↓ gaan een rij op of neer zodra de cursor de boven- of onderste regel heeft bereikt. Geen doorloop voorbij de rand van de tabel — daarvoor is Tab, die de tabel ook laat groeien. De regel staat op één plek en geldt voor de documenttabel én de diabouwer: twee tabellen die anders op een pijltje reageren is precies wat een mens niet begrijpt. ## Afweging (bewaker) Het formaat is de kern hier, en die houdt stand: alles wat er in het `.md` belandt is Pandoc-vocabulaire dat andere gereedschappen uitvoeren, er komt geen eigen sleutel bij, geen nieuwe afhankelijkheid en geen nieuwe partij om te vertrouwen. Houdt OciDeck morgen op te bestaan, dan rendert Pandoc het document inclusief de gekozen notenplaatsing. **Eén botsing, hardop:** een voetnootdefinitie die in de bron over meerdere ingesprongen regels stond, komt na een bewerking in de visuele editor als één regel terug. De tekst is identiek, alleen de regelval in de bron niet. Dat verzwakt de byte-getrouwheid van §14.3 in dat ene geval. Het alternatief was voetnoten als bron-terugval houden, en dan is de visuele stand precies bij deze functie onbruikbaar. Uitwisselbaarheid houdt stand (de bytes blijven geldige Pandoc-voetnoten), dus die kant heeft voorrang gekregen — met de beperking in FILE_FORMAT.md §14.9 in plaats van stilzwijgend. Ik verander van gedachten zodra noten van meerdere alinea's gewoon worden: dan hoort de definitie-embed de regelstructuur zelf te dragen. ## Toetsing Regressietests per onderdeel, elk één keer rood gezien tegen de onherstelde code: de opmaakregels als pure tests op `documentPageOffsets` (kopregel én de gereserveerde ruimte voor noten), de voetnoot-ontleding, de round-trip door de rijke-tekstlaag, de celnavigatie, de werkbalk met breedte en zoom, en beide exportwegen. De projectie document → deck → document is apart vastgepind, want daar reist een noot doorheen op weg naar de export. **Niet met eigen ogen gekeurd.** Dit is visueel werk en dat vraagt een beeldkeuring in de draaiende app; de schermtoegang werd geweigerd, dus die ronde staat nog open. De widget-tests bevestigen de waarden en de boom, niet of het er goed uitziet. ## Poorten `make check` groen (9781 tests, dekking 87,0%, per-bestandsvloer 0), `make check-secrets` groen, `make sast` groen (0 findings). DAST niet gedraaid — deze wijziging raakt geen geserveerd oppervlak. `pubspec.yaml` ongewijzigd, dus geen SBOM-ronde.
De paginaverdeling kende alleen blokhoogtes, dus een kop kon net zo goed het
laatste zijn wat er op een vel paste — met de tekst die erbij hoort op de
volgende bladzijde. Nu houdt een kop de tekst eronder vast: passen er onder de
kop geen twee regels meer op hetzelfde vel, dan schuift de kop mee. Twee koppen
op elkaar reizen als groep, en een te hoge tabel neemt de kop erboven mee in
plaats van hem achter te laten.

Geldt in de Pagina's-stand, in de streepjeslijnen van de schrijfstand en — als
`break-after: avoid` met weduwen/wezen — bij het afdrukken van de HTML-export.
LaTeX deed dit zelf al.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In een tabel deed een pijltje niets herkenbaars: het verplaatste de cursor
binnen de cel en liep daarna dood, of schoot de tekst buiten de tabel in. Nu
verplaatsen ← en → eerst de cursor door de tekst en springen ze naar de buurcel
zodra die aan de rand van de celinhoud staat; ↑ en ↓ gaan een rij op of neer,
maar pas nadat de cursor de bovenste of onderste regel van de cel heeft
bereikt. Geen doorloop voorbij de rand van de tabel — daarvoor is Tab, die de
tabel ook laat groeien.

De regel staat in `tableArrowTarget` en wordt gedeeld door de tabel in de
documentmodus en die in de diabouwer: twee tabellen die anders op een pijltje
reageren is precies wat een mens niet begrijpt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
De volle breedte gebruiken kon niet: de schakelaar voor de pagina-einden trok
het schrijfvlak naar de tekstbreedte van het vel, en omdat die einden standaard
aanstaan deed de instelling "volledige breedte" niets. In- of uitzoomen kon
helemaal niet.

Nu kiest de werkbalk de breedte — paginabreedte, leeskolom of het hele venster
— en staat er een zoom naast (Cmd/Ctrl met + of −, en 0 terug naar ware
grootte). De zoom schaalt de tekst, de kolombreedte én de paginahoogte waarmee
de einden worden uitgerekend, alledrie met dezelfde factor: de regelval blijft
zo gelijk aan die op papier en een pagina-einde valt op elke zoomstand op
dezelfde plek in de tekst. In de Pagina's-stand schaalt hij het vel zelf, met
een horizontale rol voor wat dan niet meer in het venster past.

Buiten paginabreedte worden de einden niet meer getekend: daar zouden ze iets
aanwijzen wat niet gebeurt. De knop zegt dat ook in plaats van stil te vallen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`[^1]` in de tekst en `[^1]: de noot` eronder: de voetnootsyntaxis van Pandoc,
die GitHub en Obsidian ook kennen, zodat een document mét noten een gewone `.md`
blijft. De definitieregels verdwijnen uit de lopende tekst; de verwijzing wordt
een klein volgnummer, doorgenummerd in leesvolgorde zodat een noot ertussen
voegen niets laat hernummeren.

Waar ze belanden staat per document in de front matter, als
`reference-location: document` — een sleutel die Pandoc en Quarto zelf
uitvoeren. De standaard (onderaan de bladzijde) schrijft niets: een document dat
niets bijzonders wil, blijft een `.md` zonder front matter.

In de Pagina's-stand staan de noten echt onderaan het blad waar de verwijzing
valt. De ruimte daarvoor hangt aan het blok dat de noot aanhaalt en niet aan de
pagina — schuift dat blok door, dan schuift de noot mee en komt de ruimte weer
vrij, zonder heen-en-weer-rekenen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eén voetnoot was genoeg om de hele visuele modus terug te werpen op brontekst —
precies bij de functie waarvoor je hem het hardst nodig hebt. De verwijzing
reist nu als inline-embed en de definitie als blok-embed door de rijke-tekstlaag,
dezelfde route die de tabel en de inhoudsopgave al namen. Twee embeds en niet
één, zodat de definitie blijft staan waar de auteur hem in het bestand zette.

Invoegen → Voetnoot zet het merkteken op de cursor en een lege notenregel
onderaan, met de cursor er meteen in. Het label is het eerstvolgende vrije
getal; een zelfgeschreven `[^bron]` blijft `[^bron]`, want de weergave nummert
toch door op leesvolgorde.

Onder ⋮ staat waar de noten van dít document komen. Achterin schrijft
`reference-location: document` in de front matter; onderaan de bladzijde haalt
de sleutel juist weg — dat is wat elke lezer zonder aanwijzing al doet.

Het invoeg-palet verhuist naar een eigen part: het bewerkscherm stuitte voor de
derde keer op zijn regelplafond.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LaTeX kan wat papier kan: onderaan de bladzijde wordt de noot een echte
`\footnote`, achterin een merkteken plus een genummerde lijst onder een eigen
kop. De kop komt van de aanroeper mee, want de converter kent geen vertalingen.

`marked` — de renderer van de HTML-export — kent geen voetnoten, dus die worden
vóór het renderen omgezet: de verwijzing wordt een `<sup>` met een sprong naar
de noot, en de noten komen achteraan met een weg terug. Achteraan óók wanneer
het document om onderaan-de-bladzijde vraagt: een HTML-pagina heeft geen
bladzijden, en de CSS die dat wel zou kunnen voert geen browser uit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
De klasseratchet wees het aan: het bewerkscherm en de weergave groeiden mee met
de voetnoten, de breedte en de zoom. Wat eruit kon, kon eruit omdat het nooit
gedrag van de klasse was — de vellenstand, de live weergave, het rauwe
schrijfvlak, de zoomsneltoetsen en de twee bewerk-dialogen zijn functies van wat
ze meekrijgen, en `documentHeadingBlocks`/`documentForcedPageBreaks` zijn zuivere
ontledingen. De schaalgrenzen van de lezer en de editor zijn constanten, geen
gedrag, en staan nu naast elkaar in de instellingen-part.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FILE_FORMAT §14.9 zegt wat er op schijf staat en wat elke oppervlakte ermee kan;
DOCUMENT_MODE §16 waarom het zo is gebouwd — twee embeds in plaats van één,
het nummer los van het label, en de ruimte voor een noot aan het blok in plaats
van aan de pagina. De gidsen en de sneltoetsen zijn bij, de HTML-beperking staat
eerlijk in KNOWN_LIMITATIONS, en SOURCE_MAP kent de nieuwe bestanden.

De `.nl.md`-varianten zijn machinevertalingen van de Engelse bron en blijven aan
`make translate-docs`; met de hand bijwerken zou stille drift zijn.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
De voetnoottak duwde _parseInto over de 150 regels; de zoomsneltoetsen deden
hetzelfde met build. Beide zijn opgelost door eruit te halen wat er los van
staat: het herkennen van een voetnootverwijzing en van een inline-formule zijn
zuivere vragen over een tekstpositie, en het sneltoetsomhulsel is een omhulsel
en geen gedrag van het scherm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
i18n: de vervangen tekst over de schrijfbreedte uit de taaltabellen
All checks were successful
scans / scans (pull_request) Successful in 1m56s
static-gate / static-gate (pull_request) Successful in 4m36s
eccc1f8d71
De oude ondertitel sprak nog van "het schrijfoppervlak" en zweeg over de
werkbalk; hij is vervangen, en daarmee werd de oude sleutel een wees. Weg uit
alle 32 tabellen, zodat de wezenratchet op nul blijft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(document): een ingesprongen vervolgregel van een voetnoot werd een codeblok
All checks were successful
scans / scans (pull_request) Successful in 1m48s
static-gate / static-gate (pull_request) Successful in 4m24s
a567faa84e
De definitie-blokregel las één regel. Voor de markdown-parser is de ingesprongen
vervolgregel daarna een codeblok, dus `    en de rest.` kwam er na een bewerking
in de visuele editor als ```-fence weer uit — tekst van de gebruiker, veranderd
in code. De regel slokt de vervolgregels nu zelf op en plakt ze met een spatie
aan de noot, precies zoals de weergave ze al las.

Wat er nog wél verandert is de regelval in de bron: twee regels worden er één.
Dat staat in FILE_FORMAT.md §14.9 en is nu ook met een test vastgepind, samen
met de gewone heen-en-terugweg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
perf(document): de vellenweergave ontleedt het document nog één keer per tekst
Some checks failed
scans / scans (pull_request) Successful in 1m48s
static-gate / static-gate (pull_request) Has been cancelled
b627755499
De voetnoten en de blokteksten werden per opbouw opnieuw ontleed — drie hele
doorlopen per frame, voor een antwoord dat alleen verandert als de tekst
verandert. Ze staan nu naast de gemeten blokhoogtes en worden op hetzelfde
moment ververst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(document): de live weergave houdt haar uitlijning boven-links
All checks were successful
scans / scans (pull_request) Successful in 1m54s
static-gate / static-gate (pull_request) Successful in 4m33s
be6946e829
Bij het losknippen van de tekenfunctie viel `alignment: topLeft` weg. In de
praktijk vult de schuifweergave het paneel toch, maar een uitlijning die je
niet bedoeld hebt weg te halen hoort niet stilzwijgend te verdwijnen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
brenno merged commit 0e3cb0b5ca into main 2026-08-18 15:17:44 +00:00
Sign in to join this conversation.
No description provided.