# Changelog ## [Niet vrijgegeven] ### Opgelost - **De kaart "Rendement Eigen Vermogen" op Voortgang stond leeg.** Bedrag en percentage waren sinds de eerste commit hardcoded als `—` in de JSX; er zat geen berekening achter en `evRows` werd niet eens uit de context gehaald. Het oogde als een datafout — lege database, mislukte API-call — maar de kaart was nooit afgebouwd. Hij rekent nu met dezelfde formule als het blok "Rendement" op Eigen vermogen: huidige waarde min geïnvesteerde fiat, gedeeld door die inleg. Groen bij winst, rood bij verlies, en `—` zolang er niets is ingelegd, zodat er niet door nul wordt gedeeld - Beide kaarten tonen daarmee hetzelfde totaal. Dat is bewust: twee plekken die "rendement" noemen horen niet twee verschillende getallen te geven ### Gewijzigd - De achtergrond achter een popup is lichter: `rgba(0,0,0,0.75)` naar `0.45` in de gedeelde `Overlay`. Op 0.75 was van het scherm eronder vrijwel niets meer te zien, terwijl je bij het invullen van een modal juist wilt kunnen nakijken waar je vandaan kwam. Gedimd genoeg om de modal de aandacht te laten houden - De popups van NavBar, Instellingen en Gebruikersbeheer gebruiken de gedeelde `Overlay` niet maar hadden hun eigen backdrop op `rgba(0,0,0,0.55)` met `backdropFilter: blur(4px)`. Alle drie staan nu op dezelfde 0.45, en de blur is eruit: die maakte de achtergrond onscherp in plaats van zichtbaar, en zonder dat weg te halen bleven ze alsnog afwijken van de rest. Vier popup-backdrops, één waarde ## [1.0.12] - 2026-09-18 ### Beveiliging - **De postgres-container draait niet meer als root.** Beide compose-bestanden starten hem met `user: "postgres"`. Alle 46 bevindingen op `postgres:18-alpine` zitten in één bestand — `/usr/local/bin/gosu`, dat upstream met Go 1.24.6 compileert — en het entrypoint roept gosu alleen aan als het als root start. Non-root starten slaat die tak over, dus de binary draait nooit meer. De Alpine-pakketten in dat image zijn zelf schoon - Getest op een bestaande database die ooit als root was aangemaakt: die start als non-root en de data blijft intact. Ook een verse database initialiseert gewoon - Bewust géén eigen postgres-image gebouwd om die binary te verwijderen. Dat zou de scan op nul zetten maar je verplichten het image zelf te herbouwen bij elke postgres-patch — precies de achterstand die het image vier maanden op 18.4 met 143 bevindingen hield. De afweging staat in `BEHEER.md`, zodat de volgende scan niet opnieuw tot dezelfde vraag leidt - `test.ps1` waarschuwt bij stap 4 wanneer `postgres:18-alpine` in de registry nieuwer is dan wat er lokaal staat. Die achterstand is het werkelijke risico; het getal in de scan is dat niet ## [1.0.11] - 2026-09-18 ### Beveiliging - **PostgreSQL 18.4 → 18.6.** Het `postgres:18-alpine` image dat in gebruik was dateerde van vier maanden geleden. Dat is een patch binnen dezelfde major, dus de data-directory blijft geldig en er komt geen dump/restore aan te pas — het volume blijft gewoon staan. De kwetsbaarhedenscan op het image gaat van 143 naar 45 bevindingen, het grootste deel in de Alpine-pakketten eronder. Wat overblijft (1 kritiek, 21 hoog) zit allemaal in Go's `stdlib` v1.24.6 in een meegecompileerde binary; dat kan alleen upstream oplossen - `BEHEER.md` beschreef alleen de major-upgrade, met een `docker compose down -v` erin. Wie dat bij een patch-upgrade zou volgen, wist zijn database zonder dat het nodig is. De twee gevallen staan nu apart beschreven - **De imagescan staat op nul.** De scan van 1.0.10 gaf 39 bevindingen (1 kritiek, 12 hoog, 15 middel, 11 laag). Geen enkele kwam uit deze applicatie: twintig zaten in `libssl3` en `libcrypto3` (Alpine in het node-image liep achter op 3.5.8-r0) en negentien in de dependencies die npm zelf meelevert — `tar`, `pacote`, `sigstore`, `brace-expansion`, `picomatch`, `ip-address`. De eigen `ip-address` van de app stond al op 10.7.0 en was dus nooit het probleem - Het productie-image doet nu een `apk upgrade` aan het eind van de build. Het node-image wordt minder vaak herbouwd dan Alpine patches uitbrengt, dus OpenSSL loopt er structureel op achter - npm, npx, corepack en yarn gaan uit het productie-image. De container start met `node src/server.js` en gebruikt nooit een pakketbeheerder; ze zaten er alleen in omdat het base-image ze meelevert. Weg met de tool is weg met die hele categorie, ook bij de volgende ronde — npm's dependencies zijn een terugkerende bron van meldingen - Nieuwe teststap **[4/5] Imagescan** met Trivy, die het gebouwde image scant en faalt op CRITICAL of HIGH waarvoor een fix bestaat (`--ignore-unfixed`, want een CVE zonder patch hoort je release niet te blokkeren). `-Strict` trekt de lat naar MEDIUM en LOW, `-SkipScan` slaat hem over. Stap 1 kijkt naar de `package.json`'s en had geen van deze 39 gezien; deze kijkt naar wat er werkelijk in het image zit. `release.ps1` draait dezelfde scan vóór het pushen, dus een image met openstaande CVE's komt de registry niet meer in ### Toegevoegd - **Export naar HTML.** Onder Instellingen staat naast backup en restore nu "Exporteren naar HTML": één zelfdragend bestand met al je gegevens, bedoeld als laatste vangnet. Draait de app niet meer, is de database weg en ligt er geen backup, dan is dit bestand nog steeds te openen — in elke browser, op een computer zonder internet. Er zit niets van buiten in: opmaak staat inline, geen lettertypen, geen afbeeldingen, geen scripts - De pagina toont een samenvatting met de KPI's, en daaronder eigen vermogen en schulden per categorie met subtotalen en een eindtotaal, de voortgangstabel met verschil per moment, en de historie van EV en schuld. Bedragen staan met twee decimalen — anders dan het scherm, want dit is een archiefstuk waar de exacte waarde in hoort — rechts uitgelijnd met cijfers van gelijke breedte - **Het bestand is tegelijk een backup.** Onderin staat de complete ruwe data in een `` in de gegevens, sloopt de pagina niet en breekt de ingebakken backup niet open - Nieuwe `utils/htmlExport.js`, met `leesHtmlBackup` voor de weg terug. Die gebruikt `DOMParser` en geen reguliere expressie: de gegevens kunnen van alles bevatten, en een parser die HTML echt begrijpt trapt daar niet in. Geen nieuwe dependency - De voortgangstabel is Datum plus één kolom per categorie plus vier vaste kolommen — bij zes categorieën twaalf stuks, en die passen op geen enkel scherm. De tabellen scrollen daarom horizontaal in plaats van kolommen buiten beeld te laten vallen. De datumkolom blijft daarbij staan, want zonder die kolom weet je niet meer naar welk moment je kijkt, en een schaduwrandje verschijnt aan de kant waar nog inhoud zit. Dat laatste is een CSS-truc met meeschuivende achtergronden: de pagina mag geen scripts bevatten, maar zonder hint ziet niemand dat er rechts meer staat. Op print vervalt het allemaal en wordt de tabel kleiner gezet, zodat alle kolommen op papier alsnog meekomen - De **initiële schuld** stond wel in de gegevens maar niet op de pagina. Hij hoort niet bij een schuldblok, dus zonder eigen regel viel hij buiten de export. Staat er nu onder de schuldentabel, met "afgelost sinds start" erbij - Stortingen op een **verwijderde categorie** blijven in oude voortgangsrijen staan, maar de app toont alleen kolommen van bestaande categorieën — die bedragen waren dus nergens meer te zien. De export geeft ze alsnog een kolom, gemerkt als `Verwijderd (c9)`. Voor een bestand dat alles moet bewaren wat je ooit hebt ingevoerd is stil weglaten het verkeerde gedrag ## [1.0.10] - 2026-09-17 ### Opgelost - Het laatste label op de x-as viel over zijn buurman heen (`mei '26` en `26` over elkaar). De stap werd vanaf het begin van de reeks geteld en het laatste punt werd daar los bovenop geforceerd, zodat het op willekeurige afstand van zijn voorganger landde — bij een reeks die niet toevallig op een veelvoud uitkwam, pal ernaast. De stap telt nu terug vanaf het laatste punt: dat valt daarmee per definitie op een stap en houdt volle tussenruimte, zonder dat er nog een uitzondering voor nodig is - De breedte waarop de grafiek rekent ging van 46 naar 54px per label, zodat er lucht tussen staat in plaats van kop-aan-kop. Over reekslengtes van 13 tot 120 punten blijft de tussenruimte nu minimaal 57px ## [1.0.9] - 2026-09-17 ### Gewijzigd - De x-as van beide grafieken toont korte maandlabels (`sep '26`) in plaats van een volledige datum bij elk punt, en ze staan rechtop in plaats van 45 graden gedraaid. Tien tekens gedraaid kosten zo'n 45px en er passen er maar een stuk of veertien op de tekenbreedte; bij een langere reeks liepen ze door elkaar heen tot één grijze band. De grafiek rekent nu uit hoeveel labels er passen en toont elke n-de, met het laatste punt er altijd bij — groeit je reeks, dan dunt hij vanzelf verder uit - Doordat de labels niet meer gedraaid zijn is de ruimte eronder kleiner: `pb` van 52 naar 30 op het dashboard en van 62 naar 34 op Voortgang. Die hoogte gaat naar de lijn zelf - Nieuwe formatter `fmtMaand` in `utils/format.js`, naast `fmtDatum` - De lijnen in de Voortgang-grafieken zijn vloeiend, net als op het dashboard. Ze werden met `` als rechte segmenten getekend; nu met dezelfde Catmull-Rom afronding. De gradient eronder is daarmee ook een `` in plaats van een `` - `crPath` stond als lokale functie in `DashboardTab.jsx` en is verhuisd naar `utils/grafiek.js`, samen met `vlakPad` voor de vulling onder een lijn. Twee grafieken die dezelfde curve horen te tekenen moeten niet elk hun eigen kopie hebben. In dat bestand staat ook waaróm de inleglijn bewust géén afronding krijgt: een spline schiet tussen twee punten iets buiten hun bereik, en bij een reeks die nooit terugloopt tekent dat een daling die er niet is ### Opgelost - De tooltip van de Voortgang-grafiek toonde alleen een bedrag. Nu de x-as geen dagen meer noemt was dat de enige plek waar de exacte datum nog te lezen was, en die stond er niet in. De tooltip heeft er een datumregel bij ## [1.0.8] - 2026-09-15 ### Toegevoegd - **Kleurthema's.** Negen paletten in `frontend/src/constants/themas.js`, te kiezen onder Instellingen naast de dark-mode schakelaar. Thema en licht/donker staan los van elkaar: het thema levert het accent, de schakelaar de ondergrond. Dat scheelt een compleet tweede palet per thema en laat de bestaande schakelaar doen wat hij deed - `useTheme(themaId, darkMode)` leidt randen, invoervelden en schaduwen af uit het accent van het thema, dus die kleuren vanzelf mee. Nieuwe sleutels op `T`: `accent`, `accentLicht`, `accentTekst`, `accentGradient`, `chartVan`, `chartTot`, `darkMode`, `themaId` en `themaNaam`. De bestaande sleutels houden hun naam en betekenis, zodat er niets hoeft te wijzigen dat ze al gebruikt - Het accent dat tekst draagt is per ondergrond een andere stap van dezelfde kleur — de lichte op donker, de donkere op licht. Eén stap voor beide modi haalt aan één kant altijd te weinig contrast ### Opgelost - De weergavevoorkeuren overleven een herlaad. `darkMode` stond op `useState(true)` en werd nergens bewaard: zette je de app op licht en laadde je opnieuw, dan stond je weer in het donker. Thema en modus gaan nu naar localStorage, net als de kolombreedtes. Ze staan los van je financiële data en gaan dus niet mee in de backup-ZIP - `VoortgangTab.jsx` bepaalde de modus met `T.bg === "#0f1117"`. Die kleur is de achtergrond allang niet meer (dat is `#241A39`), dus de grafiek gebruikte altijd de lichte rasterlijnen — ook in het donker. Leest nu `T.darkMode`, wat met instelbare thema's sowieso de enige werkbare manier is - De kolomkoppen van de categorieën hebben een `title`, net als de Opmerkingen-cellen al hadden: bij de standaardbreedte kapt "Crowdfunding" af - De dark-mode schakelaar in `InstellingenModal.jsx` was een `
` met een klik-handler en dus niet met het toetsenbord te bedienen. Nu een `