b9be882c05b54205966f8c92321fd1531f92c5b9
Each of these was recorded with its reasoning already settled; each is fixed test-first, and two turned out to be worse than the note claimed. EMPTY_FIGURES is frozen. figuresFor returns it by shared reference for every (post, month) the fold has no entry for, so one consumer mutating its result would have moved every other unknown row with it. Split values now round by what they MEAN. A split's value is a percentage in "percent" mode but MONEY in the purchase's currency in "fixed" mode; both were rounded to a hardcoded 2 places. sliceTotal likewise rounded to DKK's minor unit whatever currency the slices carried. Note this is narrower than TODO claimed: round2 is still correct for Rule.percent, which is not money at all. Neither bug is observable while every supported currency uses hundredths, so the tests force CURRENCY_DIGITS for their duration. confirmImport guards its write. A failed import was an unhandled rejection with nothing shown; the dialog now reports it and keeps `pending` so the user can retry. A source-level guard covers the whole UI, in the spirit of eventCapture.test.ts. The manifest has one definition. The dev placeholder did not merely hardcode scope "/" — it was never served at all: the "/*" catch-all answered /manifest.webmanifest with the HTML shell, as text/html, and /icon.svg too. buildManifest moves to src/manifest.ts, the dev server generates both from the base path, and the source file stays as a valueless resolution stub because Bun.serve has no `external` option to skip the <link> href. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Budget 2.0
A personal budgeting PWA. Envelope-style monthly budgets with rollover, split purchases, finance plans, multi-currency, and JSON/ODS export. All data lives in your browser — there is no server and no account.
Development
bun install
bun --hot src/index.ts # dev server with hot reload
bun test # run the test suite
bun run build # production build into dist/
Self-hosting with Docker
docker build -t budget2 .
docker run --rm -p 3000:3000 budget2
To serve from a subpath, build and run with BUN_PUBLIC_BASE_PATH:
docker build -t budget2 --build-arg BUN_PUBLIC_BASE_PATH=/budget/ .
docker run --rm -p 3000:3000 -e BUN_PUBLIC_BASE_PATH=/budget/ budget2
GitHub Pages
Pushing to main builds and deploys to Pages automatically, using
/<repository-name>/ as the base path. Enable Pages with "GitHub Actions" as
the source in the repository settings.
Your data
Everything is stored in this browser's IndexedDB. Export regularly from Settings → Your data: the JSON export is both your backup and how you move your budget to another device. Import replaces everything and downloads a backup of your current data first.
Known limitations
- Data is per-browser. There is no account and no sync. If you clear this browser's site data, or move to a different browser or device, your budget is gone unless you exported it first. The JSON export is not just a nice-to-have backup — it is the only way to move a budget between devices.
- The install prompt is incomplete.
manifest.webmanifestcurrently only ships an SVG icon. Chrome requires PNG icons at 192×192 and 512×512 to offer the "Install app" prompt; until those are added, the app works fine as a regular tab but won't be installable on Chrome/Chromium.
Documentation
- Design:
docs/superpowers/specs/2026-09-01-budget-app-design.md - Implementation plan:
docs/superpowers/plans/2026-09-01-budget-app.md
Languages
TypeScript
98.3%
CSS
1.4%
HTML
0.2%