feat: refine month totals and recurring purchase entry
Continuous Integration / lint-and-test (push) Successful in 29s
Continuous Integration / docker-build-and-push (push) Successful in 40s

This commit is contained in:
Lucas Winther committed 2026-10-03 16:40:00 +02:00
1 parent 7cccd7a41a
commit c250bbe476
30 files changed
+634 -67

No files matched your search

@@ -0,0 +1,96 @@
# Month and purchase refinements
The owner requested this round on 2026-10-03, with the work queued in TODO
before implementation. The follow-up added a visible Total-field reset bug.
## Actual available
The month header shows entered income minus actual spending beside Spent.
This includes unassigned purchases, refunds, foreign-currency charges and the
selected month's finance-plan slices. Unpaid recurring occurrences do not
reduce it. Allocation and envelope carry do not enter this calculation.
Negative values remain visible, and arithmetic and display use dataset digits.
Purchases before the envelope fold's start still count in Spent and Actual
available. The month view uses the existing charge calculation for those
months; the rollover fold continues to begin at its configured start.
## Display choices
Monthly breakdown has a Show chart checkbox. Its header and checkbox remain
visible when the chart and detail list are hidden. Expected has an Expand /
Collapse button and shows counts for expected, coming-up and overdue entries
when closed. Closing it preserves any typed confirmation amount.
Both choices default to visible/expanded and persist across months and reloads
in browser localStorage. This follows the theme decision: display preferences
belong to this browser, not to the exported budget. Blocked localStorage must
not stop the app or the controls from working. Neither choice changes money,
occurrences or confirmation behavior.
## Purchase entry
New purchases begin with Recurring cost set to None. Their recurring option
creates a new bill from this purchase, rather than selecting an existing bill.
Creating a bill and its first charge remains one atomic mutation. Calendar
slots and last-charge date requirements retain their existing rules.
Settings → Recurring costs includes a checkbox to allow selecting existing
bills in Add purchase. The optional dataset boolean is
`settings.allowExistingRecurringCost`; absent means false, so existing budgets
adopt the new default without a migration. JSON import accepts only booleans
when this field is present. The choice travels with a JSON export.
Editing a linked purchase continues to show its saved association even with
the setting off, and choosing None can unlink it. Ordinary purchases do not
automatically become recurring bills. This resolves the default-selection
ambiguity in favor of an explicit recurring action.
The Total field has its own text state to preserve partly typed money inputs.
Resetting the dialog now resets that text alongside the numeric draft: new
purchases start blank and existing purchases start with their saved amount.
The expanded new-bill form exceeded a phone viewport in a browser check. The
purchase dialog now caps its height to the viewport and scrolls its contents,
keeping both the first fields and Save reachable.
## Default currencies
New datasets include Japanese yen (JPY, ¥), Australian dollars (AUD, AU$),
Canadian dollars (CAD, CA$) and Mexican pesos (MXN, MX$). Existing tables are
not migrated: the request specified defaults, and editing an established
table is the owner's action. Every currency still uses dataset-wide precision.
The build's FX targets derive from the seed table. All four have committed
fallback rates as well as live build-time fetching. The committed rates were
obtained from Frankfurter's DKK-base quote dated 2026-10-02 and inverted to
DKK per unit, preserving six decimal places. Like the previous constants,
these rows are marked manual and seed only new datasets.
Source: https://api.frankfurter.dev/v1/2026-10-02?base=DKK&symbols=JPY,AUD,CAD,MXN
The amount parser strips longer symbols before the plain dollar sign, so
AU$, CA$ and MX$ work without leaving stray letters in the amount. An explicit
currency code still disambiguates a symbol collision.
## Build stamp
Colophon shows the source ref and a separate build-time UTC date. An exact
commit keeps its source link; an uncommitted checkout retains the dirty suffix.
Source archives say source archive rather than inventing a Git ref, while
the service worker still receives a unique cache version. Development says
development and no build date. Browser accessors tolerate an absent process
global, following the existing Bun environment-inlining rule.
## Verification
Regression coverage exercises the actual money calculation, purchase save /
reopen, new recurring creation, enabled existing-bill selection, linked editing,
display persistence, collapse/expand preserving a typed amount, Settings write
errors, JSON preference validation, symbol parsing and new seed conversion.
Chrome checks verify the month controls, saving and reopening a purchase,
persisting the Settings choice to IndexedDB, creating a new bill and its first
charge together, keeping mobile Save reachable, and production build metadata.
Desktop and mobile captures cover light and dark themes. Screenshots establish
layout and console behavior; visual feel still requires the owner.
The complete suite passes 911 tests across 66 files, TypeScript is clean, and
production builds succeed at both root and subpath base URLs with seed rates.