phase-10: ui code enhancements
This commit is contained in:
@@ -1,5 +1,2 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
# Lint and auto-fix staged files only (fast)
|
# Lint and auto-fix staged files only (fast)
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
|
|||||||
@@ -1,5 +1,2 @@
|
|||||||
#!/bin/sh
|
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
# Run full test suite before push
|
# Run full test suite before push
|
||||||
npm test
|
npm test
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
export * from './shared/index.js';
|
export * from './shared/index.js';
|
||||||
export * from './screener/index.js';
|
export * from './screener/index.js';
|
||||||
|
export * from './portfolio/index.js';
|
||||||
|
export * from './calls/index.js';
|
||||||
|
|||||||
@@ -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';
|
||||||
+6
-170
@@ -1,170 +1,6 @@
|
|||||||
// ── UI type layer ─────────────────────────────────────────────────────────
|
/**
|
||||||
// Shared domain types are imported from the server's canonical model files
|
* Backward-compatibility shim.
|
||||||
// via the $types alias (→ server/types/). Only UI-specific types live here.
|
* Types have been split into lib/types/ subdirectory.
|
||||||
//
|
* Existing '$lib/types.js' imports continue to work unchanged.
|
||||||
// All consumers should import from '$lib/types.js' as before — nothing changes
|
*/
|
||||||
// at the call site.
|
export * from './types/index.js';
|
||||||
|
|
||||||
// ── 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[];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './shared.js';
|
||||||
|
export * from './ui.types.js';
|
||||||
|
export * from './portfolio.types.js';
|
||||||
@@ -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[];
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createCall, deleteCall } from '$lib/api.js';
|
import { createCall, deleteCall } from '$lib/api.js';
|
||||||
import { invalidateAll } from '$app/navigation';
|
import { invalidateAll } from '$app/navigation';
|
||||||
import CallForm from '$lib/calls/CallForm.svelte';
|
import CallForm from '$lib/components/calls/CallForm.svelte';
|
||||||
import CallCard from '$lib/calls/CallCard.svelte';
|
import CallCard from '$lib/components/calls/CallCard.svelte';
|
||||||
import CalendarSection from '$lib/calls/CalendarSection.svelte';
|
import CalendarSection from '$lib/components/calls/CalendarSection.svelte';
|
||||||
import type { CalendarEvent } from '$lib/types.js';
|
import type { CalendarEvent } from '$lib/types.js';
|
||||||
|
|
||||||
interface MarketCall {
|
interface MarketCall {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { portfolioStore } from '$lib/stores/portfolio.store.svelte.js';
|
import { portfolioStore } from '$lib/stores/portfolio.store.svelte.js';
|
||||||
import MarketContext from '$lib/components/shared/MarketContext.svelte';
|
import MarketContext from '$lib/components/shared/MarketContext.svelte';
|
||||||
import Spinner from '$lib/components/shared/Spinner.svelte';
|
import Spinner from '$lib/components/shared/Spinner.svelte';
|
||||||
import AddHoldingForm from '$lib/portfolio/AddHoldingForm.svelte';
|
import AddHoldingForm from '$lib/components/portfolio/AddHoldingForm.svelte';
|
||||||
import AdviceTable from '$lib/portfolio/AdviceTable.svelte';
|
import AdviceTable from '$lib/components/portfolio/AdviceTable.svelte';
|
||||||
import AccountsTable from '$lib/portfolio/AccountsTable.svelte';
|
import AccountsTable from '$lib/components/portfolio/AccountsTable.svelte';
|
||||||
|
|
||||||
const p = portfolioStore;
|
const p = portfolioStore;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user