From 41cd6490a88fd8f3870b88627c86578efa200f92 Mon Sep 17 00:00:00 2001 From: azywicki <81277290+NimbleEngineer21@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:02:45 -0500 Subject: [PATCH 1/2] fix(copy): replace legally risky terms with safer alternatives Replace "financial planning" with "personal finance calculator" across SEO meta tags, sidebar, and setup wizard to avoid implying professional financial planning services. Soften directive language ("Buy down the rate", "CAN YOU AFFORD IT?", "Ready now", "You need X more") into neutral, informational phrasing that reduces legal risk of being construed as financial advice. Rename Readiness page from "Financial Readiness" to "Purchase Readiness" and strengthen footer disclaimer. Also clean up minor code quality issues in ConformingStatus (optional chaining, nested ternary, replaceAll, Number.parseFloat). --- index.html | 14 +++++++------- src/components/ConformingStatus.jsx | 20 ++++++++++---------- src/components/Layout.jsx | 2 +- src/lib/loanLimits.js | 2 +- src/lib/mortgageCalc.js | 8 ++++---- src/pages/Dashboard.jsx | 6 +++--- src/pages/PurchasePlanning.jsx | 6 +++--- src/pages/Readiness.jsx | 12 ++++++------ src/pages/SetupWizard.jsx | 2 +- 9 files changed, 36 insertions(+), 36 deletions(-) diff --git a/index.html b/index.html index 41bdb58..9e4acec 100644 --- a/index.html +++ b/index.html @@ -7,21 +7,21 @@ - GreenLight — Financial Planning Tool - - + GreenLight — Personal Finance Calculator + + - - + + - + @@ -31,7 +31,7 @@ "@context": "https://schema.org", "@type": "SoftwareApplication", "name": "GreenLight", - "description": "Privacy-first financial planning tool that runs entirely in your browser. No account required.", + "description": "Privacy-first personal finance calculator that runs entirely in your browser. No account required.", "applicationCategory": "FinanceApplication", "operatingSystem": "Web", "url": "https://gotthegreenlight.com/", diff --git a/src/components/ConformingStatus.jsx b/src/components/ConformingStatus.jsx index 4efd22a..a4c7a92 100644 --- a/src/components/ConformingStatus.jsx +++ b/src/components/ConformingStatus.jsx @@ -7,14 +7,14 @@ import { YEAR } from "../data/conformingLimits.js"; /** * Conforming Loan Status card — shows whether the loan is conforming or jumbo, - * with rate impact analysis and a suggestion to increase down payment. + * with rate impact analysis and a conforming down-payment option. */ export default function ConformingStatus({ zipCode, loanAmount, homePrice, currentDownPercent, baseRate, termYears, jumboSpread, zipInfo, onZipChange, onSpreadChange, onApplySuggestion, }) { - const hasZip = zipCode && zipCode.length === 5; + const hasZip = zipCode?.length === 5; const jumbo = useMemo( () => hasZip ? detectJumbo(loanAmount, zipCode, !!zipInfo) : null, @@ -35,9 +35,9 @@ export default function ConformingStatus({ ? calcEffectiveRate(baseRate, true, jumboSpread) : baseRate; - const locationLabel = zipInfo - ? `${zipInfo.city}, ${zipInfo.county} Co., ${zipInfo.state}` - : hasZip ? "Looking up..." : null; + let locationLabel = null; + if (zipInfo) locationLabel = `${zipInfo.city}, ${zipInfo.county} Co., ${zipInfo.state}`; + else if (hasZip) locationLabel = "Looking up..."; return (
@@ -57,7 +57,7 @@ export default function ConformingStatus({ placeholder="e.g. 32301" value={zipCode || ""} onChange={e => { - const val = e.target.value.replace(/\D/g, "").slice(0, 5); + const val = e.target.value.replaceAll(/\D/g, "").slice(0, 5); onZipChange(val); }} style={{ ...styles.input, width: 120 }} @@ -124,7 +124,7 @@ export default function ConformingStatus({ onSpreadChange(parseFloat(e.target.value) || 0)} + onChange={e => onSpreadChange(Number.parseFloat(e.target.value) || 0)} style={{ ...styles.input, width: 80, padding: "4px 8px", fontSize: 13 }} />
% above conforming
@@ -151,7 +151,7 @@ export default function ConformingStatus({ )}
- {/* Suggestion to increase down payment */} + {/* Conforming option — down payment to avoid jumbo */} {suggestion && (
- Increase down payment to{" "} + A down payment of{" "} {suggestion.requiredDownPercent.toFixed(1)}% - {" "}({fmt(suggestion.requiredDownAmount)}) to stay conforming. + {" "}({fmt(suggestion.requiredDownAmount)}) would keep the loan conforming. +{fmt(suggestion.additionalDown)} more needed diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index 52caa85..4ccaff5 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -74,7 +74,7 @@ export default function Layout({ sellDate, onSellDateChange, purchaseDate, onPur letterSpacing: 1, textTransform: "uppercase", }}> - Financial Planning + Personal Finance
diff --git a/src/lib/loanLimits.js b/src/lib/loanLimits.js index 079bcd0..719953d 100644 --- a/src/lib/loanLimits.js +++ b/src/lib/loanLimits.js @@ -43,7 +43,7 @@ export function calcEffectiveRate(baseRatePercent, isJumbo, jumboSpreadPercent = } /** - * Suggest a down payment increase to stay under the conforming limit. + * Calculate the down payment needed to stay under the conforming limit. * Returns null if the loan is already conforming. * @param {number} homePrice * @param {number} conformingLimit diff --git a/src/lib/mortgageCalc.js b/src/lib/mortgageCalc.js index 1dacf8a..96032c0 100644 --- a/src/lib/mortgageCalc.js +++ b/src/lib/mortgageCalc.js @@ -144,18 +144,18 @@ export function calcPointsBreakEven(pointsCost, monthlySavings, opportunityRateP } /** - * Buy-down recommendation signal based on break-even vs expected stay. + * Buy-down break-even signal based on break-even vs expected stay. * green: adjusted break-even <= 60% of stay (clear win) * yellow: 60-100% of stay (marginal) - * red: > stay or Infinity (don't buy down) + * red: > stay or Infinity (unlikely to break even) */ export function calcBuyDownSignal(adjustedBreakEvenMonths, expectedStayYears) { const stayMonths = expectedStayYears * 12; if (adjustedBreakEvenMonths === Infinity || adjustedBreakEvenMonths > stayMonths) { - return { signal: "red", label: "Skip the buy-down, invest instead" }; + return { signal: "red", label: "Buy-down unlikely to break even" }; } if (adjustedBreakEvenMonths <= stayMonths * 0.6) { - return { signal: "green", label: "Buy down the rate" }; + return { signal: "green", label: "Buy-down likely to break even" }; } return { signal: "yellow", label: "Marginal \u2014 depends on priorities" }; } diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 5037b3f..f1f1f80 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -332,10 +332,10 @@ function ReadinessWidget({ state, projections, statusResult, cashNeeded, monthly
{label}
{isReady - ? "Ready now" + ? "Funds available" : readinessDate - ? `Ready in ~${readinessDate.month} months` - : "Not reachable in 5 years"} + ? `Estimated in ~${readinessDate.month} months` + : "Not estimated within 5 years"}
diff --git a/src/pages/PurchasePlanning.jsx b/src/pages/PurchasePlanning.jsx index 1de4e88..bff9689 100644 --- a/src/pages/PurchasePlanning.jsx +++ b/src/pages/PurchasePlanning.jsx @@ -299,10 +299,10 @@ export default function PurchasePlanning({ state, updateState, prices }) { - {/* Liquidation Analysis — Can You Afford It? */} + {/* Liquidation Analysis — Purchase vs. Available Funds */}
- CAN YOU AFFORD IT? + PURCHASE VS. AVAILABLE FUNDS
@@ -342,7 +342,7 @@ export default function PurchasePlanning({ state, updateState, prices }) { {!liquidation.canAfford && (
- You need {fmt(liquidation.shortfall)} more to cover this purchase at current asset values. + There is a {fmt(liquidation.shortfall)} gap at current asset values.
)}
diff --git a/src/pages/Readiness.jsx b/src/pages/Readiness.jsx index 2aea751..e76a7fd 100644 --- a/src/pages/Readiness.jsx +++ b/src/pages/Readiness.jsx @@ -108,7 +108,7 @@ export default function Readiness({ state, updateState, prices }) { if (!purchase.category) { return (
-
Financial Readiness
+
Purchase Readiness
Activate purchase planning from the Dashboard to use readiness projections.
); @@ -116,7 +116,7 @@ export default function Readiness({ state, updateState, prices }) { return (
-

Financial Readiness

+

Purchase Readiness

{/* Hero: You need / You have / Gap */}
{isReady - ? "Ready now" + ? "Funds available" : readinessDate - ? `Ready by ${readinessDate.date}` - : "Not reachable in 5 years"} + ? `Estimated by ${readinessDate.date}` + : "Not estimated within 5 years"}
{targetPurchaseDate && !isReady && readinessDate && targetMonth != null && (
@@ -326,7 +326,7 @@ export default function Readiness({ state, updateState, prices }) { {/* Footer */}
- Projections use current prices and tax rates · Growth assumptions are opt-in + Estimates use current prices and tax rates · For illustration only · Growth assumptions are opt-in
); diff --git a/src/pages/SetupWizard.jsx b/src/pages/SetupWizard.jsx index 71cae36..1ac3a57 100644 --- a/src/pages/SetupWizard.jsx +++ b/src/pages/SetupWizard.jsx @@ -661,7 +661,7 @@ function WelcomeStep() { marginBottom: 14, }} />
- Free, open-source financial planning. + Free, open-source personal finance calculator.
All your data lives in your browser — nothing is sent to a server. No account needed. From 3c3463f2bc97f25eda3281d561be54a46f2e55b2 Mon Sep 17 00:00:00 2001 From: azywicki <81277290+NimbleEngineer21@users.noreply.github.com> Date: Wed, 4 Mar 2026 19:30:58 -0500 Subject: [PATCH 2/2] fix(copy): deepen legal copy cleanup and fix browser compat regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert .replaceAll() to .replace() to prevent runtime crash on older mobile browsers (ES2021 API not available on Android WebView targets this project already polyfills for). Remove remaining legally risky language from comments/JSDoc ("can afford", "Financial readiness", "suggest", "Consider increasing"). Rename identifiers to match neutral tone: suggestConformingDown → calcConformingDown, canAfford → isCovered, onApplySuggestion → onApplyConformingDown, suggestion → conformingOption. Normalize parseFloat/parseInt to Number.parseFloat/Number.parseInt in touched files. --- src/components/ConformingStatus.jsx | 20 ++++++++--------- src/lib/__tests__/loanLimits.test.js | 18 ++++++++-------- src/lib/__tests__/purchasePlanner.test.js | 12 +++++------ src/lib/loanLimits.js | 7 +++--- src/lib/purchasePlanner.js | 6 +++--- src/lib/readiness.js | 6 +++--- src/pages/LoansCalc.jsx | 26 +++++++++++------------ src/pages/PurchasePlanning.jsx | 18 ++++++++-------- 8 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/components/ConformingStatus.jsx b/src/components/ConformingStatus.jsx index a4c7a92..8240592 100644 --- a/src/components/ConformingStatus.jsx +++ b/src/components/ConformingStatus.jsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { colors, styles } from "../theme.js"; import { fmt } from "../lib/calculations.js"; -import { detectJumbo, suggestConformingDown, calcJumboImpact, calcEffectiveRate } from "../lib/loanLimits.js"; +import { detectJumbo, calcConformingDown, calcJumboImpact, calcEffectiveRate } from "../lib/loanLimits.js"; import { YEAR } from "../data/conformingLimits.js"; @@ -12,7 +12,7 @@ import { YEAR } from "../data/conformingLimits.js"; export default function ConformingStatus({ zipCode, loanAmount, homePrice, currentDownPercent, baseRate, termYears, jumboSpread, zipInfo, - onZipChange, onSpreadChange, onApplySuggestion, + onZipChange, onSpreadChange, onApplyConformingDown, }) { const hasZip = zipCode?.length === 5; @@ -21,8 +21,8 @@ export default function ConformingStatus({ [hasZip, loanAmount, zipCode, zipInfo], ); - const suggestion = useMemo( - () => jumbo?.isJumbo ? suggestConformingDown(homePrice, jumbo.conformingLimit, currentDownPercent) : null, + const conformingOption = useMemo( + () => jumbo?.isJumbo ? calcConformingDown(homePrice, jumbo.conformingLimit, currentDownPercent) : null, [jumbo, homePrice, currentDownPercent], ); @@ -57,7 +57,7 @@ export default function ConformingStatus({ placeholder="e.g. 32301" value={zipCode || ""} onChange={e => { - const val = e.target.value.replaceAll(/\D/g, "").slice(0, 5); + const val = e.target.value.replace(/\D/g, "").slice(0, 5); onZipChange(val); }} style={{ ...styles.input, width: 120 }} @@ -152,7 +152,7 @@ export default function ConformingStatus({
{/* Conforming option — down payment to avoid jumbo */} - {suggestion && ( + {conformingOption && (
A down payment of{" "} - {suggestion.requiredDownPercent.toFixed(1)}% + {conformingOption.requiredDownPercent.toFixed(1)}% - {" "}({fmt(suggestion.requiredDownAmount)}) would keep the loan conforming. + {" "}({fmt(conformingOption.requiredDownAmount)}) would keep the loan conforming. - +{fmt(suggestion.additionalDown)} more needed + +{fmt(conformingOption.additionalDown)} more needed
@@ -219,7 +219,7 @@ function MortgageView({ updateMortgage("propertyTax", parseFloat(e.target.value) || 0)} + onChange={e => updateMortgage("propertyTax", Number.parseFloat(e.target.value) || 0)} style={styles.input} />
@@ -228,7 +228,7 @@ function MortgageView({ updateMortgage("homeInsurance", parseFloat(e.target.value) || 0)} + onChange={e => updateMortgage("homeInsurance", Number.parseFloat(e.target.value) || 0)} style={styles.input} />
@@ -237,7 +237,7 @@ function MortgageView({ updateMortgage("hoaDues", parseFloat(e.target.value) || 0)} + onChange={e => updateMortgage("hoaDues", Number.parseFloat(e.target.value) || 0)} style={styles.input} /> @@ -246,7 +246,7 @@ function MortgageView({ updateMortgage("expectedStayYears", parseInt(e.target.value) || 10)} + onChange={e => updateMortgage("expectedStayYears", Number.parseInt(e.target.value) || 10)} style={styles.input} /> @@ -265,7 +265,7 @@ function MortgageView({ zipInfo={zipInfo} onZipChange={val => updatePurchase("zipCode", val)} onSpreadChange={val => updateMortgage("jumboSpreadPercent", val)} - onApplySuggestion={pct => updatePurchase("downPaymentPercent", pct)} + onApplyConformingDown={pct => updatePurchase("downPaymentPercent", pct)} /> {/* Rate Chart */} @@ -377,7 +377,7 @@ function MortgageView({ updateMortgage("pointsBought", parseFloat(e.target.value) || 0)} + onChange={e => updateMortgage("pointsBought", Number.parseFloat(e.target.value) || 0)} style={styles.input} /> @@ -386,7 +386,7 @@ function MortgageView({ updateMortgage("opportunityCostRate", parseFloat(e.target.value) || 0)} + onChange={e => updateMortgage("opportunityCostRate", Number.parseFloat(e.target.value) || 0)} style={styles.input} />
S&P 500 avg: ~7%
@@ -512,7 +512,7 @@ function AutoLoanView({ purchase, autoLoan, updateAutoLoan, onOverride, onToggle
Term (months)
updateAutoLoan("ratePercent", parseFloat(e.target.value) || 0)} + onChange={e => updateAutoLoan("ratePercent", Number.parseFloat(e.target.value) || 0)} style={styles.input} /> @@ -537,7 +537,7 @@ function AutoLoanView({ purchase, autoLoan, updateAutoLoan, onOverride, onToggle updateAutoLoan("tradeInValue", parseFloat(e.target.value) || 0)} + onChange={e => updateAutoLoan("tradeInValue", Number.parseFloat(e.target.value) || 0)} style={styles.input} /> diff --git a/src/pages/PurchasePlanning.jsx b/src/pages/PurchasePlanning.jsx index bff9689..b6b8595 100644 --- a/src/pages/PurchasePlanning.jsx +++ b/src/pages/PurchasePlanning.jsx @@ -75,7 +75,7 @@ export default function PurchasePlanning({ state, updateState, prices }) { ); } - const affordColor = liquidation.canAfford + const statusColor = liquidation.isCovered ? (liquidation.surplus > cashNeeded.total * 0.1 ? colors.green : colors.amber) : colors.red; @@ -138,7 +138,7 @@ export default function PurchasePlanning({ state, updateState, prices }) { updatePurchase(isHome ? "homePrice" : "carPrice", parseFloat(e.target.value) || 0)} + onChange={e => updatePurchase(isHome ? "homePrice" : "carPrice", Number.parseFloat(e.target.value) || 0)} style={styles.input} /> @@ -149,7 +149,7 @@ export default function PurchasePlanning({ state, updateState, prices }) { updatePurchase("downPaymentPercent", parseFloat(e.target.value) || 0)} + onChange={e => updatePurchase("downPaymentPercent", Number.parseFloat(e.target.value) || 0)} style={styles.input} /> @@ -166,7 +166,7 @@ export default function PurchasePlanning({ state, updateState, prices }) { updatePurchase("carDownPayment", parseFloat(e.target.value) || 0)} + onChange={e => updatePurchase("carDownPayment", Number.parseFloat(e.target.value) || 0)} style={styles.input} /> @@ -253,7 +253,7 @@ export default function PurchasePlanning({ state, updateState, prices }) { borderRadius: 6, padding: 12, marginBottom: 12, fontSize: 13, color: colors.text, }}> Loan exceeds the {fmt(jumbo.conformingLimit)} conforming limit by {fmt(jumbo.overage)}. - Consider increasing the down payment on the Loans page. + A higher down payment on the Loans page would bring the loan under the conforming limit. ); })()} @@ -333,14 +333,14 @@ export default function PurchasePlanning({ state, updateState, prices }) {
{fmt(liquidation.totalAvailable)}
-
{liquidation.canAfford ? "Surplus" : "Shortfall"}
-
- {liquidation.canAfford ? fmt(liquidation.surplus) : `(${fmt(liquidation.shortfall).replace("$", "")})`} +
{liquidation.isCovered ? "Surplus" : "Shortfall"}
+
+ {liquidation.isCovered ? fmt(liquidation.surplus) : `(${fmt(liquidation.shortfall).replace("$", "")})`}
- {!liquidation.canAfford && ( + {!liquidation.isCovered && (
There is a {fmt(liquidation.shortfall)} gap at current asset values.