D10 — Drop the silent .limit(50) on the contributions query and put the
list in a max-h-[480px] scrollable container. Goals with many
contributions now show all of them without truncation, but the page
stays compact via the inner scroll.
Polish — reached / completed goals no longer render the combo monthly-
pace card. After the goal is hit, comparing actual vs required pace is
moot (and target $0/mo · Above target pace was awkward filler). Only the
Total contributions card remains in the stat row.
D12 (clickable contribution rows) deferred — adding a dedicated
contribution detail/edit route adds enough scope to warrant its own
ticket. The per-row delete X already covers the only mutation people
need from this view.
D1 — Drop the "Linked balance" stat. It summed the linked accounts'
total balances (e.g. $204K) rather than the amount saved toward the
goal ($1K), so it overstated progress by ~200x. Replace with a
"Target pace" card showing required $/mo.
D2 — Drop the redundant "← All goals" link. The breadcrumb nav above
the header already shows Home › Savings Goals.
D3 — Hide the Add-contribution button on reached / completed goals.
Logically you don't keep contributing after the goal is done.
D5 — Add last-contribution recency to the header sub ("Last
contribution 3d ago"), matching the goal card footer pattern from
the index refactor.
D11 — Stat row now 3 cards instead of 4 (avg monthly, total
contributions, target pace). Drop the "Started" card — low-signal for
new users.
The Accounts grid duplicated the sidebar account list. Removing it gives
the Goals section more breathing room and the page a tighter narrative:
header → KPIs → Goals.
Delete Savings::AccountCardComponent, Family#savings_subtype_accounts,
the @savings_accounts / @account_goal_counts controller refs, and the
related locale keys. Sidebar still shows the savings-subtype Depository
accounts under "Cash" — no information is lost.
Each card now answers "what's my next move" without clicking into the
detail page. Under the amount/target row, a pace line shows actual avg
contributions vs the monthly target. The footer (previously "$X left")
switches by status:
- behind → "Save $Y/mo to catch up"
- on_track → "Last contribution Nd ago" (or "today" / "No contributions yet")
- reached / completed → "Goal reached"
- no_target_date → "No deadline set"
- paused → "Paused"
Add SavingsGoal#last_contribution_at and #last_contribution_days_ago.
Both these methods and average_monthly_contribution now respect a loaded
:savings_contributions association so the index page doesn't N+1.
Controller eager-loads :savings_contributions + :linked_accounts.
P4: status pills now carry an icon alongside the colored tint
(circle-check / triangle-alert / star / infinity / pause), so color is
no longer the sole signal. Drop the redundant dot.
P4: default sort on the active goals list becomes attention-first —
behind → on_track → no_target_date → paused, alphabetical within bucket.
The user opens the page and lands on the goals that need them.
P5: add a Paused filter chip + render paused goal cards with opacity-75
so they read as inactive at a glance. Rename "No date" chip to
"Open-ended" — clearer to non-jargon readers.
P1: drop the sparkline + the single mixed hero. Hero became 3 separate
KPI cards (Contributed last 30d, Needs this month, Goals on track),
matching the Transactions page pattern. Each KPI answers a question the
user opens the page asking — saving rate, this-month action, overall
health.
P3: empty state copy + CTA now reflect the reason it is empty. Search
returns 0 → "No goals match X" + Clear search. Chip set to non-all → "No
goals match this filter" + Show all. Both → both reasons + both
buttons.
Drop: total_savings_balance, savings_balance_series,
savings_balance_30d_delta on Family (no other consumers).
Add: Family#contribution_velocity(range:).
- Page header: title "Savings" + "Your savings accounts and the goals
you're working toward." Removed the top-right New goal button (moves
into the Goals section).
- Hero card: "Total in savings" with sum-of-savings-subtype balance,
30-day delta vs last 30 days (Family#savings_balance_30d_delta),
3-stat sub-row (Accounts / Active goals / Saved toward goals), and a
D3 sparkline area chart on the right (new
`savings-sparkline` Stimulus controller, sourced from
Family#savings_balance_series).
- Accounts section: lists Depository accounts with subtype = "savings"
as cards (blue avatar, name, subtype, balance, "Funds N goals"). New
Savings::AccountCardComponent.
- Goals section header: "Goals" + "Save toward what matters." + "New
goal" button right-aligned to the section (not the page header).
- Removed state-filter pill nav. Active goals render in the main grid;
Completed goals get a "Completed · N" divider w/ check-circle icon
and their own grid below.
- Goal card layout reworked: horizontal bar replaced with a 64px donut
ring on the right side of the card header (ring colour tracks
goal.status — yellow=behind, primary=on-track, green=reached). Pill
is inline with the goal name.
- Status pill copy: "Behind pace" → "Behind".
- Filter bar (copied from settings/providers): search input + status
chips (All / On track / Behind / No date). Hidden when ≤ 6 active
goals. Powered by `savings-goals-filter` Stimulus controller —
toggles `.hidden` on cards by goal name + status.
- Family#savings_subtype_accounts, total_savings_balance,
savings_balance_series, savings_balance_30d_delta helpers; controller
computes hero payload + account-goal counts for the cards.
Brings the savings goals UI closer to the Claude Design reference shared
by the user. Changes:
- Sidebar nav label: "Savings goals" → "Savings".
- Status pill copy: "Behind" → "Behind pace" (matches Pill component
from GoalsCommon.jsx).
- Empty state rewritten with a large target icon, "No goals yet"
heading, and the descriptive body copy from the design.
Goal detail page (matches GoalDetail.jsx):
- New "← All goals" back link above the header.
- 2-column hero: ring card on the left (320px column), Projection card
on the right.
- Projection card uses a new D3 Stimulus controller
(`savings-goal-projection-chart`) that draws:
· saved area + line from goal creation → today (solid, primary)
· dashed projection segment from today → target date (yellow when
behind, green when on track)
· horizontal dashed target line with label
· today marker (vertical dashed line + dot)
Data shape comes from `SavingsGoal#projection_payload`.
- Card subtitle generates a contextual sentence ("At $X/mo you'll fall
short. Bump to $Y/mo to hit it on time." / "At your current pace
you'll reach this goal around Month YYYY." / "Goal reached. Nice
work.") with a strong tag highlighting the actionable figure.
- Stat row now shows Linked balance (sum across linked accounts) +
"N accounts" sub-caption instead of duplicate "Target date" stat.
New goal modal (matches the design images 2 + 3):
- DS::Dialog custom header: DS::FilledIcon target glyph + title + step
subtitle ("Step 1 of 2 · Goal details" / "Step 2 of 2 · Review &
start") that updates as the user advances.
- Connected stepper at top of body: numbered circles connected by a
bar, step-1 circle flips to ✓ when complete.
- Step 1 heading "What are you saving for?" + supporting copy.
- Name field paired with a target glyph affordance on its left.
- Target amount + Target date in a 2-col grid.
- Funding accounts list now grouped by account subtype with uppercase
section headers (CHECKING / SAVINGS / HSA / CD / MONEY MARKET /
OTHER), each row showing avatar + name + subtype + balance.
- Step 2 heading "Looks good?" + Review card (goal target + funding
accounts summary + suggested monthly = target/months_remaining), and
a disclosure for the optional initial contribution.
- Footer: "Cancel" left text-button (closes modal) / "Back" left text
when on step 2; "Continue →" or "Create goal →" right arrow button.
Demo generator: Depository accounts now set `subtype` ("checking" /
"savings") on the accountable so they group correctly in the modal.
Tests: all green, 35 runs in the savings suite, 92 assertions.
Previous savings goals UI looked nothing like the Claude Design output
(see sure-design-context/design/savings-goals/project/goals/*.jsx) and
the hand-rolled ring did not match the segmented D3 donut used at
app/views/budgets/_budget_donut.html.erb. This rewires the surface end
to end.
Donut chart:
- SavingsGoal#to_donut_segments_json returns the same segment shape as
Budget#to_donut_segments_json: filled portion in goal color, unused
remainder as `var(--budget-unallocated-fill)`. Visual identity is now
the same: segmented arc with cornerRadius and gap, courtesy of the
shared `donut-chart` Stimulus controller and D3.
- ProgressRingComponent renders a `data-controller="donut-chart"` div
with the same default-content/inner-text pattern as `_budget_donut`.
Index page (matches GoalsIndex.jsx):
- Page header: title + "Save toward what matters." subtitle + "New goal"
primary CTA right-aligned.
- Summary strip card: total saved / target, overall bar, active goals,
on-track ratio, behind count.
- State filter rendered as DS::Tabs-style pill nav (`bg-surface-inset
p-1 rounded-lg`, white-pill active state).
- Cards rebuilt: avatar (44px, rounded-xl, white initial on goal color)
+ name + secondary line ("N days left · by date" / "No target date" /
"Completed" / "Past due"), status pill with leading dot, big
$current/$target line + percent, bar in status colour, AccountStack
(overlapping initials) + "N accounts" + "to go".
Goal detail (matches GoalDetail.jsx):
- Header: 64px avatar + h1 name + status pill + "Target $X by date ·
N days left" subline + Edit (outline) + Add contribution (primary) +
kebab (DS::Menu for AASM transitions).
- Donut-chart ring card with stats overlay.
- 4-col stat row (Avg monthly, Total contributions, Target date,
Started) with mono numerals and "Needs $X/mo" / "Above target pace"
sub-captions where relevant.
- Two-col bottom: contributions list (avatar + account · date · source
· green +$amount) and funding accounts breakdown (stacked bar +
per-account row with $ and % of saved).
New components: Savings::AccountStackComponent (overlapping account
initials with ring-2 ring-container). StatusPillComponent now uses a
leading colored dot instead of an icon. GoalAvatarComponent radii
match Claude Design (rounded-md/lg/xl/2xl) and white initial.
Locale: new keys under savings_goals.{index.subtitle, index.summary.*,
goal_card.{accounts,days_left,completed,past_due,no_target_date},
show.header.*, show.ring.{of,to_go}, show.stats.*, show.funding_balance,
show.of_saved, show.notes}.
Adds a standalone Savings goals feature: a piggy-bank style tracker that
lets a family set a target, link one or more Depository accounts as
funding sources, and log manual contributions over time. Supersedes #1569
(closed) — same intent, redesigned per reviewer + Discord feedback.
What this adds:
- New `/savings_goals` sidebar entry (piggy-bank icon) with index, show,
state-filtered tabs (all/active/paused/completed/archived), and a
2-step modal stepper for creation (Identity → Review).
- Multi-account funding via a `SavingsGoalAccount` join: a goal requires
≥1 linked Depository account (checking/savings/HSA/CD/money-market),
and all linked accounts must share the goal's currency.
- Tracker balance model: goal balance = SUM(contributions.amount). No
auto-flow from account balances. Contributions are pure logical
records and don't move money between accounts.
- Manual contributions modal scoped to the goal's linked accounts.
Initial contributions seeded at creation can't be deleted; manual
ones can.
- AASM lifecycle: active / paused / completed / archived.
Hard-delete only after archive.
- Status pills (On track / Behind / Reached / No date) derived from
pace vs target_date.
- AI Assistant tool `create_savings_goal` lets the sidebar chat create
a goal end-to-end from a natural-language prompt; soft errors carry
the available-accounts list back to the LLM (mirrors the existing
`import_bank_statement` pattern).
- Family-scoped throughout (`Current.family`-only access, account
family-scoping enforced both in controllers and the AI tool).
- Demo data seed wires up 4 sample goals across the Depository accounts.
Intentionally out of scope (separate PRs / v1.1):
- Auto-fund from budget surplus + Sidekiq cron + budget-show card.
- Dashboard "Savings goals" widget.
- "Behind pace" projection chart on the detail page.
- `evaluate_savings_goal_feasibility` LLM tool (level-setting before
create_savings_goal).
- Spend-less goals inside Budgets.
- Family-member-private goals (deferred investigation).