@retiregolden/planner-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +181 -0
  3. package/package.json +77 -0
  4. package/src/App.tsx +246 -0
  5. package/src/RouteErrorBoundary.tsx +45 -0
  6. package/src/assets/hero.png +0 -0
  7. package/src/assets/react.svg +1 -0
  8. package/src/assets/vite.svg +1 -0
  9. package/src/data/fedInvestClient.ts +113 -0
  10. package/src/data/localStore.ts +42 -0
  11. package/src/data/planOrigin.ts +24 -0
  12. package/src/data/planStore.ts +165 -0
  13. package/src/data/v2Backup.ts +101 -0
  14. package/src/import/ImportPage.tsx +347 -0
  15. package/src/import/ReviewChecklistView.tsx +38 -0
  16. package/src/import/brokerCsv.ts +395 -0
  17. package/src/import/csv.ts +133 -0
  18. package/src/import/genericCsv.ts +224 -0
  19. package/src/import/projectionLab.ts +350 -0
  20. package/src/import/reviewChecklist.ts +33 -0
  21. package/src/import/tenForty.ts +275 -0
  22. package/src/index.css +630 -0
  23. package/src/index.ts +16 -0
  24. package/src/learn/ArticleBody.tsx +78 -0
  25. package/src/learn/ArticlePage.tsx +57 -0
  26. package/src/learn/GlossaryPage.tsx +33 -0
  27. package/src/learn/LearnAboutScreen.tsx +41 -0
  28. package/src/learn/LearnCards.tsx +41 -0
  29. package/src/learn/LearnLink.tsx +91 -0
  30. package/src/learn/LearningCenterPage.tsx +114 -0
  31. package/src/learn/SourcesPage.tsx +98 -0
  32. package/src/learn/components/ArticleFigure.tsx +34 -0
  33. package/src/learn/components/ArticleShell.tsx +86 -0
  34. package/src/learn/components/ComparisonTable.tsx +42 -0
  35. package/src/learn/components/FormulaBlock.tsx +34 -0
  36. package/src/learn/components/PurchasingPowerChart.tsx +41 -0
  37. package/src/learn/components/RelatedArticles.tsx +27 -0
  38. package/src/learn/components/ScenarioCard.tsx +24 -0
  39. package/src/learn/components/SourceList.tsx +23 -0
  40. package/src/learn/components/charts.tsx +21 -0
  41. package/src/learn/content/about-retiregolden.ts +100 -0
  42. package/src/learn/content/aca-premium-tax-credits-and-magi.ts +103 -0
  43. package/src/learn/content/account-types-overview.ts +106 -0
  44. package/src/learn/content/after-tax-estate.ts +111 -0
  45. package/src/learn/content/agi-magi-and-taxable-income.ts +112 -0
  46. package/src/learn/content/appealing-irmaa-ssa-44.ts +95 -0
  47. package/src/learn/content/assumption-general-inflation.ts +82 -0
  48. package/src/learn/content/assumption-healthcare-inflation.ts +85 -0
  49. package/src/learn/content/assumption-heir-tax-rate.ts +79 -0
  50. package/src/learn/content/assumption-investment-returns.ts +90 -0
  51. package/src/learn/content/assumption-longevity-planning-age.ts +78 -0
  52. package/src/learn/content/assumption-recent-magi.ts +83 -0
  53. package/src/learn/content/assumption-social-security-cola.ts +89 -0
  54. package/src/learn/content/assumption-social-security-trust-fund.ts +83 -0
  55. package/src/learn/content/assumption-state-tax-override.ts +79 -0
  56. package/src/learn/content/beneficiaries-and-account-titling.ts +99 -0
  57. package/src/learn/content/break-even-useful-lens.ts +94 -0
  58. package/src/learn/content/building-a-retirement-spending-budget.ts +100 -0
  59. package/src/learn/content/cola-and-inflation-protection.ts +102 -0
  60. package/src/learn/content/divorced-spousal-and-survivor-records.ts +104 -0
  61. package/src/learn/content/dynamic-spending-guardrails.ts +90 -0
  62. package/src/learn/content/earnings-test-before-fra.ts +100 -0
  63. package/src/learn/content/employer-match-and-contribution-order.ts +104 -0
  64. package/src/learn/content/examplePlanArticles.ts +525 -0
  65. package/src/learn/content/fees-expense-ratios-and-compounding-drag.ts +98 -0
  66. package/src/learn/content/fi-number-and-four-percent-rule.ts +64 -0
  67. package/src/learn/content/filling-a-tax-bracket-with-roth-conversions.ts +98 -0
  68. package/src/learn/content/funded-ratio.ts +70 -0
  69. package/src/learn/content/healthcare-after-65.ts +103 -0
  70. package/src/learn/content/healthcare-before-65.ts +104 -0
  71. package/src/learn/content/historical-vs-random-return-models.ts +101 -0
  72. package/src/learn/content/how-assumptions-change-the-answer.ts +105 -0
  73. package/src/learn/content/how-much-can-i-spend.ts +105 -0
  74. package/src/learn/content/how-social-security-is-taxed.ts +95 -0
  75. package/src/learn/content/how-the-optimizer-thinks.ts +102 -0
  76. package/src/learn/content/how-the-optimizer-values-after-tax-estate.ts +97 -0
  77. package/src/learn/content/how-to-model-accumulation.ts +67 -0
  78. package/src/learn/content/how-to-read-a-retirement-projection.ts +115 -0
  79. package/src/learn/content/hsas-and-qualified-medical-expenses.ts +108 -0
  80. package/src/learn/content/hsas-as-retirement-accounts.ts +101 -0
  81. package/src/learn/content/inflation-risk.ts +98 -0
  82. package/src/learn/content/inherited-ira-10-year-rule.ts +105 -0
  83. package/src/learn/content/insurance-in-your-retirement-plan.ts +103 -0
  84. package/src/learn/content/irmaa-two-year-lookback.ts +99 -0
  85. package/src/learn/content/long-term-care-costs-and-insurance.ts +103 -0
  86. package/src/learn/content/long-term-care-insurance-as-risk-transfer.ts +98 -0
  87. package/src/learn/content/longevity-risk.ts +99 -0
  88. package/src/learn/content/marginal-vs-effective-tax-rate.ts +98 -0
  89. package/src/learn/content/medicare-part-b-vs-part-d-irmaa.ts +102 -0
  90. package/src/learn/content/mortality-weighted-social-security.ts +113 -0
  91. package/src/learn/content/moving-to-retiregolden.ts +86 -0
  92. package/src/learn/content/niit-high-income-investment-tax.ts +98 -0
  93. package/src/learn/content/ordinary-income-vs-capital-gains.ts +103 -0
  94. package/src/learn/content/paying-conversion-taxes-taxable-vs-ira.ts +102 -0
  95. package/src/learn/content/pensions-and-annuities.ts +101 -0
  96. package/src/learn/content/permanent-life-insurance-in-a-plan.ts +106 -0
  97. package/src/learn/content/pia-aime-and-bend-points.ts +103 -0
  98. package/src/learn/content/planner-overview.ts +106 -0
  99. package/src/learn/content/planning-for-couples-and-survivor-years.ts +108 -0
  100. package/src/learn/content/privacy-what-stays-in-your-browser.ts +99 -0
  101. package/src/learn/content/qcds-qualified-charitable-distributions.ts +101 -0
  102. package/src/learn/content/reading-the-results-page.ts +96 -0
  103. package/src/learn/content/reading-the-social-security-analysis-page.ts +106 -0
  104. package/src/learn/content/real-estate-home-equity-and-debt.ts +100 -0
  105. package/src/learn/content/reports-csv-exports-and-sharing.ts +101 -0
  106. package/src/learn/content/risk-based-guardrails.ts +100 -0
  107. package/src/learn/content/rmds-required-minimum-distributions.ts +100 -0
  108. package/src/learn/content/roth-conversion-basics.ts +104 -0
  109. package/src/learn/content/rsus-and-espp.ts +101 -0
  110. package/src/learn/content/rule-of-55-and-72t.ts +107 -0
  111. package/src/learn/content/savings-rate-biggest-lever.ts +66 -0
  112. package/src/learn/content/seed-your-plan-from-your-tax-return.ts +93 -0
  113. package/src/learn/content/sensitivity-testing-what-changes-the-answer.ts +104 -0
  114. package/src/learn/content/sequence-of-returns-risk.ts +98 -0
  115. package/src/learn/content/social-security-bridge.ts +67 -0
  116. package/src/learn/content/social-security-claiming-age-basics.ts +113 -0
  117. package/src/learn/content/social-security-taxes-vs-benefits.ts +76 -0
  118. package/src/learn/content/spending-profiles-and-the-retirement-smile.ts +92 -0
  119. package/src/learn/content/spousal-and-survivor-benefits.ts +120 -0
  120. package/src/learn/content/ssdi-and-retirement-planning.ts +72 -0
  121. package/src/learn/content/standard-deduction-senior-deduction-and-itemizing.ts +97 -0
  122. package/src/learn/content/state-income-taxes-in-retirement.ts +97 -0
  123. package/src/learn/content/step-up-in-basis.ts +102 -0
  124. package/src/learn/content/survivor-planning-for-couples.ts +110 -0
  125. package/src/learn/content/survivor-spending-in-couple-plans.ts +98 -0
  126. package/src/learn/content/tax-cliffs-and-bracket-edges.ts +105 -0
  127. package/src/learn/content/tax-loss-and-gain-harvesting.ts +99 -0
  128. package/src/learn/content/taxable-brokerage-basis-and-capital-gains.ts +99 -0
  129. package/src/learn/content/three-big-questions-spending-time-risk.ts +103 -0
  130. package/src/learn/content/tips-ladders.ts +92 -0
  131. package/src/learn/content/todays-dollars-vs-future-dollars.ts +107 -0
  132. package/src/learn/content/traditional-vs-roth-contributions.ts +113 -0
  133. package/src/learn/content/troubleshooting-surprising-results.ts +105 -0
  134. package/src/learn/content/trust-fund-haircut-scenarios.ts +101 -0
  135. package/src/learn/content/understanding-monte-carlo-success-rate.ts +118 -0
  136. package/src/learn/content/understanding-your-plan-assumptions.ts +134 -0
  137. package/src/learn/content/using-assumptions-and-provenance.ts +98 -0
  138. package/src/learn/content/using-scenarios-to-compare-choices.ts +99 -0
  139. package/src/learn/content/what-changes-when-you-move-states.ts +141 -0
  140. package/src/learn/content/what-is-fire.ts +65 -0
  141. package/src/learn/content/what-monte-carlo-proves.ts +98 -0
  142. package/src/learn/content/what-retiregolden-models.ts +103 -0
  143. package/src/learn/content/what-retirement-healthcare-really-costs.ts +117 -0
  144. package/src/learn/content/why-95-percent-is-not-a-guarantee.ts +98 -0
  145. package/src/learn/content/why-roth-conversions-raise-other-costs.ts +106 -0
  146. package/src/learn/content/why-small-tax-cliffs-can-matter.ts +109 -0
  147. package/src/learn/content/widows-penalty-and-survivor-brackets.ts +106 -0
  148. package/src/learn/content/withdrawal-order-basics.ts +105 -0
  149. package/src/learn/glossary.ts +191 -0
  150. package/src/learn/inlineMarkdown.tsx +54 -0
  151. package/src/learn/learn.css +537 -0
  152. package/src/learn/learningRegistry.ts +502 -0
  153. package/src/longevity/LongevityResults.tsx +85 -0
  154. package/src/longevity/LongevityWizard.tsx +305 -0
  155. package/src/longevity/constants.ts +15 -0
  156. package/src/longevity/factors.ts +125 -0
  157. package/src/longevity/model.ts +31 -0
  158. package/src/longevity/persistedGuard.ts +129 -0
  159. package/src/longevity/storage.ts +40 -0
  160. package/src/mc/messages.ts +118 -0
  161. package/src/mc/monteCarlo.worker.ts +44 -0
  162. package/src/mc/pool.ts +267 -0
  163. package/src/mc/runRequest.ts +125 -0
  164. package/src/optimize/messages.ts +84 -0
  165. package/src/optimize/optimize.worker.ts +29 -0
  166. package/src/optimize/runOptimize.ts +92 -0
  167. package/src/optimize/runSpendingSolve.ts +47 -0
  168. package/src/optimize/runner.ts +21 -0
  169. package/src/optimize/spendingMessages.ts +44 -0
  170. package/src/optimize/spendingRunner.ts +21 -0
  171. package/src/optimize/spendingSolve.worker.ts +18 -0
  172. package/src/planner/AssumptionsCardPage.tsx +136 -0
  173. package/src/planner/BucketLensCard.tsx +114 -0
  174. package/src/planner/ComparePlansPage.tsx +219 -0
  175. package/src/planner/DisclaimerPage.tsx +88 -0
  176. package/src/planner/HowTestedPage.tsx +159 -0
  177. package/src/planner/LiveStatus.tsx +15 -0
  178. package/src/planner/LongevityModal.tsx +55 -0
  179. package/src/planner/Modal.tsx +97 -0
  180. package/src/planner/MonteCarloPage.tsx +907 -0
  181. package/src/planner/OptimizePage.tsx +611 -0
  182. package/src/planner/PlanContext.tsx +198 -0
  183. package/src/planner/PlanPickerPage.tsx +124 -0
  184. package/src/planner/PlanWorkspace.tsx +290 -0
  185. package/src/planner/ProvenancePanel.tsx +45 -0
  186. package/src/planner/RelocationComparePage.tsx +485 -0
  187. package/src/planner/ReportPage.tsx +375 -0
  188. package/src/planner/ResultsPage.tsx +817 -0
  189. package/src/planner/ScenariosPage.tsx +285 -0
  190. package/src/planner/SocialSecuritySection.tsx +556 -0
  191. package/src/planner/SpendingSolverPage.tsx +512 -0
  192. package/src/planner/SsAnalysisPage.tsx +1134 -0
  193. package/src/planner/SurvivalPercentileModal.tsx +161 -0
  194. package/src/planner/SurvivorTransitionPage.tsx +286 -0
  195. package/src/planner/assumptionsExport.ts +371 -0
  196. package/src/planner/bucketLens.ts +89 -0
  197. package/src/planner/chartFrame.ts +8 -0
  198. package/src/planner/chartStyle.ts +11 -0
  199. package/src/planner/dialogViews.tsx +184 -0
  200. package/src/planner/dialogs.tsx +133 -0
  201. package/src/planner/examples/ExampleLibrary.tsx +189 -0
  202. package/src/planner/examples/ExamplePreviewBanner.tsx +55 -0
  203. package/src/planner/examples/ExamplesPage.tsx +25 -0
  204. package/src/planner/examples/OpenExampleButton.tsx +61 -0
  205. package/src/planner/examples/buildAggressiveSaver.ts +102 -0
  206. package/src/planner/examples/buildAnnuityEstate.ts +137 -0
  207. package/src/planner/examples/buildBaristaFire.ts +115 -0
  208. package/src/planner/examples/buildBracketFillRoth.ts +65 -0
  209. package/src/planner/examples/buildBridgeEarlyRetirement.ts +94 -0
  210. package/src/planner/examples/buildBrokerageNoHsa.ts +109 -0
  211. package/src/planner/examples/buildCoastFire.ts +88 -0
  212. package/src/planner/examples/buildContext.ts +20 -0
  213. package/src/planner/examples/buildEarlyCareerMatch.ts +93 -0
  214. package/src/planner/examples/buildEarlyRetireeAca.ts +61 -0
  215. package/src/planner/examples/buildExampleCouple.ts +103 -0
  216. package/src/planner/examples/buildFixedTargetSpending.ts +74 -0
  217. package/src/planner/examples/buildGlidepathAllocation.ts +131 -0
  218. package/src/planner/examples/buildGuardrailsFlex.ts +120 -0
  219. package/src/planner/examples/buildHsaPropertyDepth.ts +109 -0
  220. package/src/planner/examples/buildHsaStealthRetirement.ts +97 -0
  221. package/src/planner/examples/buildLeanFatFire.ts +109 -0
  222. package/src/planner/examples/buildLtcShock.ts +62 -0
  223. package/src/planner/examples/buildMovingStateTax.ts +53 -0
  224. package/src/planner/examples/buildNoAnnuityBrokerage.ts +92 -0
  225. package/src/planner/examples/buildRmdIrmaa.ts +55 -0
  226. package/src/planner/examples/buildSalaryGrowthEscalation.ts +96 -0
  227. package/src/planner/examples/buildStaticAllocationControl.ts +96 -0
  228. package/src/planner/examples/buildSurvivorYears.ts +62 -0
  229. package/src/planner/examples/buildUnderSavedSingle.ts +51 -0
  230. package/src/planner/examples/exampleCopy.ts +23 -0
  231. package/src/planner/examples/loadExample.ts +90 -0
  232. package/src/planner/examples/registry.ts +313 -0
  233. package/src/planner/explainPanels.tsx +233 -0
  234. package/src/planner/fields.tsx +381 -0
  235. package/src/planner/format.ts +33 -0
  236. package/src/planner/home/DataAndPrivacyCard.tsx +56 -0
  237. package/src/planner/home/GettingStartedPaths.tsx +46 -0
  238. package/src/planner/home/GettingStartedReopener.tsx +32 -0
  239. package/src/planner/home/StartHereLinks.tsx +22 -0
  240. package/src/planner/home/WelcomeHero.tsx +39 -0
  241. package/src/planner/home/YourPlans.tsx +72 -0
  242. package/src/planner/home/importErrorMessage.ts +22 -0
  243. package/src/planner/home/startHereSlugs.ts +7 -0
  244. package/src/planner/home/useHomeData.ts +190 -0
  245. package/src/planner/home/useHomeMode.ts +47 -0
  246. package/src/planner/householdActions.ts +22 -0
  247. package/src/planner/insights/InsightCardView.tsx +340 -0
  248. package/src/planner/insights/InsightsPage.tsx +204 -0
  249. package/src/planner/insights/categoryLabels.ts +11 -0
  250. package/src/planner/learnLinks.ts +85 -0
  251. package/src/planner/marketModelPicker.ts +172 -0
  252. package/src/planner/optimizePageChart.ts +40 -0
  253. package/src/planner/optimizePageClaim.ts +64 -0
  254. package/src/planner/planCompleteness.ts +27 -0
  255. package/src/planner/planContextCore.ts +26 -0
  256. package/src/planner/planner.css +2304 -0
  257. package/src/planner/provenanceLinks.ts +25 -0
  258. package/src/planner/sections/AccountFields.tsx +872 -0
  259. package/src/planner/sections/AccountsSection.tsx +89 -0
  260. package/src/planner/sections/AllocationPanel.tsx +261 -0
  261. package/src/planner/sections/AssumptionsSection.tsx +256 -0
  262. package/src/planner/sections/HouseholdSection.tsx +243 -0
  263. package/src/planner/sections/IncomeFloorSection.tsx +418 -0
  264. package/src/planner/sections/IncomeSection.tsx +170 -0
  265. package/src/planner/sections/InsuranceSection.tsx +362 -0
  266. package/src/planner/sections/SpendingSection.tsx +904 -0
  267. package/src/planner/sections/StrategySection.tsx +349 -0
  268. package/src/planner/sections/UpdateBalancesPanel.tsx +182 -0
  269. package/src/planner/sections/sectionHelpers.ts +48 -0
  270. package/src/planner/sections/shared.tsx +15 -0
  271. package/src/planner/sections.tsx +15 -0
  272. package/src/planner/ssAnalysis.ts +325 -0
  273. package/src/planner/successBand.ts +20 -0
  274. package/src/planner/survivorAnalysis.ts +277 -0
  275. package/src/planner/usStates.ts +19 -0
  276. package/src/planner/useMcSuccessRate.ts +77 -0
  277. package/src/planner/useProjection.ts +63 -0
  278. package/src/relocation/messages.ts +21 -0
  279. package/src/relocation/relocation.worker.ts +18 -0
  280. package/src/relocation/runRelocation.ts +17 -0
  281. package/src/relocation/runner.ts +22 -0
  282. package/src/report/brandingContext.ts +15 -0
  283. package/src/report/downloadReport.ts +34 -0
  284. package/src/report/reportHtml.ts +547 -0
  285. package/src/routes/LearnRoutes.tsx +46 -0
  286. package/src/routes/PlanRoutes.tsx +55 -0
  287. package/src/routes/RouteFallback.tsx +9 -0
  288. package/src/socialSecurity/breakEven.ts +107 -0
  289. package/src/socialSecurity/expectedPv.ts +164 -0
  290. package/src/socialSecurity/explain.ts +92 -0
  291. package/src/socialSecurity/ficaReturn.ts +81 -0
  292. package/src/socialSecurity/persistedSsGuard.ts +138 -0
  293. package/src/socialSecurity/ssFormUtils.ts +48 -0
  294. package/src/socialSecurity/ssaStatementXml.ts +156 -0
  295. package/src/socialSecurity/storage.ts +69 -0
  296. package/src/socialSecurity/survivorSwitching.ts +153 -0
  297. package/src/testSupport/samplePlan.ts +2 -0
  298. package/src/workers/run.ts +45 -0
