Commit Graph

5 Commits

Author SHA1 Message Date
Guillem Arias
f6108e2a7b fix(goals/chart): drop title-tooltip, step projection cursor, dot follows projection line, collision-aware y-ticks, clean tooltip
Four chart fixes in one pass.

1) Browser was rendering the <title> child as a native hover tooltip
that fought with the custom crosshair tooltip. Drop <title>; use
aria-label on the <svg role="img"> instead — same SR accessible name,
no native tooltip side-effect.

2/3) The hover crosshair clamped at today: bisector ran the saved
series, which ends at today, so future hovers stuck the dot at the
last saved point. Now the controller forks:
  - Past hover: snap to nearest contribution via bisector.
  - Future hover: snap to whole-week intervals along the projection
    segment ([today, target_date]) and place the dot at the
    interpolated y on the dashed line. Movement steps cleanly week
    by week instead of pixel-by-pixel jitter.

4) Tooltip drops the redundant line:
  - Past: "<date> · Saved: $X" (no Projected — there isn't one).
  - Future: "<date> · Projected: $X" (no Saved — it's the future).

5) Y-axis tick label suppressed when its value falls within 5% of the
target line so "$2.5K" and "Target · $2,400" stop overlapping near
the right edge. Gridline stays; only the y-axis label drops.

Verified live via Playwright on House downpayment goal: <title>
absent, aria-label populated, past tooltip "Feb 10, 2026 · Saved:
$11,750", future tooltip "Nov 29, 2027 · Projected: $32,235",
neighbouring future x snaps to "Dec 13, 2027 · $32,704" (2-week jump
across the snapping boundary).
2026-05-11 20:56:45 +02:00
Guillem Arias
3fa762289a feat(goals/stepper+chart): Step 2 derived projection + JS i18n + Intl.NumberFormat
B — Step 2 of the create stepper used to echo Step 1 fields back at
the user in three labelled rows (Funding accounts: 2 · $123,456 balance;
Suggested monthly: $1,003/mo over 12 months). Replaces those rows with
a single derived sentence:

  "Save $1,003/mo across 2 accounts to hit it on time."

If no target date is set: "Set a target date to project a finish line."
The previous "Suggested monthly" + "Funding accounts" rows are dropped;
review block shows only Name, "$12,000 by May 11 2027", and the
derived insight sentence.

L — All hard-coded English templates + currency symbols in the JS
controllers go through Stimulus values now:

- goal_stepper_controller: new {currency, summaryWithDate, summaryNoDate,
  accountCountOne, accountCountOther, suggestedWithDate, suggestedNoDate}
  values. Money formatted via Intl.NumberFormat(undefined, { style:
  "currency", currency: this.currencyValue, maximumFractionDigits: 0 }).
- goal_projection_chart_controller: _fmtMoney upgraded to Intl.NumberFormat
  (was $/€/£ ternary fallback that lost JPY/INR/CHF/...).

Locale: new goals.form_stepper.step2.review.{summary_*,account_count,
suggested_*}. Old funding_accounts / suggested_monthly keys retained
(unused by the new ERB) so any translator paths in flight don't break.

Verified live via Playwright: step-2 review reads "Save $1,003/mo
across 2 accounts to hit it on time." for a $12,000 / 12-month / 2-
account goal.
2026-05-11 20:42:13 +02:00
Guillem Arias
e03204bb96 feat(goals/chart): hover crosshair + dot + tooltip
Chart had no way to read the value at a specific date — users had to
infer Saved amounts from line position relative to the y-axis labels
added in the previous commit.

- Transparent <rect> overlay covers the plot area + catches pointer.
- pointermove uses d3.bisector to snap to the nearest saved series
  point, draws a dashed crosshair + a saved-line dot + a projection-line
  dot (linearly interpolated between today and target).
- HTML tooltip lives inside the chart root (cleared on next _draw)
  showing "date / Saved: $X / Projected: $Y". Clamps to viewport so it
  doesn't overflow the card.
- pointerleave hides everything.

Pointer events unify mouse + touch — single handler covers both
desktop hover and mobile tap-and-drag. No keyboard nav yet; tracked as
follow-up (Stimulus controller is the right home but won't ship in
this round).
2026-05-11 20:39:14 +02:00
Guillem Arias
baa5f11b30 feat(goals/chart): y-axis labels + horizontal gridlines
Chart had no value anchor on the y-axis; users had to read the target
line label to know what amount the saved line represented. Add 3
right-aligned y-ticks ($0, $25K, $50K-style K/M shorthand) plus faint
borderSubdued gridlines at the same y values. Left margin widens to 44
when room allows.

Mobile (<320px chart inner-width) keeps the original tight 16px left
margin and skips the y-axis entirely so the short-window readout
stays uncluttered.

Verified live: desktop reads $0/$20K/$40K + Target $50,000; 375px
viewport drops the y-axis text + keeps target line + x-ticks only.
2026-05-11 20:35:20 +02:00
Guillem Arias
9b61e4a41b refactor: rename Savings Goals feature to Goals
User-facing rename + structural rename. Feature is now called just
"Goals" everywhere — page title, sidebar nav, modal headings, flash
messages, AI assistant tool. Code identifiers follow:

- Models: SavingsGoal → Goal, SavingsContribution → GoalContribution,
  SavingsGoalAccount → GoalAccount.
- Tables: savings_goals → goals, savings_contributions → goal_contributions,
  savings_goal_accounts → goal_accounts. FK columns savings_goal_id →
  goal_id. New migration db/migrate/20260511100003_rename_savings_to_goals.rb
  uses rename_table + rename_column; PG handles index renaming and FK
  redirection automatically.
- Controllers: SavingsGoalsController → GoalsController,
  SavingsContributionsController → GoalContributionsController.
- Routes: /savings_goals → /goals, nested /goals/:id/contributions
  (resource name shifts; old route name aliases dropped).
- ViewComponent namespace: Savings::* → Goals::*. Component class
  names drop their redundant "Goal" prefix where the namespace already
  carries it: Savings::GoalCardComponent → Goals::CardComponent,
  Savings::GoalAvatarComponent → Goals::AvatarComponent. Others keep
  their names (Goals::ProgressRingComponent, Goals::StatusPillComponent,
  Goals::AccountStackComponent, Goals::FundingAccountsBreakdownComponent).
- Stimulus controllers: savings_goal_* → goal_*, savings_goals_filter
  → goals_filter. Stimulus identifiers in data-controller / data-*
  attributes follow.
- Locale keys: savings_goals: → goals: (top level), savings_contributions:
  → goal_contributions: (top level). All t() callers updated.
- AI assistant tool: Assistant::Function::CreateSavingsGoal →
  Assistant::Function::CreateGoal, tool name "create_savings_goal" →
  "create_goal", description / response text updated.
- Sidebar nav label "Savings" → "Goals". Goals/show + index page title
  "Savings" → "Goals". Empty goals_section heading/subtitle dropped
  (duplicated the page title post-rename).

Original migrations create_savings_goals / create_savings_goal_accounts /
create_savings_contributions remain untouched so historical replay
still works; the rename migration runs on top.
2026-05-11 20:08:32 +02:00