fix(collab): een gewijzigde paneelzoom reist weer mee, plus een poort op de synchroniseerbare oppervlakte (#1803) #1805

Merged
brenno merged 3 commits from fix/1803-collab-imagezoom-pariteit into main 2026-08-27 13:18:40 +00:00
Owner

Lost #1803 op: een gewijzigde paneelzoom bereikt de andere cliënt weer, en er
staat een poort op de richting die niemand bewaakte.

De fout

imageZoom stond niet in SlideField, en deckDiffToOps loopt uitsluitend over
die enum — er is geen terugval op een hele-dia-operatie. Wie de zoom van een
paneelafbeelding versleepte, zag hem bij de ander onveranderd blijven: geen
melding, geen conflict, twee bijsnijdingen die stil uiteenlopen tot iemand
opslaat en er één wint.

De asymmetrie maakte het lastig na te doen. slideToJson kent het veld wél, dus
een nieuwe dia droeg de zoom mee en een hersynchronisatie herstelde hem; alleen
het wijzigen op een bestaande dia kwam niet aan.

Het was vergeten, niet uitgesloten — de datums laten dat zien. Het operatiemodel
landde op 2026-07-30 mét imageSize en alle vier de focal-velden, imageZoom
kwam op 2026-08-12 in Slide, en heeft nooit in deck_op.dart gestaan.

De regressietest, en waarom hij niet op de bestaande helper leunt

De test leest de wáárde ná toepassing, niet of een hervergelijking leeg is. De
bestaande expectReproduces-helper — in het bestand omschreven als "the
strongest check" — is voor deze klasse fout blind: een veld dat de diff niet kent
is in béíde richtingen onzichtbaar, dus de hervergelijking komt leeg terug
terwijl de waarde nooit is overgekomen. Eerst rood gezien op de onherstelde code:
Expected: an object with length of <1>, Actual: [].

De poort, en waarom een losse reparatie niet genoeg was

Er stonden al twee pariteitstests op de samenwerklaag, en ze kijken allebei
dezelfde kant op: deck_op_test eist een testgeval voor elke SlideField,
collab_codec_test eist dat de codec elke SlideField afbeeldt. Beide
beantwoorden "wordt alles ín de enum afgehandeld?". Niemand vroeg "staat elk
synchroniseerbaar veld ín de enum?" — dus een veld toevoegen aan Slide haalde
geen enkele poort omlaag. Dat is niet één bug maar een gat waar de volgende
precies zo doorheen valt.

make check-collab-field-parity stelt die vraag wel. Elk veld van Slide staat
in SlideField, óf op een uitsluitingslijst mét reden ernaast, óf op een
schuldlijst die alleen mag krimpen. Bewust niet synchroniseren blijft toegestaan;
de keuze niet maken niet meer, en wie het veld toevoegt beslist op het moment dat
hij het antwoord nog weet.

De poort is in twee richtingen getoetst: stil op de echte repo, en luid op
een geplante overtreding. Daarvoor is de beslislogica een zuivere functie, zodat
een test een veld kan verzinnen in plaats van het model te moeten verminken.

Wat de poort meteen zichtbaar maakt

De schuldlijst begint op achttien velden, en die zijn niet allemaal even erg.

  • Elf hebben de vorm van imageZoom: slideToJson draagt ze wel, de diff
    niet. Nieuwe dia compleet, bewerking niet.
  • Zevenanchor, nextAnchor, ganttScale, ganttSections,
    menuLayout, tableColumnAlignments, tableNumberColumns — zitten
    helemaal niet in de samenwerklaag. Niet in slideToJson, niet in
    slideFromJson. Een codec-rondgang verliest ze, dus ook een nieuwe dia komt
    bij de ander aan met die velden op hun standaardwaarde.

Dat laatste is ernstiger dan deze fix, want het breekt een vastgelegde belofte:
de doc-comment van slideToJson zegt "Every field is carried so the receiver
reproduces the slide exactly (P3)". Dat verdient een eigen issue en zit
bewust niet in deze PR — het is een andere reparatie, met eigen tests.

Poorten

  • make check volledig groen (== OciDeck check complete ==, dekking 87,1%,
    per-bestandsvloer 0 eronder)
  • make check-secrets groen · make sast groen (0 bevindingen)
  • check-collab-field-parity: OK (76 Slide-velden — 53 gesynchroniseerd, 5 bewust niet, 18 bekende schuld)

Geen bewaker-ronde: deze wijziging raakt het bestandsformaat niet, de opslag
niet, geen afhankelijkheid, geen uitgaand verkeer en geen publieke belofte in de
interface. Expliciet overgeslagen, niet vergeten.

Eén noot over de draai: de eerste twee make check-pogingen vielen om doordat
een andere sessie in dezelfde werkkopie van tak wisselde en mijn bestanden
midden in de suite onder de run vandaan haalde. Deze PR is groen gedraaid vanuit
een eigen worktree.

Lost #1803 op: een gewijzigde paneelzoom bereikt de andere cliënt weer, en er staat een poort op de richting die niemand bewaakte. ## De fout `imageZoom` stond niet in `SlideField`, en `deckDiffToOps` loopt uitsluitend over die enum — er is geen terugval op een hele-dia-operatie. Wie de zoom van een paneelafbeelding versleepte, zag hem bij de ander onveranderd blijven: geen melding, geen conflict, twee bijsnijdingen die stil uiteenlopen tot iemand opslaat en er één wint. De asymmetrie maakte het lastig na te doen. `slideToJson` kent het veld wél, dus een *nieuwe* dia droeg de zoom mee en een hersynchronisatie herstelde hem; alleen het *wijzigen* op een bestaande dia kwam niet aan. Het was vergeten, niet uitgesloten — de datums laten dat zien. Het operatiemodel landde op 2026-07-30 mét `imageSize` en alle vier de focal-velden, `imageZoom` kwam op 2026-08-12 in `Slide`, en heeft nooit in `deck_op.dart` gestaan. ## De regressietest, en waarom hij niet op de bestaande helper leunt De test leest de wáárde ná toepassing, niet of een hervergelijking leeg is. De bestaande `expectReproduces`-helper — in het bestand omschreven als "the strongest check" — is voor deze klasse fout blind: een veld dat de diff niet kent is in béíde richtingen onzichtbaar, dus de hervergelijking komt leeg terug terwijl de waarde nooit is overgekomen. Eerst rood gezien op de onherstelde code: `Expected: an object with length of <1>, Actual: []`. ## De poort, en waarom een losse reparatie niet genoeg was Er stonden al twee pariteitstests op de samenwerklaag, en ze kijken allebei dezelfde kant op: `deck_op_test` eist een testgeval voor elke `SlideField`, `collab_codec_test` eist dat de codec elke `SlideField` afbeeldt. Beide beantwoorden "wordt alles ín de enum afgehandeld?". Niemand vroeg "staat elk synchroniseerbaar veld ín de enum?" — dus een veld toevoegen aan `Slide` haalde geen enkele poort omlaag. Dat is niet één bug maar een gat waar de volgende precies zo doorheen valt. `make check-collab-field-parity` stelt die vraag wel. Elk veld van `Slide` staat in `SlideField`, óf op een uitsluitingslijst mét reden ernaast, óf op een schuldlijst die alleen mag krimpen. Bewust niet synchroniseren blijft toegestaan; de keuze niet maken niet meer, en wie het veld toevoegt beslist op het moment dat hij het antwoord nog weet. De poort is **in twee richtingen getoetst**: stil op de echte repo, en luid op een geplante overtreding. Daarvoor is de beslislogica een zuivere functie, zodat een test een veld kan verzinnen in plaats van het model te moeten verminken. ## Wat de poort meteen zichtbaar maakt De schuldlijst begint op achttien velden, en die zijn niet allemaal even erg. - **Elf** hebben de vorm van `imageZoom`: `slideToJson` draagt ze wel, de diff niet. Nieuwe dia compleet, bewerking niet. - **Zeven** — `anchor`, `nextAnchor`, `ganttScale`, `ganttSections`, `menuLayout`, `tableColumnAlignments`, `tableNumberColumns` — zitten **helemaal niet** in de samenwerklaag. Niet in `slideToJson`, niet in `slideFromJson`. Een codec-rondgang verliest ze, dus ook een *nieuwe* dia komt bij de ander aan met die velden op hun standaardwaarde. Dat laatste is ernstiger dan deze fix, want het breekt een vastgelegde belofte: de doc-comment van `slideToJson` zegt "Every field is carried so the receiver reproduces the slide exactly (P3)". Dat verdient een eigen issue en zit **bewust niet** in deze PR — het is een andere reparatie, met eigen tests. ## Poorten - `make check` volledig groen (`== OciDeck check complete ==`, dekking 87,1%, per-bestandsvloer 0 eronder) - `make check-secrets` groen · `make sast` groen (0 bevindingen) - `check-collab-field-parity: OK (76 Slide-velden — 53 gesynchroniseerd, 5 bewust niet, 18 bekende schuld)` Geen bewaker-ronde: deze wijziging raakt het bestandsformaat niet, de opslag niet, geen afhankelijkheid, geen uitgaand verkeer en geen publieke belofte in de interface. Expliciet overgeslagen, niet vergeten. Eén noot over de draai: de eerste twee `make check`-pogingen vielen om doordat een andere sessie in dezelfde werkkopie van tak wisselde en mijn bestanden midden in de suite onder de run vandaan haalde. Deze PR is groen gedraaid vanuit een eigen worktree.
`imageZoom` stond niet in `SlideField`, en `deckDiffToOps` loopt uitsluitend
over die enum — er is geen terugval op een hele-dia-operatie. Wie de zoom van
een paneelafbeelding versleepte, zag hem bij de ander onveranderd blijven, zonder
melding en zonder conflict, tot iemand opsloeg en één van de twee bijsnijdingen
won.

De asymmetrie maakte het lastig na te doen: `slideToJson` kent het veld wél, dus
een nieuwe dia droeg de zoom mee en een hersynchronisatie herstelde hem. Alleen
het wijzigen op een bestaande dia kwam niet aan.

Het was vergeten, niet uitgesloten. Het operatiemodel landde op 2026-07-30 mét
`imageSize` en alle vier de focal-velden; `imageZoom` kwam pas twee weken later
in `Slide` en heeft nooit in `deck_op.dart` gestaan.

De regressietest leest de wáárde na toepassing en niet alleen of een
hervergelijking leeg is: de bestaande `expectReproduces`-helper is voor dit soort
fout blind, want een veld dat de diff niet kent is in béíde richtingen onzichtbaar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
De `imageZoom`-fout was geen verkeerde regel maar een ontbrekende richting. Er
staan twee pariteitstests op de samenwerklaag, en ze kijken allebei dezelfde
kant op: `deck_op_test` eist een testgeval voor elke `SlideField`,
`collab_codec_test` eist dat de codec elke `SlideField` afbeeldt. Beide
beantwoorden "wordt alles ín de enum afgehandeld?". Niemand vroeg "staat elk
synchroniseerbaar veld ín de enum?" — dus een veld toevoegen aan `Slide` haalde
geen enkele poort omlaag.

`make check-collab-field-parity` vraagt die vraag wél. Elk veld van `Slide`
staat in `SlideField`, óf op een uitsluitingslijst mét de reden ernaast, óf op
een schuldlijst die alleen mag krimpen. Bewust niet synchroniseren blijft dus
toegestaan; de keuze niet maken niet meer, en wie het veld toevoegt beslist het
op het moment dat hij het antwoord nog weet.

De schuldlijst begint op achttien velden, en die zijn niet allemaal even erg.
Elf hebben de vorm van `imageZoom`: `slideToJson` draagt ze wel, de diff niet,
dus een nieuwe dia komt compleet aan en een bewerking niet. De andere zeven —
`anchor`, `nextAnchor`, `ganttScale`, `ganttSections`, `menuLayout`,
`tableColumnAlignments`, `tableNumberColumns` — zitten helemáál niet in de
samenwerklaag, terwijl de doc-comment van `slideToJson` belooft dat elk veld
wordt meegedragen zodat de ontvanger de dia exact reproduceert. Dat is een
geschonden vastgelegde belofte en verdient een eigen issue.

De poort is in twee richtingen getoetst: stil op de echte repo, en luid op een
geplante overtreding. Daarvoor is de beslislogica een zuivere functie, zodat een
test een veld kan verzinnen in plaats van het model te moeten verminken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chore(ratchet): de samenwerk-schuldlijst meebewegen in het trendrapport (#1803)
All checks were successful
scans / scans (pull_request) Successful in 2m11s
static-gate / static-gate (pull_request) Successful in 5m19s
687bc028e9
`ratchet_trend_tool_test` viel op de nieuwe basislijn, en terecht: het eist dat
élke `*Baseline` in tool/ ook in de lijst `ratchets` staat. Een ratchet die
niemand registreert kan jaren stilstaan zonder dat het rapport het laat zien —
precies de soort onzichtbaarheid waar dit issue over gaat.

`unsyncedBaseline` staat nu in het rapport (18, omlaag is beter), en de
vaste-brontekst-fixture kent het bestand, zodat de vergelijkingen hem niet als
onvindbaar tellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
brenno merged commit 92887bf001 into main 2026-08-27 13:18:40 +00:00
Sign in to join this conversation.
No description provided.