Skip to content

Latest commit

 

History

History
98 lines (88 loc) · 32.3 KB

File metadata and controls

98 lines (88 loc) · 32.3 KB

CLAUDE.md - Net Worth Tracker (Lean)

Project Overview

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.

Current Status

  • 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" (TransactionDialog registro operazioni, AssetDialog primo acquisto a leva, PensionContributionDialog, ExpenseDialog conto pagamento spesa) ora richiede type === 'cash' && assetClass === 'cash', non più il solo assetClass — un ETF monetario (es. XEON) classificato assetClass: '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-1 mobile, tablet:grid-cols-2 da 768px) invece di flex flex-wrap.
    • tsc clean; 70 files / 1222 tests green.

Architecture Snapshot

  • App Router; protected pages under app/dashboard/*
  • Service layer lib/services/*; shared pure utilities lib/utils/*; server-only logic lib/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/services functions; keep Firestore-coupled code thin.

Key Features (Active)

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) via useActiveAccount() (contexts/ActiveAccountContext.tsx). Grants: account-access/{ownerUid} (types/account.ts). Owner adds/removes members by email in Settings → Condivisione (components/settings/AccountSharingSection.tsx) via app/api/account/members/route.ts. Enforcement: firestore.rules canAccess/isMemberOf (theme/userPreferences stays per-viewer), assertCanAccessAccount (lib/server/apiAuth.ts) on Admin routes, ownerId in data-scoped client code. Switcher in components/layout/Sidebar.tsx and components/layout/SecondaryMenuDrawer.tsx (the "Altro" menu — the Sidebar is unreachable in portrait, so this is the only switcher on a phone); shared account naming via getAccountLabel (lib/utils/userDisplayUtils.ts). Conventions/gotchas in AGENTS.md → Shared Account / Delegated Access + Mobile Navigation Structure.
  • Demo mode: app/page.tsx public landing (stacked Panoramica + Cashflow savings-ring previews, proof strip, ThemePicker toggle 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 via useDashboardOverview()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 via updateAssetMetadata) 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 on useAssetLedgerMeta. The ledger also feeds Rendimenti's "Capitale investito"/"Plusvalenze realizzate" and Dividendi's per-asset total return (see those entries below). Pure engine lib/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 Firestore userPreferences/{userId} + localStorage; ColorThemeContext sets data-theme; charts theme-aware via useChartColors (--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 = transfer type, 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 → shared TransactionFeed (day-grouped, Feed/Tabella toggle). Pure lib/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. Pure lib/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). Pure lib/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 (--warning token) + Sankey with clickable DrillBreadcrumb — all always-visible; a "Dettaglio" Collapsible (default closed) holds Confronto Annuale, savings-rate trend, per-category sparklines, and "Andamento nel Tempo" (Storico only). CompositionList ranked-row drill-down (Spese/Entrate per Categoria + sottocategorie, Spese per Tipo — pie charts replaced 2026-07-13). Pure lib/utils/cashflowTimeSeries.ts. All period/view/range toggles use the shared SegmentedPill. NOTE cashflowHistoryStartYear is 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 behind Collapsible. Pure lib/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 shared resolveCoupon/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 the MetricSections (HeroMetricBlock + MetricCard divide-y rows: Rendimento, Rischio, Contesto — with a ledger-based "Capitale investito" row alongside Contributi Netti, gated on useAssetLedgerMeta — 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 with BenchmarkComparisonChart) + components/performance/RealizedGainsSection.tsx (cross-asset realized-P&L aggregation). Reference benchmark = BENCHMARKS[0] (60/40) via page-level useBenchmarkReturns. Sign tokens via getMetricValueColor; charts via useChartColors. 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 caches benchmark-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; ActionPlanner segmented Ribilancia/Versa/PrelevaRebalancePanel trade list / ContributionPanel no-sell split / WithdrawalPanel surplus-first split, the last two being one PlanNode tree with the sign flipped, class → sub-category → instrument, rendered by the shared PlanRow) then a "Dettaglio" divider → Detail (AllocationBreakdown inline accordion + "Esclusi dall'allocazione" group + "Esposizione Portfolio" cross-ETF+stock, lazy GET /api/portfolio/exposure, 24h cache). Asset.allocationRole (tradable | frozen | excluded) is partitioned BEFORE compareAllocations, which receives tradable + frozen: a frozen asset (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; an excluded asset (the home you live in) leaves the page entirely, which is why the total is smaller than the Panoramica net worth. Executability is enforced by capacity (tradable slice) in splitFromSurplus + a sell cap in buildRebalancePlan. findOrphanedTargets + stripOrphanedSubTargets catch targets stranded by an exclusion. Action colors via useActionColors; band-independent balance score via computeBalanceScore. Pure lib/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. Pure lib/utils/snapshotAssetBreakdown.ts (tested) — also hosts deriveHoldingStartDates (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 (AssistantPeriodSelector Mese/Anno/YTD/Storico/Libera) + period-reactive scheda; SSE streaming (meta|context|status|text|done|error); 5 modes; web search gated by includeMacroContext + webSearchPolicy.ts; proactive memory + follow-ups; sees the user's full expense category taxonomy (expenseCategories) for categorization questions. Runs on claude-sonnet-5 (adaptive thinking, no prompt caching by design). Flag NEXT_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 fireService twice; job-loss income-source selector. Pure lib/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 allocation deriveTargetAllocationFromGoals (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', like realestate — 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 a transfer); TFR/Datoriale credit the fund standalone. Multi-person households: a fund links to a FamilyMember (name + RAL + eligibility, managed in Impostazioni → Preferenze → Famiglia); the annual tax recap (deducted amount + IRPEF saving) and extra-deducibilità plafond, via the pure engine lib/utils/pensionDeduction.ts (taxOf injected), 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): PensionAllocationCards look-through on Allocazione (fund stays frozen, 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.

Testing

  • 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.

Data & Integrations

  • 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)

Known Issues (Active)

  • FX conversion depends on Frankfurter availability; 24h cache fallback. Pre-migration non-EUR assets without currentPriceEur show native price as EUR until the first price update (GBp safe via the pence→GBP /100 fallback).
  • 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-ESM jose@6ERR_REQUIRE_ESM on Vercel), so the 8 moderate uuid advisories stay open until resolvable.
  • Shared account setup: (1) the guest's email must be in REGISTRATION_WHITELIST and 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.rules must 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/userPreferences intentionally follows the viewer, not the shared account.
  • YOC/Current-Yield are keyed by assetId and exclude sold assets (intended). Discontinuous-holding edge — mitigated 2026-06-13: a sold-then-rebought instrument reuses the old assetId (ISIN continuity in createAsset, or quantity 0→>0), so a window spanning the gap used to credit the prior holding's dividends against the new cost. Now yieldOnCost.ts drops dividends paid before Asset.holdingStartDate (stamped at (re)purchase in createAsset/updateAsset; snapshot fallback deriveHoldingStartDates); 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).

Key Files

  • 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 in components/layout/Sidebar.tsx; Firestore collection account-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 by calculateYocMetrics/calculateCurrentYieldMetrics + app/api/dividends/stats/route.ts
  • Assets: lib/services/assetService.ts (createAsset ISIN reuse + holdingStartDate stamping; updateAsset qty 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; server lib/server/{assetTransactionUseCase,tradeFxService}.ts + routes app/api/asset-transactions/* + rules in firestore.rules; admin read getAssetTransactionsAdmin in lib/server/assetAdminRepository.ts (Fase D, consumed by dividends/stats); client lib/services/assetTransactionService.ts + lib/hooks/useAssetTransactions.ts + queryKeys.assetTransactions; UI components/assets/{TransactionDialog,AssetMovementsDialog}.tsx + AssetDialog ledger rework + entry points in AssetManagementTab/AssetCard; migration trigger in app/dashboard/assets/page.tsx; Rendimenti surfaces components/performance/RealizedGainsSection.tsx + app/dashboard/performance/page.tsx (Capitale investito / Plusvalenze Realizzate); app/api/dividends/stats/route.ts ledger-based totalReturnAssets. Tests __tests__/assetTransaction{Utils,sRoutes,WriteTx}.test.ts. Collections assetTransactions/assetTransactionsMeta. Spec docs/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) in types/assets.ts; FamilyMember + AssetAllocationSettings.familyMembers[] (types/assets.ts); pure engines lib/utils/{pensionDeduction,pensionContributions,performanceBase,pensionFire,pensionFamilyMembers}.ts (taxOf/valueOf injected, zero Firebase); client service lib/services/pensionContributionService.ts + lib/hooks/usePensionContributions.ts + queryKeys.pensionContributions; rules block in firestore.rules (no indexes by design); UI components/pension/{PensionContributionDialog,PensionOverview}.tsx (one tax-recap card per family member) + app/dashboard/pension/page.tsx + AssetDialog pensionFund type (incl. "Membro famiglia" select) + planningNav entry + "Vai a Previdenza" links in AssetCard/AssetManagementTab; "Famiglia" card in app/dashboard/settings/page.tsx (tab Preferenze) + respectPensionLockInFire on AssetAllocationSettings (lib/services/assetAllocationService.ts); ledger-orphan cleanup on conversion via deleteAllAssetTransactionsForAsset (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 (prepareAssetClassHistoryData pension param) + app/dashboard/history/page.tsx; lib/services/performanceService.ts (getAllPerformanceData) + app/dashboard/performance/page.tsx (cachedSnapshots); components/fire-simulations/FireCalculatorTab.tsx. Collection pensionContributions. Tests __tests__/{pensionDeduction,pensionContributions,pensionContributionService,performanceBase,pensionFire,pensionFamilyMembers}.test.ts. Spec docs/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; Firestore benchmark-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) + getUserSnapshotsAdmin in lib/server/assetAdminRepository.ts (admin); collection monthly-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 + AssetClass trendFollowing/carry (types/assets.ts); pure lib/utils/assetExposureUtils.ts (expandAssetExposure/calculatePortfolioLeverage, no pensionFund special-case). Service lib/services/assetAllocationService.ts (calculateCurrentAllocationSnapshot/toLegacyAllocationResult/deriveTargetLeverageRatio + compareAllocations partitioning by allocationRole); pure lib/utils/leverageAwareAllocationUtils.ts (buildInstrumentExposures/planInstrument{Contribution,Rebalance,Withdrawal} QP solver with the full D5 bug fixclassConst+classCoeff; LeveragePlanInputs); applyRebalanceBand preserves 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; Settings app/dashboard/settings/page.tsx (validazione ≥100 + leva derivata); components/assets/AssetDialog.tsx (leverageRatio input) + lib/services/assetService.ts (updateAsset/updateAssetMetadata clear via deleteField). Tests __tests__/{assetExposure,compareAllocations,leverageAwareAllocationUtils}.test.ts. Spec docs/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. Data Asset.displayTicker?/AssetFormData.displayTicker? (types/assets.ts); components/assets/AssetDialog.tsx (form field) + lib/services/assetService.ts (updateAsset/updateAssetMetadata clear 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 by assetId), 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. Spec docs/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.)

Design Context

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.