# CLAUDE.md Guidance for working in this repository. ## Overview `market-screener-ui` is a SvelteKit 5 single-page application (CSR, no SSR) that serves as the interactive dashboard for the `market_screener` Fastify API. - All data comes from the API at `http://localhost:3000` (proxied through Vite in dev) - No SSR — `+layout.js` exports `ssr = false` - Svelte 5 runes syntax (`$state`, `$derived`, `$effect`, `$props`) --- ## Commands ```bash npm install # install dependencies (SvelteKit, Vite, Svelte 5) npm run dev # dev server on port 5173 npm run build # production build → .svelte-kit/output npm run preview # preview production build ``` To run the full stack, use `npm run dev` from the **API repo** (`market_screener/`) instead — it starts both servers together using `concurrently`. --- ## Architecture ### No SSR `src/routes/+layout.js` exports `ssr = false`. All data fetching happens in the browser. This avoids Svelte 5 SSR compatibility issues and makes sense for a live-data dashboard. ### API Proxy `vite.config.js` proxies `/api/*` → `http://localhost:3000` in dev. In production, configure your reverse proxy (nginx/Caddy) to do the same. ### Data Loading - **Screener page** (`/`): data loaded client-side on button click via `$lib/api.js` - **Portfolio page** (`/portfolio`): data loaded via SvelteKit `+page.js` `load()` function — this fires on navigation and is the correct SvelteKit pattern for CSR page data **Do not use `onMount` for initial data fetching** — use `load()` in `+page.js` instead. `onMount` does not reliably fire in SvelteKit CSR for page-level data. --- ## Project Structure ``` src/ app.html ← HTML shell app.css ← Global reset + body styles (no :global() in .svelte files) routes/ +layout.js ← exports ssr = false +layout.svelte ← nav bar (Screener / Portfolio links) +page.svelte ← Screener page portfolio/ +page.js ← load() function — fetches /api/finance/portfolio +page.svelte ← Portfolio + SimpleFIN page lib/ api.js ← All fetch calls to the Fastify API SignalBadge.svelte ← Signal pill component (Strong Buy / Avoid / etc.) MarketContext.svelte ← Benchmark strip component .claude/ launch.json ← Preview server config for Claude Code vite.config.js ← Vite config with /api proxy svelte.config.js ← SvelteKit config (adapter-auto) ``` --- ## Key Files ### `src/lib/api.js` All API calls in one place. If the API base URL changes, change it here only. ```js screenTickers(tickers) // POST /api/screen fetchCatalysts() // GET /api/screen/catalysts fetchPortfolio() // GET /api/finance/portfolio fetchMarketContext() // GET /api/finance/market-context ``` ### `src/routes/+page.svelte` (Screener) - Ticker input pre-filled with a default watchlist - `screen()` calls API and stores results in `$state` - `loadCatalysts()` fetches news tickers then **immediately calls `screen()`** — one click, full results - `results` is `null` until first screen — nothing renders below the toolbar - `verdictShort()` abbreviates long verdict strings (`"🟢 BUY (High Conviction)"` → `"Strong"`) ### `src/routes/portfolio/+page.svelte` - Receives `data` from `+page.js` load function via `let { data } = $props()` - Shows `data.error` if load failed, `data.advice` for holdings, `data.personalFinance` for SimpleFIN section --- ## Svelte 5 Patterns Used ```svelte let loading = $state(false); const totalGL = $derived(totalValue - totalCost); const cards = $derived.by(() => { ... return [...] }); let { ctx } = $props(); let { data } = $props(); {@const mode = getTab(type)} ``` --- ## API Response Shape The Fastify API serializes asset class instances before sending — `asset.getDisplayMetrics()` is called server-side and included as `asset.displayMetrics`. In the browser, use `r.asset.displayMetrics` directly (not `r.asset.getDisplayMetrics()` which doesn't exist on plain JSON objects). ```js // Screener response shape { STOCK: [{ asset: { ticker, type, currentPrice, metrics, displayMetrics }, fundamental, inflated, signal }], ETF: [...], BOND: [...], ERROR: [...], marketContext: { sp500Price, riskFreeRate, vixLevel, rateRegime, volatilityRegime, benchmarks } } ``` --- ## Styling Conventions - Dark theme throughout: page background `#0f1117`, card sections `#0d1117`/`#111827` - All colors are CSS custom values inline (no CSS variables yet — keep consistent with existing palette) - Tables: `width: max-content; min-width: 100%` inside a `.table-wrap { overflow-x: auto }` container - First column sticky: `position: sticky; left: 0; background: inherit` - Verdict pills: `.verdict-pill.green/yellow/red` — colored background tint + text - Monospace font for the ticker input field - `white-space: nowrap` on `tbody td` — tables scroll horizontally, not wrap **Color palette:** ``` page bg: #0f1117 card bg: #0d1117 / #111827 (header rows) border: #1e293b muted: #64748b / #475569 text: #e2e8f0 / #f1f5f9 green: #4ade80 (bg tint: #14532d33) yellow: #facc15 (bg tint: #71350033) red: #f87171 (bg tint: #450a0a33) blue accent: #2563eb / #3b82f6 ``` --- ## Conventions - Do not use `:global()` in `