Skip to content

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.

  1. Login — the FastAPI backend validates credentials and issues an HTTP-only redhound_session cookie. Email/password and Google OAuth are both supported; public signup is gated by REDHOUND_AUTH_SIGNUP_OPEN (the gate applies to OAuth too, so strangers cannot silently provision an account during beta).
  2. Route gatingfrontend/src/lib/auth/middleware.ts runs on protected pages. It calls GET /api/v1/auth/me against the backend (forwarding the cookie); on a non-200 it redirects to /login. Public paths (/login, /privacy, /terms, /support, …) are exempt via a PUBLIC_PATHS allowlist.
  3. API proxy — calls to /api/* are proxied to FastAPI server-side (App Router route handlers under frontend/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:

  1. The client fetches a ticket from GET /api/v1/auth/ws-token (authenticated via the session cookie).
  2. 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-based PriceChart, 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.