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:
1 parent
824e94a9bd
commit
0d2aa519b7
6 files changed
+149
-7
No files matched your search
+32
-1
@@ -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
|
||||
|
||||
Reference in new issue
Block a user