Each lever (retire age / target spend / save per mo / real return) now
pairs a numeric input with a range slider; retirement_what_if_controller
mirrors the value across the pair (data-lever) and debounces the live
forecast preview. Birth year stays a plain numeric input.
(Skinned delete confirmations already render via Sure's global
Turbo.config.forms.confirm → DS::Dialog override, so the PR2 turbo_confirm
buttons are already styled — no change needed.)
Surfaces the forecast on the page and makes the levers live.
- KPI cards (_kpis): Freedom date, Coast FIRE, Money-lasts-to + terminal
value, with a "set your birth year" prompt until a plan is projectable.
Wrapped in #retirement_kpis for Turbo Stream replacement; money carries
privacy-sensitive.
- What-if form: birth_year / retire_age / target_spend / monthly_savings /
real_return_pct. On input, retirement_what_if_controller debounces and
POSTs the current values to PATCH /retirement/forecast, which recomputes
against transient inputs and streams the KPI cards back WITHOUT
persisting. "Save plan" submits to #update to persist retirement_params.
- RetirementController gains #update (persist) and #forecast (transient
recompute → turbo_stream). Both reuse merged_plan_params, which drops
blank fields so a partial what-if doesn't clobber stored values.
Tests: KPI section renders; update persists params; forecast streams
#retirement_kpis without writing the slider value back. Rubocop +
erb_lint + biome clean.
PR4 replaces this minimal form with the designed slider rail + glide
chart; the #forecast endpoint and the engine stay.