diff --git a/.husky/pre-commit b/.husky/pre-commit index 4807cac..6e9a489 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,2 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - # Lint and auto-fix staged files only (fast) npx lint-staged diff --git a/.husky/pre-push b/.husky/pre-push index 54eb3d8..a4365a7 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,5 +1,2 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - # Run full test suite before push npm test diff --git a/ui/src/lib/calls/CalendarSection.svelte b/ui/src/lib/components/calls/CalendarSection.svelte similarity index 100% rename from ui/src/lib/calls/CalendarSection.svelte rename to ui/src/lib/components/calls/CalendarSection.svelte diff --git a/ui/src/lib/calls/CallCard.svelte b/ui/src/lib/components/calls/CallCard.svelte similarity index 100% rename from ui/src/lib/calls/CallCard.svelte rename to ui/src/lib/components/calls/CallCard.svelte diff --git a/ui/src/lib/calls/CallForm.svelte b/ui/src/lib/components/calls/CallForm.svelte similarity index 100% rename from ui/src/lib/calls/CallForm.svelte rename to ui/src/lib/components/calls/CallForm.svelte diff --git a/ui/src/lib/components/calls/index.ts b/ui/src/lib/components/calls/index.ts new file mode 100644 index 0000000..c80455b --- /dev/null +++ b/ui/src/lib/components/calls/index.ts @@ -0,0 +1,3 @@ +export { default as CallForm } from './CallForm.svelte'; +export { default as CallCard } from './CallCard.svelte'; +export { default as CalendarSection } from './CalendarSection.svelte'; diff --git a/ui/src/lib/components/index.ts b/ui/src/lib/components/index.ts index f358efe..10be495 100644 --- a/ui/src/lib/components/index.ts +++ b/ui/src/lib/components/index.ts @@ -1,2 +1,4 @@ export * from './shared/index.js'; export * from './screener/index.js'; +export * from './portfolio/index.js'; +export * from './calls/index.js'; diff --git a/ui/src/lib/portfolio/AccountsTable.svelte b/ui/src/lib/components/portfolio/AccountsTable.svelte similarity index 100% rename from ui/src/lib/portfolio/AccountsTable.svelte rename to ui/src/lib/components/portfolio/AccountsTable.svelte diff --git a/ui/src/lib/portfolio/AddHoldingForm.svelte b/ui/src/lib/components/portfolio/AddHoldingForm.svelte similarity index 100% rename from ui/src/lib/portfolio/AddHoldingForm.svelte rename to ui/src/lib/components/portfolio/AddHoldingForm.svelte diff --git a/ui/src/lib/portfolio/AdviceTable.svelte b/ui/src/lib/components/portfolio/AdviceTable.svelte similarity index 100% rename from ui/src/lib/portfolio/AdviceTable.svelte rename to ui/src/lib/components/portfolio/AdviceTable.svelte diff --git a/ui/src/lib/components/portfolio/index.ts b/ui/src/lib/components/portfolio/index.ts new file mode 100644 index 0000000..280f6f2 --- /dev/null +++ b/ui/src/lib/components/portfolio/index.ts @@ -0,0 +1,3 @@ +export { default as AddHoldingForm } from './AddHoldingForm.svelte'; +export { default as AdviceTable } from './AdviceTable.svelte'; +export { default as AccountsTable } from './AccountsTable.svelte'; diff --git a/ui/src/lib/types.ts b/ui/src/lib/types.ts index 63e2992..e464b3d 100644 --- a/ui/src/lib/types.ts +++ b/ui/src/lib/types.ts @@ -1,170 +1,6 @@ -// ── UI type layer ───────────────────────────────────────────────────────── -// Shared domain types are imported from the server's canonical model files -// via the $types alias (→ server/types/). Only UI-specific types live here. -// -// All consumers should import from '$lib/types.js' as before — nothing changes -// at the call site. - -// ── Re-export shared domain types ──────────────────────────────────────── -export type { - Signal, - AssetType, - ScoreMode, - ScoreResult, - AssetResult, - ScreenerResult, -} from '$types/asset.model.js'; - -export type { - RateRegime, - VolatilityRegime, - Benchmarks, - MarketContext, -} from '$types/market.model.js'; - -export type { HoldingType, PortfolioHolding, PortfolioAdvice } from '$types/portfolio.model.js'; - -export type { TickerSnapshot, MarketCall } from '$types/calls.model.js'; - -export type { LLMAnalysis, CatalystStory, CalendarEvent } from '$types/finance.model.js'; - -// ── UI-only types (not on the server) ──────────────────────────────────── - -import type { AssetType } from '$types/asset.model.js'; -import type { LLMAnalysis } from '$types/finance.model.js'; - -/** Detailed display metrics rendered per asset row in the screener table. */ -export interface AssetDisplayMetrics { - // ── Common ────────────────────────────────────────────────────────── - Price?: string; - - // ── Stock: classification ──────────────────────────────────────────── - Sector?: string; - 'Cap Tier'?: string; // Mega Cap / Large Cap / Mid Cap / Small Cap / Micro Cap - Style?: string; // High Growth / Growth / Stable / Value / Turnaround / Declining - - // ── Stock: valuation ───────────────────────────────────────────────── - 'P/E'?: string; - PEG?: string; - 'P/B'?: string; - - // ── Stock: quality ─────────────────────────────────────────────────── - 'GrossM%'?: string; // gross margin — key for tech/software moat - 'ROE%'?: string; - 'OpMgn%'?: string; - 'NetMgn%'?: string; - 'FCF Yld%'?: string; - 'Div%'?: string; - - // ── Stock: risk ─────────────────────────────────────────────────────── - 'D/E'?: string; - Quick?: string; - Beta?: string; - - // ── Stock: 52-week movement ─────────────────────────────────────────── - '52W Pos'?: string; // % position within the 52-week range - '52W Chg'?: string; // total price return over last 52 weeks (signed %) - 'From High'?: string; // % below 52-week high (negative = drawdown) - 'From Low'?: string; // % above 52-week low (positive = recovery) - - // ── Stock: analyst consensus ────────────────────────────────────────── - Analyst?: string; // Strong Buy / Buy / Hold / Sell / Strong Sell - '# Analysts'?: string; - Target?: string; // analyst consensus price target - Upside?: string; // % upside to analyst target (signed %) - - // ── Stock: DCF intrinsic value ──────────────────────────────────────── - 'DCF Value'?: string; // intrinsic value per share - 'DCF Safety'?: string; // margin of safety % (positive = undervalued) - - // ── Stock: REIT-specific ────────────────────────────────────────────── - 'P/FFO'?: string; - - // ── ETF ─────────────────────────────────────────────────────────────── - 'Exp Ratio%'?: string; - 'Yield%'?: string; - AUM?: string; - '5Y Return%'?: string; - - // ── Bond ────────────────────────────────────────────────────────────── - 'YTM%'?: string; - Duration?: string; - Rating?: string; - - [key: string]: string | null | undefined; -} - -/** State object for the LLM analysis slide-over sidebar. */ -export interface SidebarState { - open: boolean; - loading: boolean; - analysis: LLMAnalysis | null; - type: AssetType | null; - error: string | null; -} - -/** Transient state for inline row editing in the portfolio table. */ -export interface InlineEdit { - ticker: string; - shares: string; - costBasis: string; - type: string; - source: string; -} - -// ── Portfolio component types ───────────────────────────────────────────── - -import type { Signal } from '$types/asset.model.js'; - -/** A single row in the portfolio advice table. */ -export interface AdviceRow { - ticker: string; - type: string; - source: string; - shares: number; - costBasis: number; - currentPrice: string | null; - marketValue: string | null; - gainLossPct: string | null; - signal: Signal | null; - advice: string; - reason: string; -} - -/** Form data for adding or updating a holding. */ -export interface HoldingFormData { - ticker: string; - shares: number; - costBasis: number; - type: 'stock' | 'etf' | 'bond' | 'crypto'; - source: string; -} - -interface SimpleFINAccount { - name: string; - type: string; - org: string; - balance: number; -} - -interface CategoryBreakdown { - category: string; - amount: number; - pct: number; -} - -/** Personal finance summary from SimpleFIN. */ -export interface PersonalFinance { - netWorth: number; - totalAssets: number; - totalLiabilities: number; - totalCash: number; - totalInvestments: number; - totalIncome: number; - totalSpend: number; - cashPct: number; - investPct: number; - savingsRate: string | null; - accounts: SimpleFINAccount[]; - categoryBreakdown: CategoryBreakdown[]; -} +/** + * Backward-compatibility shim. + * Types have been split into lib/types/ subdirectory. + * Existing '$lib/types.js' imports continue to work unchanged. + */ +export * from './types/index.js'; diff --git a/ui/src/lib/types/index.ts b/ui/src/lib/types/index.ts new file mode 100644 index 0000000..686852a --- /dev/null +++ b/ui/src/lib/types/index.ts @@ -0,0 +1,3 @@ +export * from './shared.js'; +export * from './ui.types.js'; +export * from './portfolio.types.js'; diff --git a/ui/src/lib/types/portfolio.types.ts b/ui/src/lib/types/portfolio.types.ts new file mode 100644 index 0000000..96d8e3c --- /dev/null +++ b/ui/src/lib/types/portfolio.types.ts @@ -0,0 +1,54 @@ +import type { Signal } from '$types/asset.model.js'; + +/** A single row in the portfolio advice table. */ +export interface AdviceRow { + ticker: string; + type: string; + source: string; + shares: number; + costBasis: number; + currentPrice: string | null; + marketValue: string | null; + gainLossPct: string | null; + signal: Signal | null; + advice: string; + reason: string; +} + +/** Form data for adding or updating a holding. */ +export interface HoldingFormData { + ticker: string; + shares: number; + costBasis: number; + type: 'stock' | 'etf' | 'bond' | 'crypto'; + source: string; +} + +interface SimpleFINAccount { + name: string; + type: string; + org: string; + balance: number; +} + +interface CategoryBreakdown { + category: string; + amount: number; + pct: number; +} + +/** Personal finance summary from SimpleFIN. */ +export interface PersonalFinance { + netWorth: number; + totalAssets: number; + totalLiabilities: number; + totalCash: number; + totalInvestments: number; + totalIncome: number; + totalSpend: number; + cashPct: number; + investPct: number; + savingsRate: string | null; + accounts: SimpleFINAccount[]; + categoryBreakdown: CategoryBreakdown[]; +} diff --git a/ui/src/lib/types/shared.ts b/ui/src/lib/types/shared.ts new file mode 100644 index 0000000..54328cf --- /dev/null +++ b/ui/src/lib/types/shared.ts @@ -0,0 +1,21 @@ +export type { + Signal, + AssetType, + ScoreMode, + ScoreResult, + AssetResult, + ScreenerResult, +} from '$types/asset.model.js'; + +export type { + RateRegime, + VolatilityRegime, + Benchmarks, + MarketContext, +} from '$types/market.model.js'; + +export type { HoldingType, PortfolioHolding, PortfolioAdvice } from '$types/portfolio.model.js'; + +export type { TickerSnapshot, MarketCall } from '$types/calls.model.js'; + +export type { LLMAnalysis, CatalystStory, CalendarEvent } from '$types/finance.model.js'; diff --git a/ui/src/lib/types/ui.types.ts b/ui/src/lib/types/ui.types.ts new file mode 100644 index 0000000..8374b74 --- /dev/null +++ b/ui/src/lib/types/ui.types.ts @@ -0,0 +1,81 @@ +import type { AssetType } from '$types/asset.model.js'; +import type { LLMAnalysis } from '$types/finance.model.js'; + +/** Detailed display metrics rendered per asset row in the screener table. */ +export interface AssetDisplayMetrics { + // ── Common ────────────────────────────────────────────────────────── + Price?: string; + + // ── Stock: classification ──────────────────────────────────────────── + Sector?: string; + 'Cap Tier'?: string; + Style?: string; + + // ── Stock: valuation ───────────────────────────────────────────────── + 'P/E'?: string; + PEG?: string; + 'P/B'?: string; + + // ── Stock: quality ─────────────────────────────────────────────────── + 'GrossM%'?: string; + 'ROE%'?: string; + 'OpMgn%'?: string; + 'NetMgn%'?: string; + 'FCF Yld%'?: string; + 'Div%'?: string; + + // ── Stock: risk ─────────────────────────────────────────────────────── + 'D/E'?: string; + Quick?: string; + Beta?: string; + + // ── Stock: 52-week movement ─────────────────────────────────────────── + '52W Pos'?: string; + '52W Chg'?: string; + 'From High'?: string; + 'From Low'?: string; + + // ── Stock: analyst consensus ────────────────────────────────────────── + Analyst?: string; + '# Analysts'?: string; + Target?: string; + Upside?: string; + + // ── Stock: DCF intrinsic value ──────────────────────────────────────── + 'DCF Value'?: string; + 'DCF Safety'?: string; + + // ── Stock: REIT-specific ────────────────────────────────────────────── + 'P/FFO'?: string; + + // ── ETF ─────────────────────────────────────────────────────────────── + 'Exp Ratio%'?: string; + 'Yield%'?: string; + AUM?: string; + '5Y Return%'?: string; + + // ── Bond ────────────────────────────────────────────────────────────── + 'YTM%'?: string; + Duration?: string; + Rating?: string; + + [key: string]: string | null | undefined; +} + +/** State object for the LLM analysis slide-over sidebar. */ +export interface SidebarState { + open: boolean; + loading: boolean; + analysis: LLMAnalysis | null; + type: AssetType | null; + error: string | null; +} + +/** Transient state for inline row editing in the portfolio table. */ +export interface InlineEdit { + ticker: string; + shares: string; + costBasis: string; + type: string; + source: string; +} diff --git a/ui/src/routes/calls/+page.svelte b/ui/src/routes/calls/+page.svelte index fed4cd7..d9ce93c 100644 --- a/ui/src/routes/calls/+page.svelte +++ b/ui/src/routes/calls/+page.svelte @@ -1,9 +1,9 @@