From 3ebdad38e3676b9a5ca7febab6943ec27f05476e Mon Sep 17 00:00:00 2001 From: Lidizz Date: Sun, 1 Mar 2026 00:32:34 +0100 Subject: [PATCH 1/3] fix: compact AssetSelector trigger display (17a) - Trigger button shows only the ticker symbol (same height as Amount and Date inputs, fixing row alignment) - Company name displayed as a subtle label below the trigger - Dropdown option display unchanged (full symbol + name + sector) --- frontend/src/components/AssetSelector.module.css | 16 ++++++++-------- frontend/src/components/AssetSelector.tsx | 10 ++++++---- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/AssetSelector.module.css b/frontend/src/components/AssetSelector.module.css index a1ecec4..090a1fa 100644 --- a/frontend/src/components/AssetSelector.module.css +++ b/frontend/src/components/AssetSelector.module.css @@ -44,21 +44,21 @@ opacity: 0.5; } -.asset-selector__selected { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - .asset-selector__symbol { font-weight: 700; color: var(--text-primary); font-size: 1rem; } -.asset-selector__name { - font-size: 0.875rem; +.asset-selector__company-label { + display: block; + font-size: 0.75rem; color: var(--text-secondary); + margin-top: 0.25rem; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .asset-selector__placeholder { diff --git a/frontend/src/components/AssetSelector.tsx b/frontend/src/components/AssetSelector.tsx index 71eff9f..e6c822e 100644 --- a/frontend/src/components/AssetSelector.tsx +++ b/frontend/src/components/AssetSelector.tsx @@ -104,16 +104,18 @@ export function AssetSelector({ assets, selectedSymbol, onSelect, disabled = fal disabled={disabled} > {selectedAsset ? ( -
- {selectedAsset.symbol} - {selectedAsset.name} -
+ {selectedAsset.symbol} ) : ( Choose an asset... )} {isOpen ? '▲' : '▼'} + {/* Company name below trigger — keeps trigger compact */} + {selectedAsset && ( + {selectedAsset.name} + )} + {isOpen && (
{/* Filter Controls */} From 670c9f2b5cfc94e95bb8e0fcf08cc37adcc20232 Mon Sep 17 00:00:00 2001 From: Lidizz Date: Sun, 1 Mar 2026 00:34:59 +0100 Subject: [PATCH 2/3] fix: suppress validation on freshly-added investment rows (17b) - Track per-investment 'isNew' IDs in InvestmentBuilder (Set) - New rows added via '+' are marked as new; validation errors hidden - Interacting with any field clears the isNew flag for that row - Clicking Simulate clears all isNew flags to reveal errors everywhere - No changes to the Investment type (isNew is local UI state only) --- frontend/src/components/InvestmentBuilder.tsx | 20 ++++++++++++++++--- frontend/src/components/InvestmentForm.tsx | 7 +++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/InvestmentBuilder.tsx b/frontend/src/components/InvestmentBuilder.tsx index 0066133..e346a3c 100644 --- a/frontend/src/components/InvestmentBuilder.tsx +++ b/frontend/src/components/InvestmentBuilder.tsx @@ -22,6 +22,7 @@ interface InvestmentBuilderProps { export function InvestmentBuilder({ assets, onSimulate, isSimulating }: InvestmentBuilderProps) { const { investments, setInvestments } = useSimulationContext(); const [showValidation, setShowValidation] = useState(false); + const [newInvestmentIds, setNewInvestmentIds] = useState>(new Set()); // Create a new empty investment function createEmptyInvestment(): Investment { @@ -36,15 +37,26 @@ export function InvestmentBuilder({ assets, onSimulate, isSimulating }: Investme // Add new investment row const handleAddInvestment = () => { if (investments.length < 10) { - setInvestments([...investments, createEmptyInvestment()]); + const newInv = createEmptyInvestment(); + setInvestments([...investments, newInv]); + setNewInvestmentIds((prev) => new Set(prev).add(newInv.id)); } }; - // Update an investment + // Update an investment (also marks it as no longer "new") const handleUpdateInvestment = (index: number, updated: Investment) => { const newInvestments = [...investments]; newInvestments[index] = updated; setInvestments(newInvestments); + + // Once a field is interacted with, stop suppressing validation + if (newInvestmentIds.has(updated.id)) { + setNewInvestmentIds((prev) => { + const next = new Set(prev); + next.delete(updated.id); + return next; + }); + } }; // Remove an investment @@ -70,8 +82,9 @@ export function InvestmentBuilder({ assets, onSimulate, isSimulating }: Investme // Handle simulation const handleSimulate = () => { - // Always show validation when user clicks simulate + // Reveal all validation errors (including on "new" rows) setShowValidation(true); + setNewInvestmentIds(new Set()); if (!canSimulate) return; @@ -105,6 +118,7 @@ export function InvestmentBuilder({ assets, onSimulate, isSimulating }: Investme onRemove={() => handleRemoveInvestment(index)} canRemove={investments.length > 1} showValidation={showValidation} + isNew={newInvestmentIds.has(investment.id)} /> ))}
diff --git a/frontend/src/components/InvestmentForm.tsx b/frontend/src/components/InvestmentForm.tsx index 8361cf6..df4905e 100644 --- a/frontend/src/components/InvestmentForm.tsx +++ b/frontend/src/components/InvestmentForm.tsx @@ -10,6 +10,7 @@ interface InvestmentFormProps { onRemove: () => void; canRemove: boolean; showValidation?: boolean; // Only show errors when true (after simulate attempt) + isNew?: boolean; // Suppress validation on freshly-added rows } /** @@ -26,7 +27,8 @@ export function InvestmentForm({ onUpdate, onRemove, canRemove, - showValidation = false + showValidation = false, + isNew = false }: InvestmentFormProps) { const [errors, setErrors] = useState>({}); const [touched, setTouched] = useState>({}); @@ -36,8 +38,9 @@ export function InvestmentForm({ setTouched(prev => ({ ...prev, [field]: true })); }; - // Show error only if touched or showValidation is true + // Show error only if (touched OR showValidation) AND not a fresh "new" row const shouldShowError = (field: string) => { + if (isNew) return false; return (touched[field] || showValidation) && errors[field]; }; From b0afbcd8802c202bbf3080741f16ee362c759e3c Mon Sep 17 00:00:00 2001 From: Lidizz Date: Sun, 1 Mar 2026 00:37:26 +0100 Subject: [PATCH 3/3] fix: add IPO date tooltip and min-date on date picker (17c) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Info icon (ℹ) appears next to 'Purchase Date' label when asset has an IPO date hover shows 'MSFT available from Mar 13, 1986' - Date input min attribute set to IPO date so native picker greys out earlier dates - Existing error message kept as fallback for manual date entry - 107 tests pass, tsc clean --- .../src/components/InvestmentForm.module.css | 7 +++++ frontend/src/components/InvestmentForm.tsx | 26 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/InvestmentForm.module.css b/frontend/src/components/InvestmentForm.module.css index cf05205..4755cd0 100644 --- a/frontend/src/components/InvestmentForm.module.css +++ b/frontend/src/components/InvestmentForm.module.css @@ -43,6 +43,13 @@ font-family: var(--font-family); } +.investment-form__ipo-info { + margin-left: 0.375rem; + cursor: help; + font-size: 0.875rem; + vertical-align: middle; +} + .investment-form__input { width: 100%; padding: 0.75rem 1rem; diff --git a/frontend/src/components/InvestmentForm.tsx b/frontend/src/components/InvestmentForm.tsx index df4905e..1a19b40 100644 --- a/frontend/src/components/InvestmentForm.tsx +++ b/frontend/src/components/InvestmentForm.tsx @@ -13,6 +13,15 @@ interface InvestmentFormProps { isNew?: boolean; // Suppress validation on freshly-added rows } +/** + * Format an ISO date string (YYYY-MM-DD) to a readable label. + * e.g. "1986-03-13" → "Mar 13, 1986" + */ +function formatIpoDate(dateStr: string): string { + const d = new Date(dateStr + 'T00:00:00'); + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); +} + /** * Individual Investment Form - One row in the investment builder. * @@ -102,6 +111,10 @@ export function InvestmentForm({ investment.amountUsd > 0 && investment.purchaseDate; + // Resolve selected asset for IPO date tooltip + const selectedAsset = assets.find(a => a.symbol === investment.symbol); + const ipoDate = selectedAsset?.ipoDate || undefined; + return (
@@ -141,10 +154,21 @@ export function InvestmentForm({ {/* Date Picker */}
- +