Frontend Architecture
Exchange frontend data flow, state model, and component contract
The frontend is a thin consumer of 6 canonical API endpoints. No business logic in the frontend. No per-asset-type fetchers. One data flow, one state model, one way to do everything.
Data Flow
Boot -> /v1/navigation -> render rails + categories
|
Select category -> /v1/assets?marketRail=...&category=... -> render asset list
|
Select asset -> /v1/assets/{id} -> detail panel
-> /v1/assets/{id}/quote -> price ticker (poll 5s)
-> /v1/assets/{id}/chart -> candlestick chart
-> /v1/assets/{id}/book -> order book (poll 3s)State Model
// src/api/state.ts - Recoil atoms for UI state only
type ExchangeState = {
// Navigation (fetched once at boot)
navigation: Navigation | null
// UI selection state
activeMarketRail: string | null // "public" | "private" | "digital"
activeCategory: string | null // "stocks" | "crypto" | "privates" | ...
selectedAssetIdByCategory: Record<string, string | null>
// Server data (managed by hooks, not Recoil)
// Hooks return { data, isLoading, error }
}Key rule: Server data lives in hooks (useState + useEffect).
Recoil is only for UI state (which rail, which category, which asset is selected).
API Layer
src/api/
client.ts - fetch wrapper with auth headers
types.ts - TypeScript types matching API responses
hooks.ts - React hooks: useNavigation, useAssetList, useAssetDetail,
useAssetQuote, useAssetChart, useOrderBook
state.ts - Recoil atoms for UI selection stateComponent Contract
| Component | Consumes | Renders |
|---|---|---|
| NavigationRails | useNavigation() | Top market tabs (Public, Private, Digital) |
| CategoryTabs | useNavigation() + activeMarketRail | Category tabs within rail |
| AssetList | useAssetList(rail, category) | Scrollable asset list with prices |
| AssetDetail | useAssetDetail(assetId) | Asset header, metadata |
| PriceTicker | useAssetQuote(assetId) | Real-time price, change, volume |
| Chart | useAssetChart(assetId, range) | Candlestick/line chart |
| OrderBook | useOrderBook(assetId) | Bid/ask depth display |
| TradeForm | useAssetQuote(assetId) | Buy/sell order form |
What Was Deleted
The following legacy patterns were removed in the canonical migration:
Removed State
horsesState-- was Morning Line horse racing assetsprivateState-- was private securities separate statemarketIndexesState-- was market indices separate fetchwatchListstate that mixed with asset fetchingmapAssetDataStateAsPerTab-- complex tab-to-state mapping
Removed Endpoints
exchange-explorers-- replaced by/v1/assets?...exchange-assets-- replaced by/v1/assets/{id}stock-summaries-- replaced by/v1/assets?category=stockscommodity-summaries-- sameforex-summaries-- samemarket-index-summaries-- sameprivates-summaries-- sameexchange-music-- samereal-estate-summaries-- sameexchange-orderbook-- replaced by/v1/assets/{id}/book
Removed Patterns
assestTypeenum (note: typo was in original code)assetTabsmappinggetString()category label functions- Tab-to-endpoint switch statements
- Asset-type-specific fetchers
- Cross-category fallback selection logic
mapApiUrlchart URL mapping
Environments
| Env | Frontend | API | IAM |
|---|---|---|---|
| next | exchange.next.satschel.com | api.next.satschel.com/v1 | iam.next.satschel.com |
| alpha | exchange.alpha.satschel.com | api.alpha.satschel.com/v1 | iam.alpha.satschel.com |
| stage | exchange.stage.satschel.com | api.stage.satschel.com/v1 | iam.stage.satschel.com |
No .env files committed. Build injects VITE_API_HOST from branch name.
Build and Deploy
# Cloud Build (native amd64 on GCP)
gcloud builds submit --config=cloudbuild.yaml \
--substitutions=BRANCH_NAME=next \
--project=elliptical-feat-364815
# K8s deployment auto-restarts on image push
kubectl rollout restart deployment/frontend-exchange -n frontend-nextImage: us-docker.pkg.dev/elliptical-feat-364815/frontend/exchange:{branch}
Testing
# E2E tests (universe repo)
cd ~/work/liquidity/universe
E2E_BASE_URL=https://exchange.next.satschel.com npx playwright test
# Accreditation compliance tests
npx playwright test e2e/23-accreditation-guard.spec.ts
# Dividend label tests
npx playwright test e2e/24-dividend-labels.spec.ts