@@ -0,0 +1,502 @@
1
+ /**
2
+ * Learning Center registry (V9 PR1).
3
+ *
4
+ * Articles are authored as structured TypeScript so prose, visuals (added in
5
+ * PR2), and metadata live together, bundle for offline use, and stay type-safe.
6
+ * This module owns the article metadata model, the category set, the topic
7
+ * inventory, and small selectors used by the Learning Center pages.
8
+ */
9
+
10
+ export type LearningCategoryId =
11
+ | 'start-here'
12
+ | 'using-retiregolden'
13
+ | 'example-plans'
14
+ | 'accounts-saving'
15
+ | 'taxes'
16
+ | 'social-security'
17
+ | 'healthcare'
18
+ | 'withdrawals-roth'
19
+ | 'risk-uncertainty'
20
+ | 'insurance-estate'
21
+ | 'early-investing-fire'
22
+ | 'assumptions'
23
+ | 'glossary'
24
+
25
+ export type ArticleStatus = 'stub' | 'draft' | 'ready' | 'needs-review'
26
+ export type ReviewCadence = 'annual' | 'rule-change' | 'stable'
27
+ export type Audience = 'beginner' | 'intermediate'
28
+ export type Priority = 'P0' | 'P1' | 'P2'
29
+
30
+ /** One variable in a {@link FormulaBlockData}: the symbol and what it means. */
31
+ export type FormulaVariable = { symbol: string; meaning: string }
32
+
33
+ /** One labelled assumption row in a {@link ScenarioBlockData}. */
34
+ export type ScenarioAssumption = { label: string; value: string }
35
+
36
+ /** A teaching figure: a registered chart or an image, with a text equivalent. */
37
+ export type FigureBlockData = {
38
+ type: 'figure'
39
+ /** Key into the chart registry (learn/components/charts). */
40
+ chartId?: string
41
+ /** Static image alternative to a chart. */
42
+ image?: { src: string }
43
+ /** Short descriptive caption shown under the figure. */
44
+ caption: string
45
+ /** Text equivalent of the figure for screen readers and no-image fallback. */
46
+ alt: string
47
+ /** Optional note when the figure is based on rules, data, or an assumption. */
48
+ sourceNote?: string
49
+ }
50
+
51
+ export type FormulaBlockData = {
52
+ type: 'formula'
53
+ /** The formula itself, e.g. "real = nominal ÷ (1 + inflation)^years". */
54
+ expression: string
55
+ /** Each symbol used in the expression and its plain-language meaning. */
56
+ where?: FormulaVariable[]
57
+ /** Whether dollars in the formula are nominal or in today's dollars. */
58
+ basis?: 'nominal' | 'today'
59
+ /** What the simple formula leaves out. */
60
+ note?: string
61
+ }
62
+
63
+ export type ScenarioBlockData = {
64
+ type: 'scenario'
65
+ /** A short name for the example household, e.g. "The Reyes household". */
66
+ name: string
67
+ assumptions: ScenarioAssumption[]
68
+ /** Optional plain-language wrap-up of what the scenario shows. */
69
+ summary?: string
70
+ }
71
+
72
+ export type TableBlockData = {
73
+ type: 'table'
74
+ caption?: string
75
+ /** Column headers; the first column is treated as a row header. */
76
+ columns: string[]
77
+ /** Rows of cells, aligned to `columns`. Cells support inline markdown. */
78
+ rows: string[][]
79
+ }
80
+
81
+ /**
82
+ * Content blocks for a `ready` article. Text blocks render plain; the richer
83
+ * blocks (figure, formula, scenario, table) render through dedicated components
84
+ * in learn/components.
85
+ */
86
+ export type ArticleBlock =
87
+ | { type: 'prose'; md: string }
88
+ | { type: 'heading'; text: string; level?: 2 | 3 }
89
+ | { type: 'list'; ordered?: boolean; items: string[] }
90
+ | { type: 'callout'; tone?: 'note' | 'warn'; md: string }
91
+ | FigureBlockData
92
+ | FormulaBlockData
93
+ | ScenarioBlockData
94
+ | TableBlockData
95
+
96
+ export type LearningArticle = {
97
+ slug: string
98
+ title: string
99
+ description: string
100
+ category: LearningCategoryId
101
+ tags: string[]
102
+ audience: Audience
103
+ status: ArticleStatus
104
+ /** ISO date (YYYY-MM-DD) the content was last reviewed. */
105
+ lastReviewed: string
106
+ reviewCadence: ReviewCadence
107
+ sourceUrls: string[]
108
+ /** Slugs of related articles. */
109
+ relatedArticles: string[]
110
+ /** Planner route patterns this article relates to (see KNOWN_PLANNER_ROUTES). */
111
+ relatedPlannerRoutes: string[]
112
+ /** True when the article leans on current-year dollar figures or rules. */
113
+ currentYearSensitive: boolean
114
+ priority?: Priority
115
+ /** Surfaced in the Learning Center home "Featured" strip. */
116
+ featured?: boolean
117
+ /** Present once an article reaches `draft`/`ready`/`needs-review`. */
118
+ blocks?: ArticleBlock[]
119
+ /** When set, ArticleShell offers "Open this example in the planner". */
120
+ exampleId?: string
121
+ }
122
+
123
+ export type LearningCategory = {
124
+ id: LearningCategoryId
125
+ label: string
126
+ blurb: string
127
+ /** Glossary is browsed via /learn/glossary rather than per-article pages. */
128
+ isGlossary?: boolean
129
+ }
130
+
131
+ export const LEARNING_CATEGORIES: LearningCategory[] = [
132
+ { id: 'start-here', label: 'Start Here', blurb: 'Basics, vocabulary, and how to read a retirement plan.' },
133
+ { id: 'using-retiregolden', label: 'Using RetireGolden', blurb: 'Tool-specific workflows and screen explanations.' },
134
+ {
135
+ id: 'assumptions',
136
+ label: 'Assumptions',
137
+ blurb: 'The forward-looking defaults behind your plan — inflation, returns, longevity — and the sources for each.',
138
+ },
139
+ {
140
+ id: 'example-plans',
141
+ label: 'Example Plans',
142
+ blurb: 'Worked example households you can open in the planner.',
143
+ },
144
+ {
145
+ id: 'accounts-saving',
146
+ label: 'Accounts and Saving',
147
+ blurb: 'Account types, contributions, savings order, and employer match.',
148
+ },
149
+ { id: 'taxes', label: 'Taxes', blurb: 'Brackets, deductions, MAGI, capital gains, state tax, and cliffs.' },
150
+ {
151
+ id: 'social-security',
152
+ label: 'Social Security',
153
+ blurb: 'PIA, claiming age, spousal and survivor benefits, and break-even.',
154
+ },
155
+ { id: 'healthcare', label: 'Healthcare', blurb: 'ACA, Medicare, IRMAA, HSAs, and long-term-care costs.' },
156
+ {
157
+ id: 'withdrawals-roth',
158
+ label: 'Withdrawals and Roth',
159
+ blurb: 'Withdrawal order, Roth conversions, the optimizer, RMDs, and QCDs.',
160
+ },
161
+ {
162
+ id: 'risk-uncertainty',
163
+ label: 'Risk and Uncertainty',
164
+ blurb: 'Monte Carlo, sequence risk, inflation, and longevity.',
165
+ },
166
+ {
167
+ id: 'insurance-estate',
168
+ label: 'Insurance and Estate',
169
+ blurb: 'LTC insurance, permanent life, survivor planning, and heirs.',
170
+ },
171
+ {
172
+ id: 'early-investing-fire',
173
+ label: 'Early Investing & FIRE',
174
+ blurb: 'Saving in your 20s–40s, savings rate, the FI number, and early-retirement strategies.',
175
+ },
176
+ { id: 'glossary', label: 'Glossary', blurb: 'Plain-language definitions of terms used across the app.', isGlossary: true },
177
+ ]
178
+
179
+ /**
180
+ * Planner route patterns an article may reference. Used by the registry-integrity
181
+ * test so `relatedPlannerRoutes` cannot drift from real app routes.
182
+ */
183
+ export const KNOWN_PLANNER_ROUTES = [
184
+ '/',
185
+ '/compare',
186
+ '/import',
187
+ '/plan/:planId/household',
188
+ '/plan/:planId/social-security',
189
+ '/plan/:planId/accounts',
190
+ '/plan/:planId/insurance',
191
+ '/plan/:planId/income',
192
+ '/plan/:planId/income-floor',
193
+ '/plan/:planId/spending',
194
+ '/plan/:planId/strategy',
195
+ '/plan/:planId/assumptions',
196
+ '/plan/:planId/insights',
197
+ '/plan/:planId/social-security-analysis',
198
+ '/plan/:planId/survivor',
199
+ '/plan/:planId/results',
200
+ '/plan/:planId/monte-carlo',
201
+ '/plan/:planId/scenarios',
202
+ '/plan/:planId/relocation',
203
+ '/plan/:planId/optimize',
204
+ '/plan/:planId/spending-solver',
205
+ '/plan/:planId/report',
206
+ ] as const
207
+
208
+ // Articles live in ./content as pure data, keeping this module free of
209
+ // JSX/content concerns.
210
+ import { aboutRetireGoldenArticle } from './content/about-retiregolden'
211
+ import { accountTypesOverviewArticle } from './content/account-types-overview'
212
+ import { acaPremiumTaxCreditsAndMagiArticle } from './content/aca-premium-tax-credits-and-magi'
213
+ import { appealingIrmaaSsa44Article } from './content/appealing-irmaa-ssa-44'
214
+ import { afterTaxEstateArticle } from './content/after-tax-estate'
215
+ import { agiMagiAndTaxableIncomeArticle } from './content/agi-magi-and-taxable-income'
216
+ import { beneficiariesAccountTitlingArticle } from './content/beneficiaries-and-account-titling'
217
+ import { breakEvenUsefulLensArticle } from './content/break-even-useful-lens'
218
+ import { buildingRetirementSpendingBudgetArticle } from './content/building-a-retirement-spending-budget'
219
+ import { colaInflationProtectionArticle } from './content/cola-and-inflation-protection'
220
+ import { divorcedSpousalSurvivorRecordsArticle } from './content/divorced-spousal-and-survivor-records'
221
+ import { dynamicSpendingGuardrailsArticle } from './content/dynamic-spending-guardrails'
222
+ import { employerMatchAndContributionOrderArticle } from './content/employer-match-and-contribution-order'
223
+ import { earningsTestBeforeFraArticle } from './content/earnings-test-before-fra'
224
+ import { EXAMPLE_PLAN_ARTICLES } from './content/examplePlanArticles'
225
+ import { feesExpenseRatiosCompoundingDragArticle } from './content/fees-expense-ratios-and-compounding-drag'
226
+ import { fillingTaxBracketArticle } from './content/filling-a-tax-bracket-with-roth-conversions'
227
+ import { healthcareAfter65Article } from './content/healthcare-after-65'
228
+ import { healthcareBefore65Article } from './content/healthcare-before-65'
229
+ import { historicalVsRandomReturnModelsArticle } from './content/historical-vs-random-return-models'
230
+ import { howAssumptionsChangeAnswerArticle } from './content/how-assumptions-change-the-answer'
231
+ import { howMuchCanISpendArticle } from './content/how-much-can-i-spend'
232
+ import { howSocialSecurityIsTaxedArticle } from './content/how-social-security-is-taxed'
233
+ import { howTheOptimizerThinksArticle } from './content/how-the-optimizer-thinks'
234
+ import { hsasQualifiedMedicalExpensesArticle } from './content/hsas-and-qualified-medical-expenses'
235
+ import { hsasAsRetirementAccountsArticle } from './content/hsas-as-retirement-accounts'
236
+ import { optimizerAfterTaxEstateArticle } from './content/how-the-optimizer-values-after-tax-estate'
237
+ import { howToReadProjectionArticle } from './content/how-to-read-a-retirement-projection'
238
+ import { inflationRiskArticle } from './content/inflation-risk'
239
+ import { inheritedIraTenYearRuleArticle } from './content/inherited-ira-10-year-rule'
240
+ import { insuranceInYourRetirementPlanArticle } from './content/insurance-in-your-retirement-plan'
241
+ import { irmaaTwoYearLookbackArticle } from './content/irmaa-two-year-lookback'
242
+ import { longevityRiskArticle } from './content/longevity-risk'
243
+ import { longTermCareCostsInsuranceArticle } from './content/long-term-care-costs-and-insurance'
244
+ import { longTermCareInsuranceRiskTransferArticle } from './content/long-term-care-insurance-as-risk-transfer'
245
+ import { medicarePartBVsPartDIrmaaArticle } from './content/medicare-part-b-vs-part-d-irmaa'
246
+ import { mortalityWeightedSocialSecurityArticle } from './content/mortality-weighted-social-security'
247
+ import { movingToRetireGoldenArticle } from './content/moving-to-retiregolden'
248
+ import { seedYourPlanFromYourTaxReturnArticle } from './content/seed-your-plan-from-your-tax-return'
249
+ import { marginalVsEffectiveTaxRateArticle } from './content/marginal-vs-effective-tax-rate'
250
+ import { niitHighIncomeInvestmentTaxArticle } from './content/niit-high-income-investment-tax'
251
+ import { ordinaryIncomeVsCapitalGainsArticle } from './content/ordinary-income-vs-capital-gains'
252
+ import { payingConversionTaxesTaxableVsIraArticle } from './content/paying-conversion-taxes-taxable-vs-ira'
253
+ import { piaAimeBendPointsArticle } from './content/pia-aime-and-bend-points'
254
+ import { planningForCouplesAndSurvivorYearsArticle } from './content/planning-for-couples-and-survivor-years'
255
+ import { plannerOverviewArticle } from './content/planner-overview'
256
+ import { pensionsAndAnnuitiesArticle } from './content/pensions-and-annuities'
257
+ import { permanentLifeInsurancePlanArticle } from './content/permanent-life-insurance-in-a-plan'
258
+ import { privacyWhatStaysInYourBrowserArticle } from './content/privacy-what-stays-in-your-browser'
259
+ import { qcdsQualifiedCharitableDistributionsArticle } from './content/qcds-qualified-charitable-distributions'
260
+ import { readingResultsPageArticle } from './content/reading-the-results-page'
261
+ import { readingSocialSecurityAnalysisPageArticle } from './content/reading-the-social-security-analysis-page'
262
+ import { realEstateHomeEquityDebtArticle } from './content/real-estate-home-equity-and-debt'
263
+ import { reportsCsvExportsAndSharingArticle } from './content/reports-csv-exports-and-sharing'
264
+ import { fundedRatioArticle } from './content/funded-ratio'
265
+ import { riskBasedGuardrailsArticle } from './content/risk-based-guardrails'
266
+ import { socialSecurityBridgeArticle } from './content/social-security-bridge'
267
+ import { tipsLaddersArticle } from './content/tips-ladders'
268
+ import { rmdsRequiredMinimumDistributionsArticle } from './content/rmds-required-minimum-distributions'
269
+ import { rothConversionBasicsArticle } from './content/roth-conversion-basics'
270
+ import { rsusAndEsppArticle } from './content/rsus-and-espp'
271
+ import { ruleOf55And72tArticle } from './content/rule-of-55-and-72t'
272
+ import { sensitivityTestingWhatChangesAnswerArticle } from './content/sensitivity-testing-what-changes-the-answer'
273
+ import { sequenceOfReturnsRiskArticle } from './content/sequence-of-returns-risk'
274
+ import { socialSecurityClaimingAgeArticle } from './content/social-security-claiming-age-basics'
275
+ import { socialSecurityTaxesVsBenefitsArticle } from './content/social-security-taxes-vs-benefits'
276
+ import { ssdiAndRetirementPlanningArticle } from './content/ssdi-and-retirement-planning'
277
+ import { spousalSurvivorBenefitsArticle } from './content/spousal-and-survivor-benefits'
278
+ import { spendingProfilesAndRetirementSmileArticle } from './content/spending-profiles-and-the-retirement-smile'
279
+ import { standardDeductionSeniorDeductionItemizingArticle } from './content/standard-deduction-senior-deduction-and-itemizing'
280
+ import { stateIncomeTaxesRetirementArticle } from './content/state-income-taxes-in-retirement'
281
+ import { stepUpInBasisArticle } from './content/step-up-in-basis'
282
+ import { survivorPlanningForCouplesArticle } from './content/survivor-planning-for-couples'
283
+ import { survivorSpendingInCouplePlansArticle } from './content/survivor-spending-in-couple-plans'
284
+ import { taxableBrokerageBasisAndCapitalGainsArticle } from './content/taxable-brokerage-basis-and-capital-gains'
285
+ import { taxCliffsBracketEdgesArticle } from './content/tax-cliffs-and-bracket-edges'
286
+ import { taxLossGainHarvestingArticle } from './content/tax-loss-and-gain-harvesting'
287
+ import { threeBigQuestionsSpendingTimeRiskArticle } from './content/three-big-questions-spending-time-risk'
288
+ import { todaysDollarsArticle } from './content/todays-dollars-vs-future-dollars'
289
+ import { traditionalVsRothContributionsArticle } from './content/traditional-vs-roth-contributions'
290
+ import { troubleshootingSurprisingResultsArticle } from './content/troubleshooting-surprising-results'
291
+ import { trustFundHaircutScenariosArticle } from './content/trust-fund-haircut-scenarios'
292
+ import { understandingMonteCarloArticle } from './content/understanding-monte-carlo-success-rate'
293
+ import { usingAssumptionsAndProvenanceArticle } from './content/using-assumptions-and-provenance'
294
+ import { usingScenariosToCompareChoicesArticle } from './content/using-scenarios-to-compare-choices'
295
+ import { whatChangesWhenYouMoveStatesArticle } from './content/what-changes-when-you-move-states'
296
+ import { widowsPenaltyAndSurvivorBracketsArticle } from './content/widows-penalty-and-survivor-brackets'
297
+ import { whatRetireGoldenModelsArticle } from './content/what-retiregolden-models'
298
+ import { whatMonteCarloProvesArticle } from './content/what-monte-carlo-proves'
299
+ import { whatRetirementHealthcareReallyCostsArticle } from './content/what-retirement-healthcare-really-costs'
300
+ import { withdrawalOrderBasicsArticle } from './content/withdrawal-order-basics'
301
+ import { why95PercentIsNotGuaranteeArticle } from './content/why-95-percent-is-not-a-guarantee'
302
+ import { rothConversionsRaiseOtherCostsArticle } from './content/why-roth-conversions-raise-other-costs'
303
+ import { whySmallTaxCliffsCanMatterArticle } from './content/why-small-tax-cliffs-can-matter'
304
+ import { whatIsFireArticle } from './content/what-is-fire'
305
+ import { savingsRateBiggestLeverArticle } from './content/savings-rate-biggest-lever'
306
+ import { fiNumberAndFourPercentRuleArticle } from './content/fi-number-and-four-percent-rule'
307
+ import { howToModelAccumulationArticle } from './content/how-to-model-accumulation'
308
+ import { understandingYourPlanAssumptionsArticle } from './content/understanding-your-plan-assumptions'
309
+ import { assumptionGeneralInflationArticle } from './content/assumption-general-inflation'
310
+ import { assumptionHealthcareInflationArticle } from './content/assumption-healthcare-inflation'
311
+ import { assumptionInvestmentReturnsArticle } from './content/assumption-investment-returns'
312
+ import { assumptionSocialSecurityColaArticle } from './content/assumption-social-security-cola'
313
+ import { assumptionSocialSecurityTrustFundArticle } from './content/assumption-social-security-trust-fund'
314
+ import { assumptionLongevityPlanningAgeArticle } from './content/assumption-longevity-planning-age'
315
+ import { assumptionStateTaxOverrideArticle } from './content/assumption-state-tax-override'
316
+ import { assumptionRecentMagiArticle } from './content/assumption-recent-magi'
317
+ import { assumptionHeirTaxRateArticle } from './content/assumption-heir-tax-rate'
318
+
319
+ /** Every article in registry order. */
320
+ export const LEARNING_ARTICLES: LearningArticle[] = [
321
+ aboutRetireGoldenArticle,
322
+ howToReadProjectionArticle,
323
+ todaysDollarsArticle,
324
+ whatRetireGoldenModelsArticle,
325
+ readingResultsPageArticle,
326
+ understandingMonteCarloArticle,
327
+ rothConversionBasicsArticle,
328
+ fillingTaxBracketArticle,
329
+ optimizerAfterTaxEstateArticle,
330
+ marginalVsEffectiveTaxRateArticle,
331
+ rothConversionsRaiseOtherCostsArticle,
332
+ socialSecurityClaimingAgeArticle,
333
+ piaAimeBendPointsArticle,
334
+ breakEvenUsefulLensArticle,
335
+ spousalSurvivorBenefitsArticle,
336
+ ssdiAndRetirementPlanningArticle,
337
+ socialSecurityTaxesVsBenefitsArticle,
338
+ earningsTestBeforeFraArticle,
339
+ colaInflationProtectionArticle,
340
+ divorcedSpousalSurvivorRecordsArticle,
341
+ dynamicSpendingGuardrailsArticle,
342
+ riskBasedGuardrailsArticle,
343
+ tipsLaddersArticle,
344
+ socialSecurityBridgeArticle,
345
+ fundedRatioArticle,
346
+ buildingRetirementSpendingBudgetArticle,
347
+ spendingProfilesAndRetirementSmileArticle,
348
+ survivorSpendingInCouplePlansArticle,
349
+ howMuchCanISpendArticle,
350
+ trustFundHaircutScenariosArticle,
351
+ mortalityWeightedSocialSecurityArticle,
352
+ irmaaTwoYearLookbackArticle,
353
+ appealingIrmaaSsa44Article,
354
+ acaPremiumTaxCreditsAndMagiArticle,
355
+ agiMagiAndTaxableIncomeArticle,
356
+ healthcareBefore65Article,
357
+ healthcareAfter65Article,
358
+ whatRetirementHealthcareReallyCostsArticle,
359
+ hsasQualifiedMedicalExpensesArticle,
360
+ longTermCareCostsInsuranceArticle,
361
+ medicarePartBVsPartDIrmaaArticle,
362
+ withdrawalOrderBasicsArticle,
363
+ rmdsRequiredMinimumDistributionsArticle,
364
+ qcdsQualifiedCharitableDistributionsArticle,
365
+ widowsPenaltyAndSurvivorBracketsArticle,
366
+ payingConversionTaxesTaxableVsIraArticle,
367
+ ruleOf55And72tArticle,
368
+ inheritedIraTenYearRuleArticle,
369
+ accountTypesOverviewArticle,
370
+ traditionalVsRothContributionsArticle,
371
+ employerMatchAndContributionOrderArticle,
372
+ hsasAsRetirementAccountsArticle,
373
+ taxableBrokerageBasisAndCapitalGainsArticle,
374
+ pensionsAndAnnuitiesArticle,
375
+ realEstateHomeEquityDebtArticle,
376
+ rsusAndEsppArticle,
377
+ feesExpenseRatiosCompoundingDragArticle,
378
+ longTermCareInsuranceRiskTransferArticle,
379
+ permanentLifeInsurancePlanArticle,
380
+ insuranceInYourRetirementPlanArticle,
381
+ survivorPlanningForCouplesArticle,
382
+ afterTaxEstateArticle,
383
+ stepUpInBasisArticle,
384
+ beneficiariesAccountTitlingArticle,
385
+ sequenceOfReturnsRiskArticle,
386
+ whatMonteCarloProvesArticle,
387
+ historicalVsRandomReturnModelsArticle,
388
+ inflationRiskArticle,
389
+ longevityRiskArticle,
390
+ threeBigQuestionsSpendingTimeRiskArticle,
391
+ howAssumptionsChangeAnswerArticle,
392
+ planningForCouplesAndSurvivorYearsArticle,
393
+ whySmallTaxCliffsCanMatterArticle,
394
+ plannerOverviewArticle,
395
+ usingScenariosToCompareChoicesArticle,
396
+ readingSocialSecurityAnalysisPageArticle,
397
+ usingAssumptionsAndProvenanceArticle,
398
+ reportsCsvExportsAndSharingArticle,
399
+ privacyWhatStaysInYourBrowserArticle,
400
+ movingToRetireGoldenArticle,
401
+ seedYourPlanFromYourTaxReturnArticle,
402
+ troubleshootingSurprisingResultsArticle,
403
+ howTheOptimizerThinksArticle,
404
+ ordinaryIncomeVsCapitalGainsArticle,
405
+ standardDeductionSeniorDeductionItemizingArticle,
406
+ howSocialSecurityIsTaxedArticle,
407
+ niitHighIncomeInvestmentTaxArticle,
408
+ stateIncomeTaxesRetirementArticle,
409
+ whatChangesWhenYouMoveStatesArticle,
410
+ taxCliffsBracketEdgesArticle,
411
+ taxLossGainHarvestingArticle,
412
+ why95PercentIsNotGuaranteeArticle,
413
+ sensitivityTestingWhatChangesAnswerArticle,
414
+ whatIsFireArticle,
415
+ savingsRateBiggestLeverArticle,
416
+ fiNumberAndFourPercentRuleArticle,
417
+ howToModelAccumulationArticle,
418
+ understandingYourPlanAssumptionsArticle,
419
+ assumptionGeneralInflationArticle,
420
+ assumptionHealthcareInflationArticle,
421
+ assumptionInvestmentReturnsArticle,
422
+ assumptionSocialSecurityColaArticle,
423
+ assumptionSocialSecurityTrustFundArticle,
424
+ assumptionLongevityPlanningAgeArticle,
425
+ assumptionStateTaxOverrideArticle,
426
+ assumptionRecentMagiArticle,
427
+ assumptionHeirTaxRateArticle,
428
+ ...EXAMPLE_PLAN_ARTICLES,
429
+ ]
430
+
431
+ const ARTICLES_BY_SLUG = new Map(LEARNING_ARTICLES.map((a) => [a.slug, a]))
432
+
433
+ export function getArticle(slug: string): LearningArticle | undefined {
434
+ return ARTICLES_BY_SLUG.get(slug)
435
+ }
436
+
437
+ /** An article is browsable as its own page only once it has real content. */
438
+ export function isReadable(article: LearningArticle): boolean {
439
+ return article.status === 'ready' && (article.blocks?.length ?? 0) > 0
440
+ }
441
+
442
+ export function getCategory(id: LearningCategoryId): LearningCategory | undefined {
443
+ return LEARNING_CATEGORIES.find((c) => c.id === id)
444
+ }
445
+
446
+ export function articlesInCategory(id: LearningCategoryId): LearningArticle[] {
447
+ return LEARNING_ARTICLES.filter((a) => a.category === id)
448
+ }
449
+
450
+ /**
451
+ * Readable articles that relate to a planner route, for contextual "Learn about
452
+ * this screen" links. Derived from each article's `relatedPlannerRoutes` so the
453
+ * planner↔article wiring has a single source of truth and a screen's links
454
+ * light up automatically as content lands. Stubs are excluded so a contextual
455
+ * link never dead-ends on a "Planned article" placeholder.
456
+ */
457
+ export function articlesForRoute(routePattern: string): LearningArticle[] {
458
+ return LEARNING_ARTICLES.filter((a) => isReadable(a) && a.relatedPlannerRoutes.includes(routePattern))
459
+ }
460
+
461
+ /** Non-glossary categories paired with their article counts, for the home grid. */
462
+ export function categorySummaries(): Array<{ category: LearningCategory; count: number }> {
463
+ return LEARNING_CATEGORIES.filter((c) => !c.isGlossary).map((category) => ({
464
+ category,
465
+ count: articlesInCategory(category.id).length,
466
+ }))
467
+ }
468
+
469
+ export function featuredArticles(): LearningArticle[] {
470
+ return LEARNING_ARTICLES.filter((a) => a.featured)
471
+ }
472
+
473
+ /** Readable articles, most recently reviewed first. */
474
+ export function recentlyReviewed(limit = 5): LearningArticle[] {
475
+ return LEARNING_ARTICLES.filter(isReadable)
476
+ .slice()
477
+ .sort((a, b) => b.lastReviewed.localeCompare(a.lastReviewed))
478
+ .slice(0, limit)
479
+ }
480
+
481
+ const PRIORITY_ORDER: Record<Priority, number> = { P0: 0, P1: 1, P2: 2 }
482
+
483
+ /** Sort for listing: readable first, then by priority, then alphabetically. */
484
+ export function byListingOrder(a: LearningArticle, b: LearningArticle): number {
485
+ const ra = isReadable(a) ? 0 : 1
486
+ const rb = isReadable(b) ? 0 : 1
487
+ if (ra !== rb) return ra - rb
488
+ const pa = a.priority ? PRIORITY_ORDER[a.priority] : 3
489
+ const pb = b.priority ? PRIORITY_ORDER[b.priority] : 3
490
+ if (pa !== pb) return pa - pb
491
+ return a.title.localeCompare(b.title)
492
+ }
493
+
494
+ /** Case-insensitive search across title, description, and tags. */
495
+ export function searchArticles(query: string): LearningArticle[] {
496
+ const q = query.trim().toLowerCase()
497
+ if (!q) return []
498
+ return LEARNING_ARTICLES.filter((a) => {
499
+ const haystack = [a.title, a.description, ...a.tags].join(' ').toLowerCase()
500
+ return haystack.includes(q)
501
+ }).sort(byListingOrder)
502
+ }
@@ -0,0 +1,85 @@
1
+ import { BASELINE_CITATION } from './constants'
2
+ import type { LongevityPersisted } from '@retiregolden/engine/longevity/types'
3
+
4
+ export interface LongevityResultsProps {
5
+ data: LongevityPersisted
6
+ onEdit: () => void
7
+ /** Clear persisted storage for this profile and reset parent UI. */
8
+ onClear: () => void
9
+ /** Overrides the main results `<h2>` text */
10
+ resultsHeading?: string
11
+ }
12
+
13
+ export function LongevityResults({ data, onEdit, onClear, resultsHeading }: LongevityResultsProps) {
14
+ const { answers, result, updatedAt } = data
15
+ const updated = new Date(updatedAt).toLocaleString()
16
+
17
+ return (
18
+ <div className="results">
19
+ <section className="disclaimer-box" aria-label="Disclaimer">
20
+ <p>
21
+ <strong>Educational estimate only.</strong> Not medical advice. This tool applies simple
22
+ adjustments to a population life table; it cannot predict your individual lifespan.
23
+ </p>
24
+ </section>
25
+
26
+ <section className="results-hero">
27
+ <h2>{resultsHeading ?? 'Estimated remaining years'}</h2>
28
+ <p className="results-central" aria-live="polite">
29
+ About <strong>{result.centralRemainingYears.toFixed(1)}</strong> years
30
+ </p>
31
+ <p className="muted">
32
+ Illustrative band (not a statistical confidence interval):{' '}
33
+ <strong>
34
+ {result.bandLowRemainingYears.toFixed(1)} – {result.bandHighRemainingYears.toFixed(1)}
35
+ </strong>{' '}
36
+ years remaining
37
+ </p>
38
+ <p className="muted">
39
+ For planning visuals only: living to roughly age{' '}
40
+ <strong>{result.illustrativePlanningAge}</strong> aligns with the central estimate above
41
+ (rounded).
42
+ </p>
43
+ </section>
44
+
45
+ <section className="results-detail">
46
+ <h3>How we got this</h3>
47
+ <ul className="results-list">
48
+ <li>
49
+ <strong>Population baseline:</strong> {result.baselineRemainingYears.toFixed(2)} remaining
50
+ years at age {answers.age} ({answers.sex === 'average' ? 'average of male/female' : `${answers.sex} table`}).
51
+ </li>
52
+ <li>
53
+ <strong>Lifestyle / health factor (combined):</strong>{' '}
54
+ {(result.appliedMultiplier * 100).toFixed(1)}% of baseline (raw {(result.rawMultiplier * 100).toFixed(1)}%,
55
+ clamped for stability).
56
+ </li>
57
+ <li>
58
+ <strong>Source:</strong>{' '}
59
+ <a href={BASELINE_CITATION.url} target="_blank" rel="noreferrer">
60
+ {BASELINE_CITATION.label}
61
+ </a>
62
+ . {BASELINE_CITATION.note}
63
+ </li>
64
+ </ul>
65
+ </section>
66
+
67
+ <p className="muted small">Last saved locally: {updated}</p>
68
+
69
+ <div className="wizard-actions">
70
+ <button type="button" className="btn btn-secondary" onClick={onEdit}>
71
+ Edit answers
72
+ </button>
73
+ <button
74
+ type="button"
75
+ className="btn btn-secondary"
76
+ onClick={() => {
77
+ onClear()
78
+ }}
79
+ >
80
+ Clear saved data
81
+ </button>
82
+ </div>
83
+ </div>
84
+ )
85
+ }