## What Render the comparison block on the expense-impact page. Display the **retirement year** and **retirement income** for both baseline and adjusted projections, plus the delta for each (e.g. "Retires 2 years later", "Income reduced by $4,200/yr"). ## Why This is the headline answer the user came to the page for. A clear, numerical before/after on the two metrics they care about most is more valuable than any chart and should land before visualization work. ## Acceptance criteria * Two metrics shown side-by-side: retirement year, retirement income. * Delta is signed and human-readable; positive deltas (better outcomes) and negative deltas are visually distinguishable. * Handles edge cases: no change (zero expense), retirement year pushed past projection horizon, income drops to zero. * Updates live (or on submit, depending on form pattern from the input issue) when inputs change.
## What Add the calculation that takes the user's current plan inputs, subtracts the hypothetical expense from the appropriate asset bucket(s) at the specified time, and re-runs the existing retirement projection engine to produce an "adjusted" projection. Return both the baseline and adjusted projections so the UI can compare them. ## Why This is the core of the feature. We want to reuse the existing projection engine so numbers on this page stay consistent with the rest of the app. Reducing assets at a specified year (rather than just lopping it off today) makes the tool useful for planned future expenses. ## Acceptance criteria * Pure function (no UI coupling) that accepts plan inputs + `{ amount, year }` and returns `{ baseline, adjusted }` projections. * Asset reduction draws from a defensible default (e.g. taxable/cash first, then tax-deferred) — document the order chosen. * If the expense exceeds available assets at that year, surface a clear "depletes assets" result rather than throwing. * Unit tests cover: expense today, expense in N years, expense larger than assets, zero expense (no-op).
## What Add the input form on the expense-impact page. At minimum it captures **expense amount** (currency, validated > 0) and **when it happens** (today vs. a future year, using the existing date/year input pattern). Optional: a free-text label for the expense. ## Why The user needs a simple, low-friction way to describe the hypothetical expense. Keeping the form minimal in v1 avoids scope creep while still supporting the most common questions ("what if I spend $X today?", "what if I spend $X in 3 years?"). ## Acceptance criteria * Amount field validates positive numeric input and formats as currency. * Timing field defaults to "today" and accepts a future year up to the user's projection horizon. * Form state is local to the page (not persisted) and resets when the user navigates away. * Submitting the form triggers the impact calculation (wired to the issue that adds the calc).
## What Build the results UI that displays baseline and post-expense scenarios side by side after the user submits an expense. Show, at minimum: projected retirement year (baseline vs. post-expense, with delta in years/months) and projected retirement income (baseline vs. post-expense, with delta in currency and percent). Include a one-sentence plain-language summary at the top (e.g. "A $45,000 expense delays your retirement by \~8 months and reduces projected retirement income by \~$1,200/yr."). ## Why The comparison is the answer the user came for. A side-by-side layout with a clear delta is what makes the impact legible at a glance; the plain-language summary is what makes it shareable and memorable. ## Acceptance criteria * Two-column (or stacked on narrow screens) layout: Baseline | After this expense. * Headline numbers: retirement year and projected retirement income, both with deltas. * Plain-language summary sentence above the columns. * Empty/initial state before the user submits is friendly (not an error). * Numbers formatted consistently with the rest of the app (currency, dates).
## What Add the input form to the expense-impact page: a single currency-formatted amount input (with thousands separators), an optional short label/description field ("What's this for?"), and a submit/calculate button. On submit, call the calculation function from the impact-calc issue and pass results to the comparison view. ## Why A clean, low-friction input is the entire on-ramp to the feature. Users are typically thinking of a specific purchase ("$45,000 for a kitchen reno") and the form should make that fast to type and impossible to misread. ## Acceptance criteria * Currency input formats as the user types (e.g. `45000` displays as `$45,000`). * Validation: required, must be a positive number, reasonable upper bound (e.g. < $100M) with an inline error message. * Optional label field, max \~60 chars, plain text. * Submit triggers the calculation and surfaces a loading state if the calc is async. * Form is keyboard-friendly and meets the same a11y bar as our other plan inputs.
## What Create a new page at `/plan/expense-impact` (or the closest equivalent in our existing plan/tools section) and wire it into the app's navigation so users can discover it. The page should render a basic shell with a clear title ("Estimate the impact of a large expense"), a short explainer sentence, and slots for the input form and comparison view that subsequent issues will fill in. ## Why We need the route and nav entry in place before the form and results UI can be hung off it. Doing this as its own small change keeps the diff reviewable and makes it easy to ship the page behind a feature flag if needed. ## Acceptance criteria * New route renders a page-shell component at `/plan/expense-impact`. * Navigation entry added in the same place as other planning tools, with copy that matches our existing style. * Page is reachable when logged in; redirects to login when not authenticated, matching the rest of the plan section. * No calculation logic on this page yet — placeholders are fine.
## What Add a pure calculation function that takes the user's current plan inputs plus a one-time expense amount, applies the expense as a reduction to current liquid/investable assets, and re-runs the existing retirement projection engine to produce a post-expense scenario. Return both baseline and post-expense results (retirement year, projected retirement income, and any other headline numbers the existing engine already produces) so the UI can render a comparison. ## Why This is the load-bearing piece of the feature. Users want to answer "can I afford this large purchase?" without permanently editing their plan inputs. Implementing the math as a pure, side-effect-free function (rather than mutating user state) keeps the feature safe to explore and easy to test. ## Acceptance criteria * A new function (e.g. `computeExpenseImpact({ plan, expenseAmount })`) that returns `{ baseline, postExpense, delta }`. * Expense is subtracted from the appropriate asset bucket(s) following whatever rule we already use for asset draws (document the rule in code comments; flag any ambiguity for the human triager). * Negative or zero asset balances after the expense are handled gracefully (no NaNs, no infinite loops in the projection). * Unit tests cover: typical case, expense larger than current assets, zero expense, and a sanity-check that baseline matches the existing projection output exactly.
## What Polish the page: friendly empty state when the user has no plan yet (deep-link to onboarding), error state when the projection engine fails, and a discoverability entry point — a "What if I spent..." link or card on the main retirement dashboard that routes here. ## Why Without a discovery hook, this page will be invisible to most users. Empty/error states are needed before we can ship the page broadly without surprising users who hit it in unusual states. ## Acceptance criteria * Empty state: user with no plan sees a clear CTA to finish onboarding instead of a broken form. * Error state: projection failure shows a recoverable message ("Couldn't compute — try again") rather than a blank screen. * Entry point on the retirement dashboard navigates to the page with no parameters. * Basic analytics event fires on page view and on first successful calculation.
## What Add a chart that overlays the baseline projection and the adjusted (post-expense) projection over time. Reuse the existing chart component used elsewhere in the app if possible. Highlight the year the expense occurs. ## Why The numerical comparison answers "how much worse?" — the chart answers "when and how does the gap evolve?" Useful for users who want to see whether the impact compounds or stays roughly flat. ## Acceptance criteria * Single chart with two series (baseline, adjusted), clearly labeled. * Marker / annotation on the year the expense occurs. * Y-axis formatted as currency; tooltip shows both values and the delta at a given year. * Chart is responsive and matches the visual style of other projection charts in the app.