Web frontend
The web/ directory holds a SolidJS single-page app that connects to
chartgen --serve via MCP Streamable HTTP and renders a candlestick
chart with an RSI panel. It implements
ADR-0003.
The v0.1 scope is deliberately narrow: ticker + timeframe + a Load button, and that’s it. Alerts UI, order entry, multi-chart layouts, and LLM chat are explicitly out of scope until the core loop is solid.
Getting started
Section titled “Getting started”# Terminal 1: run chartgencargo run -- --serve
# Terminal 2: run the frontendcd webnpm installnpm run devVite serves at http://localhost:5173 and proxies OAuth + MCP
endpoints to chartgen on port 9315, so no CORS or
VITE_CHARTGEN_BASE_URL configuration is needed for local dev.
Clicking Load triggers the OAuth 2.1 PKCE flow on first use:
discovery → dynamic client registration → redirect to
/authorize → token exchange. The access token stays in memory;
the client_id/client_secret pair persists in localStorage so
subsequent sessions skip registration.
Build and deploy
Section titled “Build and deploy”cd webnpm run build # static bundle in dist/npm run typecheck # strict + noUncheckedIndexedAccessThe build output is a static bundle ready for any CDN or object store. The recommended target is Cloudflare Pages:
- Connect the GitHub repository in the Pages dashboard.
- Set Build command to
cd web && npm install && npm run build. - Set Build output directory to
web/dist. - Add
VITE_CHARTGEN_BASE_URL=https://chartgen.example.comas an environment variable if the API is on a different origin.
For same-origin deployments (frontend and backend behind one
domain) leave VITE_CHARTGEN_BASE_URL unset — the app will hit
/mcp, /register, /token etc. on the current origin.
Architecture
Section titled “Architecture”- SolidJS — fine-grained reactivity, ~7 KB runtime. Candle ticks update only the DOM nodes that depend on them. See ADR-0003 for why Solid over React/Svelte/Vue.
- TanStack Solid Query — cache,
retry, stale-while-revalidate. Cache keys are
['generate_chart', ticker, timeframe], so switching timeframes on a symbol we already loaded is instant. @modelcontextprotocol/sdkwithStreamableHTTPClientTransport. Bearer tokens flow throughrequestInit.headers;fallbackNotificationHandlertaps intonotifications/alert_triggeredframes on the GET SSE stream (dormant logging in v0.1).- Kobalte — WAI-ARIA-correct headless
primitives (
Select,TextField). Visual styling via Tailwind. - Tailwind v4 — CSS-first config
(no
postcss.config.js). OKLCH dark palette. - klinecharts — candles + RSI
panel. The RSI values come from chartgen; the browser doesn’t
recompute them. A custom indicator reads per-bar stashed values
and returns them verbatim;
calc’snulloutputs become gaps in the panel. - Zod — every MCP response is parsed with
the schemas in
web/src/types/responses.ts(ADR-0004). Tool-argument types come fromweb/src/types/generated-input.ts, regenerated via./scripts/gen-mcp-types.sh.
Configuration
Section titled “Configuration”| Variable | Purpose | Default |
|---|---|---|
VITE_CHARTGEN_BASE_URL | chartgen origin | unset → same-origin |
Copy web/.env.example to web/.env.local for local overrides.
Production builds bake the value at build time (standard Vite
behavior for VITE_* variables).