Commit graph

178 commits

Author SHA1 Message Date
Ramon
322147d3d1 feat: voeg curriculumwiki voor volwassenen toe (v0.4.37-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m1s
2026-07-19 21:13:11 +02:00
Ramon
216ab8d5dd docs: leg SLO-kerndoelen 2026 vast (v0.4.36-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 39s
2026-07-19 20:58:17 +02:00
Ramon
8ccfa5dc8c feat: voortgang flinke UI-upgrade + bug (1 opdracht i.p.v. groep) + grafiek (v0.4.35-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m17s
- Bug gevonden en bevestigd (niet blind aangenomen): math.js riep
  onProgress maar op ÉÉN plek aan, in endRound(), NA een heel blok van
  10 sommen, met alleen het geaggregeerde totaal. Eén sessie van 10
  sommen - "een groep opdrachten" - leverde dus precies 1 rij op in
  progress_events, niet 10, en maakte "welke som ging fout" onmogelijk
  te zien (alleen een totaaltelling per blok).
- Fix: onProgress verplaatst naar check(), bij elke afgeronde som (goed
  op de eerste keer, of opgeven na 3 pogingen) - een sessie van 10
  sommen levert nu 10 progress_events-rijen op, elk met een nieuw
  optioneel detail-veld (welke som, gegeven antwoord, juist antwoord).
  letters.js/ball.js blijven deze ronde ongemoeid (hun granulariteit
  veroorzaakte niet deze bug).
- Nieuwe kolom progress_events.detail (JSONB, nullable, puur additief -
  db/020_progress_detail.sql). POST /my/progress accepteert het
  optioneel; te groot/geen plain object wordt stilzwijgend genegeerd
  zonder de rest van de log-poging te laten falen (detail is decoratief,
  geen kernfunctionaliteit).
- Voortgang-tab (admin.js) herontworpen, zelfde ontwerptaal als de
  eerdere School-navigator-herbouw: vakgebied-tabs, een
  klassenmanagement-infobadge (Groep {n} · Niveau {n}, opgehaald via de
  bestaande /levels/pupil/:id-route) zodat resultaten meteen tegen het
  toegewezen niveau afgezet kunnen worden, een met de hand getekende
  SVG-nauwkeurigheidsring + staafdiagram (goed/fout per dag, laatste 14
  dagen - geen library, zelfde aanpak als avatar.js) en een "recente
  fouten"-lijst die precies toont welke som fout ging. De bestaande
  ruwe tijdlijn blijft eronder staan, niets is verwijderd.
- Terzijde gevonden en gefixt: .am-btn.active bestond nergens in de
  CSS, waardoor de vakgebied-tabs (hier én in Klassenmanagement,
  v0.4.34-beta) geen enkele visuele aanduiding hadden welke actief was.
- Tests: test/progress-logging.test.js uitgebreid met 4 tests voor het
  detail-veld (opgeslagen, te groot genegeerd, geen plain object
  genegeerd, afwezig blijft null). Volledige testsuite: 132/132.
- Live geverifieerd: een math.js-sessie met een mix van goede/foute
  sommen doorlopen en bevestigd dat er 3 aparte POST /my/progress-
  aanroepen gebeuren voor 3 sommen (niet 1 aggregaat), elk met het
  juiste detail; de herbouwde Voortgang-tab geopend met gemockte data
  en met screenshots bevestigd dat de nauwkeurigheidsring (38% = 3/8,
  rekenkundig geverifieerd), het klassenmanagement-infobadge, de
  staafdiagram en de "recente fouten"-lijst (met de daadwerkelijke som)
  correct tonen, én dat de active-tab-fix ook op de Klassenmanagement-
  tab zichtbaar is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 15:56:14 +02:00
Ramon
e7bb76b76d feat: klassenmanagement - niveaugroep + niveau per leerling per vakgebied (v0.4.34-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m9s
- Grootste openstaande roadmap-punt. Nieuwe tabel pupil_levels
  (db/019_pupil_levels.sql, zelfde class_id/pupil_id-vorm als
  assignments) legt per vakgebied (taal/rekenen/world) een niveaugroep
  vast (groep 1-8, gebaseerd op de leerjaren van de basisschool waaraan
  de leerdoelen gekoppeld zijn) + niveau (1-3, fijnmazige verfijning
  BINNEN die groep). Klas-standaard met individuele leerling-overrides,
  net zo laagdrempelig instelbaar als de standaard - het speciaal-
  onderwijs-geval waar dat vanaf het begin de norm is.
- Server: GET/PUT/DELETE /levels/class/:id en /levels/pupil/:id
  (src/api.js) hergebruiken exact het assignments-patroon (upsert via
  partial-unique-index, pupil-eerst-dan-klas-resolutie), plus
  GET /my/level voor de leerling-kant. Zelfde permissie
  (assignments.manage) en sameSchool/teacherOnly/pupilAccessible-
  guards als de bestaande toewijzingsroutes.
- Nieuwe admin-tab "Klassenmanagement": klas + vakgebied kiezen, een
  klas-standaard instellen, en per leerling de effectieve groep/niveau
  zien (overerft van de klas tenzij een eigen afwijking is ingesteld)
  met bewerk- en terug-naar-standaard-knoppen.
- ECHTE inhoudskoppeling, niet alleen een standaardwaarde: rekenen is
  het eerste vakgebied waarvan de SLO/TULE-leerlijn direct in
  getalgrenzen te vertalen is (groep 3 t/m 20, groep 4 t/m 100, groep 5
  t/m 1000, groep 6 t/m 10.000). Nieuwe SLO_MATH_RANGES/mathRangeFor()
  in data.js; math.js's gen() gebruikt dit voor de daadwerkelijke
  getallen zodra een leerling een groep heeft (S.yearGroup), met het
  bestaande niveau 1-3 als verfijning daarbinnen. Zonder groep blijft
  het oude vaste 10/20/100-gedrag exact bestaan.
- pupil.js geeft groep/niveau door aan taal/rekenen-widgets bij het
  mounten: niveau is een standaardwaarde (een al opgeslagen bord-niveau
  blijft leidend), groep wordt voor rekenen-widgets altijd meegegeven
  (er is geen "oud" groep-concept om te respecteren). Terzijde ook de
  stille .catch() op de nieuwe async renderPupilWidgets() opgelost.
- Taal/wereldoriëntatie krijgen deze ronde nog geen eigen
  inhoudskoppeling (vraagt een eigen contentbron per groep, nog niet
  uitgezocht) - eerlijk benoemd als vervolgstap, geen loze belofte.
- Tests: test/pupil-levels.test.js (10 tests) dekt de server-routes
  (mocked-pool-patroon) én - via dezelfde vm-techniek als elders in
  test/widgets.test.js voor MONEY_LEVELS - een ECHTE (niet regex-)test
  van mathRangeFor die bevestigt dat elke groep de juiste getalgrens
  oplevert en niveau nooit boven de groep-grens uitkomt. Twee bugs
  gevonden en gefixt tijdens het schrijven van deze tests: null-yearGroup
  werd per ongeluk als groep 1 behandeld i.p.v. "geen groep", en de
  testrol had geen klas-eigenaarschap waardoor 403 in plaats van 200
  terugkwam. Volledige testsuite: 128/128.
- Live geverifieerd in de sandbox: klas-standaard + leerling-override
  instellen in de nieuwe admin-tab, en drie scenario's in de
  leerling-omgeving die bevestigen dat de daadwerkelijk gegenereerde
  sommen binnen de juiste groep-grens blijven (geen klassenmanagement →
  ongewijzigd oud gedrag; groep zonder eigen bord-niveau → volledige
  groep-grens; groep mét een al opgeslagen bord-niveau → dat niveau
  blijft leidend, geschaald naar de juiste groep-grens).
- public/js/data.js bevatte al niet-gerelateerde, nog niet gecommitte
  wijzigingen van vóór deze sessie (extra taalthema's/woorden) - alleen
  de SLO_MATH_RANGES/mathRangeFor-hunk is hier meegenomen (git add -p),
  de rest blijft bewust ongemoeid staan, net als test/language-library.test.js.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 15:31:26 +02:00
Ramon
36695a06f6 fix: leerling in werken-modus kreeg abusievelijk readonly:true (v0.4.33-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 46s
- Roadmap-punt "de voortgangen werken niet" onderzocht (niet blind
  herbouwd - eerst gediagnosticeerd via de code en git-geschiedenis).
- Gevonden: pupil.js zette sinds v0.3.15-beta (toen kijken/werken-modus
  werd toegevoegd) readonly:true in BEIDE takken van de modus-keuze -
  een kopieerfout, want de hele reden voor die commit was net dat
  "werken" de leerling wél moet laten werken. In "werken"-modus hoort
  er geen readonly-vlag te staan; "kijken" (blijft ongewijzigd) is de
  enige modus die dat nodig heeft.
- Concreet effect: de twee widgets die opts.readonly daadwerkelijk
  lezen (letters.js/anchor.js) verborgen voor leerlingen in
  werken-modus onterecht hun eigen bewerk-gereedschap (woordenlijst-
  editor, opslaan/bibliotheek) - gereedschap dat een leerling in
  werken-modus wél hoort te zien. De kernoefening zelf (spellen,
  slepen) werd hierdoor niet geblokkeerd, dus dit verklaart niet de
  volledige klacht - overige widgets negeren opts.readonly toch al.
- Tegelijk de stille `.catch(()=>{})` op het voortgang-loggen vervangen
  door `console.warn` bij een mislukte POST /my/progress: een mislukte
  log (verlopen sessie, weggehaalde toewijzing, servergolf) verdween
  eerder spoorloos, wat een "werkt niet"-klacht onmogelijk te
  onderzoeken maakte.
- Nieuwe test/progress-logging.test.js dekt het serverpad dat nog
  helemaal geen tests had: leerling-eigen toewijzing wint van
  klasbrede, thuiskind-zonder-school valt terug op de gekoppelde
  ouder, geen-toewijzing geeft 400 zonder insert, niet-leerling geeft
  403, ongeldige widgetId/widgetType geeft 400 - alle zes bevestigen
  dat de SQL/toewijzingslogica zelf klopt, wat het probleem verder
  vernauwt tot de nu twee gefixte punten hierboven of iets dat alleen
  in de echte omgeving van de gebruiker reproduceerbaar is.
- Live geverifieerd: werken-modus toont nu het bewerk-gereedschap in
  letters.js (voorheen verborgen), kijken-modus blijft exact
  ongewijzigd (overlay + verborgen gereedschap). Volledige testsuite:
  118/118.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 14:39:00 +02:00
Ramon
643a00c1c3 feat: personage-aanmoediging uitgebreid naar klok, geld, plaatswaarde en spellen (v0.4.32-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 55s
- De math.js-pilot (v0.4.23-beta: eigen personage kort naast "Goed!" bij
  een juist antwoord) hergebruikt in vier andere widgets, zoals destijds
  al voorzien in de roadmap: klok (mountClock), geld (mountMoney),
  plaatswaarde (mountPlacevalue) en de wereldoriëntatie-spelopdracht
  (mountWorldQuiz - dekt bomen/vogels/planten in één keer - en
  mountTopography).
- Zelfde patroon overal: bij S.feedback==='good' (of S.spellSolved) en
  een ingesteld personage verschijnt renderAvatarFace(...,"md") met de
  av-cheer-animatie naast het bestaande feedbackbericht - puur visueel,
  geen nieuw feedbacktype. Zonder personage blijft het gedrag exact
  ongewijzigd.
- Geverifieerd met echte oplos-interacties (niet alleen mounten): klok
  (typemodus + toetsenbord), geld (typemodus + toetsenbord), plaatswaarde
  (blokken plaatsen via toetsenbord-Enter) en de spelopdracht (letters
  plaatsen via toetsenbord-Enter) - in alle vier verschijnt het
  personage daadwerkelijk in de feedback-HTML na een echt juist
  antwoord. Volledige testsuite: 112/112.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 14:03:58 +02:00
Ramon
c183c8f931 perf: opstart-fetches /auth/me en /me/data parallel i.p.v. serieel (v0.4.31-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 37s
- Beide verzoeken hangen alleen af van dezelfde sessie-cookie, niet van
  elkaars antwoord - ze werden tot nu toe na elkaar aangeroepen
  (hydrateFromServer() startte pas na een voltooide /auth/me), wat een
  hele round-trip extra kost bij elke opstart voor staf/thuisgebruikers.
- app.js vraagt ze nu tegelijk op via Promise.allSettled; hydrateFromServer()
  krijgt het /me/data-resultaat optioneel meegegeven i.p.v. het zelf
  (opnieuw) op te halen. Voor een leerling/ouder wordt dat resultaat
  genegeerd (zij krijgen sowieso geen whiteboard-data) - nooit verspild
  werk, soms ongebruikt, wat de server niet meer kost dan de bestaande
  losse aanroep die er toch al was.
- De twee andere hydrateFromServer()-aanroepen (na inloggen, na een
  save-conflict) geven bewust niets mee en halen gewoon vers op, zoals
  voorheen - alleen de allereerste opstart kan dit parallelliseren.
- Geverifieerd met de volledige testsuite (112/112) en drie losse
  boot-scenario's in de sandbox (gast/staf/leerling): in alle drie komen
  /auth/me en /me/data binnen ~1ms van elkaar aan (voorheen serieel), en
  blijft het bestaande gedrag (gast blijft gast, staf krijgt het bord,
  leerling krijgt de afgeschermde weergave) exact hetzelfde.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 13:43:32 +02:00
Ramon
4e4aeb4b6d feat: exporteren van vers-gegenereerde wachtwoorden als CSV (v0.4.30-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 38s
- "Exporteer als CSV"-knop toegevoegd aan het bestaande multireset-
  resultatenscherm (klas-wachtwoorden resetten in de School-navigator),
  naast de bestaande "kopieer alles"-knop.
- BELANGRIJKE BEPERKING, bewust zo: dit exporteert alleen wat op dit
  scherm al staat (net-gegenereerde wachtwoorden). Wachtwoorden worden
  na het zetten nergens leesbaar bewaard (alleen gehashet in de
  database) - er is dus geen "haal wachtwoorden van bestaande accounts
  op"-mogelijkheid en die komt er ook niet.
- CSV bevat gebruikersnaam (het echte inlogveld) naast de naam, ook al
  toont het scherm zelf alleen de naam - zonder gebruikersnaam is een
  uitdeelvel niet bruikbaar om mee in te loggen.
- Hergebruikt de csvValue()/downloadCsv()-helpers uit de vorige
  resultaten-exportfeature (v0.4.29-beta) - eerste echte tweede
  gebruiker, dus die keuze om ze lokaal in admin.js te houden i.p.v.
  te verplaatsen was terecht.
- Geverifieerd met de volledige testsuite (112/112) en een live
  browserdoorloop: klas-wachtwoorden resetten (dubbele bevestigingsklik)
  → CSV-download met correcte escaping van een leerlingnaam met komma
  én aanhalingsteken.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 13:31:10 +02:00
Ramon
8fe6fc10cc feat: exporteren van resultaten per klas (CSV) (v0.4.29-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 51s
- Nieuwe sectie "Exporteren per klas" in de bestaande Voortgang-tab: klas
  kiezen + "Exporteer als CSV" downloadt de volledige activiteitenlijst
  van die klas (leerling, widget, pogingen, goed, sterren, datum/tijd).
- Bouwt voort op de bestaande route GET /progress/class/:id, die tot nu
  toe geen enkele frontend-consument had.
- Server: nieuwe optionele ?limit=-queryparameter (begrensd op 5000) -
  de export vraagt expliciet meer dan de standaard activiteitenweergave
  (200) op, zodat een drukke klas niet stilzwijgend wordt afgekapt in het
  exportbestand. Zonder de parameter blijft het bestaande gedrag (200)
  ongewijzigd.
- Eerste CSV-export in deze codebase: kleine lokale csvValue()/
  downloadCsv()-helpers in admin.js (komma/quote/newline-escaping,
  BOM zodat Excel-NL accenten en komma-scheiding herkent) - bewust nog
  niet gedeeld/verplaatst naar core.js, want er is nu nog maar één
  gebruiker (de geplande wachtwoorden-export kan dit later hergebruiken).
- Tests: test/progress-export.test.js (limit wordt doorgegeven, te hoge
  waarde wordt afgekapt op 5000, standaardgedrag zonder parameter blijft
  200) + volledige testsuite (112/112). Ook live geverifieerd: klas
  kiezen zonder selectie toont een hint i.p.v. te crashen, een echte
  CSV-download met correcte escaping van komma's/aanhalingstekens in
  een leerlingnaam, en de vertaalde widget-namen in de kolommen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 12:54:48 +02:00
Ramon
09a1326c7e feat: meer personage-accessoires incl. zonnebril (v0.4.28-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m10s
- Nieuwe hoofdaccessoires: zonnebril, koptelefoon, feesthoedje.
- Nieuwe lichaamsaccessoires: ketting, rugtas (bandjes over de
  schouders, zichtbaar van voren).
- Server-validatie (src/api.js) en vertalingen (nl+en) meegewerkt met
  dezelfde nieuwe waarden - anders zou de client een optie tonen die de
  server als "ongeldig hoofdaccessoire" afwijst.
- Geen wijziging aan de bestaande pet/kroon-onderdrukkingsregel nodig:
  zonnebril/koptelefoon/feesthoedje bedekken het bovenhoofd niet volledig,
  dus haar blijft er gewoon zichtbaar naast/onder.
- Geverifieerd: alle select-opties komen aan in de instellingen-editor,
  een PUT met de nieuwe waarden slaagt (bestaande server-validatie
  accepteert ze nu), en alle soort/accessoire-combinaties oogden goed in
  screenshot (zonnebril op de dino, koptelefoon op het monster,
  feesthoedje + ketting samen, rugtas op het dier).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 11:55:25 +02:00
Ramon
817f12b70d fix: dino-rugstekels en beer-staart oogden vreemd (v0.4.27-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 35s
- Dino: rugstekels stonden als een rij spitse vormen voor de hals/borst
  ("Ga als designer kijken" - gebruiker vond dit "wat vreemd"), wat als
  een rare kraag oogde. Verplaatst naar een klein randje bovenop de kop
  (zoals kleine hoorntjes) - dichter bij de anatomische plek (rug/kruin)
  en rustiger op klein formaat.
- Dier (beer): had een los staartje opzij op dezelfde hoogte en met
  vrijwel dezelfde vorm/grootte als de armen/benen, waardoor het als een
  extra ledemaat oogde. Verwijderd - een beer laat van de voorkant
  sowieso nauwelijks iets van zijn staart zien, dus dit is ook
  anatomisch kloppender dan een zichtbaar zij-staartje.
- Overwogen: SVG vs. PNG-lagen als basis. SVG blijft de juiste keuze
  hier - lichaams-/haarkleur zijn vrije kleurkiezers (elke hex-waarde),
  wat via fill-attributen triviaal is maar bij PNG een vast palet of
  canvas-hertintplugin zou vereisen; SVG schaalt bovendien scherp op
  alle drie weergavegroottes zonder aparte assets.
- Geverifieerd: alle vijf bestaande scenario's, volledige testsuite,
  en een School-navigator met een echte klas (leerling met dier-
  personage toont nu geen vreemd zij-staartje meer).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 11:44:55 +02:00
Ramon
46696b0d7d refactor: personage-tekening volledig herbouwd als SVG (v0.4.26-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m40s
- Kernprobleem: de CSS-laagjesversie (losse divs met percentage-posities
  en z-index) had onderdelen die los van het lichaam hingen en een
  tekenvolgorde die niet klopte (soort-specifieke delen soms achter
  hoofd/lijf verstopt, soms er los voor zwevend). SVG lost dit
  structureel op: tekenvolgorde = laagvolgorde, geen z-index nodig, en
  elk onderdeel is een gebogen path/ellips die het silhouet van het
  vorige onderdeel ruim overlapt in plaats van ernaast te zweven.
- Onderzocht hoe gangbare avatar-generators (DiceBear, Avataaars) dit
  aanpakken: vaste 100x100 viewBox, onderdelen als SVG-groepen in de
  juiste volgorde, kleur via directe fill-waarden i.p.v. CSS-variabelen
  in SVG. Dat patroon hier toegepast, geen code overgenomen.
- Elke soort (dino/monster/dier) is nu een samenhangend lichaam: poten,
  armen, staart/oren, buikvlek en gezicht overlappen allemaal duidelijk
  met het hoofd/lijf. Dino: rugstekels, snuit met neusgaten, glimlach.
  Monster: hoorntjes, brede mond met slagtandjes, asymmetrische ogen.
  Dier: ronde oren, beer-snuit, kleine staart.
- Haar (spits/krullen/paardenstaart/mohawk) en accessoires (pet/kroon/
  bril/strik/sjaal/cape/badge) opnieuw getekend zodat ze ook echt op
  hun naam lijken, i.p.v. simpele gekleurde blokjes.
- Regel toegevoegd: een pet of kroon dekt het hele bovenhoofd af, dus
  onderliggend haar wordt dan niet getekend (gaf anders rommelige,
  half verstopte plukjes rond de rand) - een paardenstaart blijft wel
  zichtbaar, want die hangt naar opzij.
- Kleuren (lichter voor buik/snuit, donkerder voor poten/staart/
  rugstekels) worden nu berekend uit de gekozen lichaamskleur zelf
  (avatarShade-helper), i.p.v. via CSS filter:brightness().
- CSS: alle oude .av-* laag-regels (~65 regels) verwijderd; alleen
  afmetingen (sm/md/lg) en de bestaande instellingen/widget/
  aanmoedigings-CSS blijven staan.
- Geverifieerd: alle vijf bestaande scenario's (opslaan, widget,
  School-navigator-badge met een echte klas vol verschillende
  personages, math.js-aanmoediging, ongeldige input), plus uitgebreide
  screenshot-rondes van losse onderdelen, alle soort/haar/accessoire-
  combinaties en alle drie weergavegroottes (24/44/180px) vóór het
  committen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 10:19:19 +02:00
Ramon
4438fad52b fix: dino-personage krijgt een echt dinolichaam i.p.v. een blote romp (v0.4.25-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m5s
- Buikvlek (lichter gekleurde ovaal) op de romp - een klassiek dino-kenmerk
  dat meteen de "kleurvlek zonder vorm"-indruk wegneemt.
- Twee kleine pootjes onderaan en twee stompe armpjes opzij (T-rex-stijl) -
  het lichaam stond eerder als een rond blok zonder ledematen.
- Staart herzien naar een echte taps toelopende vorm (clip-path) i.p.v. de
  ronde druppelvorm van hiervoor.
- Alleen voor soort "dino" - monster/dier ongewijzigd, geen regressie
  (gecontroleerd via de screenshot-galerij van alle drie soorten samen).
- Geverifieerd op alle vijf bestaande scenario's (opslaan, widget,
  School-navigator-badge, math.js-aanmoediging, ongeldige input) én
  opnieuw op alle drie weergavegroottes (24/44/180px) vóór het committen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 09:57:48 +02:00
Ramon
9bf91f0bd1 fix: personage-tekening had een gezicht noch duidelijke dino-vorm (v0.4.24-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
- Elk personage kreeg ogen (was volledig gezichtsloos - de kernoorzaak van de
  lage beeldkwaliteit: een gekleurde vlek zonder gezicht oogt nooit als een
  figuur, ongeacht soort/haar/accessoires).
- Dino kreeg een snuitje met neusgaten, een duidelijk zichtbaar staartje en
  een derde, groter/donkerder rugstekel-plaatje - hiervoor waren de
  rugstekels aanwezig maar onzichtbaar (z-index onder het lijf/hoofd, zie
  vorige commit), nu bewust vóór het silhouet getekend zodat ze altijd tonen.
- Snuitje stond eerst z-index-onder de rugstekels (dus zelf ook onzichtbaar,
  identieke fout als hierboven) - z-index verhoogd zodat het snuitje altijd
  vóór de rugstekels ligt, wat ook anatomisch klopt (snuit zit vooraan).
- Geverifieerd met alle vijf scenario's uit de vorige verificatie (opslaan,
  widget, School-navigator-badge, math.js-aanmoediging) plus een losse
  screenshot-galerij op alle drie weergavegroottes (24/44/180px).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 09:50:18 +02:00
Ramon
24d89b8bb5 feat: eigen personage (avatar) voor leerlingen, ouders en staf (v0.4.23-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
- Nieuwe migratie db/018_user_avatar.sql: kolom avatar_data (jsonb) op users.
- publicUser() geeft avatar overal automatisch mee (login, /auth/me,
  /admin/users) - bestaande school/klas-rechten regelen vanzelf wie welke
  personages ziet, geen nieuwe permissie-sleutel nodig.
- Nieuwe route PUT /me/avatar (open voor elke ingelogde rol, zelfde patroon
  als /me/data): valideert soort/kleuren/haar/accessoires, slaat de eigen rij op.
- Nieuwe gedeelde module public/js/avatar.js: personage samenstellen uit
  soort (dino/monster/dier - elk met een vast eigen lichaamsdeel, ook de
  gevraagde dinostekels), lichaams- en haarkleur, haarstijl en hoofd-/
  lichaamsaccessoires; gedeelde editor-UI voor zowel de instellingen-sectie
  "Mijn personage" als de nieuwe widget.
- Instellingen-sectie "Mijn personage" (cms.js-patroon) - de leerling-toegang,
  want widgets van categorie "class" zijn niet toewijsbaar in het
  afgeschermde leerling-speelscherm.
- Widget 🦖 toegevoegd aan het bord (categorie "class") voor staf en
  volledige-whiteboard-gebruikers.
- School-navigator (admin.js): leerkracht/beheerder ziet nu het personage
  van elk klaslid in de personenlijst i.p.v. altijd de initialen-cirkel.
- Voorbereiding gerelateerd roadmappunt: math.js toont het eigen personage
  kort naast "Goed!" als aanmoediging (pilot in één widget, geen nieuw
  feedbacktype); zonder ingesteld personage blijft het gedrag ongewijzigd.
- Vertalingen (nl+en) voor de nieuwe widget, instellingen-sectie en editor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 07:34:25 +02:00
Ramon
8b57922307 fix: schoolbeheerder zichtbaar als groepsleiding + A+-knop verspringt niet meer (v0.4.22-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
- Een schoolbeheerder (of systeemmanager) die aan een klas gekoppeld werd,
  kwam daar niet zichtbaar te staan: de klas-detailweergave en de
  staf-telling op de klastegel filterden op role==="teacher", terwijl de
  server al sinds v0.3.38 elke stafrol (teacher/admin/super) toestaat in
  class_teachers. Puur een weergavebug op de client - de koppeling zelf
  werkte al goed. Nieuwe isClassStaff()-helper matcht nu exact de
  serverkant se toegestane rollenset.
- De "+" in de A+-zoomknop (toolbar én het per-widget contentzoom-knopje)
  gebruikte de CJK-volle-breedte "+" (U+FF0B) naast de gewone letter "A" -
  dat teken valt vermoedelijk terug op een ander lettertype dan Quicksand,
  waardoor de relatieve positie t.o.v. de "A" verspringt zodra
  document.body.style.zoom verandert. Vervangen door de gewone ASCII "+"
  (net als de A−-knop al een normaal minteken gebruikt, niet de volle-
  breedte variant).
2026-07-19 01:06:39 +02:00
Ramon
9920fd3855 feat: vier sociaal-emotionele widgets, waaronder een geluidsmeter (v0.4.21-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
- Geluidsmeter: meet het klasgeluid live via de microfoon in een verticale
  meter (rustig/pratend/te luid). Twee aanpasbare streefniveaus ("Rustig
  tot" / "Te luid vanaf") laten de leerkracht de meter afstemmen op het
  eigen lokaal en apparaat. Microfoon staat standaard uit en start alleen
  na een expliciete klik; er wordt niets opgenomen of verstuurd, alles
  blijft lokaal in de browser. Stopt de microfoonstream vanzelf zodra de
  widget van het bord verdwijnt, ook als "Stop" niet eerst is ingedrukt.
- Gevoelszones: individuele check-in met vier zones (blauw/groen/geel/
  rood, Zones of Regulation-stijl) - elke zone geeft een korte uitleg én
  een concrete tip om jezelf te helpen. Anders dan de bestaande
  Gevoelsmeter (mood.js), die een anonieme klaspoll is.
- Ademhalingsoefening: een groeiende/krimpende cirkel begeleidt rustig
  ademhalen in drie instelbare tempo's (4-4, 4-7-8, box breathing),
  met rondjesteller.
- Complimentenmachine: trekt een willekeurig compliment uit twintig
  kindvriendelijke complimenten, met dezelfde spin-en-onthul-animatie als
  de namenkiezer.
- Alle vier volledig tweetalig (nl/en), toegevoegd aan de mobiele-dekking-
  test, en geverifieerd in de browser inclusief een echte (fake) micro-
  foonstream voor de geluidsmeter.
2026-07-19 00:44:39 +02:00
Ramon
63490719d6 fix: letterblokken bij wereldoriëntatie verschillen nu ook per niveau (v0.4.20-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 51s
- De nieuwe "Letterblokken"-werkvorm (v0.4.18) verklapte op elk niveau
  altijd elke letter - alleen de soorten-/plaatsenpool werd moeilijker,
  de spelopdracht zelf bleef identiek. Bij de originele letterblokken-
  widget (letters.js) maakt niveau juist de opdracht zelf moeilijker:
  niveau 1 verklapt alles, niveau 3 niets.
- Zelfde schaal nu ook hier: niveau 1 toont elke letter, niveau 2 ongeveer
  de helft (willekeurig gekozen, vast per opdracht), niveau 3 toont geen
  enkele letter. Het puntenpatroon per vak blijft op elk niveau zichtbaar
  als tastbare hint, ook wanneer de letter zelf verborgen is.
- Geverifieerd: niveau 1 toont 11/11 letters, niveau 2 ~helft, niveau 3
  0/13 (met de puntenpatronen nog intact).
2026-07-19 00:23:39 +02:00
Ramon
705bb7800a fix: niveaus bij wereldoriëntatie nu als knoppenrij, net als de andere oefenwidgets (v0.4.19-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 56s
- Bomen/vogels/planten/topografie verstopten "Niveau" als een los
  keuzeveld onderin het inklapbare Instellingen-paneel; de oefenklok,
  het geldwidget, plaatswaarde en rekenwidgets tonen niveau altijd als
  een knoppenrij bovenaan, direct zichtbaar tijdens het spelen.
- Niveau is nu overal dezelfde knoppenrij (Niveau 1/2/3, met uitleg per
  knop via title, net als bij de oefenklok) direct in de bovenbalk naast
  de score - geen Instellingen meer nodig om niveau te wisselen.
- Niveau wisselen zet score en reeks terug naar 0, zoals bij de andere
  oefenwidgets (voorheen bleef de score gewoon doorlopen).
- Topografie: de knoppenrij verdwijnt bij "Landsdelen", waar niveau toch
  al niet van toepassing was - zelfde gedrag als voorheen, nu alleen
  consistent met de rest.
2026-07-19 00:16:09 +02:00
Ramon
5cdc854e69 feat: flitskaarten en letterblokken bij wereldoriëntatie (v0.4.18-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 37s
- Bomen, vogels en wilde planten krijgen twee nieuwe werkvormen naast
  Leren/Quiz: Flitskaarten (naam kort tonen, dan zelftoets - zelfde opzet
  als het losse Flitswoorden-widget) en Letterblokken (de naam met
  sleepblokken spellen, zelfde mechaniek als de letterblokken-widget,
  inclusief puntenpatroon per letter).
- Topografie krijgt dezelfde twee werkvormen: flitskaarten tonen de kaart
  met de plek pas gelabeld na onthullen, letterblokken laat de naam van de
  gemarkeerde provincie/plaats/rivier/werelddeel spellen.
- Letterblokken werkt ook bij namen met spaties (bv. "Witte
  paardenkastanje") - spaties worden een visuele tussenruimte, geen vak.
- Geverifieerd door alle vier widgets (bomen/vogels/planten/topografie)
  volledig te spelen: flitskaart onthullen + zelftoets, en een woord
  volledig spellen puur op basis van de getoonde puntenpatronen.
2026-07-18 19:35:08 +02:00
Ramon
daad79c8b9 fix: vakken tonen nu het puntenpatroon van het júíste cijfer, zoals bij letterblokken (v0.4.17-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 56s
- De vorige puntenpatroon-fix (v0.4.16) loste alleen op dat de tien
  cijferblokken onderling verschillen. De écht ontbrekende stap: bij de
  letterblokken en de rekenwidgets (madd/msub/mmul/mdiv/mpct) toont een
  leeg vakje altijd het puntenpatroon van het cijfer/de letter die er
  hoort te staan - een tastbare hint, ongeacht wat er al ligt. Bij de
  oefenklok en het geldwidget toonde het vakje in plaats daarvan niets
  (leeg) of het patroon van wat er toevallig al in lag - geen hint.
- Oefenklok (blockAnswer), geldwidget (digitAnswer) en de nieuwe
  plaatswaarde-oefening (buildAnswer) tonen nu overal het patroon van het
  juiste doelcijfer per vak, consistent met letterblokken/rekenwidgets.
- Geverifieerd door drie oefeningen (geldrekenen niveau 2/3, oefenklok
  niveau 3, plaatswaarde niveau 3) volledig op te lossen met uitsluitend
  de puntenpatronen als leidraad (geen tekst gelezen) - in alle gevallen
  "Goed gedaan!".
2026-07-18 16:33:07 +02:00
Ramon
bab1018142 fix: cijferblokken kregen elk een eigen puntenpatroon + niveaus voor plaatswaarde (v0.4.16-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 35s
- De cijferblokken (oefenklok, geldwidget) hergebruikten de braille a-j-
  patronen, die nooit de onderste rij (posities 3/6) gebruiken - daardoor
  leken alle tien blokken op elkaar. Eigen DIGIT_DOTCODE-tabel die alle
  zes stippen inzet, geeft elk cijfer nu een echt eigen vorm.
- Plaatswaardetabel-widget had geen enkele oefenvorm (alleen een vrij
  invoerveld); nieuwe "Bouwen"-stand met drie niveaus (T/E, H/T/E, of
  HD/TD/D/H/T/E) laat kinderen een getal opbouwen door cijferblokken in
  de juiste kolom te slepen, met score/reeks en directe feedback - zelfde
  bouwsteen als de oefenklok/het geldwidget. De vrije verkenstand blijft
  ongewijzigd bestaan.
- Bijvangst: instellingenvelden met .hidden (bv. "24-uursklok" bij de
  oefenklok) werden nooit echt verborgen door een CSS-specificiteitsbotsing
  (.edu-controls label versloeg [hidden]); toegevoegde regel herstelt dit
  voor alle leerwidgets.
2026-07-18 15:46:24 +02:00
Ramon
e555876fa4 feat: cast bord/widget vanaf whiteboard naar klas of leerling (v0.4.15-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 38s
- Nieuwe Cast-knop (📡) op de whiteboard-toolbar opent een sheet om het
  geopende bord - of één widget erop met kijken/werken - direct naar een
  klas of leerling uit te zenden, zonder om te weg via Instellingen.
- Hergebruikt de bestaande PUT/DELETE /assignments/class|pupil routes
  ongewijzigd; geen serverwijziging nodig.
- Instellingen > Toewijzingen blijft bestaan maar toont nu alleen de
  huidige toewijzing + een "Loskoppelen"-knop (aanmaken/wijzigen gebeurt
  voortaan via Cast).
2026-07-18 13:26:26 +02:00
Ramon
3c3638d114 feat: "School" herbouwd als drill-down navigator (v0.4.14-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 56s
Volledige herbouw van het "School"-tabblad naar het goedgekeurde concept
(tegel-overzicht -> gefocust detailscherm), i.p.v. drie secties op één lange
pagina. Zelfde patroon op elk niveau:
- "alle scholen" (systeemmanager): tegel per school + Systeemmanagers/Ouders/
  Zonder school + "+ School"
- een school: tegel per klas + Schoolbeheerder/Niet ingedeeld/Ouders/
  Aanmeldingen + "+ Klas"; "⋯" toont schoolbeheer (naam/verwijderen, alleen
  systeemmanager)
- een klas: Groepsleiding en Leerlingen als twee eigen sublijstjes, elk met
  een eigen "+"; "⋯" toont klasbeheer (verplaatsen/resetten/verwijderen)
- een platte groep: één lijst, evt. met "+"

Personen en aanmeldingen openen nu in een sheet (bestaande userTools()/
requestTools() vrijwel ongewijzigd hergebruikt) i.p.v. een los detailpaneel.
Zoeken vult, op tegel-niveau, het scherm met een systeembrede resultatenlijst.
Een leerkracht (groepsleiding) blijft de eenvoudige platte leerlingenlijst
zien, ongewijzigd.

Vervallen (overbodig dankzij drill-down, elke lijst is al kort en gefocust):
het hele inklap-/inspring-mechanisme (group(), openGroups/closedGroups,
editToggle, classHeadRow, applyUserFilter, .am-classhead, .am-indent), het
oude master-detail-paneel (selectedUserKey/syncSelection/renderDetail), en de
generieke "Nieuwe gebruiker"-knop (elke tegel se eigen "+" dekt dit al,
rol+context-correct).

Geen serverwijziging - pure client-herbouw van navigatie/weergave.
Geverifieerd in een echte headless Chromium voor systeemmanager (root +
school + klas + sheet + "+Groepsleiding" koppelt nog echt), schoolbeheerder
(eigen school, geen schoolkiezer/kebab-op-schoolniveau) en groepsleiding
(platte lijst, ongewijzigd).
2026-07-18 09:47:16 +02:00
Ramon
8580e46f4c fix: School-menu opnieuw ontworpen naar één consistente visuele taal (v0.4.13-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m0s
Was rommelig/te veel niveaus door TE veel verschillende stijlen op dezelfde
pagina: kaarten (Scholen) naast platte rijen (Gebruikers), een teal-getint
klasrijtje, een grijs getint paneel om Scholen - vier stijlen die om
aandacht streden i.p.v. één samenhangend geheel.

- Scholen gebruikt nu dezelfde platte .am-list/.am-row-stijl als de
  klassen/gebruikers-boom eronder i.p.v. losse kaarten in een eigen paneel;
  het paneel (.am-panel) is weg
- de ingeklapte beheerderslijst per school is weg - die informatie stond al
  in Gebruikers (gegroepeerd per school bij "alle scholen"), dubbel tonen
  droeg alleen bij aan de herrie
- klasrij heeft geen eigen teal-kader meer (alleen de naam iets zwaarder);
  hiërarchie loopt nu via een lichte inspringing (.am-indent) voor
  Groepsleiding/Leerlingen en hun rijen/+-knoppen, niet via kleur
- nieuwe editToggle()-helper: gebruikers-, klas- en schoolrijen delen nu
  dezelfde "Bewerken"-knop-implementatie i.p.v. drie keer bijna-dezelfde
  code, met een eigen openSchoolTools-Set (schoolrol- en klas-id's kunnen
  samenvallen met gebruikers-id's)
- geverifieerd in een echte headless Chromium: Scholen en Gebruikers ogen nu
  als één systeem, klas-substructuur is duidelijk ingesprongen, en de
  bestaande functionaliteit (rename/delete/reset/koppelen) werkt nog
2026-07-18 06:16:37 +02:00
Ramon
8db67b9cd6 fix: overzicht en scheiding in de Scholen-sectie van School (v0.4.12-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m15s
De losse schoolkaarten stonden zonder eigen kader los in de pagina en liepen
zonder duidelijke grens over in de Gebruikers-sectie eronder - verwarrend
omdat dezelfde schoolnaam daar soms letterlijk direct weer opduikt (nu als
groepskop i.p.v. kaart).

- Scholen krijgt een eigen omkaderd paneel (.am-panel) dat alle schoolkaarten
  en "+ School" samenbindt tot één zichtbaar geheel
- duidelijke scheidingslijn + meer ruimte vóór de volgende sectiekop, maar
  alleen ná een echte sectie (geen ongewenste lijn boven Gebruikers wanneer
  Scholen niet getoond wordt, bv. bij een gekozen school)
- geverifieerd in een echte headless Chromium: zowel de "alle scholen"-
  weergave (met paneel) als de weergave voor een gekozen school (zonder,
  geen regressie)
2026-07-18 05:46:26 +02:00
Ramon
5b4c3163a5 feat: Klassen en Gebruikers samengevoegd tot één boom per school (v0.4.11-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 51s
- klas, Groepsleiding en Leerlingen staan nu letterlijk bij elkaar: elke klas
  krijgt een eigen rij (naam + "Bewerken" voor verplaatsen/resetten/
  verwijderen, overgenomen uit het oude losse Klassen-paneel) met daaronder
  twee eigen sublijstjes - Groepsleiding (leerkrachten gekoppeld aan die
  klas) en Leerlingen - elk met een eigen "+"-knop
- "+ Groepsleiding" bij een klas maakt de nieuwe leerkracht niet alleen aan,
  maar koppelt die ook meteen aan die klas (hergebruikt de bestaande, al
  geteste POST /admin/classes/:id/teachers-route - geen serverwijziging)
- Schoolbeheerder blijft een eigen, vlakke groep los van de klassenboom (ook
  als een schoolbeheerder toevallig aan een klas gekoppeld is, om dubbele
  vermeldingen te voorkomen); Niet ingedeeld bundelt leerkrachten zonder klas
  en leerlingen zonder klas
- renderKlassenPanel is verwijderd (zijn inhoud leeft nu in de klas-tak van
  renderGebruikersPanel); de losse "Klassen"-kop in het School-tabblad is weg
- nieuwe openClassTools-Set voor het in-/uitklappen van klasbeheer, apart van
  openRows (klas- en gebruiker-id's kunnen samenvallen)
- geverifieerd in een echte headless Chromium: klasrij + twee sublijsten per
  klas, Schoolbeheerder/Niet ingedeeld correct gevuld, en "+ Groepsleiding"
  koppelt de nieuwe leerkracht daadwerkelijk aan de klas (classIds bevestigd)
2026-07-18 05:31:00 +02:00
Ramon
e088f83d1f fix: schoolkiezer reageert nu echt op Scholen/Klassen binnen "School" (v0.4.10-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 35s
- Scholen-blok toont alleen bij "alle scholen" (selSchool leeg), Klassen-blok
  alleen bij een gekozen school - voorheen toonde Scholen altijd, ongeacht de
  kiezer erboven, wat leek alsof het paneel de kiezer negeerde
- de kiezer defaultet voor de systeemmanager weer op "alle scholen" (was
  omgezet naar de eerste school in een eerdere commit; teruggedraaid op
  uitdrukkelijk verzoek)
- geverifieerd in een echte headless Chromium: bij "alle scholen" tonen
  Scholen+Gebruikers, bij een gekozen school Klassen+Gebruikers, en wisselen
  werkt in beide richtingen
2026-07-18 05:11:38 +02:00
Ramon
56125c6968 feat: Scholen, Klassen en Gebruikers samengevoegd tot één menu-item "School" (v0.4.09-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 40s
- ADMIN_SECTIONS: de drie losse tabbladen zijn samengevoegd tot 1 sectie
  ("school"); zoeken op een school-, klas- of gebruikersnaam werkt nog
  hetzelfde, navigeert nu alleen naar de ene samengevoegde sectie
- nieuwe renderSchoolPanel() zet de drie bestaande, ongewijzigde
  deelpanelen (renderScholenPanel/renderKlassenPanel/renderGebruikersPanel)
  na elkaar, elk met een eigen kop - geen van de drie is herschreven, alleen
  samengesteld, om het risico op regressies te beperken
- rolgedrag blijft exact zoals het was (nu allemaal binnen die ene sectie):
  systeemmanager ziet Scholen+Klassen+Gebruikers en de schoolkiezer,
  schoolbeheerder ziet Klassen+Gebruikers voor de eigen school (geen
  schoolkiezer, geen Scholen-blok), groepsleiding ziet alleen Gebruikers
  (geen Klassen-blok) en daarbinnen alleen de leerling-gerelateerde knoppen
- geverifieerd in een echte headless Chromium voor alle drie rollen: juiste
  secties, juiste knoppen, juiste zichtbaarheid van de schoolkiezer

Backup van de oude driedelige UI staat als git-tag
ui-before-merged-beheer-v0.4.08 (commit e34bda5).
2026-07-18 05:04:43 +02:00
Ramon
e34bda5251 test: expliciete regressietest voor de beperkte rechten van groepsleiding (v0.4.08-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 42s
Geen gedragswijziging - dit legt vast (en bevestigt) wat al klopte: een
groepsleiding kan geen rol wijzigen, geen gebruiker aan een school
koppelen/losmaken, geen klas aanmaken en geen school beheren. Alleen
systeemmanager (en voor klassen: ook schoolbeheerder) mag dit.
2026-07-18 04:49:10 +02:00
Ramon
e2ee4820dc fix: schoolbeheerder aan klas koppelen, systeemmanager-promotie dichten, UI-afstand (v0.4.07-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
- db/017: de class_teachers-trigger uit migratie 006 stond alleen 'teacher'
  toe en blokkeerde daarmee (met een verwarrende "groepsleiding en klas..."
  foutmelding) wat de applicatielaag allang toestond voor schoolbeheerder en
  systeemmanager - de trigger is nooit meegegroeid toen die rollen later ook
  aan een klas gekoppeld mochten worden. Nu ook admin/super toegestaan,
  super school-onafhankelijk zoals de rest van de app dat al behandelt.
- rolwijziging kon een bestaande groepsleiding/schoolbeheerder via een simpel
  dropdown-veld naar systeemmanager promoveren, zonder enige bevestiging -
  in tegenstelling tot de nieuwe systeemmanager-aanmaak (met wachtwoord-
  bevestiging). 'super' is nu geen geldig doel meer voor PATCH .../users/:id
  (ook niet via het extraRoles-mechanisme); het rolwijzigingsveld zelf toont
  zich niet meer voor een bestaande systeemmanager. Alleen de aparte
  aanmaakstap (met bevestiging) kan nog een systeemmanager opleveren.
- meer verticale ruimte tussen rijen in het beheerpaneel (was vrijwel 0),
  zodat je niet per ongeluk de verkeerde rij raakt
- de "+ ..."-toevoegknoppen staan nu rechts uitgelijnd i.p.v. links, zodat ze
  duidelijker als aparte actie ogen i.p.v. als onderdeel van de lijst

Geverifieerd in een echte headless Chromium: rolwijziging toont voor een
groepsleiding alleen nog teacher/admin als opties.
2026-07-18 04:40:39 +02:00
Ramon
111e48b044 fix: toevoegformulier duidelijk anders dan een bestaande-gebruikersrij (v0.4.06-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 34s
Een uitgeklapt "+ ..."-formulier (nieuwe schoolbeheerder/leerkracht/leerling/
school/klas) zag er in het beheerpaneel bijna hetzelfde uit als het
bewerkpaneel van een bestaande gebruiker - beide gewoon een rij velden
zonder duidelijk onderscheid.

- .am-add krijgt nu een eigen kader (accentkleur, stippellijn) i.p.v. de
  neutrale achtergrond van een gebruikersrij
- het formulier toont bovenaan een titel die herhaalt wát je toevoegt (bv.
  "LEERLING"), ook zodra de knoptekst erboven uit beeld is
- de dichtgeklapte "+ ..."-knop zelf krijgt ook de accentkleur, zodat
  collapsed en uitgeklapt dezelfde "dit is toevoegen"-taal spreken
- geverifieerd in een echte headless Chromium: duidelijk visueel onderscheid
  tussen een toevoegformulier en een echte gebruikersrij
2026-07-18 04:22:17 +02:00
Ramon
e5890280cc feat: snel-toevoegen-knop onder elke groep in Gebruikers (v0.4.05-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 34s
Net als bij systeemmanagers (vorige commit) krijgt nu elke groep in de
Gebruikers-tab (schoolbeheerder, groepsleiding, en elke klas incl. "geen
klas") direct een eigen "+ ..."-knop eronder om iemand toe te voegen zonder
naar een ander tabblad te hoeven. Bij een klas staat die klas al vast
ingevuld (presetClassId, bestond al als parameter maar werd nog nergens
gebruikt). Een groep blijft zichtbaar zolang er iets aan toegevoegd kan
worden, ook als hij nu leeg is (bv. een gloednieuwe klas zonder leerlingen) -
anders was er nergens een knop om de eerste aan toe te voegen.

Geverifieerd in een echte headless Chromium (gemockte /api/** responses):
groepen en knoppen verschijnen zoals verwacht, en de klas-select bij een
klasgroep staat al goed vooringesteld.
2026-07-18 04:14:27 +02:00
Ramon
c6638d63f1 feat: systeemmanager aanmaken verplaatst naar Gebruikers + eigen wachtwoord ter bevestiging (v0.4.04-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
- het aanmaken van een systeemmanager stond onder Scholen, los van de rest
  van het gebruikersbeheer - verplaatst naar de Gebruikers-tab (bij "alle
  scholen", naast de bestaande systeemmanagers-groep)
- extra veiligheidsstap: de aanmaker moet het eigen wachtwoord invullen ter
  bevestiging vóórdat een nieuwe systeemmanager wordt aangemaakt - server
  verifieert dit (POST /admin/users), niet alleen de client
- wie dit al mocht (alleen systeemmanagers, via creatableRoles) blijft
  ongewijzigd - de bevestigingsstap is een aanvullende, geen vervangende,
  controle
2026-07-18 04:00:30 +02:00
Ramon
cae700f76d fix: leerling verplaatsen naar een andere klas ververst het paneel niet (v0.4.03-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 33s
De klas-select bij een leerling (uitwisselen naar een andere klas) miste de
reload() die elders bij een klaswijziging wel gebeurt: de server sloeg de
nieuwe klas gewoon op, maar het paneel liet nog de oude toestand zien - leek
dus alsof verplaatsen niet werkte. Test toegevoegd die de serverkant van
deze route bevestigt (die werkte al correct); de fix zelf is puur de
ontbrekende reload() in public/js/admin.js.
2026-07-18 03:51:21 +02:00
Ramon
45f66044f2 fix: alle DELETE-verzoeken faalden op een lege JSON-body (v0.4.02-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 38s
De echte, uiteindelijke oorzaak achter elke "serverfout" die niet door een
route-eigen foutafhandeling kwam (klas verwijderen, en zeer waarschijnlijk
ook de oorspronkelijke gebruiker-verwijderen-bug): de client stuurde bij élk
verzoek altijd "Content-Type: application/json" mee, ook bij een DELETE
zonder body. Fastify's standaard JSON-parser weigert zo'n leeg lichaam met
"Body cannot be empty when content-type is set to 'application/json'" -
vóórdat er ook maar één hook of route-handler heeft kunnen draaien, dus nog
vóór req.user gezet wordt en vóór enige try/catch in een route. Dat verklaart
waarom eerdere per-route foutafhandeling dit nooit kon vangen: de fout zat
vóór elke route.

- public/js/core.js: api() stuurt Content-Type alleen nog mee als er
  werkelijk een JSON-body is
- src/body-parser.js (nieuw): serverside vangnet dat een leeg lichaam bij
  Content-Type: application/json gewoon toestaat i.p.v. te weigeren -
  ongeacht wat een client meestuurt
- test/body-parser.test.js: reproduceert de exacte foutmelding tegen een
  echte Fastify-requestcyclus (de gemockte-pool-tests konden dit principieel
  nooit vangen, vandaar dat dit alle eerdere testrondes overleefde)
2026-07-18 03:44:39 +02:00
Ramon
4517f592f7 chore: versienummering terug naar tweecijferig patchformaat (v0.4.01-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 55s
0.3.99 rolde per ongeluk door naar 0.4.0/0.4.1 i.p.v. het gebruikelijke
tweecijferige patchformaat (0.4.00, 0.4.01, ...) - hersteld.
2026-07-18 03:40:51 +02:00
Ramon
6742e07363 fix: altijd de echte serverfout tonen + klasnamen uniek per school (v0.4.1-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 35s
- de generieke errorhandler gaf alleen detail mee als req.user al gezet was;
  bleek de fout zelf al vóór/tijdens dat moment te zitten (zoals bij klas
  verwijderen), dan bleef het toch de kale "serverfout" - nu altijd de echte
  oorzaak, dit is een intern beheertool zonder publieke bezoekers
- klasnamen konden onbeperkt dubbel aangemaakt worden binnen dezelfde school
  (geen enkele controle) - POST /admin/classes weigert dit nu met 409, en een
  nieuwe unieke index (school_id, lower(name)) is het echte vangnet
- migratie 016 hernoemt eerst bestaande dubbele klasnamen (niets verwijderd)
  vóórdat de unieke index wordt gezet: anders was de migratie zelf op deze
  database mislukt en had de app niet meer opgestart
2026-07-18 03:39:04 +02:00
Ramon
4a31dc9351 fix: klas verwijderen toont echte oorzaak i.p.v. kale serverfout (v0.4.0-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 43s
- DELETE /admin/classes/:id had geen try/catch: elke mislukking (bv. een FK
  die nog niet cascade/set-null is op deze database) viel terug op de
  generieke handler en toonde alleen "serverfout" zonder aanknopingspunt
- zelfde aanpak als eerder bij het verwijderen van een gebruiker: de echte
  Postgres-foutmelding komt nu terug i.p.v. een kale "serverfout"
- test toegevoegd voor zowel het gewone pad als de foutmelding
2026-07-18 03:28:31 +02:00
Ramon
be8ecc5378 fix: gebruikerslijst per school laadde nooit (SELECT DISTINCT/ORDER BY) (v0.3.99-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m13s
- linkedParents() gebruikte "SELECT DISTINCT p.* ... ORDER BY lower(p.username)":
  Postgres weigert dit omdat de ORDER BY-expressie niet letterlijk in de
  select-lijst staat. Deze query loopt bij élke school-gescopede
  gebruikerslijst (elke schoolbeheerder, en elke systeemmanager die een
  specifieke school kiest i.p.v. "alle scholen") - dus die lijst laadde
  nooit, klassen werden om dezelfde reden niet (opnieuw) opgehaald, en
  "alle scholen" was de enige werkende weergave
- fix: lower(p.username) expliciet als kolom (sort_key) in de select-lijst,
  ORDER BY daarop i.p.v. op de losse expressie
- regressietest die de exacte SQL-vorm vastlegt (een gemockte pool voert geen
  echte SQL uit en had deze Postgres-grammaticafout niet kunnen vangen)
2026-07-18 03:19:36 +02:00
Ramon
205ba74eff fix: beheerpaneel toont laadfout i.p.v. stilletjes leeg te blijven (v0.3.98-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 34s
- reload() in het beheerpaneel (scholen/klassen/gebruikers) ving elke fout
  stilletjes op en zette alles terug naar lege lijsten, zonder enige melding -
  dat gaf een verwarrend leeg paneel bij een specifieke school terwijl "alle
  scholen" wel werkte, zonder enig aanknopingspunt waarom
- de echte oorzaak verschijnt nu bovenin het paneel (amLoadError), zodat een
  volgende mislukking direct te diagnosticeren is i.p.v. te moeten gokken
2026-07-18 03:08:55 +02:00
Ramon
4ffb69ca61 fix: gebruiker verwijderen toont echte oorzaak i.p.v. kale serverfout (v0.3.97-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m19s
- DELETE /admin/users/🆔 hele handler (niet alleen deleteUserFully) in
  één try/catch, zodat ook een fout vóór het verwijderen zelf (bv. de
  opzoekquery) de echte reden teruggeeft i.p.v. terug te vallen op de
  generieke 500 "serverfout"
- generieke errorhandler logt voortaan altijd het volledige fouteobject en
  geeft ingelogde gebruikers de werkelijke foutmelding mee i.p.v. alleen
  "serverfout"
- test toegevoegd die dit nieuwe pad dekt
2026-07-17 22:27:20 +02:00
Ramon
e48a3a4e46 fix: gebruiker verwijderen werkt gegarandeerd - ruimt afhankelijke rijen zelf op (v0.3.96-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 51s
- De "serverfout" bij verwijderen komt van een foreign key die op de live-database geen ON DELETE CASCADE heeft, waardoor DELETE FROM users faalt zodra er nog een gekoppelde rij bestaat
- Nieuw: de verwijderroute haalt in één transactie eerst alle afhankelijke rijen weg (sessies, rollen, klaskoppelingen, toewijzingen, voortgang, ouderkoppelingen, aanmeldingen, reservekopieën, gedeelde items, afbeeldingen, borden/mappen) en verwijdert daarna de gebruiker - dit werkt ongeacht de exacte FK-instelling en heeft migratie 015 niet nodig
- Mocht verwijderen tóch falen, dan komt nu de echte databasereden terug (constraint-detail) i.p.v. een kale "serverfout", zodat de oorzaak zichtbaar is
- 3 servertests: volledige opruiming in transactievolgorde (afhankelijke rijen vóór users), echte foutreden bij een gesimuleerde fout; 97 tests groen

LET OP: dit werkt pas na deploy van deze versie; v0.3.94 (huidige live) bevat de fix nog niet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-17 21:50:48 +02:00
Ramon
e3036580ce fix: gebruiker verwijderen werkt weer (herstel foreign-key cascade) (v0.3.95-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 32s
- Verwijderen gaf op de live-database een 500 "serverfout". Oorzaak: op databases die zijn ontstaan vóór alle foreign keys ON DELETE CASCADE waren, houdt een tabel de oude constraint (CREATE TABLE IF NOT EXISTS werkt een bestaande constraint nooit bij), waardoor DELETE FROM users een foreign-key-fout geeft
- Migratie 015: zoekt elke FK naar users(id) die niet cascade en niet set-null is en zet die alsnog op ON DELETE CASCADE (bewuste SET NULL-constraints blijven ongemoeid). Draait bij de volgende deploy en is idempotent
- Verwijderroute vangt een resterende FK-fout (23503) op als een begrijpelijke 409 i.p.v. een kale serverfout
- Geverifieerd: alle INSERT INTO users-paden vangen al 23505 op (gebruikersnaam uniek per school); dezelfde naam in verschillende scholen is bewust toegestaan (inloggen kiest de school) en staat sinds 0.3.94 per school gegroepeerd
- 3 nieuwe servertests (FK-fout -> 409, normale verwijdering, migratie-inhoud); 97 tests groen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-17 21:39:29 +02:00
Ramon
30818dc5f2 fix: gebruikers gegroepeerd per school in alle-scholen-weergave + zichtbare verwijderfouten (v0.3.94-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 35s
- In de "alle scholen"-weergave stonden gebruikers op rol gegroepeerd, waardoor je niet zag bij welke school ze horen (Ramon/Rubi/leerlingb1b leken los te staan). Ze worden nu per school gegroepeerd (inklapbaar), met systeemmanagers, ouders en losse accounts apart eronder - je ziet meteen wie bij De Ziep hoort
- Bij één gekozen school blijft de indeling op rol/klas; die rolgroepen zijn nu ook inklapbaar
- Verwijderfouten (en andere actie-fouten in het detailpaneel) verschijnen nu in het detailpaneel zelf i.p.v. alleen bovenaan (buiten beeld op mobiel). Als een verwijdering serverzijdig faalt, zie je nu de reden - het lijkt niet langer of "niets gebeurt"
- Verwijderen zelf is end-to-end geverifieerd (DELETE verstuurd, gebruiker verdwijnt); een gesimuleerde 409 toont de melding in het paneel en laat de gebruiker staan
- 7 nieuwe Playwright-checks (school-groepering, zichtbare fout, geslaagde verwijdering) + volledige regressie van 8 suites; 94 servertests groen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-17 21:21:05 +02:00
Ramon
c86abcf794 fix: onzichtbare knopteksten, lege klassenlijst en correcte klas-schoolregels (v0.3.93-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 32s
- Onzichtbare knoppen opgelost: .tbtn houdt standaard witte tekst (wit-op-blauw), maar in het beheer staat hij op een lichte achtergrond -> tekst was wit-op-wit. Nu expliciet donkere tekst; de primaire accentknop houdt bewust witte tekst
- Systeemmanager kreeg standaard "alle scholen" waardoor klassen leeg leken en niet toe te voegen waren; er wordt nu meteen een concrete school gekozen (klassen/gebruikers direct gevuld). Contextbalk laat wisselen; "alle scholen" blijft een bewuste keuze
- Lege school toont nu een duidelijke uitleg dat bestaande klassen bij een andere school kunnen horen, met verwijzing naar de schoolkeuze
- Correcte gegevensregels: classes.school_id is NOT NULL met ON DELETE CASCADE, dus een klas kan niet "los" bestaan (die eerdere aanname is teruggedraaid). Een klas is nu te VERPLAATSEN naar een andere school (PATCH weigert null/ongeldig); een verwijderde school neemt zijn klassen mee (cascade) - dat verklaart verdwenen klassen
- Bij een leerling naar een andere school koppelen blijft de klas behouden als die al bij de doelschool hoort
- Verificatie uitgebreid met een echte kleurcontrast-scan (vangt wit-op-wit) over alle secties + de default-school-flow; alle 94 servertests en 8 Playwright-suites groen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-17 20:22:36 +02:00
Ramon
aee1c59fbd fix: leerlingen zichtbaar bij gekozen school + uniforme controls in het beheer (v0.3.92-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 35s
- Leerlingen stonden onder standaard-ingeklapte klasgroepen, dus met een gekozen school leek de lijst leeg terwijl de detailweergave de juiste school toonde. Klasgroepen staan nu standaard open zodra een school is gekozen (de schoolkeuze wás al het filter); bewust dichtklappen wordt onthouden
- Losse klassen (school verwijderd) waren nergens zichtbaar: de systeemmanager ziet ze nu zonder gekozen school met een uitleg, en kan ze via een School-veld terugkoppelen; nieuwe PATCH /admin/classes/:id + GET ?school=none (super) met servertests
- Bij het aan een school koppelen van een leerling blijft de klasindeling behouden als die klas al bij de doelschool hoort (eerst klas terugkoppelen, dan de leerlingen)
- UI-consistentie: één uniforme control-hoogte (32px) voor álle knoppen, selects en invoervelden in Instellingen - einde aan de grote maatverschillen; T() valt terug op NL en dan de sleutelnaam zodat een knop nooit zonder tekst kan staan
- Status in de gebruikerstabel als compacte glyph (● in gebruik / ○ wacht) met uitleg bij aanwijzen i.p.v. een afgekapte "nog niet ing…"-chip
- Geverifieerd in headless Chromium: nieuwe suite (21 checks: zichtbaarheid met schoolfilter, losse klassen, geen lege knoppen, uniforme hoogte over 8 secties) + volledige regressie van 5 bestaande suites

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-17 19:46:09 +02:00
Ramon
15618348dc style: alle menu's nagelopen - alles verborgen tot erom gevraagd wordt (v0.3.91-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 33s
- Voortgang: tijdlijn toont 30 regels per keer met een "meer tonen (n)"-knop i.p.v. 200 regels ineens
- Inhoud: de vier blokken (welkomstwoord, melding, widget-uitleg, systeembrede inhoud) zijn inklapbare groepskaarten - alleen het welkomstwoord staat standaard open; open-status overleeft her-renders
- Account: de drie wachtwoordvelden verschijnen pas na een klik op de kop "Wachtwoord"
- Daarmee volgt elke sectie hetzelfde principe: compacte koppen met samenvatting, detail pas op verzoek, verwante items in één kader
- Geverifieerd in headless Chromium (9 nieuwe checks) + volledige regressie van de vier bestaande suites (56 checks)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-17 14:35:24 +02:00
Ramon
b4eee9f78f feat: gebruikers aan een school koppelen en losmaken (v0.3.90-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 52s
- PATCH /admin/users/:id ondersteunt schoolId (alleen systeemmanager, rollen teacher/admin/pupil): null = losmaken, anders koppelen met bestaanscheck (404); bij wissel worden klas, klaskoppelingen en (voor leerlingen) oude toewijzingen opgeruimd; naamconflicten in doelschool of school-loze naamruimte geven een nette 409
- Detailpaneel toont voor de systeemmanager een School-veld bij deze rollen - losse accounts (ooit aan een school gekoppeld) zijn hiermee terug te zetten en bewust los te maken; werkt ook in de beheerder-subrijen op het Scholen-tabblad
- Gebruikers-tab (alle scholen): losse accounts staan in een eigen inklapbare groep "Zonder school (n)" i.p.v. onopvallend tussen de rolgroepen; de oudergroep is nu ook inklapbaar
- 3 nieuwe servertests (koppelen incl. opruiming, losmaken, 404, geen rechten/no-op voor admin en parent, 409 bij naamconflict) + 10 Playwright-checks voor de volledige flow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-17 14:30:41 +02:00
Ramon
08618ba0b3 feat: Scholen, Klassen en Toewijzingen als groepskaarten (v0.3.89-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 31s
- Scholen: één gezamenlijk kader per school - getinte kop met grijs schoolicoon, naam en de beheerder-teller als uitklaptoggle; beheerders standaard verborgen en als compacte subrijen (36px) met status-glyph (● actief / ○ nog niet geactiveerd, uitleg via title); hernoemen/verwijderen klapt uit binnen hetzelfde kader - geen verwarring meer tussen scholen en beheerders
- Klassen: kaart per klas met leerlingtal in de kop; de reset-/verwijderacties verborgen tot Bewerken
- Toewijzingen: kaart per klas met de huidige stand compact in de kop; bord-/widget-/moduskeuze klapt uit via Bewerken
- Het status-glyph en de kaartkop-wrap zijn onderdeel van het gedeelde groepskaart-component; bewust klein grijs schoolicoon terug (functioneel onderscheid, emoji-scan-uitzondering)
- Geverifieerd in headless Chromium (14 checks) + screenshots 390px/desktop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-17 14:27:19 +02:00