docs(recurring): record the invariants and what they cost

Two rules that only review catches, so they go in AGENTS.md §1: occurrences are
computed and never stored, and a confirmation's date and its slot are different
facts. Collapsing the second pair into one field would silently delete the
phone-bill behaviour, which is why it is written down rather than left to the
types.

§2 gets the termination guard. n >= 1 is rejected at both write boundaries
rather than clamped, and the walk throws rather than breaking, because a bill
silently dropped from a budget is worse than a visible error.

Demo data seeds a monthly rent and a 28-day subscription — the second is what
puts two occurrences in one month, which is the case worth looking at. It also
seeds a biweekly cost and a lastCharge-anchored one, so every recurrence kind
and both anchorings show up in Settings and the month view.

DECISIONS.md, PRODUCT.md, ARCHITECTURE.md and TODO.md are updated to match:
schema is at 7, the month view carries a second Projected balance beside
Remaining, and TODO.md records what a week of real use — not a test — will
have to answer.
This commit is contained in:
Lucas Winther committed 2026-09-03 19:03:35 +02:00
1 parent 824e94a9bd
commit 0d2aa519b7
6 files changed
+149 -7

No files matched your search

+32 -1
View File
@@ -18,7 +18,7 @@ record.
no household mode. → [`PRODUCT.md`](PRODUCT.md)
- **Schema migrations are mandatory, not optional.** `schemaVersion` plus
ordered migration functions applied on load, because IndexedDB holds the
user's only copy of their financial data. Now at version 6.
user's only copy of their financial data. Now at version 7.
→ [`specs/2026-09-01-budget-app-design.md`](specs/2026-09-01-budget-app-design.md)
- **A destructive write downloads a backup first.** Import and reset both export
before replacing, and abort if the export fails. → commit `ac3d18a`
@@ -75,6 +75,37 @@ record.
- **Posts are archived, never deleted**, because purchases reference them.
Archived posts still fold. → [`ARCHITECTURE.md`](ARCHITECTURE.md)
## Recurring costs
- **Two-stage confirmation, over projection-only or generation-only.** A
recurring cost projects expected occurrences that stay expected until the
owner confirms one into an ordinary `Purchase`. Projection-only would charge
automatically with no reconciliation and would also force a dated amount
series, since nothing else would hold what was actually paid; generation-only
needs a horizon and a notion of "today" the domain is built to avoid.
→ [`specs/2026-09-03-recurring-costs-design.md`](specs/2026-09-03-recurring-costs-design.md)
- **A new `RecurringCost` entity, over extending `Purchase.schedule` with a
generative variant.** Under two-stage confirmation a template's only job is
to spawn purchases, so every consumer of `Purchase` would have to learn to
skip templates — a different case from splits and finance plans composing on
one entity.
→ [`specs/2026-09-03-recurring-costs-design.md`](specs/2026-09-03-recurring-costs-design.md)
- **`lastCharge` anchoring, over a `dataCap` concept for the phone-bill case.**
Rebasing the series on the confirmation date reproduces the behaviour exactly,
with no usage tracking and no third recurrence kind.
→ [`specs/2026-09-03-recurring-costs-design.md`](specs/2026-09-03-recurring-costs-design.md)
- **Day-less `everyNMonths`, over `monthlyOnDay(day)` with short-month
clamping.** The owner does not think of rent as landing on a date, `IsoDate`
already supports month-only values, and it keeps day arithmetic out of the
most common case entirely.
→ [`specs/2026-09-03-recurring-costs-design.md`](specs/2026-09-03-recurring-costs-design.md)
- **A mutable `amount`, over a dated series like `Post.rules`.** Safe only
because two-stage confirmation already holds every past amount in a confirmed
`Purchase`; the rule describes what is expected next, the ledger holds what
happened. Under projection-only this would not have held, and a dated series
would have been required.
→ [`specs/2026-09-03-recurring-costs-design.md`](specs/2026-09-03-recurring-costs-design.md)
## Design system
- **Two token layers.** `--budget-*` is the app's own vocabulary; shadcn's