Skip to content

B1c etappe 3: verdeeldialoog en schrijfpad over meerdere documenten - #121

Open
Nozzit wants to merge 138 commits into
mainfrom
t3code/b1c-etappe3
Open

Nozzit wants to merge 138 commits into
mainfrom
t3code/b1c-etappe3

Conversation

@Nozzit

@Nozzit Nozzit commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

B1c — etappe 3: het schrijfpad en de verdeeldialoog

Sluit B1c af (spec docs/superpowers/specs/2026-08-17-b1c-nivelleren-restcapaciteit-design.md, plan 3 docs/superpowers/plans/2026-08-31-b1c-plan3-schrijfpad-paneel.md, en het herontwerp van 12 september: spec docs/superpowers/specs/2026-09-12-b1c-verdeeldialoog-herontwerp-design.md, plan 4 docs/superpowers/plans/2026-09-12-b1c-plan4-verdeeldialoog-herontwerp.md). W0 en etappe 2 (de verdeler-kern) stonden al op main; deze PR brengt alles wat een gebruiker ervan ziet.

Wat er nieuw is

  • Verdeeldialoog naar het Interface-lab-prototype (docs/superpowers/prototypes/2026-08-27-b1c-interface-lab.html, tabblad 4, eigenaarsbesluit 2026-08-27; opnieuw bevestigd en aangescherpt op 2026-09-12 na een gebruikstest): per project één balk als bediening op de gedeelde tijdas (telbare dagblokjes, arcering voor pauzedagen, meetlat grijs = speling / rood = einddatum-verschuiving, gestippelde rest voor toegestaan-maar-niet-benut), een handle aan het fase-einde (15×30, pointer capture, snappen per werkdag, role="slider" met echte datum in aria-valuetext, toetsenbord ←/→/PageUp/PageDown/Home/End), live meerekenen tijdens het slepen onder de ondersteunde schaal, een pin-tekstknop per rij, een gekleurde uitkomstpil ("+N dagen · max · benut N", rood bij tekort), het voor/na-histogram kolom-op-kolom onder de balken, en een altijd aanwezige validatiestrook (opgelost / tekort met data / alles vast / niet meer actueel). Geen rangordelijst meer: de rijvolgorde is de plaatsingsvolgorde op speling; sturen gaat via handle en pin.
  • Schakelaar "Onderbrekingen toestaan" met een verschil-prijskaartje ("zou N werkdagen besparen" / "bespaart N werkdagen"), plus de MS Project-term.
  • Geen flitsen: de dialoog is aan de bovenkant verankerd en elk onderdeel heeft een gereserveerde hoogte; de boundingBox is identiek vóór, tijdens en na een herberekening of sleep (browsertest).
  • Keuzelijst bij openen via de Resources-ribbon zonder gekozen item; "Ander item kiezen…" in een lopende verdeling; Reset zet plafonds en pins terug.
  • Toepassen schrijft in meerdere documenten tegelijk (actief document via het gewone pad, slapers via een headless scratch-instantie); per document één event in de sessiehistorie; "Alles terugdraaien" in een permanente strook die documentwissels overleeft en een intussen bewerkt project overslaat en meldt. Toepassen faalt nooit stil.
  • Voorstel-invalidatie (vingerafdruk + mutationSeq), documentwissel sluit de dialoog (sneltoetsen zijn modaal geblokkeerd sinds Status-update view and spreadsheet round-trip for progress data #27), degradatie boven 1000 taken/doc, 40 boekende taken of 6 documenten.
  • Bibliotheek-tab: items die al in het project zitten tonen een badge "In project (N×)" in plaats van de knop "Toewijzen aan project".
  • Motor: applyLeveling scope-behoudend en schrijft splitGaps (leveling-gaten met herkomst); idempotent hernivelleren; tijdbasis-, voortgangs- én constraintbewerkingen wissen leveling-gaten; eerlijke NO_WINDOW_IN_HORIZON bij onderbrekingen; poolitem-grootboek per item; verdeler levert bookingByDay/afterLoadByDay.
  • Showcases: de drie voorbeeldprojecten delen de demo-bibliotheek en tonen bij openen precies twee bedoelde kruisconflicten (Masonry crew, Plasterers), oplosbaar met "Verdeel automatisch"; de demo-bibliotheek is realistisch gedimensioneerd en een al-opgeslagen exemplaar wordt via een seed-versie bijgewerkt.
  • Gedeelde Switch-component (titelbalk + dialoog), gecentreerde knop gemeten.
  • i18n in veertien talen met CLDR-pluralen; gidsen gids-verdelen-restcapaciteit (nl + en, incl. "Uitproberen met de voorbeelden"), gids-resourcebibliotheken, docs/library.md.

Eigenaarsbesluiten die erin verwerkt zijn

Losse dialoog i.p.v. drill-down; tune-standen zijn sessie-stand; onderbrekingen in v1; Toepassen schrijft ook met "Automatisch berekenen" uit; rangordelijst weg uit beeld (12 sep); verschil-prijskaartje (12 sep); geen flitsen (12 sep); live meerekenen per gesnapte werkdag (bijstelling van spec-2026-08-17 §3.4).

Kwaliteit

  • Twee hyperkritische reviews (Opus): etappe 3 NO-GO → 12 bevindingen verwerkt in twee fixrondes; herontwerp GO-MITS → 5 blokkers + 3 parkeerpunten verwerkt (o.a. as-projectie over gecomprimeerde gaten, anker bij volledig tekort, prijspas rekent het hoofdvoorstel niet dubbel).
  • npm run verify exit 0 op de eindstand (168 browsertests). Nieuwe headless checks: check-distribution-strip-geometry (7 gevallen), check-distribution-chart-scale, check-showcase-occupancy, check-apply-distribution, check-distribute (uitgebreid), check-apply-leveling-scope, check-mutation-seq, check-scratch-document; browserspec tests/browser/leveling-distribution.spec.ts (21 flows) en library-view-badge.spec.ts.
  • Visueel gecontroleerd met screenshots; boundingBox-stabiliteit gemeten.

Parkeerlijst (niet in deze PR)

  • --success/--error/--warning zijn niet thema-adaptief (projectbreed, high-contrast-thema).
  • aria-live van de validatiestrook tijdens een sleep dempen.
  • MCP-tools voor de verdeler (spec §10); pins die een herstart overleven; gidsen in de overige twaalf talen; de hele balk verslepen als harde plaatsing (mogelijke latere etappe).

🤖 Generated with Claude Code

Nozzit and others added 30 commits August 31, 2026 14:10
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erdeelpaneel als dialoog

Twee besluiten van de eigenaar over etappe 3 verwerkt in spec en plan3: (1) pin/plafond/
rangorde zijn sessie-stand, geen projectdata — bevestigt de bestaande keuze in plan3 en
komt als §11.7 + een §6-bullet in de spec; (2) de verdeelflow wordt een losse dialoog via
de gedeelde Dialog-component en hasBlockingDialogOpen, geen drill-down binnen
ResourceOccupancyView — draait taak 8's eerdere keuze om en trekt taken 9–13, de
browsertest-voorschriften in taak 15, de Zelfreview en de i18n-/gebruikersgidsteksten recht.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… een lege dagenset (B1c)

Bevinding 12: `scatterSlot` gaf `[]` terug zodra `need === 0` (fractionele
duur strikt tussen 0 en 1, waarbij `distributeUnits` een lege vraagarray
levert). `[]` is truthy, dus `findSlot` deed `parseDate(scatterDays[0])` op
`undefined` — een Invalid Date die als starttijd de hele hoofdlus in reisde.
De guard valt nu door naar het "geen slot"-vangnet. Onderzocht en in de
testcase gedocumenteerd: met de huidige fits()/occurrenceFor()-implementatie
is deze tak in de praktijk niet bereikbaar (een lege vraagarray maakt de
aaneengesloten scan altijd triviaal geslaagd), dus de guard is een vangnet
voor een toekomstige wijziging aan die symmetrie, geen live-reproduceerbare
bug meer.

Bevinding 11: zonder bindend venster (geen plafond, geen constrainToFloat)
had de scatter-scan alleen `HARD_SCAN_CAP` (200.000) als rem. `scatterSlot`
krijgt nu dezelfde tweetrapsgrens als de aaneengesloten scan (`scanLimit`,
eventueel verlengd door een grootboekhorizon) — gemeten: 20 onafhankelijke
onplaatsbare taken gingen van ~2,9s naar ~26ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…één hoop (B1c)

`makeLedgerForDoc` negeerde `itemId` volledig (`residualOn`/`book` gingen
rechtstreeks naar één gedeelde `placed`-emmer). Dat was tot dusver correct
omdat `poolItemOf` uitsluitend `libraryItemId` teruggeeft, maar het is een
ongeschreven invariant terwijl `LevelingPoolLedger`s contract expliciet een
per-item-boekhouding belooft. `placed` is nu `placedByItem`, geboekt en
gelezen op de sleutel; een boeking op een ander itemId raakt het poolitem
van deze run niet meer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… ingevoegde pauzedagen (B1c)

`applyLeveling` accepteert nu `Pick<LevelingResult, 'delays' | 'gaps'>` plus
een optionele `opts.scopeTaskIds`: met scope raakt de reset-lus alleen de
gescopete taken (een delay op een taak buiten de scope is vaste last
waarop het voorstel gerekend heeft), zonder scope blijft het gedrag
byte-identiek. `applyLeveling` schrijft nu ook `splitGaps` — de volledige
waarde uit de verdeler, idempotent herschreven via `clearLevelingGaps`
wanneer een taak niet in `write.gaps` voorkomt. `clearLeveling` wist nu ook
leveling-gaten en de no-op-guard telt ze mee; de ribbon-enable-check voor
"Nivellering wissen" is letterlijk gelijk gehouden aan die guard. Dezelfde
twee wijzigingen zijn doorgevoerd in de MCP-transactievariant
(`createMcpTransactions.ts`).

De motor-idempotentie die dit pas waar maakt: `levelResources`' `workTasks`-
baseline stript nu ook `splitGaps` (symmetrisch met `levelingDelay`) voor
in-scope taken, en `bookDemandAt`/`findSlot`/`scatterSlot` lezen hun
occurrence-taak voortaan uit de gestripte `workById` in plaats van het
ongestripte `taskById` — zonder die tweede stap bleef een leveling-gat uit
een vorige nivellering via de kandidaat-scan doorsijpelen (getest en
bevestigd met een falende idempotentiecase vóór deze uitbreiding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… van die taak (B1c)

`clearLevelingGaps` (etappe-2 taak 7) had tot nu toe nul aanroepplekken.
Bedraad op elke bestaande `clearTimephasedWindow`-aanroepplek — de
discoverable regel die het plan voorschrijft (`grep -rn
"clearTimephasedWindow" src/`): `taskSlice.ts` (updateTask,
setTaskCalendar), `resourceSlice.ts` (assignResource, unassignResource,
moveAssignment ×2, removeCalendar, commitCalendarLibrary) en
`createMcpTransactions.ts` (dezelfde acht MCP-draft-tegenhangers). Een
gat met `source: 'leveling'` wordt gewist; een importsplit (geen
`source`) blijft staan. Geen melding — dit is app-eigen afgeleide
nivelleeruitvoer op een as die de gebruiker zelf net verzet heeft, geen
importverlies zoals de M10-afronding.

AFWIJKING — de spec (§4, "Invalidatie") noemt "voortgang" (completion/
actualStart/actualFinish) als vierde klasse naast duur/kalender/
handmatige datums. De discoverable regel wijst die klasse echter niet aan:
`setTaskProgress`/`setActualStart`/`setActualFinish` roepen
`clearTimephasedWindow` nergens aan (geverifieerd in de bron), dus een
leveling-gat aan voortgang koppelen zou een NIEUW triggerpunt zijn, niet
het overnemen van een bestaand patroon. Niet toegevoegd; gemeld i.p.v.
stilzwijgend uitgebreid. De testcase dekt in plaats daarvan de "toewijzingen"-
klasse, die de code zelf al als vierde trigger voert naast duur/kalender/
datums.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rafdruk (B1c)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… stille host-events (B1c)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eerdere documenten (B1c)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n (B1c)

Plan 3 taak 7: één gedeelde LevelingReason→sleutel-mapping (levelingReasonKey.ts, met
satisfies-poort op de taxonomie), de vier ontbrekende reden-teksten en de volledige
resource.distribution-sleutelset in alle veertien locales met CLDR-pluralcategorieën.
Poorten: verify:i18n, tsc en tests/library/run.sh exit 0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Main verving intussen het undo-model: `undoStack`/`redoStack` per document(payload)
is één app-globale sessiechronologie geworden (`historyEvents`/`nextHistorySequence`,
`src/state/sessionHistory.ts`). De drie B1c-etappe-3-onderdelen die daarop leunden
zijn in deze commit meegetrokken, want zonder dat compileert de merge niet:

- taak 4: de `mutationSeq()`-bump staat nu als eerste regel van main's
  `beginUndoable` (vóór de batch-/lease-guard én vóór de pending-tak);
  `pushUndoSnapshot` bestaat niet meer.
- taak 5: `StoreRuntimeOptions`/`emitHostEvent` mechanisch op main's runtime; elke
  `emitExtensionEvent(HOST_EVENTS.…)` in `src/state/slices/` loopt weer via
  `runtime.emitHostEvent` — ook main's nieuwe aanroep in `fileSlice`.
- taak 6: `DistributionApplyRecord.docs[]` draagt niet langer `undoDepthAfterApply`
  maar het `historyEventId` (+ `sequence`) van het event dat het toepassen per
  document achterliet. `applyDistribution` registreert dat event voor een slaper zelf
  met `recordSessionHistoryDeltas` + de nieuwe pure `snapshotOfPayload`;
  `undoDistribution` poort per document op "dit event is nog het event dat
  `selectUndoHistoryEvent` voor dat document zou kiezen".

Conflictoplossingen verder: `src/types/task.ts` main-inhoud (de branch-commit deed
alleen CRLF→LF, main is al LF), `tests/library/run.sh` + `tsconfig.check.json` main
(glob i.p.v. handopsomming), en in taskSlice/resourceSlice/createMcpTransactions de
`clearLevelingGaps`-aanroepen van taak 3 bovenop main's `runtime.finishMutation`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sessiehistorie)

Geen herschrijving — alleen gemarkeerde noten bij wat feitelijk anders is nu
`undoStack`/`redoStack` per document vervangen zijn door de app-globale
sessiechronologie: de mutatieteller bumpt in main's `beginUndoable`, de
scratch-instantie levert geen undo-stap meer op, en het toegepast-record draagt
per document een `historyEventId` in plaats van een undo-diepte.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…p.v. geïmporteerd (B1c)

`verify:cycles` vond na de merge een echte importcyclus:
appStore → librarySlice → scratchDocument → appStore. Die ontstond doordat
`scratchDocument.ts` `createAppStoreContext` als WAARDE importeerde terwijl
`librarySlice` (die appStore samenstelt) hem gebruikt. Plan-taak 5 stap 4
voorzag dit geval al en wees de route aan: de fabriek losmaken.

Opgelost met hetzelfde patroon als `bindDefaultGridTransactionStore`: een late
binding (`bindScratchDocumentContextFactory`) die `appStore.ts` één keer zet,
naast de gridtransactie-binding. De typen blijven rechtstreeks geïmporteerd —
`import type` wordt geërase en telt niet in de importgraaf, wat precies is
waarom `verify:cycles` ná type-erasure meet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…p.v. een vaste x (weekend-flake)

De fixture hangt aan vandaag: op een weekenddag lag x=20 px vóór de balk en gaf getTaskBarBounds
null (gemeten 2026-09-05, groen op 09-04 en 09-07). Pre-existent op main, los van B1c.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tel op discrete momenten (B1c)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…loog (B1c)

De tijdas van het bezettingsoverzicht is letterlijk uit `OccupancyHistogram` getrokken naar
`occupancyAxis.ts` (domein, gatcompressie, dagbreedte, segmenten/breuken), zodat de stroken en het
histogram per constructie op dezelfde x-posities uitkomen. Per document één SVG-rij: de vaste last
als achtergrondband, de eigen boeking als blokken (gaten blijven gaten), een gestippelde staart voor
toegestaan-maar-niet-benut, en een `role="slider"`-handle met volledige toetsenbordbediening
(pijltjes/PageUp/PageDown/Home/End). Het label bij de handle toont het EINDDATUM-effect, niet de
sleepafstand; blijft er ruimte over, dan staat er apart bij wat er gevraagd en wat er haalbaar was.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing als het grootboek (B1c)

DistributionProposal krijgt bookingByDay (letterlijk row.docs[].dailyLoad per document) en
afterLoadByDay (wat er via bookPlaced daadwerkelijk geboekt wordt, incl. de ongewijzigde
boeking van gepinde/#63-documenten) plus afterIncomplete. Zo hoeft de verdeeldialoog
computeLibraryOccupancy niet nog een keer aan te roepen voor de "voor"-stand.

Meegenomen: tests/library/check-apply-distribution.ts se hand-gebouwde makeProposal() kreeg de
drie nieuwe verplichte velden erbij — zonder die aanvulling faalde de tsc-compile-afdwinging in
tests/library/run.sh voor de hele suite, niet alleen voor dit werk.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…j loslaten (B1c)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nte terugweg en voorstel-invalidatie (B1c)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hapsstand (B1c)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…berekening en de levensduur van de terugdraai-strook kloppend gemaakt (B1c)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… (B1c fixronde 1, B2)

`INITIAL_DOC_ID` stond op MODULE-niveau, dus elke `createAppStoreContext()` — ook
elke wegwerpbare scratch-context uit `runInScratchDocument` — begon met hetzelfde
`activeDocumentId` als document 1 van de gemounte app. `applyLeveling` in zo'n
scratch-run claimde daarmee de sessie-permanente M10-gate
(`notifiedLevelingDelayDocIds`, app-globale module-state in
`timephasedLossNotice.ts`) voor het ECHTE eerste document, dat de melding daarna
deze sessie nooit meer kreeg.

Twee lagen:
- `documentSlice` genereert het id nu per slice-instantie
  (`initialDocumentRegistry()`), niet per module;
- `runInScratchDocument` neemt een `docId` en zet die op
  `activeDocumentId`/`documents`, zodat de bewerking onder het ECHTE document-id
  van de payload draait. `librarySlice.applyDistribution` geeft `w.docId` mee.

Regressie: `check-scratch-document.ts` geval 5b — de scratch draait onder een
eigen id, de gate van het app-document blijft vrij, en met een expliciet docId
landt de claim op precies dat document. Zonder de fix faalt die case op vier
poorten (gemeten).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… fixronde 1, B3)

`resetDocumentScopedUI` gooide bij elke documentwissel/-opening/-sluiting niet
alleen de verdeeldialoog dicht maar ook `ui.levelingDistribution` weg — inclusief
`applied`, het record achter "Alles terugdraaien". Dat is de ENIGE manier om een
verdeling die in meerdere documenten geschreven is in één keer terug te draaien,
terwijl je die documenten juist moet kunnen bekijken (dus wisselen!) om te
beoordelen of je haar wilt houden. Een tabwissel liet de verdeling zo
onterugdraaibaar achter.

Besluit (orkestrator, in lijn met de reviewer): `showDistributionDialog = false`
blijft — het voorstel op het scherm hoorde bij een momentopname die niet meer
geldt — maar de tune-state en het toegepast-record blijven staan. Verdwenen of
verschoven history-events zijn geen reden om het record te wissen:
`undoDistribution` toetst dat per document en meldt de rest via `skippedDocIds`.
Het record vervalt alleen door "Alles terugdraaien", een NIEUW Toepassen, of het
kiezen van een ANDER poolitem.

Meegenomen: de docblokken bij `UIState.levelingDistribution`,
`UIState.showDistributionDialog` en `DistributionUiState.applied`, het commentaar
in `resetDocumentScopedUI` en bij de "toegepast"-strook in `DistributionDialog`,
plus geval 12 in `check-apply-distribution.ts` (nu met een NIET-leeg record, zodat
de terugweg zelf getoetst wordt) en de browsercase "van document wisselen sluit de
dialoog".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Nozzit and others added 30 commits September 14, 2026 18:48
De as werd op een vaste richtbreedte van 560 gebouwd terwijl hij tegen zijn
dagbreedteplafond van 16 px aan liep: de hele track was 74 px en driekwart van
de dialoog bleef leeg. De dialoog meet nu de ruimte tussen label en
uitkomstlabel en geeft die als `minWidth` mee, met een ruimer dagbreedteplafond
van 28 px; weinig dagen geeft dus lege track in plaats van metersbrede blokken.

Daarnaast legt de eerste as van een dialoogsessie de dagbreedte vast
(`fixedDayWidth`). Zonder dat fitte elke herberekening de groeiende dagenset
opnieuw, kromp de tijdas onder de muis tijdens het slepen en sprong de andere
rij mee. Een aanroep zonder de nieuwe opties — het bezettingshistogram — blijft
onveranderd.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…is compacter

De ondertitel herhaalde de itemnaam die er als titel al boven stond; hij zegt nu
alleen nog wát je hier doet. Sleutel aangepast in alle veertien locales, dus
`{{item}}` is nergens meer nodig. Daarnaast gaat de plothoogte van "Nu" en "Na"
van 72 naar 56 px: twee lage staafjes in een paneel van 253 px las als "er is
iets niet geladen".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Een tekort gaf een volkomen lege track — dat las als een weergavefout in plaats
van als "dit past niet". De balk tekent nu waar dat werk vóór de verdeling stond
als omlijnde blokjes in de projectkleur; een dag die wél geplaatst is krijgt
geen spook. De rode pil en strook blijven onveranderd.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…teken

De pin was een onderstreept woordje tussen twee regels grijze tekst; hij krijgt
de vorm van "Reset" onderin dezelfde dialoog, met het accent in gepinde stand.
`aria-pressed` en het testanker blijven.

De statusstrook plakte clausules aan elkaar met een spatie, waardoor er twee
em-dashes achter elkaar stonden ("… niet meer actueel. — druk op Herbereken") en
de tekortkop zijn "…" als scheidingsteken leek te gebruiken. Eén helper zet er
nu " · " tussen. De legenda krijgt een breedtecap zodat hij wrapt in plaats van
de scroll-container breder te duwen dan de balken.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Twee regressies die de polishronde afdekt: een sleep in rij 2 laat de greep van
rij 1 op dezelfde x staan (tijdens én na de herberekening) en verandert de
dagbreedte niet, en een rij met een tekort toont spookblokjes in plaats van een
lege balk terwijl de rij zonder tekort er geen krijgt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lokjes

Bij een onbegrensd plafond staat de greep aan de rechterrand, dus die doos
beslaat sinds de breedtefix de hele track. Op volle sterkte was hij daarmee het
luidste element in de dialoog, terwijl hij ruimte aanduidt en geen boeking.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…kqmzu

Rapport: resourcediagram — typelaag, rapportageperiode en toewijzingskolommen (#113, manuvarkey 1–3)
…kolommen laten vallen, weekendcijfers, getalnotatie, artifacts-beleid, middernacht-doc

Alle zes vervolgpunten die na PR #138 waren blijven liggen, in één keer:

- Eén weergaveregel voor de curvetoestand van een toewijzing
  (`src/engine/contour/curveState.ts`: `assignmentCurveState`,
  `contouredAssignmentIds`) die het eigenschappenpaneel én het resourcediagram
  delen; het verschil met de verdeelregel van `assignmentDayUnits` staat daar
  gedocumenteerd in plaats van op twee plekken herhaald.
- De toewijzingskolommen vallen automatisch weg zodra de tabel minder dan
  `MIN_CHART_WIDTH_PX` (240 px) tijdlijn per papierbreedte overlaat (lange
  taaknamen met afkappen uit); `RenderReportResult.assignmentColumnsDropped`,
  melding in het overzichtsblok, sleutel in 14 locales.
- Een tijdvenster zonder werkdag dat op de kalender-as terugvalt nummert nu ook
  de weekenddagen (anders stond er geen enkel dagcijfer).
- Eén getalnotatie voor alle rapporten (`src/utils/reportNumber.ts`): hoogstens
  twee decimalen, geen duizendtalscheiding, decimaalteken van de app-taal —
  tabelrapporten via `dd.num`/`dd.signed`, de eenhedenkolom via
  `PrintOptions.numberLocale`; zonder taal de neutrale punt (tests ongewijzigd).
- Artifacts-beleid vastgelegd: `artifacts/<onderwerp>/` = klein gecommit
  schermbewijs bij een PR, `qa/` = lokaal (CLAUDE.md, .gitignore).
- Gids nl+en: presets zonder statusdatum schuiven na middernacht een dag op;
  de kolommen kunnen wegvallen bij een brede naamkolom.

Regressie: check-print-report (weglaten bij 800 px naamkolom op A4 liggend,
niet bij de gewone; weekendcijfers 10/11 op een weekendvenster; "1,5" bij
numberLocale nl), check-reports (curvetoestand-helper, contourkoppeling,
getalnotatie nl/en/de/plus). Browserspecs resourcediagram, tabelrapporten,
contourdialoog en rapportopties groen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq
… het helpt, één getalnotatie in alle rapporten, docs en melding waarheidsgetrouw

Hyperkritische review op de restpunten-PR (twaalf bevindingen, alle opgepakt):

- Toewijzingskolommen laten vallen (bevindingen 1, 2, 4): de grens is niet
  langer een vaste 240 px maar een vijfde van de printbreedte met een vloer van
  160 px, en de render laat de kolommen alléén weg als de tabel zónder die
  kolommen wél past. A4 staand met verse instellingen houdt de kolommen dus;
  een naamkolom van 800 px op A4 liggend houdt ze óók (weglaten loste daar
  niets op: 8 px tijdlijn). De melding en beide gidsen noemen nu de echte
  hefbomen (papier/richting, lettergrootte, naamkolom, Voltooiing tonen)
  in plaats van "kap de namen af" — dat stond al aan (bevinding 3).
- Getalnotatie (bevindingen 5–7): `formatReportNumber` dwingt Latijnse cijfers
  af (`-u-nu-latn`, fa koos anders arabext naast Latijnse tellingen), stript
  de bidi-markering U+200E die Inter in de vector-PDF als spatie zette, geeft
  "0" in plaats van "-0", en de plus komt uit `signDisplay: 'exceptZero'`
  in plaats van een ASCII-plus naast een typografische min. De Duur-cel van
  de Gantt-afdruk, `fmtDelta` in het variance-rapport (DOM én PDF) en de
  lag-tekst in het gezondheidsrapport (`localizeDecimalPoint`) volgen nu
  dezelfde notatie als de Eenh./d-cel en de tabelrapporten.
- Middernacht-doc (bevinding 8): "vandaag" volgt de UTC-kalender en wordt
  bij elke weergave bepaald, niet "bij elke herberekening".
- Artifacts-beleid (bevinding 9): benoemt dat `artifacts/tabel-overhaul/`
  van vóór de regel dateert.
- Melding (bevindingen 10, 11): browserassertie op de melding (verschijnt op
  A4 staand 125 %, verdwijnt op 100 %); de vlag wordt gewist zodra er geen
  Gantt-render meer is.
- `curveState` (bevinding 12): lege `curveValues` zijn geen geïmporteerde curve.

Regressie: check-print-report (drop/geen drop/staand/lettergrootte, duurcel),
check-reports (fa/ar/-0/NaN/onbekende taal/lag-tekst/lege curveValues),
resource-gantt-report.spec.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq
…p monotoon, melding zonder loze hefbomen, formatters gecachet

Zeven bevindingen van de tweede hyperkritische review, alle opgepakt:

- Bidi (bevinding 1, hoog): de U+200E die Intl in ar/fa vóór een teken zet
  wordt niet meer gestript — zonder staat "-2" in een RTL-alinea als "2-"
  (DOM én de bidi-js-levels van de vector-PDF). De tofu die Inter ervan
  maakte is een emissieprobleem en zit nu dáár: `bidiShape.stripBidiControls`
  haalt de stuurtekens (LRM/RLM/ALM/embeddings/isolaten) pas weg bij het
  shapen van elke run, ná de levelberekening; het Latijnse/CJK-snelpad en
  `measureText` strippen vóór het encoden; beide coverage-checks slaan ze
  over. De test die de strip eiste is omgekeerd, plus een headless
  `layoutRuns`-test met nepfonts.
- Kolomdrop (bevinding 3): weer monotoon in de tabelbreedte — de tussen-
  variant "alleen als weglaten helpt" liet een dode zone open (A4 staand,
  naamkolom 350: tabel past de pagina niet, kolommen bleven, tijdas 1 px,
  geen melding) en een bredere naamkolom bracht de kolommen terug.
  Regressiecase op precies die zone; de 800-px-case laat nu wél vallen.
- Melding + gidsen (bevinding 2): geen "zet Voltooiing tonen uit" en geen
  verwijzing naar de naamkolomslider (die in de automodus niet bestaat);
  alleen wat aantoonbaar ruimte geeft, zonder te beloven dat de kolommen
  terugkomen. 14 locales.
- Formatters gecachet per taal (bevinding 4: ~35 µs → ~0,7 µs per cel),
  ook het decimaalteken voor `localizeDecimalPoint`.
- Duurcel: niet-eindig ⇒ "—" in plaats van een losse "d" (bevinding 5).
- WarningsPanel toont de lag met hetzelfde decimaalteken als het
  gezondheidsrapport (bevinding 6); bewerkbare cellen blijven ASCII.
- `formatSignedReportNumber` past dezelfde "-0"-vangnet toe en een
  welgevormde maar onbekende taalcode valt expliciet terug (bevinding 7).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014zosxbSXvi3YSGmeN1MsQq
…kqmzu

Rapport: restpunten uit de reviews op #138 (curvetoestand, kolommen laten vallen, weekendcijfers, getalnotatie, artifacts-beleid)
…ollt

Rekt een opgerekt plafond de tijdas voorbij de dialoogbreedte, dan schoven de
projectnaam links en de uitkomstpil rechts mee uit beeld — de eigenaar zag
"eind +5 dage" en "max onbegrensd · benu" afgekapt tegen de rand. Beide kolommen
zijn nu bevroren; legenda en einddatum-badges horen bij de rijen en schuiven
evenmin mee. Het histogram schuift wél mee (kolom-op-kolom onder de dagblokjes)
en krijgt aan weerszijden een even brede afdekstrook, zodat er geen staaf
zichtbaar blijft voor een dag waarvan het blokje erboven achter het label zit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…reep

Eigenaarsbesluit 2026-09-14, dat het "live meerekenen per gesnapte werkdag" uit
spec §5 vervangt: "het flikkert enorm omdat het elke keer herberekent; het moet
pas herberekenen en de nieuwe statusmelding tonen wanneer ik de muis loslaat."

Tijdens een pointer-sleep is alles lokaal — alleen de greep, de gestippelde rest
en de plafondtekst van de eigen rij bewegen mee. Er gaat geen onCeilingChange
uit, dus geen computeDistribution en geen hertekening van pil, badges, histogram,
validatiestrook of prijskaartje. Het pointerup commit één keer, dus de
bezig-toestand gaat precies één keer aan en uit. Toetsenbordstappen blijven wél
discrete rekenmomenten (spec §3.4). De liveCommit-prop is daarmee weg; de
in-flight-bewaking in de hook blijft, maar niet langer als sleep-throttle.

Spec §5 krijgt een gedateerde eigenaarsnoot, de gidsen nl+en de nieuwe zin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ar verschil (Masonry crew)

KLEIN's ankerschuif gaat van 63 naar 66 kalenderdagen. Daardoor begint "Build
masonry walls" op 2027-06-03 in plaats van 2027-05-31 en heeft MIDDEL's "Ground
floor masonry — House 6" VIER vrije werkdagen vóór de blokkade in plaats van één.

Waarom dat het verschil is: de schakelaar "Onderbrekingen toestaan" is in
ResourceLeveler.findSlot geen tweede zoekstrategie maar een terugval — eerst de
aaneengesloten scan, en pas als díé binnen het venster faalt komt scatterSlot met
de pauzedagen. Het venster bestaat alleen met een plafond ("maximale uitloop"),
en het bruikbare plafondbereik is precies zo breed als het aantal vrije werkdagen
vóór de blokkade. Bij 63 was dat bereik één enkel getal (plafond 14); bij 66 is
het 14 t/m 17, met het ronde "15 werkdagen" in het midden.

Gemeten op de drie showcases samen, poolitem Masonry crew, plafond 15 werkdagen
op De Akkers: onderbrekingen UIT ⇒ 1 tekort, geen uitloop, Toepassen geblokkeerd;
onderbrekingen AAN ⇒ geen tekort, 6 werkdagen uitloop, één taak met
leveling-gaten. Zonder plafond blijven beide standen gelijk (10/10) — dat is
engine-gedrag, geen data-eigenschap.

Beide kruisconflicten blijven staan; alleen de overlapstrook van de Masonry crew
wordt korter (2 dagen i.p.v. 5).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
De kop is in de gevulde stand drie regels hoog (itemnaam, ondertitel,
introregel), en `items-center` centreerde het kruisje over die hele hoogte —
het zweefde halverwege de kop. Met `items-start` lijnt hij uit op de titelregel,
zoals in de eenregelige koppen elders. De kophoogte verandert niet (§7).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…aakt

Twee nieuwe blokken in check-showcase-occupancy.ts, plus de gidsalinea nl/en.

(b3) bewijst het verschil zelf: met een plafond van 15 werkdagen op De Akkers
geeft de uit-stand een tekort (Toepassen geblokkeerd) en de aan-stand een
toepasbaar voorstel met zes werkdagen uitloop en minstens één taak met
leveling-gaten.

De eis is bewust NIET "kleinere uitloop bij gelijke tekorten". Dat is met deze
nivelleerder onbereikbaar, niet ongemeten: scatterSlot is een terugval na de
aaneengesloten scan, dus de twee standen kunnen alleen verschillen op precies het
punt waar de uit-stand vastloopt — en daar levert die stand een tekort op, geen
grotere uitloop. Het commentaarblok bij (b3) legt dat uit zodat niemand deze
assert "strenger" maakt in een richting die niet bestaat.

(b4) bewaakt de BREEDTE: het plafondbereik waarin de schakelaar verschil maakt
moet minstens drie opeenvolgende waarden tellen en het ronde 15 bevatten. Gemeten
bereik nu: 14 t/m 17. Vóór de ankerschuif was het alleen 14 — dat is de reden dat
het verschil in de demo niet te vinden was.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Zonder Home vooraf staat de greep al tegen de rechterrand van de as (plafond
onbegrensd), en levert één sleep tot de vensterrand maar een handvol werkdagen
op — te weinig om de as buiten de dialoog te laten groeien, dus toetste de test
zijn eigen aanname niet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Vervolg op het rekenen-bij-loslaten: zonder herberekening groeit de tijdas
tijdens het slepen niet mee, en dan kan de geometrie de greep niet plaatsen —
`advanceWorkdays` landt op een datum buiten de as en `rightEdgeX` klemt die
terug op de rechterrand van het laatste assegment. Gemeten op de screenshot van
deze ronde: het uitkomstlabel liep door naar "max 22 okt" terwijl de greep bij
het fase-einde bleef plakken, dus je sleepte en er bewoog niets.

De greep wordt tijdens een sleep daarom getekend op zijn anker plus het aantal
gesnapte werkdagen × de dagbreedte — dezelfde omrekening die de waarde zelf
bepaalt, dus greep en muis lopen per constructie synchroon. De gestippelde rest
loopt mee tot diezelfde x. Het anker wordt pas op de EERSTE beweging vastgelegd,
zodat een plafond dat van onbegrensd komt niet verspringt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
De eigenaar vroeg in de gebruikstest wat "taken passen niet" betekent. De
dialoog gebruikt een vocabulaire dat nergens anders in het programma
voorkomt — speling, plafond, benut, vaste last, pauzedag, tekort,
einddatum-verschuiving, vastzetten en het besparingsprijskaartje — en
dat stond nergens uitgelegd op de plek waar je het leest.

Onder de legenda staat nu een uitklapregel "Wat betekenen de termen?"
met per term één zin. Ingeklapt kost hij één regel; uitklappen is een
gebruikershandeling en mag dus de hoogte veranderen (spec §7 verbiedt
alleen dat een REKENtoestand de maat laat verspringen). De open/dicht
stand blijft lokale componentstate: in `ui.levelingDistribution` zou hij
de fingerprint raken en een rekenronde uitlokken.

Alleen niet-vanzelfsprekende termen; gewone app-begrippen als resource,
capaciteit en project staan er bewust niet in. De veertien vertalingen
nemen per taal de woorden over uit de labels die ze uitleggen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…terug

Bewaakt drie dingen die makkelijk stilletjes wegvallen: hij staat
ingeklapt bij het openen, alle negen termen staan erin (poort op acht
dt-regels), en inklappen brengt de boundingBox van de dialoog exact
terug — de §7-belofte dat alleen een gebruikershandeling de hoogte
verandert.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rand uit

De lijst erfde de breedtecap van de legenda — de AS-breedte. Dat werkt
voor één korte regel, maar de definities zijn lopende tekst, dus bij een
opgerekt plafond liepen ze mee de scroller in en las de eindscreenshot
"… zonder dat zijn einddatum opschu". `trackSpace` is per constructie de
breedte die naast de bevroren labelkolom zichtbaar blijft; daarmee wrapt
de tekst waar hij hoort.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Het "…" stond in de vertaalstring zelf, dus "Nog 1 ploegdag tekort op
7 sep…" beloofde ook bij één datum dat er meer volgden. Het teken hoort
bij de lijst: de dialoog plakt het nu aan de dagenparameter zodra er
meer dan drie data zijn, en de veertien locales laten het weg.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
De native title van vier regels dekte na het loslaten de legenda en de
knop naar de begrippenlijst af. De lange uitleg staat inmiddels in die
begrippenlijst, dus de tooltip is in alle veertien locales ingekort tot
één regel. aria-valuetext blijft ongewijzigd.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
"Toegestaan maar niet benut" is een maat; onbegrensd is dat niet, dus de
doos vulde de hele track tot de rand van de as en beloofde ruimte die de
as niet uitdrukt. buildStripGeometry geeft bij ceiling null nu freeBox
null, ook tijdens een sleep. Vastgelegd als noot bij spec §4 (2026-09-14),
met een geometriecase en een browserassertie erbij.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
De semantische tokens waren op wit gekozen: als tekst op een donkere
kaart haalde de validatiestrook van de verdeeldialoog 2,55:1. Donker en
high-contrast krijgen nu lichtere varianten (gemeten 7,3-8,3:1 resp.
10,5-15,1:1); het lichte thema zet de verzadigde waarden terug, want het
gedeelde blok is :root plus dark.

De handvol plekken waar zo'n token juist een ACHTERGROND onder witte
tekst is (meldingsballon, gevaar-knoppen) krijgt in die twee thema's het
verzadigde rood terug - een lichte tint is een tekstkleur, geen vlak.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…leen de thema-overrides blijven als verschil

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants