Net Worth Tracker is a Next.js app for Italian investors to track net worth, assets, cashflow, dividends, performance metrics, and long-term planning with Firebase.
- Stack: Next.js 16, React 19, TypeScript 5, Tailwind v4, Firebase, Vitest, Framer Motion, Recharts, Yahoo Finance, Borsa Italiana scraping, Anthropic (Claude Sonnet 5)
- Latest (2026-07-26): Fix "conto di regolamento" + versamento previdenza volontario opzionale — user-facing bug fixes, post-test utente sull'app reale.
- Filtro conti di regolamento hardened: ogni picker "scegli un conto per un movimento diretto di denaro" (
TransactionDialogregistro operazioni,AssetDialogprimo acquisto a leva,PensionContributionDialog,ExpenseDialogconto pagamento spesa) ora richiedetype === 'cash' && assetClass === 'cash', non più il soloassetClass— un ETF monetario (es. XEON) classificatoassetClass: 'cash'per l'allocazione non compare più come conto selezionabile. Stesso fix lato server (assertVoluntarySourceIsCashAccount,assertCashSettlementAsset), altrimenti il filtro client sarebbe aggirabile via API diretta. NON toccati i calcoli di liquidità aggregata (dashboard overview, storico prezzi, budget Impostazioni) — lì un ETF cash-class continua a contare come liquidità nei totali, per scelta esplicita. - Conto di provenienza sempre facoltativo per il versamento volontario in previdenza: un dipendente può avere anche il volontario trattenuto in busta paga, mai transitato su un conto personale — rimosso l'obbligo (
zod superRefine); se lasciato vuoto, il fondo viene accreditato direttamente, stesso path già usato per TFR/Datoriale. - Hero Panoramica: le chip di variazione (mese/YTD/ATH) ora hanno larghezza uniforme via CSS grid (
grid-cols-1mobile,tablet:grid-cols-2da 768px) invece diflex flex-wrap. tscclean; 70 files / 1222 tests green.
- Filtro conti di regolamento hardened: ogni picker "scegli un conto per un movimento diretto di denaro" (
- App Router; protected pages under
app/dashboard/* - Service layer
lib/services/*; shared pure utilitieslib/utils/*; server-only logiclib/server/* - React Query for caching/invalidation
- Italy timezone helpers in
lib/utils/dateHelpers.ts(getItalyMonth/Year, day bounds) - Convention: extract logic into pure, tested
lib/utils/lib/servicesfunctions; keep Firestore-coupled code thin.
Each entry = what it is + key file(s). Detailed gotchas/conventions live in AGENTS.md; the aesthetic spec is DESIGN.md.
- Shared account (delegated access): a second user signs up with their own account and gets full co-owner read/write on another user's data. viewer (logged-in Firebase user — token/theme/profile) is decoupled from owner (
ownerId, whose data is shown) viauseActiveAccount()(contexts/ActiveAccountContext.tsx). Grants:account-access/{ownerUid}(types/account.ts). Owner adds/removes members by email in Settings → Condivisione (components/settings/AccountSharingSection.tsx) viaapp/api/account/members/route.ts. Enforcement:firestore.rulescanAccess/isMemberOf(theme/userPreferencesstays per-viewer),assertCanAccessAccount(lib/server/apiAuth.ts) on Admin routes,ownerIdin data-scoped client code. Switcher incomponents/layout/Sidebar.tsxandcomponents/layout/SecondaryMenuDrawer.tsx(the "Altro" menu — the Sidebar is unreachable in portrait, so this is the only switcher on a phone); shared account naming viagetAccountLabel(lib/utils/userDisplayUtils.ts). Conventions/gotchas in AGENTS.md → Shared Account / Delegated Access + Mobile Navigation Structure. - Demo mode:
app/page.tsxpublic landing (stacked Panoramica + Cashflow savings-ring previews, proof strip,ThemePickertoggle shared with login/register + "Prova la Demo" auto-login);useDemoMode()(lib/hooks/useDemoMode.ts) gates every mutation (disabled={isDemo}). Credentials baked into the bundle (acceptable for a public demo; empty vars hide the CTA). Landing conventions in AGENTS.md → Public Landing Page Hero. - Panoramica (Overview): dashboard home, Bento Asimmetrico hero (
[2fr_1fr]) — dominant Patrimonio Totale Lordo + variation chips + "Nuovo massimo storico" ATH chip + period-selectable sparkline (3M/6M/YTD/1A/3A/All) + "Guidato da" top-movers digest, next to a Sintesi Patrimoniale companion card (Liquidità/Investimenti/Illiquidi breakdown, conditional Impatto Fiscale block, featured Goal-Based Investing progress bar) — then a full-width Cashflow KPI card and deferred charts. Data viauseDashboardOverview()→GET /api/dashboard/overview(materialized-summary cache).app/dashboard/page.tsx,components/dashboard/*. Detail in AGENTS.md → Panoramica: hero critique follow-up (2026-07-16) + Dashboard Data Isolation. - AssetDialog: 2-step create (type picker → type-filtered form), edit reuses the field-visibility logic;
TYPE_TO_CLASS. For ledger types (stock/etf/bond/crypto/commodity) once migrated, edit shows read-only qty/PMC (submit viaupdateAssetMetadata) and create opens the position as a first buy. Optional "Alias visualizzato" (displayTicker) field, gated/hidden alongside the ticker (cash/realestate/pensionFund) — see Ticker Display Alias in Key Files.components/assets/AssetDialog.tsx. - Asset trade ledger (Registro operazioni): per-asset BUY/SELL/ADJUSTMENT operations with optional cash settlement (net-worth-neutral). From Patrimonio: "Registra operazione" (
TransactionDialog— segmented Compra/Vendi/Rettifica, estimated realized-P&L preview via the shared pure engine,__none__settlement sentinel) and "Movimenti" (AssetMovementsDialog— vitals P&L/Rendimento/XIRR, movement list, baseline-protected). Writes are Admin-API-only; the asset doc stays authoritative (quantity/PMC rewritten by a full replay). Row actions gated onuseAssetLedgerMeta. The ledger also feeds Rendimenti's "Capitale investito"/"Plusvalenze realizzate" and Dividendi's per-asset total return (see those entries below). Pure enginelib/utils/assetTransactionUtils.ts; detail in AGENTS.md → Asset Trade Ledger. - ExpenseDialog: single-step form + "Impostazioni avanzate"
Collapsible;ResponsiveModal(Drawer ≤768px / Dialog); inline category/subcategory creation.components/expenses/ExpenseDialog.tsx. - Multi-theme color system: 6 themes (
default/solar-dusk/elegant-luxury/midnight-bloom/cyberpunk/retro-arcade), persisted in FirestoreuserPreferences/{userId}+ localStorage;ColorThemeContextsetsdata-theme; charts theme-aware viauseChartColors(--chart-1..5).contexts/ColorThemeContext.tsx,lib/hooks/useChartColors.ts,app/globals.css. - Portfolio (Patrimonio): equities/bonds/crypto/real-estate/commodity/cash; Trade-Republic hierarchy, token-driven sign colors, sortable table, 2-click inline delete, Δ columns behind an "Andamento" toggle. Auto price updates (Yahoo Finance / Borsa Italiana bonds).
app/dashboard/assets/page.tsx. - Cashflow: tabs Tracciamento / Dividendi / Budget / (optional) Centri di Costo. Transfers =
transfertype, net-zero for all metrics, atomic dual-balance reconcile (updateCashAssetBalancesAtomic,cashBalanceReconciliation.ts). Tracciamento = single-answer IA:CashflowHero(dominant Risparmio Netto + one health verdict + top-5 spese) → contextual filter toolbar → sharedTransactionFeed(day-grouped, Feed/Tabella toggle). Purelib/utils/trackingSummary.ts(tested).app/dashboard/cashflow/page.tsx,components/cashflow/{ExpenseTrackingTab,CashflowTrackingMobile,TransactionFeed}.tsx,components/cashflow/cashflow-kpi/CashflowHero.tsx. - Budget: opt-in budgets (create/edit/delete + auto-save
useBudgetConfig); overall ceiling + income budgets + per-item monthly|annual period; Forecast / Insights / Alerts. Purelib/utils/budgetUtils.ts(tested).components/cashflow/budget/*,types/budget.ts. - Cost Centers: optional 6th Cashflow tab (
costCentersEnabled); group expenses by project; period axis + ranked list + per-center budget/projection/lifecycle. Detail also has a per-subcategory breakdown (buildSubCategoryComposition) with session-only exclusion toggles → "Totale al netto" (analysis-only; never alters hero/budget/chart). Purelib/utils/costCenterUtils.ts.components/cashflow/{CostCentersTab,CostCenterDetail,CostCenterDialog}.tsx,types/costCenters.ts. - Analisi page (
/dashboard/analisi): period selector (deep-linked in querystring) + KPI trio (section-hero scale) + anomaly block (--warningtoken) + Sankey with clickableDrillBreadcrumb— all always-visible; a "Dettaglio"Collapsible(default closed) holds Confronto Annuale, savings-rate trend, per-category sparklines, and "Andamento nel Tempo" (Storico only).CompositionListranked-row drill-down (Spese/Entrate per Categoria + sottocategorie, Spese per Tipo — pie charts replaced 2026-07-13). Purelib/utils/cashflowTimeSeries.ts. All period/view/range toggles use the sharedSegmentedPill. NOTEcashflowHistoryStartYearis shared (Cashflow/History/Assistant/overview) — do not rename.components/cashflow/AnalisiTab.tsx. Detail in AGENTS.md → Analisi: critique follow-up (2026-07-21). - Dividends: Trade-Republic IA — in-memory period axis (
DividendPeriod) → net-income hero + KPI grid + income-reliability strip + payer leaderboard; Table/Calendario; charts/advanced behindCollapsible. Purelib/utils/dividendAnalytics.ts(tested);DividendStats= server YOC/DPS/total-return block — its "Total Return per Asset" table is ledger-based (replayTransactions/computeAssetTotalReturn) for tracked investments, including closed positions (isClosed, "Chiusa" badge) and partial sells; assets without a ledger doc keep the static price-vs-PMC fallback. Inflation-linked bonds (BTP Italia Sì): additive FOI coupon via sharedresolveCoupon/buildCouponNote(couponUtils.ts); cron stores the next coupon provisional until the user announces the rate (InflationRateDialog).components/dividends/*. - Rendimenti (Performance): single-answer IA —
PerformanceHero(dominant TWR + verdict + "vs benchmark" delta + drawdown chip + Sharpe/MaxDD/Contributi/YOC vital signs) → return-consistency strip → "Mostra tutte le metriche" Collapsible wrapping theMetricSections (HeroMetricBlock+MetricCarddivide-y rows: Rendimento, Rischio, Contesto — with a ledger-based "Capitale investito" row alongside Contributi Netti, gated onuseAssetLedgerMeta— Proventi Finanziari, and a ledger-gated "Plusvalenze Realizzate" per-fiscal-year section) → charts in "Andamento"/"Rischio" clusters. Pure:lib/utils/performanceSummary.ts(summarizePerformance/computeReturnConsistency/computeDrawdownStatus/computeBenchmarkDelta) +lib/utils/benchmarkPeriodReturn.ts(shared withBenchmarkComparisonChart) +components/performance/RealizedGainsSection.tsx(cross-asset realized-P&L aggregation). Reference benchmark =BENCHMARKS[0](60/40) via page-leveluseBenchmarkReturns. Sign tokens viagetMetricValueColor; charts viauseChartColors.app/dashboard/performance/page.tsx,lib/services/performanceService.ts. - Benchmark comparison (Rendimenti): growth-of-100 chart + risk/return table for 6 model portfolios; Sharpe/Sortino use an ECB deposit-rate period average (FRED ECBDFR, cached). Env:
FRED_API_KEY.components/performance/BenchmarkComparison{Chart,Section}.tsx; Firestore cachesbenchmark-cache/*,fx-rate-cache/usd-eur,ecb-rate-cache/deposit-rate. - Allocazione: single-question IA, two zones — Decision (
AllocationHero= dominant total +AllocationCompositionBar+BalanceScoreGauge+ one clickable caption per non-tradable role;RebalanceBandControl±2/±5/5·25/custom labeled strip;ActionPlannersegmented Ribilancia/Versa/Preleva →RebalancePaneltrade list /ContributionPanelno-sell split /WithdrawalPanelsurplus-first split, the last two being onePlanNodetree with the sign flipped, class → sub-category → instrument, rendered by the sharedPlanRow) then a "Dettaglio" divider → Detail (AllocationBreakdowninline accordion + "Esclusi dall'allocazione" group + "Esposizione Portfolio" cross-ETF+stock, lazyGET /api/portfolio/exposure, 24h cache).Asset.allocationRole(tradable|frozen|excluded) is partitioned BEFOREcompareAllocations, which receivestradable + frozen: afrozenasset (locked pension fund, private equity) stays in the denominator and the percentages but never appears in a plan, so the plans compensate for it by moving the others; anexcludedasset (the home you live in) leaves the page entirely, which is why the total is smaller than the Panoramica net worth. Executability is enforced bycapacity(tradable slice) insplitFromSurplus+ a sell cap inbuildRebalancePlan.findOrphanedTargets+stripOrphanedSubTargetscatch targets stranded by an exclusion. Action colors viauseActionColors; band-independent balance score viacomputeBalanceScore. Purelib/utils/allocationUtils.ts.app/dashboard/allocation/page.tsx. - Storico (History): hero (patrimonio + CAGR + crescita) → Evoluzione → Raddoppi → Composizione → Driver (Savings vs Investment, Lavoro & Investimenti, YoY). Charts via
useChartColors.app/dashboard/history/page.tsx. - Valore per Strumento (Storico): read-only per-instrument value for a chosen month from
MonthlySnapshot.byAsset(no recompute), subset sum + cross-month trend with price/quantity attribution. Purelib/utils/snapshotAssetBreakdown.ts(tested) — also hostsderiveHoldingStartDates(holding-continuity for YOC).components/history/MonthlyAssetBreakdownSection.tsx. - Hall of Fame: records + rankings, Trade-Republic hierarchy, period+category switchers.
app/dashboard/hall-of-fame/page.tsx,lib/constants/hallOfFame.ts. - Assistente AI: single period axis (
AssistantPeriodSelectorMese/Anno/YTD/Storico/Libera) + period-reactive scheda; SSE streaming (meta|context|status|text|done|error); 5 modes; web search gated byincludeMacroContext+webSearchPolicy.ts; proactive memory + follow-ups; sees the user's full expense category taxonomy (expenseCategories) for categorization questions. Runs onclaude-sonnet-5(adaptive thinking, no prompt caching by design). FlagNEXT_PUBLIC_ASSISTANT_AI_ENABLED; demo-blocked.components/assistant/*,app/api/ai/assistant/*,lib/server/assistant/*,lib/services/assistantMonthContextService.ts,types/assistant.ts. - FIRE Calculator + Coast FIRE: settings-collapsible-at-top; FIRE Number hero, runway, Bear/Base/Bull projection; Coast FIRE discounts the FIRE number to today via real return, optional state pensions + editable IRPEF brackets.
components/fire-simulations/{FireCalculatorTab,CoastFireTab,FIREProjectionSection}.tsx,lib/services/fireService.ts. - What If Analysis (FIRE 5th tab): job loss / purchase / savings-expense change / windfall → before→after on FIRE + Coast FIRE by re-running
fireServicetwice; job-loss income-source selector. Purelib/services/whatIfService.ts,types/whatIf.ts.components/fire-simulations/WhatIfAnalysisTab.tsx. - Monte Carlo (FIRE tab): success-probability hero, base/advanced params,
useChartColors.components/monte-carlo/*. - Goal-Based Investing: trajectory-led (required monthly pace + projected date + verdict); pure
lib/utils/goalTrajectory.ts(tested). Optional goal-driven allocationderiveTargetAllocationFromGoals(weight = gap × priority).components/fire-simulations/GoalBasedInvestingTab.tsx,components/goals/*,lib/services/goalService.ts. - Periodic summary emails: monthly/quarterly/semi-annual/yearly (Resend, opt-in, shared recipient list), sent by the daily snapshot cron; deterministic Confronti table + one AI comment. Weekly budget email (Sundays, Cron Phase 6) — sent weekly but its figures are month-to-date (monthly + overall budgets) and year-to-date (annual budgets), with end-of-month projections; every horizon is stated explicitly in the HTML caption and in
buildCommentContext(the AI-comment prompt) after the model once mislabelled the monthly overall projection as year-end. Env:RESEND_API_KEY,RESEND_FROM_EMAIL,ANTHROPIC_API_KEY.lib/server/{monthlyEmailService,weeklyBudgetEmailService,emailPeriodComparison}.ts. - Fondo Pensione (Previdenza): manually-valued asset (
AssetType 'pensionFund', likerealestate— no ticker/auto-update/cost-basis),allocationRole: 'frozen'by default (counts in Allocazione's denominator, never in a plan). Contributions in three natures (TFR/Volontario/Datoriale) recorded from/dashboard/pension— only Volontario leaves a cash account (modelled as atransfer); TFR/Datoriale credit the fund standalone. Multi-person households: a fund links to aFamilyMember(name + RAL + eligibility, managed in Impostazioni → Preferenze → Famiglia); the annual tax recap (deducted amount + IRPEF saving) and extra-deducibilità plafond, via the pure enginelib/utils/pensionDeduction.ts(taxOfinjected), run once per member — an unlinked fund shows a prompt instead of a number.components/pension/*,app/dashboard/pension/page.tsx. Converting a pre-existing ledger-tracked fund is a type edit (never delete+recreate) — see AGENTS.md → Fondo Pensione. Integrated app-wide (P3):PensionAllocationCardslook-through on Allocazione (fund staysfrozen, never in a plan); a type-based "Previdenza" band on Storico (chartService.ts); excluded from the Rendimenti metrics base (lib/utils/performanceBase.ts); optional locked-capital toggle on the FIRE Calculator (lib/utils/pensionFire.ts). - PDF export and AI-powered performance analysis.
- Vitest. Commands:
npx vitest run <file>,npm test -- <file>,npx tsc --noEmit. - Targeted tests for pure utilities/services + private-API auth regression. New tests in
__tests__/; prefer testing pure functions over Firestore-coupled code. - Local dev/testing without prod data: Firebase Emulator Suite —
npm run emulators+npm run emulators:seed+npm run dev:emulator(offline, requires a JDK; never touches prod). Guide: SETUP.md → Step 6.
- Firestore client + admin
- Yahoo Finance (prices, benchmark ETF history)
- Borsa Italiana scraping (Italian bonds, dividend data)
- Frankfurter API (FX for asset prices + historical monthly EUR/USD for benchmarks)
- FRED API (series ECBDFR, ECB deposit rate; cached
ecb-rate-cache/deposit-rate;FRED_API_KEY) - Anthropic (
claude-sonnet-5— AI analysis + assistant;claude-haiku-4-5— memory extraction)
- FX conversion depends on Frankfurter availability; 24h cache fallback. Pre-migration non-EUR assets without
currentPriceEurshow native price as EUR until the first price update (GBp safe via the pence→GBP/100fallback). - Demo account requires manual Firebase setup (user + realistic fake Firestore data + three env vars).
- Security review 2026-06-10: all 8 findings (SEC-1…SEC-8) done (
docs/security-review-spec.md). Caveat: firebase-admin pinned at^13.6.0(the @14 bump pulled pure-ESMjose@6→ERR_REQUIRE_ESMon Vercel), so the 8 moderateuuidadvisories stay open until resolvable. - Shared account setup: (1) the guest's email must be in
REGISTRATION_WHITELISTand they must register their own account before the owner can add them (member add resolves email→uid via Admin — a non-existent user 404s). (2)firestore.rulesmust be deployed (firebase deploy --only firestore:rules) for the client-SDK shared path to work — the app code only enables the feature once the app itself is deployed. (3) Theme/userPreferencesintentionally follows the viewer, not the shared account. - YOC/Current-Yield are keyed by
assetIdand exclude sold assets (intended). Discontinuous-holding edge — mitigated 2026-06-13: a sold-then-rebought instrument reuses the oldassetId(ISIN continuity increateAsset, orquantity 0→>0), so a window spanning the gap used to credit the prior holding's dividends against the new cost. NowyieldOnCost.tsdrops dividends paid beforeAsset.holdingStartDate(stamped at (re)purchase increateAsset/updateAsset; snapshot fallbackderiveHoldingStartDates); the same scoping is applied to Rendimento Totale per Asset (stats/route.ts). DPS-growth still counts all received dividends by design. Caveat: the snapshot fallback is monthly-granular and needs a recorded gap; legacy rebuys lack the stamp until re-added. - TWR cash-flow / liquidity month-bucket artifact (by design, not a formula bug): monthly snapshots freeze end-of-month NW; TWR neutralizes a tracked expense only if the NW drop and the expense's cash flow fall in the SAME month. A late-month event whose balance is updated the following month (e.g. taxes/credit-card spending paid on the next statement) splits into an overstated leg and a phantom-negative leg that don't fully cancel. It's data-entry timing, not code — record balance changes in the month of competence, or model the credit card as a liability so the NW drop lands at expense time. Cash/liquidity IS part of the return base (no exclusion). Full explanation in AGENTS.md (History and Snapshot Baselines → TWR neutralization).
- Shared account / delegated access:
contexts/ActiveAccountContext.tsx(useActiveAccount→ viewerId/ownerId/switchAccount),lib/services/accountAccessService.ts(client-SDK discovery),app/api/account/members/route.ts(owner-scoped member management),lib/server/apiAuth.ts(assertCanAccessAccount),types/account.ts,firestore.rules(canAccess/isMemberOf),components/settings/AccountSharingSection.tsx, switcher incomponents/layout/Sidebar.tsx; Firestore collectionaccount-access/{ownerUid} - Overview:
app/dashboard/page.tsx,app/api/dashboard/overview/route.ts,lib/services/dashboardOverviewService.ts,lib/hooks/useDashboardOverview.ts,components/dashboard/*(incl.PeriodSelector.tsx),lib/utils/{dashboardOverviewUtils,sparklinePeriod}.ts,types/dashboardOverview.ts - Composition primitives (ranked-bar / stacked-bar, replaced all remaining pie/donut surfaces):
components/ui/composition-list.tsx,components/ui/composition-bar.tsx,lib/utils/compositionShading.ts(computeShadeOpacities); consumers:components/cashflow/AnalisiTab.tsx,components/dashboard/OverviewChartsSection.tsx,components/dividends/DividendTrackingTab.tsx,components/allocation/AllocationCompositionBar.tsx - Segmented pill / drill-down primitives:
components/ui/segmented-pill.tsx(roving-tabindex period/view/range toggle),components/ui/drill-breadcrumb.tsx(clickable jump-to-level breadcrumb); consumers:components/cashflow/{AnalisiTab,ConfrontoAnnualeSection,SavingsRateTrendSection,AndamentoStoricoSection,CashflowSankeyChart}.tsx - Shared utils:
lib/utils/formatters.ts(cachedFormatCurrencyEUR),lib/utils/metricColors.ts(getMetricValueColor),lib/utils/firestoreData.ts(removeUndefinedDeep— deep undefined-strip before every Firestore write),lib/utils/dateHelpers.ts - Performance / yields:
lib/services/performanceService.ts,performance-cache/{userId};lib/utils/yieldOnCost.ts(computeDividendYieldMetrics— single source, per-share, current-cost, sold-excluded, holding-start-scoped) consumed bycalculateYocMetrics/calculateCurrentYieldMetrics+app/api/dividends/stats/route.ts - Assets:
lib/services/assetService.ts(createAssetISIN reuse +holdingStartDatestamping;updateAssetqty 0→>0 stamping),components/assets/*,types/assets.ts - Asset trade ledger (Fasi A+B+C+D — complete): engine
lib/utils/assetTransactionUtils.ts+types/assetTransactions.ts; serverlib/server/{assetTransactionUseCase,tradeFxService}.ts+ routesapp/api/asset-transactions/*+ rules infirestore.rules; admin readgetAssetTransactionsAdmininlib/server/assetAdminRepository.ts(Fase D, consumed bydividends/stats); clientlib/services/assetTransactionService.ts+lib/hooks/useAssetTransactions.ts+queryKeys.assetTransactions; UIcomponents/assets/{TransactionDialog,AssetMovementsDialog}.tsx+ AssetDialog ledger rework + entry points inAssetManagementTab/AssetCard; migration trigger inapp/dashboard/assets/page.tsx; Rendimenti surfacescomponents/performance/RealizedGainsSection.tsx+app/dashboard/performance/page.tsx(Capitale investito / Plusvalenze Realizzate);app/api/dividends/stats/route.tsledger-basedtotalReturnAssets. Tests__tests__/assetTransaction{Utils,sRoutes,WriteTx}.test.ts. CollectionsassetTransactions/assetTransactionsMeta. Specdocs/specs/1-asset-transactions/*(all phases done). - Fondo pensione (Fasi P0+P1+P2+P3 + family-member split — complete): types
types/pension.ts+AssetType 'pensionFund'/Asset.pensionFundDetails(incl.familyMemberId) intypes/assets.ts;FamilyMember+AssetAllocationSettings.familyMembers[](types/assets.ts); pure engineslib/utils/{pensionDeduction,pensionContributions,performanceBase,pensionFire,pensionFamilyMembers}.ts(taxOf/valueOfinjected, zero Firebase); client servicelib/services/pensionContributionService.ts+lib/hooks/usePensionContributions.ts+queryKeys.pensionContributions; rules block infirestore.rules(no indexes by design); UIcomponents/pension/{PensionContributionDialog,PensionOverview}.tsx(one tax-recap card per family member) +app/dashboard/pension/page.tsx+AssetDialogpensionFundtype (incl. "Membro famiglia" select) +planningNaventry + "Vai a Previdenza" links inAssetCard/AssetManagementTab; "Famiglia" card inapp/dashboard/settings/page.tsx(tab Preferenze) +respectPensionLockInFireonAssetAllocationSettings(lib/services/assetAllocationService.ts); ledger-orphan cleanup on conversion viadeleteAllAssetTransactionsForAsset(lib/server/assetTransactionUseCase.ts) +app/api/asset-transactions/by-asset/[assetId]/route.ts. P3 integrations:components/allocation/PensionAllocationCards.tsx+app/dashboard/allocation/page.tsx;lib/services/chartService.ts(prepareAssetClassHistoryDatapension param) +app/dashboard/history/page.tsx;lib/services/performanceService.ts(getAllPerformanceData) +app/dashboard/performance/page.tsx(cachedSnapshots);components/fire-simulations/FireCalculatorTab.tsx. CollectionpensionContributions. Tests__tests__/{pensionDeduction,pensionContributions,pensionContributionService,performanceBase,pensionFire,pensionFamilyMembers}.test.ts. Specdocs/specs/2-pension-fund/*(all phases done; family-member split is a post-spec follow-up). - Dividends:
components/dividends/*,lib/utils/{dividendAnalytics,couponUtils}.ts,lib/services/couponScheduling.ts,lib/constants/dividendTypes.ts,types/dividend.ts - Cashflow / budget / cost centers:
app/dashboard/cashflow/page.tsx,components/cashflow/*(+budget/*,cashflow-kpi/*,TransactionFeed.tsx,cashflow-kpi/CashflowHero.tsx),lib/utils/{budgetUtils,costCenterUtils,cashflowTimeSeries,trackingSummary}.ts,lib/services/{budgetService,costCenterService,cashBalanceReconciliation}.ts,types/{budget,costCenters}.ts - Allocation / exposure:
app/dashboard/allocation/page.tsx,components/allocation/*(incl.WithdrawalPanel.tsx,PlanRow.tsx),lib/utils/allocationUtils.ts(resolveAllocationRole/partitionByAllocationRole/buildHoldings/sumTradableByClass/findOrphanedTargets/stripOrphanedSubTargets/splitFromSurplus/buildContributionPlan/buildWithdrawalPlan),lib/hooks/useActionColors.ts;lib/server/portfolioExposureService.ts,app/api/portfolio/exposure/route.ts,exposure-cache/{userId} - Benchmarks:
lib/constants/benchmarks.ts,app/api/benchmarks/*,lib/server/ecbRatesService.ts,components/performance/BenchmarkComparison*.tsx; Firestorebenchmark-cache/*,fx-rate-cache/usd-eur,ecb-rate-cache/deposit-rate - FIRE / goals:
components/fire-simulations/*,lib/services/{fireService,whatIfService,goalService}.ts,lib/utils/goalTrajectory.ts,components/goals/*,types/{whatIf,goals}.ts - History / snapshots:
app/dashboard/history/page.tsx,components/history/*(incl.MonthlyAssetBreakdownSection.tsx),lib/utils/snapshotAssetBreakdown.ts,lib/services/chartService.ts;lib/services/snapshotService.ts(client) +getUserSnapshotsAdmininlib/server/assetAdminRepository.ts(admin); collectionmonthly-snapshots - Assistant:
app/dashboard/assistant/page.tsx,components/assistant/*,app/api/ai/assistant/*,lib/server/assistant/*,lib/services/assistantMonthContextService.ts,types/assistant.ts - Settings:
app/dashboard/settings/page.tsx,lib/services/assetAllocationService.ts - Layout / nav:
components/layout/*,components/ui/responsive-modal.tsx(ResponsiveModal),lib/constants/navigation.ts,lib/hooks/useMediaQuery.ts - Server use cases / emails:
lib/server/{assetAdminRepository,dividendUseCase,dividendProcessor,monthlyEmailService,weeklyBudgetEmailService,emailPeriodComparison}.ts,app/api/cron/monthly-snapshot/route.ts(Phases 2-6) - Allocazione a Leva (L0→L2 — complete, user-facing): data model
Asset.leverageRatio+AssetClasstrendFollowing/carry(types/assets.ts); purelib/utils/assetExposureUtils.ts(expandAssetExposure/calculatePortfolioLeverage, nopensionFundspecial-case). Servicelib/services/assetAllocationService.ts(calculateCurrentAllocationSnapshot/toLegacyAllocationResult/deriveTargetLeverageRatio+compareAllocationspartitioning byallocationRole); purelib/utils/leverageAwareAllocationUtils.ts(buildInstrumentExposures/planInstrument{Contribution,Rebalance,Withdrawal}QP solver with the full D5 bug fix —classConst+classCoeff;LeveragePlanInputs);applyRebalanceBandpreserves leverage fields (allocationUtils.ts). UI:components/allocation/{AllocationHero,AllocationCompositionBar,ActionPlanner,RebalancePanel,ContributionPanel,WithdrawalPanel,InstrumentTradeList}.tsx+components/ui/composition-bar.tsx(displayPct) +app/dashboard/allocation/page.tsx; Settingsapp/dashboard/settings/page.tsx(validazione ≥100 + leva derivata);components/assets/AssetDialog.tsx(leverageRatioinput) +lib/services/assetService.ts(updateAsset/updateAssetMetadataclear viadeleteField). Tests__tests__/{assetExposure,compareAllocations,leverageAwareAllocationUtils}.test.ts. Specdocs/specs/3-leveraged-etf-allocation/*(all phases done). - Ticker Display Alias (complete, user-facing): single fallback resolver
getAssetDisplayTicker(lib/utils/assetDisplay.ts) — never inline?? ticker. DataAsset.displayTicker?/AssetFormData.displayTicker?(types/assets.ts);components/assets/AssetDialog.tsx(form field) +lib/services/assetService.ts(updateAsset/updateAssetMetadataclear via bare=== undefined). Sweep:components/assets/{AssetCard,AssetManagementTab,AssetPriceHistoryTable,AssetMovementsDialog,TaxCalculatorModal}.tsx,components/dividends/DividendDialog.tsx,components/goals/{GoalDetailCard,AssetAssignmentDialog}.tsx,components/fire-simulations/GoalBasedInvestingTab.tsx,components/history/MonthlyAssetBreakdownSection.tsx(live-asset resolver byassetId),lib/services/{chartService,pdfDataService}.ts,lib/utils/{allocationUtils,leverageAwareAllocationUtils,assetPriceHistoryUtils}.ts,app/api/dividends/stats/route.ts. Intentionally NOT touched:CreateManualSnapshotModal,DividendTrackingTab(scraping logs),BenchmarkComparisonSection,ExposureSection. Test__tests__/assetDisplay.test.ts. Specdocs/specs/4-ticker-display-alias.md(done).
Last updated: 2026-07-26 (Fix conti di regolamento — type === 'cash' && assetClass === 'cash' in tutti i picker di conto per un movimento diretto di denaro, client + server; versamento previdenza volontario ora sempre facoltativo; chip hero Panoramica a larghezza uniforme. See Current Status → Latest, AGENTS.md → Fondo Pensione — Contributi + Panoramica: hero critique follow-up.)
Authoritative aesthetic spec is DESIGN.md (Apple + Linear/Vercel + Trade Republic; Jony Ive form-follows-function). Users: Italian self-directed investors who want to understand their position quickly and confidently. Brand: elegant, sophisticated, personal. Principles: (1) data first, decoration second; (2) motion with purpose; (3) density is a feature; (4) precision builds trust; (5) personality lives in the details.