Lucas Winther c2b3d00381 docs(specs): the anchored model for recurring costs
The first version modelled the phone bill as "confirm a projected slot early",
and that was wrong at the root. A charge is not a slot claimed ahead of
schedule — it is an event that can happen at any moment, any number of times
per cycle, each one restarting the clock. There is no grid to claim; there is a
moving anchor.

Confirming twice in one September pushed the next due date to November, because
each confirmation consumed a future slot instead of resetting the anchor.
Reproduced under BOTH anchorings, which is what proves the defect is in the
slot model rather than in the rebase rule.

So the two anchorings get two projections, and the asymmetry is the design.
Paying September AND October's rent really does mean nothing is due until
November — for a calendar bill, consuming two slots is correct. Only the
anchored case is wrong, so only the anchored case changes.

The anchored walk cannot fail to advance: it steps forward from a real date
with n >= 1. That makes the entire class of "confirmation dated behind its
slot" defects unreachable — the class that produced three Criticals in round 1
— so wouldAdvancePast, the band's offerability guard and PATH A's
updatePurchase guard are all deleted. This change removes more code than it
adds.

everyNMonths becomes day-granular under lastCharge, since a plan refreshing on
20 September is next due 20 October, not "October". That finally forces a
month-clamp rule (31 Jan + 1 month is 28 Feb), confined to the anchored path.

Also closes the two additive requests: a cost's splits become editable through
the same SplitEditor purchases use, and the purchase dialog can tag a purchase
as a charge for a cost or create one from the purchase being typed. Tagging is
the refresh mechanism, so the data cap stops needing a flow of its own.

Self-review caught the band's Confirm being removed for anchored costs, which
would have made paying a bill on time slower to serve the exceptional case.
2026-09-05 00:38:10 +02:00

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 run dev               # dev server with hot reload, on :3000
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.

Both destructive actions there — Import, and "Reset everything" — download a backup of your current data before replacing it, and abort if that download fails. Reset puts the app back to a brand-new install: the three starter posts, the DKK/USD/EUR currency table, this month at zero income, no purchases.

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.
  • Installing and offline use are unverified. The manifest ships the SVG plus 192×192 and 512×512 PNG icons, and the service worker demonstrably installs, activates and precaches every asset — but nobody has yet confirmed that Chrome offers its "Install app" prompt, or loaded the app with the network actually cut. It works as a regular tab regardless.

Documentation

  • docs/ARCHITECTURE.md — layers, data flow, and why the non-obvious choices are what they are.
  • docs/PRODUCT.md — who this is for and what must never break.
  • AGENTS.md — the rules for changing this codebase, and the test that catches each one when broken. CLAUDE.md and GEMINI.md point at it.
  • docs/DECISIONS.md — an index of the choices that shaped the app, and what each was chosen over.
  • docs/TODO.md — what is still queued.
  • docs/specs/ — one design document per piece of work, each recording the alternatives that were rejected.
  • docs/plans/ — task-by-task implementation plans.
S
Description
No description provided
Readme
1.7 MiB
Languages
TypeScript 98.3%
CSS 1.4%
HTML 0.2%