Frontend & Auth¶
The web app is a Next.js 16 App Router application in frontend/, written in TypeScript. It talks to the FastAPI backend over a server-side proxy and streams real-time updates over WebSocket.
- UI: shadcn + Radix + Tailwind v4. Brand accent is amber/gold.
- State: Zustand (client stores) + TanStack Query (server state / caching).
- Charts: lightweight-charts (price) + Recharts (analysis sections).
Run it independently with cd frontend && npm run dev. Tests use Vitest (not Jest) in co-located __tests__/ directories.
Pages¶
App Router pages live under frontend/src/app/:
| Area | Routes |
|---|---|
| Analysis | sessions, session/[id], stocks/[symbol] |
| Discovery | screener, scanner, opportunities, signals, market, catalyst, fund |
| Tracking | watchlist, portfolio, monitor |
| Config | strategies, settings |
| Account / public | login, support, privacy, terms |
Authentication¶
First-party, cookie-session based — there is no third-party auth provider for the session itself.
- Login — the FastAPI backend validates credentials and issues an HTTP-only
redhound_sessioncookie. Email/password and Google OAuth are both supported; public signup is gated byREDHOUND_AUTH_SIGNUP_OPEN(the gate applies to OAuth too, so strangers cannot silently provision an account during beta). - Route gating —
frontend/src/lib/auth/middleware.tsruns on protected pages. It callsGET /api/v1/auth/meagainst the backend (forwarding the cookie); on a non-200 it redirects to/login. Public paths (/login,/privacy,/terms,/support, …) are exempt via aPUBLIC_PATHSallowlist. - API proxy — calls to
/api/*are proxied to FastAPI server-side (App Router route handlers underfrontend/src/app/api/), so the browser never holds a bearer token — only the HTTP-only cookie.
Backend auth routes live in backend/api/routes/auth.py.
WebSocket authentication¶
Cookies aren't sent on cross-origin WebSocket upgrades, so real-time connections use short-lived signed tickets instead:
- The client fetches a ticket from
GET /api/v1/auth/ws-token(authenticated via the session cookie). - It opens the WebSocket with that ticket; the backend verifies the signature (signed with
REDHOUND_AUTH_SECRET) and subject before accepting the stream.
State & data fetching¶
| Concern | Tool | Where |
|---|---|---|
| Server data, caching, refetch | TanStack Query | hooks under frontend/src/hooks/ |
| Client UI/session state | Zustand | useSessionStore, useSettingsStore, usePaletteStore (frontend/src/hooks/) |
| Live prices / session events | WebSocket | authenticated via ws-token tickets |
Analysis page assembly¶
The unified report at session/[id] is assembled from independent section components (frontend/src/components/analysis/):
AnalysisPage.tsx— root; normalizes raw WebSocket payloads (result,reports,signal,debates) into section props.sections/— one component per report section:ExecutiveSummary,TechnicalOutlook,FundamentalAnalysis,Valuation,MacroSectorContext,SentimentNews,RiskFactors,ResearchDebate,AgentScorecard,TradeRecommendation.charts/— Recharts-basedPriceChart,RSIChart,MACDChart,EarningsChart,AgentScorecardChart.AgentReasoning/— live LangGraph pipeline progress as the analysis streams in.ReportHeader/,ReportTOC/— sticky header and table-of-contents navigation.
WebSocket streaming pipeline¶
Backend backend/api/chunk_processor.py::ChunkProcessor translates LangGraph stream chunks into client-facing events. When a new pipeline node is added, update its NODE_TO_PHASE map or the frontend won't see the new phase. See Event-Driven Workers for how analysis results reach the API in the first place.