Merge main into the anchored model
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 <noreply@anthropic.com>
This commit is contained in:
commit
933027cb1b
89 files changed
+4023
-651
No files matched your search
+42
-2
@@ -58,8 +58,9 @@ remaining = carriedIn + allocation − charges (carried forward, monthly)
|
||||
|
||||
Unspent carries; overspend carries as debt. **Nothing else may recompute this.**
|
||||
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.
|
||||
aggregate envelope figures over `foldBalances` / `figuresFor`. Overall Spent
|
||||
also includes unassigned purchase slices, which have no envelope to fold. 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:
|
||||
|
||||
@@ -117,6 +118,21 @@ uses on a first run, so "reset" and "a brand-new browser" cannot drift apart.
|
||||
Both are preceded in the UI by a JSON backup download that must succeed before
|
||||
the destructive write is attempted.
|
||||
|
||||
## Session undo and redo
|
||||
|
||||
The snapshot store keeps up to 50 prior saved datasets in memory. A changed
|
||||
`mutate` adds one step after its write succeeds; an unchanged mutation adds
|
||||
none. Undo and redo run through the same queue and persist their target dataset
|
||||
before changing the visible snapshot or either history stack. A failed write
|
||||
therefore leaves both the data and the available undo/redo actions intact.
|
||||
|
||||
Import and reset clear the stacks only after their write succeeds. They are
|
||||
whole-dataset replacement flows with backup-first protection, not ordinary
|
||||
undoable edits. History is not in `Dataset` or IndexedDB and vanishes on reload.
|
||||
The header reads a stable history capability snapshot from the store to enable
|
||||
its controls; keyboard shortcuts skip editable fields so browser text undo
|
||||
still works.
|
||||
|
||||
## Money
|
||||
|
||||
A float, deliberately, with two rules that make it safe:
|
||||
@@ -157,6 +173,20 @@ those parts*. Both split modes collapse to one code path per slice.
|
||||
Applying a split's raw value per-slice looks simpler and passes every percentage
|
||||
test — and charges a fixed "2000 to Video Games" in full on every slice.
|
||||
|
||||
A purchase with no post has an empty `splits` array. Its dated amount counts in
|
||||
monthly, yearly and summary Spent totals, but charges no envelope and changes no
|
||||
rollover balance. Recurring bills still require a destination post. JSON import
|
||||
and ODS export preserve these unassigned purchases. In the month Budget tree,
|
||||
they appear in the permanent, derived Unassigned section for each month their
|
||||
purchase or finance plan touches. Creating a post from one assigns its full
|
||||
amount to the new post without changing the purchase total or schedule.
|
||||
|
||||
A post with no applicable allocation rule has no overspend status, even if its
|
||||
balance is negative. The balance remains visible and carries normally. An
|
||||
explicit zero rule, including a zero monthly override, does have an overspend
|
||||
status when its balance is negative. The views use the presence of a rule rather
|
||||
than infer it from the numeric allocation, since both cases fold to zero.
|
||||
|
||||
## Things the app deliberately does NOT validate
|
||||
|
||||
Adding a "missing" guard here is a regression, not a fix:
|
||||
@@ -220,6 +250,16 @@ is first and stored, every XML part parses, and numeric cells carry
|
||||
behavioural one, because the bug it prevents depends on real browser render
|
||||
timing that happy-dom does not reproduce.
|
||||
|
||||
## Budget placement
|
||||
|
||||
Folders organize posts and recurring bills in one sibling order. The pure
|
||||
`budgetTree` selector combines month post rows with computed scheduled slots;
|
||||
folder Bills due sums bill prices in the selected slot month, including
|
||||
descendants, using confirmed purchase totals where available. Folder placement
|
||||
is independent of bill split destinations. Only posts have allocations and
|
||||
rollover balances. The month UI renders the same tree as a desktop table and a
|
||||
mobile list, and keeps occurrence confirmation in the Expected band.
|
||||
|
||||
## Where the reasoning lives
|
||||
|
||||
- `AGENTS.md` — the rules, in enforceable one-line form, and which test fails
|
||||
|
||||
Reference in new issue
Block a user