Commit Graph
5 Commits
Author SHA1 Message Date
Lucas WintherandClaude Opus 5 b41b219d94 feat(domain): project recurring occurrences, never store them
A walk rather than a per-month query: under lastCharge anchoring, where a
series sits in a given month depends on every confirmation before it, so the
projection starts at startDate and steps forward. Bounded by upToMonth, which
the caller supplies — the domain still reads no clock.

The data-cap behaviour is one line. `from` is the confirmation's own date
under lastCharge, so confirming a slot early moves every later occurrence with
it. Confirm the 2026-01-31 slot with a purchase dated the 12th and the next
lands on 2026-02-11, not 2026-03-02.

The walk throws if a step fails to advance. n >= 1 is enforced at both write
boundaries so this should be unreachable, but a hang is worse than a throw and
dropping a bill silently is worse than both.

Rewrote the "deleting the confirming purchase" test per controller ruling: the
brief's version was byte-identical to the unconfirmed-series test above it and
asserted nothing new. It now walks the phone cost with the capHit
confirmations, then walks the same cost with an empty map, and asserts the
dates differ and match the unconfirmed series — which is what "deleting the
purchase restores the series" actually means.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 03:47:21 +02:00
Lucas Winther de333685a2 feat(domain): day arithmetic without Date
Recurrences of 28 and 30 days do not align to months, so the domain needs
day-level maths for the first time. It gets it the way months.ts does: an
integer ordinal, no Date anywhere. A Date would make 2026-03-29 a different
instant per timezone while being the same day everywhere, and a 28-day step
must not gain or lose a day at a DST boundary.

Hinnant's days_from_civil, which shifts the year to start in March so the leap
day is the last day rather than an insertion, removing every special case.

No isDayGranular export: nothing in the plan consumes it, later tasks branch
on a recurrence kind and import validation uses a regex directly.
2026-09-03 03:34:32 +02:00
Lucas WintherandClaude Opus 5 ccb97818d7 feat(store): add plan slicing and dataset actions
Every mutation is a pure function over a draft dataset, so the write
surface is unit-tested without React. Money amounts (income, purchase
totals, split values, schedule slices, fixed/percent rule amounts) are
rounded to their currency's minor unit before they land in the draft,
per CLAUDE.md's round-before-persisting rule -- actions.ts is the
single boundary every UI mutation passes through. FxRate.baseUnitsPerOne
is deliberately exempt: it is stored at six decimal places, not money.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 20:25:14 +02:00
Lucas WintherandClaude Opus 5 438d53d016 feat(domain): add seed dataset and domain barrel
Add SCHEMA_VERSION and createSeedDataset to initialize first-run state
with three budget envelopes (Video Games, Food, Events and Social),
newId() for generating unique identifiers, and complete the domain
export barrel with all pure domain modules.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 19:51:57 +02:00
Lucas Winther 26752aa225 chore: scaffold Bun + React + shadcn project
bun init --react=shadcn, with the demo API routes removed since this app
has no backend. Adds project invariants to CLAUDE.md and a smoke test
proving the domain layer imports with no DOM.
2026-09-01 16:18:55 +02:00