This project is a full rebuild of the retirement drawdown optimizer. The goal is to move from a proof‑of‑concept (in poc/) to a cleanly separated, testable TypeScript simulation engine with a modern Next.js UI. The rebuild follows the implementation plan in .cursor/plans/retirement_optimizer_rebuild_88fc8727.plan.md.
The result:
- A pure TypeScript engine (
lib/engine/) that calculates year‑by‑year cash flows, taxes, RMDs, and Social Security. - A ledger system that logs all cash movements for auditability and UI transparency.
- A React/Next.js UI with shadcn‑style components and slider‑driven inputs.
- Expandable yearly rows that show a compact ledger table.
- Interactive analysis tools: Roth Conversion Impact heatmap and Withdrawal Strategy comparison charts.
The POC combined UI and calculations, which made the logic harder to test and evolve. This rebuild separates the calculation engine from the UI, making it:
- Easier to test and reason about.
- Easier to extend with new rules or new UI.
- Ready for future use in Web Workers or other environments.
The architecture mirrors the rebuild plan:
UI (Next.js + shadcn-style) -> Zustand Store -> Simulation Engine
-> Optimizers (compare scenarios)
Key layers:
- Engine (lib/engine): Pure TypeScript. No React/DOM dependencies.
- State (lib/store.ts): Zustand store that holds the scenario and latest results.
- UI (components + app): Sliders, charts, tables, and layout.
app/
layout.tsx # Root layout, metadata, fonts
page.tsx # Main page: controls + charts + yearly table
components/
layout/ # Header and main layout
scenario/ # Input cards (sliders)
results/ # Summary cards, charts, year table
optimize/ # Analysis charts (Roth heatmap, withdrawal comparison)
ui/ # shadcn-style primitives (card, table, slider, etc.)
lib/
engine/ # Simulation engine (types, tax, SS, RMD, optimize)
defaults.ts # Default scenario values
format.ts # Formatting helpers
store.ts # Zustand store
poc/ # Legacy proof-of-concept (reference only)
lib/engine/types.ts defines all core data shapes:
Scenario: all user inputs (ages, balances, spending, SS, tax assumptions).YearRow: UI-ready year data with balances, taxes, spending sources, etc.LedgerEntry: transaction log entries.SimulationResult: full simulation output (rows + ledger + summary).
lib/engine/rmd.ts: Uniform Lifetime Table logic for RMDs.lib/engine/social-security.ts: Claim age adjustment + taxable SS calculation.lib/engine/tax.ts: Flat effective ordinary + capital gains tax model.
lib/engine/ledger.ts provides createLedgerEntry, used to record all movements:
spending withdrawals, tax payments, conversions, growth, and reinvestment.
lib/engine/simulation.ts orchestrates the annual loop:
- Apply investment and cash returns.
- Compute spending needs.
- Apply Social Security income (if enabled).
- Calculate RMD requirement.
- Withdraw funds based on chosen order.
- Apply Roth conversion.
- Calculate and pay taxes.
- Reinvest excess RMD net cash into taxable.
- Build
YearRow+ ledger entries.
The engine provides functions to analyze strategy tradeoffs:
lib/engine/roth-conversion-grid.ts: Computes a 2D grid of TANW outcomes across conversion amounts and end ages. Powers the heatmap visualization.lib/engine/withdrawal-comparison.ts: Compares all 12 withdrawal order permutations to find the optimal sequence.lib/engine/optimize.ts: Legacy optimization helpers (SS claim ages, etc.).
All analysis tools score scenarios by final TANW (tax‑adjusted net worth).
lib/store.ts holds:
- Current
scenario. -,Latestresultsfrom the engine. - Optimization results.
- Expansion state for the yearly ledger rows.
Any scenario change triggers a new simulation run.
All numeric inputs are sliders for fast experimentation. Cards are compact and arranged in a two-column grid above the yearly table.
Key components:
components/scenario/account-balances-form.tsxcomponents/scenario/simulation-range-form.tsxcomponents/scenario/economic-assumptions-form.tsxcomponents/scenario/spending-form.tsxcomponents/scenario/social-security-form.tsxcomponents/scenario/strategy-form.tsx
components/results/summary-cards.tsx: ending values for each account + totals.components/results/year-table/year-table.tsx: yearly breakdown table with expandable rows.components/results/year-table/ledger-entries-view.tsx: compact ledger table per year.
Recharts visualizations:
- Balances over time
- Spending source breakdown
- Taxes over time
- TANW over time
Each chart includes horizontal gridlines via CartesianGrid.
Interactive tools to explore strategy tradeoffs:
Roth Conversion Impact (components/optimize/roth-conversion-heatmap.tsx):
- 2D heatmap showing final TANW across conversion amounts ($0–$300K) and end ages.
- Color scale from red (lower TANW) to green (higher TANW).
- Highlights best configuration and your current settings.
- "Apply Best Settings" button to adopt the optimal conversion strategy.
- Explains why results may be non‑monotonic (RMD interactions, SS taxability thresholds, IRA exhaustion).
Withdrawal Strategy Comparison (components/optimize/withdrawal-strategy-chart.tsx):
- Bar chart comparing all 12 withdrawal order permutations.
- Ranked table showing TANW, total taxes, and difference from best.
- "Apply Best Strategy" button to adopt the optimal withdrawal order.
The rebuild plan in .cursor/plans/retirement_optimizer_rebuild_88fc8727.plan.md is implemented as follows:
lib/engine/types.ts: Scenario, YearRow, LedgerEntry, SimulationResultlib/engine/rmd.ts: RMD calculatorlib/engine/social-security.ts: SS benefits + taxabilitylib/engine/tax.ts: tax calculatorlib/engine/ledger.ts: ledger entry helperlib/engine/simulation.ts: main simulation looplib/engine/roth-conversion-grid.ts: Roth conversion heatmap datalib/engine/withdrawal-comparison.ts: withdrawal order comparisonlib/engine/optimize.ts: legacy optimization functionslib/engine/index.ts: exports
lib/store.ts: Zustand store + simulation integrationlib/format.ts: currency/percent formatterslib/defaults.ts: default scenario values
- Scenario forms under
components/scenario/ - Summary cards under
components/results/summary-cards.tsx - Yearly table + ledger expansion under
components/results/year-table/ - Charts under
components/results/charts/ - Optimization under
components/optimize/ - Layout in
components/layout/andapp/page.tsx
Engine unit tests are in lib/engine/__tests__ (Vitest):
rmd.test.tssocial-security.test.tstax.test.tswithdrawal.test.tssimulation.test.tsoptimize.test.ts
Install dependencies:
pnpm install
Run the dev server:
pnpm dev
Run tests:
pnpm test
- Effective Tax Rates: The engine uses flat effective tax rates (ordinary + cap gains) to keep the model transparent and easy to test.
- Slider UI: Inputs are slider‑based for quick tuning; values are displayed alongside.
- Ledger Transparency: Every movement is recorded as a ledger entry so users can inspect how a year was funded.
- SS Toggle: Social Security can be disabled and the form collapses to simplify scenarios without SS income.
- Compact Layout: Controls are arranged in two columns above the yearly table, with charts to the right for live visual feedback.
- shadcn Slider: A lightweight range input slider is used (
components/ui/slider.tsx). It supports single and dual‑knob ranges. - Analysis Tools: The Roth Conversion heatmap and Withdrawal Strategy comparison run on‑demand (button click) since they execute hundreds of simulations. Results show fixed parameters so users understand what was held constant.
- Non‑Monotonic Results: Roth conversion outcomes can oscillate due to complex interactions between RMDs, SS taxability thresholds ($25k/$34k), IRA exhaustion timing, and tax payment cascading. This is expected behavior, not a bug.
The original proof‑of‑concept is still in poc/. It is not used by the rebuild, but kept for reference.