Allow recurring costs and confirmations to remain unassigned when created from a purchase without a post. Add browser backup retention and restore controls, with coverage and documentation updates.
Main moved on by 27 commits since this branch forked, including three schema
steps of its own (budget folders, placement, nested bills). Resolutions worth
knowing:
- The anchored-model migration becomes 10 -> 11; main's 7 -> 10 run first.
- Main's nextUnconfirmedOccurrenceOnOrAfter walked the calendar grid for
every cost, which would have shown the slot-model "next due" for an
anchored bill in the budget tree. Both walks are now generators and the
lookup dispatches on anchoring, like occurrencesOf.
- A folder's "Bills due" summed only occurrences, and an anchored bill's
charges are never occurrences, so a charged phone plan read as 0 due.
budgetTree now adds an anchored bill's charges in the month.
- Main's RecurringSection (with RecurringSplitEditor) supersedes this
branch's split editor; the anchoring rules are ported onto it: start-date
validation via startDateNeedsDay, month precision hidden for lastCharge,
the anchoring control locked once a cost has charges.
- PurchaseDialog keeps main's placement control. The "Recurring cost" select
replaces main's "Unlink from bill" checkbox, since "None" does the same.
A bill created from the dialog nests under its post like one from Settings.
- Dropped main's "bounded projection ignores invalid confirmations" test: it
exercised slot confirmations on a lastCharge cost, which no longer exist.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AGENTS.md §1's old invariant was half wrong the moment anchored costs lost
their slots: occurrenceDate is identity for calendar only. The replacement says
what each schedule is and, more usefully, warns against collapsing them —
that collapse is exactly the bug where paying twice in September pushed the
next charge to November.
§2's termination bullet now describes a defensive assert rather than a live
guard, because the anchored walk steps forward from a real date and the grid
walk never rebases. Also added the anchoring-change-refused-once-charged
guard, which was undocumented in AGENTS.md despite having its own test suite
and UI-level guard.
DECISIONS.md, PRODUCT.md, ARCHITECTURE.md and TODO.md catch up to what
shipped: two projection models and the three guards it deleted, a recurring
cost charging more than once per cycle and being creatable from the purchase
dialog, the anchored walk beside the grid walk, and schema 8 at 730 tests.
The 2026-09-03 spec gets a header note pointing at the new spec and nothing
else — it stays an accurate record of what was decided then.
Demo data seeds two charges in one month on an anchored cost (Car insurance),
so the fixed behaviour is the first thing a screenshot shows.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
PRODUCT.md described recurring costs as repeating "monthly, every N days,
or every N weeks", understating everyNMonths, which supports an interval
like the other two kinds.
demo-data.ts gave "Rent" both an ordinary monthly purchase and a
never-confirmed recurring cost from 2026-06 onward, so the demo's Rent
Projected read as double-charged for June through September. The ordinary
purchase now stops once the recurring cost exists.
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.
ARCHITECTURE, PRODUCT and TODO move out of the repository root, and
docs/superpowers/{specs,plans} flattens to docs/{specs,plans}:
"superpowers" names the tool that wrote those documents, not a kind of
document.
No content changes. This commit is the move, plus the roughly thirty
cross-references the move invalidated.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>