Rate fetching is optional and fails soft: any network or parse error
leaves cached rates untouched and shows a message, never throwing into
render. A cleared or non-positive FX rate input now calls the new
removeFxRate action instead of storing 0, since toBase() silently
converts a stored 0 to a 0-value conversion while an absent row
correctly throws MissingRateError. Posts are archived, never deleted.
Import downloads a backup of the current data before replacing it and
requires confirmation showing before/after counts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PostMonthRoute's "Already committed" list filtered future schedule slices
only by month, never consulting cancelledFromMonth, so a cancelled plan's
remaining slices kept appearing as future obligations the fold would never
actually charge. Reuse sliceAmountForMonth's existing cancellation rule
instead of re-deriving it inline, to avoid a second copy of the >= boundary
drifting from the domain's.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Future finance-plan slices are surfaced as already-committed so a plan is
visible before it lands.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Plans default to an equal split, each slice is individually editable, and
plans compose with splits. Cancelling keeps past slices.
planBalance delegates to domain slicesBalance rather than recomputing the
same total-minus-slices arithmetic, per the project's one-implementation
rule for shared math.
Validation lives in purchaseForm.ts as pure functions. Unbalanced splits
and over-budget purchases are deliberately not errors.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A bare --accent in src/index.css silently overrode shadcn's own
--accent/--accent-foreground pairing (near-white background paired with
near-black text) with the design contract's dark teal, without touching
the paired foreground. That dropped the computed contrast ratio on every
outline/ghost button, select item, badge link and dialog close button
that uses bg-accent/text-accent-foreground to 3.23:1 -- below WCAG AA's
4.5:1 minimum for text.
Renamed the token to --budget-accent everywhere it's declared and
consumed (src/index.css, src/ui/components/PostTable.tsx's carry meter),
leaving shadcn's own --accent/--accent-foreground pair untouched. The
other five contract tokens (--paper, --ink, --rule, --surplus,
--overspend) don't collide with any shadcn token name in
styles/globals.css, so they are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Negative remaining balances are shown in a warning treatment; nothing
blocks going over budget.
Applies the Tasks 18-23 design contract: --paper/--ink/--rule/--accent/
--surplus/--overspend tokens and the three type-role font variables in
src/index.css, --font-money on every money figure, and the carry meter
(a 3px hard-stopped gradient beneath each PostTable row) encoding
carried-in surplus, allocation track, spend fill and overrun.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Base path comes from BUN_PUBLIC_BASE_PATH so the same bundle serves both
a root Docker deploy and a GitHub Pages subpath.
Deviation from the task-16 brief: does not install @happy-dom/global-registrator,
does not create test/setup.ts, and does not add a [test] preload to bunfig.toml.
A global DOM preload would make src/domain/smoke.test.ts's "importable without a
DOM" assertion vacuously true, silently retiring the check that guards the
domain-layer-stays-pure invariant. Nothing in this task needs a DOM: basePath.test.ts
is pure string logic, and the shell render test was intentionally omitted by the
brief itself. A later task will register happy-dom per-file where a DOM is
genuinely required.