Files
budget2.0/src
Lucas WintherandClaude Opus 5 f4223573e4 feat(currencies): ship sterling, and migrate it into existing datasets
GBP joins the currencies the app comes with: £, two decimals, and a
committed fallback rate of 8.704735 DKK to the pound. build.ts now
fetches it too, so a build with a network bakes a live GBP rate and one
without still covers it — the coverage check derives from the seed table,
so it demanded GBP as soon as the table listed it.

The seed table alone would not have delivered this. Baked currencies and
rates seed a NEW dataset and nothing else, so an existing dataset would
have been left typing the code, name, symbol and decimals by hand. A
4 -> 5 migration adds sterling to a dataset that lacks it, with its rate.

Supplying a rate is the one thing the "rates never backstop a cleared
rate" rule permits here: it forbids re-supplying a number the owner
deleted, not introducing one for a currency they have never had. A GBP
the owner already defined, or a GBP rate they already hold, is left
exactly as it is — asserted, including the case where their definition is
"wrong" (zero decimals), because it is theirs.

This exposed a real latent bug. The 2 -> 3 step seeded the live
seed-currency table BY REFERENCE, so adding sterling there would have
retroactively changed what an old dataset received from a step it had
already been through — silently, and only for whoever had not migrated
yet. Every step now carries its own frozen copy, and a source-level guard
fails the build if a live default is imported again. Scoping that guard
took two goes: matching the whole file flagged the comment explaining the
rule, and then flagged the frozen local copy that is the fix.

Two test fixtures used GBP as their example of an UNSUPPORTED currency
and now use CHF, which no seed table lists.

Verified in a browser by writing a schemaVersion 4 dataset into IndexedDB
and reloading: the currency table gains "British pound / £ / 2" with
"manual · 2026-09-02", the post currency selector offers GBP, and the
income and post survive untouched. Note for the next person — a migration
applies in memory on load and is written back on the next mutation, so
reading IndexedDB straight after a reload still shows the OLD version.
That is recorded in docs/TODO.md, because it made this verification look
like a failure at first.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-02 22:27:28 +02:00
..