Titandocs

Overview

Stack, directory map, and the rule that decides where a given piece of state belongs.

The interface is a React 19 + Vite trading terminal. It renders three screens — Trade, Portfolio, and TLP (built, disabled for the MVP) — against the BFF at /v1.

Stack

ConcernChoiceWhy this one
FrameworkReact 19.2
RouterReact Router DOM 7, data routerLoader-level redirects fire before any element mounts: no flash
BuildVite 8
Server stateTanStack Query 5Party-scoped keys, one retry policy
Client stateZustand 5Slim selectors, persist for preferences
Charts (price)TradingView Charting LibraryVendored in-tree, fed by a custom datafeed
Charts (portfolio)@visx/*Equity area, ring donut, sparklines
StylingTailwind CSS v4Tokens in src/index.css under @theme; no tailwind.config.ts
MotionmotionSpring micro-interactions
Numbers@number-flow/reactAnimated tickers
AnalyticsPostHogMasked session replay

Path alias: @/*src/*.

Commands

npm run dev            # vite :3000, proxies /v1 (+ws) → :4000
npm run build          # tsc -b && vite build --mode $(node scripts/build-mode.mjs)
npm run test:run       # vitest single run — CI + pre-push
npm run knip           # dead-code / unused-export check

scripts/build-mode.mjs picks the Vite mode from the deploying branch: next / mainproduction (mainnet), everything else including develop and PR previews → staging (devnet). Cloudflare Pages supplies CF_PAGES_BRANCH; locally use BRANCH=main npm run build.

Directory map

main.tsx — QueryClient, PostHog, lazy wallet providers, RouterProvider
router.tsx — React Router v7 lazy() chunks
index.css — Tailwind v4 @theme tokens + @layer components

Where does this state go?

The single question that causes the most churn. The answer is a decision tree, not a preference:

Does it come from the BFF? → TanStack Query. Never mirror server data into Zustand.

Should it survive a shared link? → the URL. The active market is /trade/:marketSlug, full stop — there is no currentSymbol in any store.

Should it survive a reload? → a persist-ed Zustand store (preferences, active-party, onboarding).

Everything else → a plain Zustand store, or component state if only one subtree cares.

The one deliberate exception is stores/pending-tpsl: TP/SL levels chosen on a LIMIT order that has not filled yet. It is not persisted, because it is an intent, not state — a reload drops it, and the toast says so.

Current state

  • Trade — wired to the BFF: pair strip, TradingView chart, live order book, order form with per-market grid validation and TP/SL, positions panel. Market is URL-driven; every read is market-scoped.
  • Portfolio — positions, PnL, balances, equity chart, transactions.
  • Wallet — exclusive Canton / EVM / Auth0. Placement is wired; cancel and leverage are not.
  • TLP — built, route and nav tab both gated off for the MVP.

On this page