- 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)
- 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
- 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).
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.
- 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.
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
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.
- 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
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.
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)
- 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
- 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
- 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)
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- Nieuw herbruikbaar groepskaart-component (amGroupCard): één kader om items die bij elkaar horen - koprij met grijs icoon, titel, samenvatting-als-uitklaptoggle en Bewerken; compacte subrijen; open-status overleeft re-renders (injecteerbare Set zodat ook andere modules hem gebruiken)
- Opslag-tab volledig herbouwd: eerst één school kiezen (of "Systeem (zonder school)" voor systeemmanagers/ouders/losse accounts) i.p.v. alle scholen én alle gebruikers plat met altijd-open editors
- Na keuze één kaart: schoolnaam + quota-samenvatting in de kop (schoolquota bewerken klapt uit), daaronder alleen de gebruikers van díe school als compacte regels; bewerkvelden pas na "Bewerken" per rij; zoeken binnen de school
- "Standaard"-optie benoemt nu de echte rolstandaard ("Standaard (200 MB)") via defaultBytes
- Mobiel: beheer-rijen stapelen niet meer verticaal (kaartkop en subrijen wrappen netjes; subrij ± twee compacte regels i.p.v. >100px)
- Geverifieerd in headless Chromium (13 checks, o.a. dat schoolkeuze en open bewerkrij een her-render overleven - module-state) + screenshots 390px/desktop
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Alle resterende emoji uit het beheer: kopieerknoppen zijn tekstknoppen ("kopieer"), wachtwoord-genereerknop heet "genereer", "alles kopiëren" zonder klembord-icoon, schoolrijen zonder 🏫, contextbalk-label is het woord "School", bevestiging is "zeker?" (de rode armed-stijl is het signaal), kopieer-feedback een net vinkje
- Scholen-tabblad: "Bewerken"-tekstknoppen i.p.v. het ✏-potlood; zoek-placeholders zonder vergrootglas-emoji
- CMS-widgetkeuze zonder emoji-prefix in de opties
- Playwright-emoji-scan over alle vijf beheersecties (knoppen/labels/groepen schoon, ▸▾✕✓+ toegestaan) + volledige master-detail-regressie (19 checks) groen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Gebruikers staan in een echte tabel (initiaal-avatar + naam | functie | klas | status) met kolomkoppen en uitgelijnde kolommen; klikken op een rij selecteert (accent-markering) zonder her-render, dus het zoekveld houdt focus
- Rechts een vast detailpaneel: bewerken (functie, klaskoppeling, klas, wachtwoord, acties), aanmeldverzoeken afhandelen én nieuwe gebruikers aanmaken gebeuren allemaal dáár - één plek, één patroon
- Eenmalige wachtwoorden/codes verschijnen als badge in het paneel; na aanmaken is de nieuwe gebruiker direct geselecteerd; selectie overleeft herladen en wordt gevalideerd tegen de data
- Aanmeldverzoeken zijn gewone tabelrijen met een "nieuw"-chip; de losse toevoeg-formulieren per klas zijn vervangen door één "Nieuwe gebruiker"-knop in de werkbalk (met rolkeuze)
- Smal scherm: het detailpaneel wordt een overlay met sluitknop (onder de tabel stapelen is onbruikbaar bij grote scholen)
- Menu-items herzien: de sectie heet "Scholen" (geen "Systeem > Systeem"-verdubbeling meer) en de Systeem-groep is geordend als Scholen / Inhoud / Opslag
- Eerste emoji-sweep in de omgebouwde delen (actieknoppen zijn tekstknoppen); zoekfilter en inklapbare klasgroepen werken ongewijzigd
- Geverifieerd in headless Chromium (19 checks, incl. focusbehoud, overlay-gedrag en selectie-na-reload)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Volledig gescoped op #settingsModal zodat het bord speels blijft: systeemfont i.p.v. Quicksand, controls 32px (40px op aanraakschermen), vlakke 1px-randen i.p.v. schaduwen en pastel-pills
- Rijen worden tabelregels met scheidingslijnen en hover; groepskoppen zijn subtiele sticky sectierijen; navigatie-actief is neutraal grijs met accent-inzetlijn; iconen gedempt in grijstinten
- Knoppen één systeem: subtiel omrand, danger als rode tekst die pas gevuld rood wordt na wapenen; chips/resultaten/segmentknoppen omrand vlak
- Hoog contrast expliciet teruggebracht binnen de nieuwe scope; sectiekop-icoon in eigen span zodat CSS hem kan dempen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Nieuwe sectie "Inhoud" in Instellingen (groep Systeem, alleen systeemmanager, doorzoekbaar via het sectieregister)
- Welkomstwoord (NL/EN) verschijnt op het inlogscherm, direct aanpasbaar zonder release
- Melding voor alle gebruikers: aan/uit, niveau informatie/belangrijk, per taal; verschijnt als wegklikbare banner onder de menubalk en elke wijziging krijgt een nieuw id zodat een eerder weggeklikte melding opnieuw verschijnt
- Widget-uitleg: per widget een eigen beschrijving die de standaardtekst in de widgetbibliotheek vervangt (leeg = standaard, placeholder toont de standaardtekst)
- Snelkoppelingen naar de bestaande kanalen voor systeembrede inhoud: gedeelde bibliotheek (borden & ankerdiagrammen, map 🌐 Systeem) en de afbeeldingencatalogus
- settings.js kreeg een generiek haakje (showSettingsDom) zodat modules met een eigen paneel zich in het sectieregister kunnen voegen
- Geverifieerd in headless Chromium (15 checks): banner + dismiss + nieuw-id-gedrag, welkomstwoord, bewerken/opslaan, galerij-override, sectie onzichtbaar voor andere rollen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Migratie 014: site_content (sleutel/waarde, JSONB) voor site-inhoud die zonder release aanpasbaar moet zijn
- GET /content (publiek): welkomstwoord, update-melding en widget-uitleg in één antwoord - het inlogscherm toont het welkomstwoord al vóór het inloggen
- PUT /admin/content/:key (alleen systeemmanager, nieuwe permissie content.manage): upsert per toegestane sleutel (welcome/announcement/widgetInfo) met maat-limieten
- 4 servertests: publiek lezen, upsert door super, 403/401 voor anderen, whitelist/vorm/omvang-validatie
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- gzip on + gzip_proxied any (zonder die laatste comprimeert nginx geproxyde responses helemaal niet) + gzip_vary + min_length 1024 voor css/js/json/svg/tekst
- Geen dubbele compressie: responses die al Content-Encoding dragen (brotli/gzip uit de app) laat nginx ongemoeid; dit vangt alleen wat de app mist
- Cache-beleid blijft bewust op één plek (de app), zodat lokaal en productie identiek gedragen
- Configtest erbij in security-config-stijl
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- @fastify/compress (brotli quality 4 + gzip, global): alle statische assets én API-JSON (zoals /me/data) gaan gecomprimeerd over de lijn - eerste lading van ~715 KB naar grofweg een derde
- Nieuw src/frontend.js: index.html wordt bij boot één keer gelezen, ?v=__V__ wordt vervangen door de app-versie en de pagina wordt uit het geheugen geserveerd met no-cache
- Cache-beleid per prefix: js/css een jaar immutable (veilig dankzij de geversioneerde URL's), fonts 7 dagen, afbeeldingen 1 dag met zwakke etag als 304-vangnet - herhaalbezoek kost vrijwel geen bytes meer
- Alle 31 scripts staan nu met defer in de head: parallel downloaden, uitvoeren in dezelfde dwingende volgorde (core → widgets → board → app); font-preload bewust ongeversioneerd (moet byte-gelijk blijven aan de url() in teach.css)
- Compress geregistreerd vóór de api/frontend-registraties (onSend-hook geldt alleen voor latere routes) - geborgd met een teksttest
- 6 nieuwe tests (test/frontend.test.js): versie-injectie, cache-headers per prefix, gzip+brotli round-trip, 304 op etag, defer/?v= op elk script
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- De CI faalde omdat een test het versienummer hardcodeerde (0.3.77-beta) en dus bij elke reguliere versiebump breekt
- De test bewaakt nu het echte beleid: de dev-branch voert altijd een -beta-versie en de VERSION-file loopt gelijk met de const in core.js (die controle stond er al en blijft)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Op een aanraakscherm wiebelt een vinger in de eerste millimeters: de richtingstoets wees de snap daardoor af en de haal bleef de vinger volgen
- Vastgezet gereedschap (🔒) kleeft nu onvoorwaardelijk binnen 28px van de rand - geen richtingstoets; het slot betekent "hier teken ik langs"
- Losse lat: richting wordt over een langere basis gemeten (10px i.p.v. 7) met een ruimere hoek (40° i.p.v. 30°), zodat vinger-jitter de evenwijdige intentie niet meer maskeert
- Inktlaag kreeg touch-action:none zodat de browser vingerhalen nooit als scroll-gebaar afvangt
- Geverifieerd met échte touch-events (CDP) inclusief nagebootste vinger-jitter: 7 nieuwe checks (vastgezet kleeft mét jitter, op de lat tekenen zonder verschuiven, los evenwijdig kleeft, wegdraaiend blijft vrij) + de vier bestaande muis-suites (42 checks) blijven groen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Slot-knop (🔓/🔒) op liniaal én geodriehoek: vastgezet gereedschap kan niet meer verschuiven of draaien (draaiknop verdwijnt), er kan alleen nog langs getekend worden; tweede klik ontgrendelt
- Naast de lat beslist nu de richting i.p.v. één vaste afstand: binnen 22px van een rand wacht de haal de eerste ~7px beweging af - loopt die grofweg evenwijdig aan de rand (≤30°), dan kleeft hij, anders blijft hij vrij; een tik zet gewoon een stip
- Daarmee is langs de lat tekenen betrouwbaar (ook met dikke digibord-vingers) zónder dat de lat vrije halen in de buurt grijpt
- Op de lat zelf blijft de intentie ondubbelzinnig: direct kleven binnen 24px van de rand
- Geverifieerd in headless Chromium: 12 nieuwe checks (richtingsregel, stip-tik, slot aan/uit, vastgezet = onverplaatsbaar maar betekenbaar) + de drie bestaande suites (35 checks) blijven groen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Snapzone naast de liniaal/geodriehoek verkleind van 24 naar 10px: vrije halen in de buurt worden niet meer "gegrepen"; op de lat zelf blijft de ruime 24px-zone gelden
- Diktekiezer uitgebreid met dun (1 en 2, naast 3/5/8)
- ✏️ heet nu Potlood en tekent zo: een zachte, gelijkmatige lijn (~85% dekking) via de overlay-laag, dus égaal ook waar de haal zichzelf kruist
- Vulpen schrijft nu echt kalligrafisch met een 45°-penpunt: dik in de neerhaal, dun in de ophaal (gemeten 11px vs 1,5px bij dikte 5); stylusdruk versterkt het effect
- Geverifieerd in headless Chromium (9 checks, pixelmetingen van dekking, snapzones en lijndiktes)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- In de praktijk zet je de pen óp de liniaal-/geodriehoekrand, zoals bij een echte lat - maar dat versleepte het gereedschap, waardoor "langs de lat tekenen" niets leek te doen
- Landt de pen (pen/vulpen/stift, penmodus actief) binnen 24px van een tekenrand - ook óp de lat zelf - dan start een vastgeklikte haal en blijft het gereedschap liggen; de events lopen via pointer capture gewoon door naar de inktlaag
- Verder van de rand blijft het gereedschap gewoon te verslepen, en de draaiknop houdt voorrang
- Startzone naast de lat verruimd van 16 naar 24px (digibord-vingers)
- Geverifieerd in headless Chromium: 7 nieuwe checks (tekenen op de randzone zonder de lat te verschuiven, verslepen vanuit het midden, draaien in penmodus, idem geodriehoek) + de bestaande 14 snap-checks blijven groen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Langs de lat tekenen: begint een haal binnen 16px van een rand van de liniaal of geodriehoek, dan kleeft de hele haal aan die rand - kaarsrecht, ook gedraaid (randen draaien mee); bewust wegtrekken (>60px) laat weer los; gum en vrije halen blijven onaangetast
- Gereedschappen liggen nu boven de inktlaag, zoals een echte lat op het bord: ook in penmodus te verplaatsen, draaien en instellen
- Geodriehoek herbouwd met twee niveaus: basis (grote hoofdhoeken 30/45/60/…, hele centimeters - voor jonge leerlingen) en volledig (graden per 2° met dubbele schaal links- én rechtsom, millimeters, evenwijdige hulplijnen per cm - voor meetkunde in de bovenbouw en daarna)
- Geodriehoek: cm-schaal vanaf 0 in het midden zoals echt, maat in hele cm op de knop (6/8/12 cm), 90°-markering, begrensd op de bordbreedte; alleen de driehoek zelf vangt aanrakingen zodat er vlak naast de schuine randen getekend kan worden
- Geverifieerd in headless Chromium (14 checks, o.a. pixelmetingen van rechtgetrokken halen op rechte én 30°-gedraaide randen)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Pen, vulpen en stift tekenen nu met kwadratische curves door de middelpunten van de pointer-stappen: geen hoekige knikken meer bij snelle halen (het "blikkerige" beeld)
- Markeerstift tekent tijdens de haal dekkend op een eigen doorschijnende laag en wordt bij het loslaten in één keer met vaste transparantie op de inktlaag gezet: overlappingen binnen één haal geven geen donkere vlekken meer, een tweede haal eroverheen wordt wél donkerder (zoals een echte stift)
- devicePixelRatio wordt bij elke resize vers gelezen (scherp blijven na verplaatsen naar een ander scherm of zoomen)
- Liniaal: logische lengtes in hele centimeters (5/10/15/20 cm, knop toont de actuele maat), schaal telt vanaf 0 met een kantlijn zoals een echte liniaal, lengte begrensd op de bordbreedte, en de lengte staat op de liniaal zelf
- Liniaal en geodriehoek: draaien toont live de hoek in graden en klikt vast op veelvouden van 45° (badge kleurt groen); knoppen hebben leesbare titels (Lengte / Hoog contrast)
- Geverifieerd in headless Chromium (12 checks, o.a. pixelmeting van de markeerstift-kruising en exacte 45°-snap)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
- Oorzaak van het "vanzelf" openende uploadvenster gevonden: elk leeg plaatjesvak in een ankerdiagram had eigen pointer/click-handlers die het pannen blokkeerden en de plaatjeskiezer (met uploadtab) openden bij elke veeg die erop begon - op een telefoon was dat vrijwel elke aanraking, dus ook direct na het verversen
- Lege plaatjesvakken lopen nu via dezelfde tik/sleep-logica als de cellen (6px-drempel): vegen pant het diagram, alleen een stilstaande tik opent de kiezer; woord bewerken blijft ongewijzigd
- Tekenbalk op telefoons weer gecentreerd op inhoudsbreedte i.p.v. vastgepind aan beide randen: geminimaliseerd is hij weer smal (121px i.p.v. volle breedte), nog steeds in de eigen laag boven +/borden
- Hardening: een onbekende achtergrond-waarde in opgeslagen borddata crashte de hydrate en logde de gebruiker stilletjes uit bij verversen - valt nu terug op de standaardachtergrond
- Geverifieerd in iPhone-emulatie (8 checks): geen venster na verversen, veeg pant, tik kiest, tekenbalk smal en vrij van +
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF