Data integrity (A):
- "Apply … to my budget" overwrote the bill's expected_amount (its real
recurring amount, used in budgets + payment recording) with the simulated
payoff payment. Replace with "Save APR to this bill" — persists interest_rate,
leaves expected_amount untouched. Fixes the corruption AND the interest-free
root cause (future runs use the saved APR). BillModal debt section also opens
when a bill has an interest_rate.
Error handling (B):
- Undo now has try/catch + error toast (was an unhandled rejection that left the
wrong value applied); Apply uses errMessage.
- loadData no longer snaps the selection back to the first bill after a
save/undo (was a stale-closure over selectedId); background refreshes are
"silent" so a transient failure no longer replaces the whole page.
Makes-sense (C):
- Distinguish "no APR" from a real 0% and show a notice (was silently interest-
free with no indication).
- Without a real minimum, show absolute Time-to-Payoff + Total Interest instead
of a broken "$0 savings" / "~580 mo sooner".
- Remove the arbitrary, mislabeled "Snowball plan" line (was extra applied only
to the alphabetically-first bill) from the page + chart.
- Detect the 600-month cap (incl. 0% tiny payment) as "won't pay off".
- Correct the "set a minimum on the Snowball page" guidance (no such editor).
Clarity (D):
- Plain-language toasts on every write; targeted HelpCircle tooltips (Interest/
Time Savings, Total Interest, Required Minimum, APR, Save-APR); a clearer page
description; states that explain why + what to do.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Also removed a stale PayoffChart.jsx duplicate (its .tsx was already committed).
Branded Dollars on income transactions/bank-tracking balances. typecheck 0.
The client had ~15 hand-rolled currency formatters (local `fmt`/`money`/
`fmtFull`/`fmtDollars`/…) plus a canonical `fmt` in lib/utils used at ~190
sites — same rules copy-pasted, with inconsistent handling of negatives,
whole-dollar, cents vs dollars, and blank input.
Add client/lib/money.js as the one implementation:
- formatUSD(dollars) — "$1,234.56" (whole/dash options)
- formatUSDWhole(dollars) — "$1,235"
- formatCentsUSD(cents) — from integer cents; signed "+/-" and dash options
Inputs are coerced so null/''/NaN never render as "$NaN", and -0 is
normalized so it never shows as "-$0.00".
lib/utils.fmt now delegates to formatUSD (byte-identical — the existing
utils.test.js fmt suite is the regression guard), and the 15 local
formatters delegate to money.js. No display currency formatting remains
outside money.js; the /100 conversions left behind are calculations
(form prefill), not display.
Tests: client/lib/money.test.js (13). Full client suite 46 pass; build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CategoriesPage: category rows are now a plain container with a dedicated
chevron toggle button, instead of role=button rows nesting action buttons
- PlanStatusBanner: split the collapsible header into a name/progress toggle,
sibling action buttons, and a chevron toggle (actions no longer nested in the
trigger button)
- add e2e/categories.spec.js expand regression; all 8 authed pages now pass axe
- docs: archive QA-B14-02 to HISTORY v0.41.0; QA plan status/cycle-log
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>