# Changelog
## [Niet vrijgegeven]
### 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
`