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
@@ -27,6 +27,11 @@ server.ts the Docker image's static file server.
|
||||
The dependency rule is one-directional: `ui -> store -> domain`. `domain` imports
|
||||
nothing from the others; `store` never imports from `ui`.
|
||||
|
||||
Two `src/domain/` modules worth naming individually: `days.ts` is day-ordinal
|
||||
arithmetic with no `Date` at all, and `occurrences.ts` walks a recurring cost's
|
||||
schedule forward from `startDate`, computing its occurrences fresh on every
|
||||
fold rather than storing them.
|
||||
|
||||
`src/domain/smoke.test.ts` asserts the domain is importable with no DOM. That is
|
||||
why happy-dom is registered **per test file** rather than as a global bunfig
|
||||
preload — a global DOM would make that assertion vacuously true and silently
|
||||
@@ -45,6 +50,21 @@ The month view, year matrix, summary, per-post views and the ODS export all
|
||||
aggregate over `foldBalances` / `figuresFor`. A closing balance is read from
|
||||
`byMonth[11].remaining`, never re-summed.
|
||||
|
||||
A recurring cost adds a SECOND balance beside it, on its own carry:
|
||||
|
||||
```
|
||||
projected = projectedCarriedIn + allocation − charges − expected
|
||||
```
|
||||
|
||||
`remaining` never changes shape or value because of a recurring cost — it is
|
||||
still the figure the owner reconciles against their bank statement. `projected`
|
||||
runs beside it, additionally subtracting this month's unconfirmed occurrences,
|
||||
so the month view shows both a `Remaining` and a `Projected` column per post
|
||||
rather than only a separate list of upcoming bills. With no recurring costs the
|
||||
two tracks are identical in every month; once every occurrence in a month is
|
||||
confirmed they reconverge, because a confirmation is an ordinary purchase
|
||||
counted in `charges` and skipped by `expected`.
|
||||
|
||||
If you find yourself writing `carriedIn + allocation - charges` outside
|
||||
`fold.ts`, that is the invariant breaking. It matters because two screens
|
||||
silently disagreeing about someone's money is the worst failure this app has.
|
||||
|
||||
Reference in new issue
Block a user