3a113aaa8f
Pre-launch audit findings (frontend):
- trades: unlock private history in-page. TradesPageClient only read a CACHED
view envelope, so a connected user who hadn't visited Settings first saw
"load settings once to unlock" with no way forward. Added an explicit
"Unlock trade history" button that mints the view envelope via
getOrCreateViewEnvelope (one signature, no gas) and reloads — no detour.
- wagmi: broaden connector from injected({target:'metaMask'}) to generic
injected(). The MetaMask pin made Rabby / Coinbase ext / Brave / Frame / OKX
and non-MetaMask browsers fail to connect. wagmi v2 EIP-6963 discovery covers
all injected wallets; still uses the native provider path (not the MM SDK).
- sitemap: drop /settings. It was emitted in sitemap.xml while robots.ts
disallows it — a self-contradicting crawler signal. Private pages stay out
of both.
- docs: note the app runs on port 3001 (package.json), not 3000. The CLAUDE.md
verify sweep targeted 3000 and would have reported every page as down.
- routing: COMMIT the Next.js 16 migration that was sitting uncommitted —
delete middleware.ts, add root proxy.ts (next-intl createMiddleware), update
next-env.d.ts. Vercel deploys from git, so leaving proxy.ts untracked would
have shipped the old routing. `next build` confirms "Proxy (Middleware)" is
active and all routes compile. tsc clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
305 lines
14 KiB
Markdown
305 lines
14 KiB
Markdown
# Trump Alpha — Frontend (trumpsignal.com)
|
||
|
||
> Next.js 16 dashboard for the Trump Alpha backend. Real-time signals,
|
||
> Hyperliquid position management UI, KOL talks-vs-trades view, Macro Vibes
|
||
> regime panel. Read-only for the public; wallet-bound features (Pro) for
|
||
> auto-trade subscribers.
|
||
|
||
This is the AI-readable entry doc. Read this first on entering the repo.
|
||
|
||
---
|
||
|
||
## What this repo is
|
||
|
||
- **Pure frontend** — Next.js 16 App Router, TypeScript, server components +
|
||
client components, deployed to Vercel.
|
||
- **Talks to a Python backend** at `https://api.trumpsignal.com` (or
|
||
`localhost:8000` in dev). All trading state, signals, AI scoring,
|
||
Hyperliquid integration lives in the sibling **`/Users/k/Public/Claude/
|
||
backend`** repo. See its CLAUDE.md.
|
||
- **No server-side trading logic** lives here. Frontend is a thin layer
|
||
over the API + WebSocket — even "open a position" routes to a backend
|
||
endpoint that does the signed-request verification + HL call.
|
||
|
||
---
|
||
|
||
## Stack quirks (Next.js 16 specific)
|
||
|
||
- **`proxy.ts` replaces `middleware.ts`** in Next.js 16. Routes that need
|
||
CORS / locale rewriting go through `proxy.ts` at the root.
|
||
- **`params` is async (`Promise<{...}>`)** in all dynamic routes. ALL page
|
||
components await it:
|
||
```tsx
|
||
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
|
||
const { locale } = await params
|
||
...
|
||
}
|
||
```
|
||
Forgetting the `await` redirects to `/undefined/...` silently. Several
|
||
pages were broken this way pre-v2.0 — be alert if you copy older patterns.
|
||
- **i18n is shelved.** The `[locale]` segment exists, `messages/zh.json`
|
||
exists, but Chinese branches are kept as dead code with a comment
|
||
`// i18n shelved — see messages/zh.json`. Don't activate without an ADR.
|
||
Currently `isZh = false` is hardcoded in metadata generators.
|
||
- **`runtime = 'edge'`** on a few routes (`app/icon.tsx`, OG image generator).
|
||
Edge runtime has no fs / no Node APIs — don't import server-side libs there.
|
||
|
||
---
|
||
|
||
## Route map
|
||
|
||
```
|
||
app/
|
||
├── layout.tsx Root layout: JSON-LD schema.org, fonts, meta
|
||
├── page.tsx / (root) → redirects to /en
|
||
├── icon.tsx Dynamic favicon (α on dark bg, edge runtime)
|
||
├── apple-icon.tsx PWA install icon
|
||
├── opengraph-image.tsx OG card generator (1200x630)
|
||
├── manifest.ts PWA manifest
|
||
├── sitemap.ts SEO sitemap.xml
|
||
├── robots.ts robots.txt
|
||
└── api/
|
||
└── proxy/[...path]/ Catch-all proxy to backend (CORS workaround)
|
||
|
||
app/[locale]/
|
||
├── page.tsx Landing — pinned signals, live ticker, Trump feed
|
||
├── DashboardClient.tsx Main 'use client' wrapper for the home dashboard
|
||
├── trump/page.tsx /en/trump — Trump Truth Social signal stream
|
||
├── macro/page.tsx /en/macro — Macro Vibes regime panel ★ renamed from /btc
|
||
├── kol/page.tsx /en/kol — KOL talks-vs-trades
|
||
├── trades/page.tsx /en/trades — closed trade history
|
||
├── posts/page.tsx /en/posts — all Posts (signal + Trump)
|
||
├── archive/page.tsx /en/archive — historical / paginated
|
||
├── settings/page.tsx /en/settings — wallet connect, HL key, prefs
|
||
├── analytics/page.tsx /en/analytics — performance metrics
|
||
├── case-studies/page.tsx /en/case-studies — documented event walkthroughs
|
||
├── methodology/page.tsx /en/methodology — how signals work
|
||
├── glossary/page.tsx /en/glossary — AHR999, MVRV-Z, KOL, etc.
|
||
├── privacy/page.tsx
|
||
├── terms/page.tsx
|
||
├── contact/page.tsx
|
||
└── globals.css Design system tokens + .macro-* classes
|
||
```
|
||
|
||
---
|
||
|
||
## Components map (the meaningful ones)
|
||
|
||
```
|
||
components/
|
||
├── nav/Navbar.tsx Top nav + tabs (Trump | Macro Vibes | KOL ...)
|
||
├── signals/
|
||
│ ├── SignalMonitor.tsx Live signal stream (WS-driven)
|
||
│ ├── SourceChips.tsx Filter chips per source
|
||
│ ├── SystemControl.tsx Sys1/sys2 toggle widgets, Auto-Trade switch
|
||
│ └── ConfirmCloseTrade.tsx Modal for manual close
|
||
├── dashboard/
|
||
│ ├── PostCards.tsx Trump post cards
|
||
│ ├── SignalMonitor.tsx (older — being consolidated)
|
||
│ └── BtcReversalAlert.tsx Pinned alert when sys2 fires
|
||
├── btc/MacroPanel.tsx ★ 8-indicator Macro Vibes layout (4 sections,
|
||
│ composite needle, threshold chips, peak-trail viz)
|
||
├── positions/
|
||
│ ├── OpenPositions.tsx Polls /positions/open + /positions/today every 15s
|
||
│ └── TradeCard.tsx Single trade row w/ grow toggle + close button
|
||
├── kol/KolDigest.tsx Daily KOL summary widget
|
||
├── telegram/
|
||
│ ├── TelegramCard.tsx Settings — connect via 6-char code
|
||
│ └── SignConfirmSheet.tsx EIP-191 signed request preview
|
||
├── nav/WalletConnect.tsx Wagmi-style wallet connect (MetaMask etc.)
|
||
├── ui/
|
||
│ ├── InfoTip.tsx CSS-only tooltip with `?` icon
|
||
│ ├── PageHint.tsx Strong page subtitle (replaces page-sub)
|
||
│ ├── Toast.tsx
|
||
│ └── Modal.tsx
|
||
└── ws/WsProvider.tsx WebSocket singleton context provider
|
||
```
|
||
|
||
---
|
||
|
||
## API layer
|
||
|
||
All API calls go through **`lib/api.ts`**. Single `fetchJson<T>` wrapper:
|
||
|
||
- Reads base URL from env (`NEXT_PUBLIC_API_URL`) or proxies through
|
||
`/api/proxy/[...path]/` for same-origin CORS-free calls.
|
||
- Wallet-scoped reads (`/user/.../public`, `/positions/open`) pass wallet
|
||
as query param + signed ts/sig.
|
||
- Mutations (subscribe, manual_close, set_auto_trade, manual-window) build
|
||
an EIP-191 signed payload via wagmi → POST signed envelope.
|
||
|
||
**Auth model**: no sessions, no cookies. Wallet address + signed timestamp
|
||
+ signature on every mutating call. Verification lives in the backend
|
||
(`signed_request.verify_signed_request`). The same wallet that signed the
|
||
HL API key envelope at subscribe time is the only one that can later
|
||
mutate the subscription.
|
||
|
||
---
|
||
|
||
## State management
|
||
|
||
- **No global store** (no Redux/Zustand). Tree-local state + props.
|
||
- **SWR-style cache** in `lib/cache.ts` for the API-heavy pages.
|
||
- **WebSocket** singleton via `WsProvider` — used by `SignalMonitor` and
|
||
live price tickers.
|
||
- **localStorage** holds wallet address only (for re-connect UX). HL API
|
||
keys NEVER touch the client — they're submitted once at subscribe, sent
|
||
encrypted, and decrypted only inside the backend.
|
||
|
||
---
|
||
|
||
## Design system
|
||
|
||
Tokens in `app/[locale]/globals.css`:
|
||
|
||
- **Colors** — `oklch()` color space throughout. Brand orange = `#f5a524`,
|
||
brand dark = `#0a0907`. `var(--up)` green / `var(--down)` red / `var(--ink-1..3)`
|
||
text tiers.
|
||
- **Dark mode** via `[data-theme="dark"]` attribute on `<html>`. Most
|
||
components use `oklch(L% c h)` so dark/light switching is automatic.
|
||
- **Macro Vibes specific** — extensive `.macro-*` classes for the 8-indicator
|
||
layout. Threshold chips have `:not(.active) { opacity: 0.45 }` and `.active`
|
||
is tone-filled (green/red/amber).
|
||
|
||
---
|
||
|
||
## Things that LOOK like bugs but aren't
|
||
|
||
- **Old code references "/en/btc"** — Macro Vibes was renamed from "BTC
|
||
Signal" in v2.0. All routes are now `/en/macro`. `app/[locale]/btc/page.tsx`
|
||
EXISTS intentionally as a permanent redirect to `/en/macro` — that's fine.
|
||
If you find a stale `/en/btc` hardcoded link anywhere else, it's a real
|
||
bug — fix it.
|
||
- **`isZh = false` in metadata generators** — i18n was shelved pre-launch.
|
||
See "Stack quirks" above.
|
||
- **Multiple `proxy.ts` patterns** — Next.js 16 split middleware behaviour.
|
||
Keep proxy logic in the single `proxy.ts` at root.
|
||
- **`'use client'` directives on many pages** — necessary for client-side
|
||
WS / wallet interactions. Don't try to convert without verifying the WS
|
||
+ wagmi imports work in server components.
|
||
- **`telegram.send_message` accepting `int | str` for chat_id** — the backend
|
||
uses string form for the public channel (`"@trumpalpha"`) and integer for
|
||
private user chats. Both are valid; no frontend change needed.
|
||
- **`x-forwarded-for` is deleted in the proxy** — KNOWN BUG (see below).
|
||
Don't "fix" it by just removing the delete; the correct fix is to relay the
|
||
real client IP. See Open Known Issues.
|
||
|
||
---
|
||
|
||
## Open known issues (frontend)
|
||
|
||
- ~~**Rate limit bypass via proxy** (FIXED 2026-05-29):~~ `route.ts` no longer
|
||
deletes `x-forwarded-for`. It now reads the real client IP from the incoming
|
||
`x-forwarded-for` or `x-real-ip` header (set by Vercel/CDN) and sets it on
|
||
the upstream request so `slowapi` can distinguish individual clients.
|
||
|
||
- ~~**`signMessageAsync` in `OpenPositions` polling deps** (MEDIUM, FIXED 2026-05-29):~~
|
||
Removed `signMessageAsync` and `isZh` from the `useEffect` dependency array
|
||
in `components/positions/OpenPositions.tsx`. Neither is used inside the effect;
|
||
including them caused wagmi reference churn to restart the 15s poll timer.
|
||
|
||
- ~~**Stale types after BUG-08/BUG-14** (FE-01–06, FIXED 2026-05-29):~~
|
||
- `types/index.ts`: `price_impact.asset` widened from `'BTC' | 'ETH'` to
|
||
`string` (BUG-14 now tracks the actually-traded asset which may be
|
||
SOL/TRUMP/etc.).
|
||
- `lib/useRealtimeData.ts`: `PriceMsg.asset` and `Handlers.onPrice` widened
|
||
to `string` (BUG-08 expanded the WS price stream to all ASSET_MAP entries).
|
||
- `store/dashboard.ts`: `livePrices` changed from `{ BTC; ETH }` to
|
||
`Record<string, number | null>`; `setLivePrice` param widened to `string`.
|
||
- `lib/api.ts`: `getPrices` asset param widened to `string`.
|
||
- `components/trades/TradeTable.tsx`: hardcoded `ASSETS = ['all','BTC','ETH','SOL']`
|
||
replaced by a dynamic `useMemo` that builds the asset list from the actual
|
||
trade set — new perps (TRUMP, BNB, etc.) appear in the filter automatically.
|
||
- `DashboardClient.tsx` + `TradesPageClient.tsx`: removed `isZh` from
|
||
`useEffect` deps (compile-time constant; including it restarted effects
|
||
on every render).
|
||
|
||
- ~~**Interaction/button bugs sweep** (UI-A..G, FIXED 2026-05-29):~~
|
||
- **A** `OpenPositions.tsx` close-modal backdrop now dismisses in `'err'`
|
||
state too (previously stuck — only `'idle'` dismissed). State is reset on
|
||
dismiss so the next open is clean.
|
||
- **B** `confirmCloseTrade` distinguishes user-cancelled signature
|
||
(`isUserRejection`) from real failure: cancellation closes the modal
|
||
silently; real errors stay open in `'err'` state with the message.
|
||
- **C** `SystemControl.flipAuto` claims `busy=true` **before** `await
|
||
confirmSign(...)`. Rapid double-click on the ON/OFF pill no longer slips
|
||
past the guard. The slot is released if the user cancels the sheet.
|
||
- **D** `BotConfigPanel` "Rotate" API-key button resets `keyState`/`keyErr`
|
||
so a previously-failed save's red error doesn't bleed into the new edit.
|
||
- **E** Paper/Live `Switch` clears `subErr` when toggled so a stale
|
||
subscribe-error doesn't sit next to the new choice.
|
||
- **F** `confirmSign` keeps a module-scope `_activeCancel`; a second call
|
||
while a sheet is mounted resolves the first promise with `false` instead
|
||
of leaving its caller hanging forever. All 9 call sites verified to
|
||
cleanly handle the `false` return (`setBusy(false)` / `setLoadState('idle')`).
|
||
- **G** `OpenPositions.toggleGrow` errors now write to a dedicated
|
||
`growErr` state with a 4-second auto-clear, instead of squatting in the
|
||
panel-header `err` banner shared with the 15-s poll error.
|
||
|
||
## How to verify changes locally
|
||
|
||
```bash
|
||
# Type check (must pass with 0 errors)
|
||
npx tsc --noEmit
|
||
|
||
# Production build (catches missing pages, stale imports)
|
||
rm -rf .next && npx next build
|
||
|
||
# Dev server — NOTE: this app runs on port 3001 (see package.json:
|
||
# `next dev -p 3001` / `next start -p 3001`), NOT the Next.js default 3000.
|
||
# Any external doc, script, or healthcheck must target 3001.
|
||
npm run dev
|
||
|
||
# Pages-200 sweep (every locale page returns 200) — port 3001
|
||
for p in /en /en/trump /en/macro /en/kol /en/trades /en/posts /en/settings \
|
||
/en/methodology /en/glossary /en/case-studies /en/privacy /en/terms; do
|
||
echo "$p $(curl -so /dev/null -w '%{http_code}' http://localhost:3001$p)"
|
||
done
|
||
```
|
||
|
||
---
|
||
|
||
## SEO / GEO surface (don't break)
|
||
|
||
- **`app/layout.tsx`** has JSON-LD schema.org markup (Organization,
|
||
SoftwareApplication, FAQ). Search engines + AI crawlers read this.
|
||
- **`public/llms.txt`** — canonical doc for AI crawlers (Perplexity, ChatGPT
|
||
browse, Google AI Overviews). Edit BOTH this file AND `layout.tsx` when
|
||
renaming product features.
|
||
- **`app/sitemap.ts`** — sitemap.xml. Add new routes here.
|
||
- **OG image** — `app/opengraph-image.tsx` dynamically renders the social
|
||
card. Keep the brand pill list in sync with current features.
|
||
|
||
---
|
||
|
||
## Coupling to backend
|
||
|
||
- **API contract lives in TypeScript types under `lib/api.ts`** (e.g.
|
||
`OpenPositionsResponse`, `TodayStats`, `BotTrade`). These must match the
|
||
backend Pydantic models in `app/api/*/positions.py` etc.
|
||
- When backend adds a field (e.g. `BotTrade.released_at`), this repo's
|
||
`lib/api.ts` types may need updating to surface it in the UI. Currently
|
||
released trades are FILTERED OUT by the backend's `/positions/open` —
|
||
they don't appear here, so no UI for "managed vs released" yet.
|
||
- When backend renames a route (e.g. `/btc/page.tsx` → `/macro/page.tsx`),
|
||
search for ALL references (including telegram deep-link map in
|
||
`backend/app/services/telegram.py`).
|
||
|
||
---
|
||
|
||
## Deploy
|
||
|
||
Vercel auto-deploys `main` branch pushes. No special build flags. Env vars
|
||
in Vercel dashboard:
|
||
- `NEXT_PUBLIC_API_URL` — backend HTTPS origin
|
||
- `NEXT_PUBLIC_SITE_URL` — canonical site URL (used for OG, sitemap)
|
||
- `NEXT_PUBLIC_WS_URL` — WebSocket endpoint (usually `wss://api...`)
|
||
|
||
---
|
||
|
||
## Sibling repo
|
||
|
||
- **`/Users/k/Public/Claude/backend`** — Python/FastAPI backend, includes
|
||
Hyperliquid integration, Telegram bot, AI scoring (DeepSeek), all
|
||
signal scanners, the adoption/release flow. See its CLAUDE.md.
|