phase-1: optimize code
This commit is contained in:
+170
@@ -0,0 +1,170 @@
|
||||
# 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
|
||||
<!-- Reactive state -->
|
||||
let loading = $state(false);
|
||||
|
||||
<!-- Derived values -->
|
||||
const totalGL = $derived(totalValue - totalCost);
|
||||
|
||||
<!-- Derived with block -->
|
||||
const cards = $derived.by(() => { ... return [...] });
|
||||
|
||||
<!-- Props -->
|
||||
let { ctx } = $props();
|
||||
let { data } = $props();
|
||||
|
||||
<!-- Event handlers (no on:click, use onclick) -->
|
||||
<button onclick={screen}>Screen</button>
|
||||
|
||||
<!-- Conditionals in template -->
|
||||
{@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 `<style>` blocks — put global styles in `src/app.css`
|
||||
- Use `load()` in `+page.js` for page-level data, not `onMount`
|
||||
- `$derived` for computed values — do not recalculate in templates
|
||||
- Keep `api.js` as the single place for fetch calls
|
||||
- If adding a new page: create `+page.js` with a `load()` that fetches the needed API endpoint, receive via `$props()` in the component
|
||||
Reference in New Issue
Block a user