19 KiB
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(orlocalhost:8000in dev). All trading state, signals, AI scoring, Hyperliquid integration lives in the sibling../backendrepo. 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.tsreplacesmiddleware.tsin Next.js 16. Routes that need CORS / locale rewriting go throughproxy.tsat the root.paramsis async (Promise<{...}>) in all dynamic routes. ALL page components await it:Forgetting theexport default async function Page({ params }: { params: Promise<{ locale: string }> }) { const { locale } = await params ... }awaitredirects 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.jsonexists, but Chinese branches are kept as dead code with a comment// i18n shelved — see messages/zh.json. Don't activate without an ADR. CurrentlyisZh = falseis 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) → marketing / landing page
├── 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/
│ ├── SourceChips.tsx Filter chips per source
│ └── SystemControl.tsx Sys1/sys2 toggle widgets, Auto-Trade switch
├── dashboard/
│ ├── PostCards.tsx Trump post cards
│ ├── SignalMonitor.tsx Breakout Monitor (ETH/LINK 5m, WS-driven).
│ │ UNMOUNTED 2026-06-12 — backend scanner is
│ │ disabled and its 5-min poll job unscheduled.
│ │ Kept for revival; see Open known issues.
│ └── ChartPanel.tsx Price chart panel
├── 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
│ Includes grow toggle + manual close button
├── trades/
│ ├── TradeTable.tsx Closed trade history table (dynamic asset filter)
│ └── BotConfigPanel.tsx Bot configuration (SL, TP, leverage, paper mode).
│ Includes 3-step onboarding stepper + inline Auto-Trade toggle.
├── telegram/
│ └── TelegramCard.tsx Settings — connect via 6-char code
├── wallet/
│ └── SignConfirmSheet.tsx EIP-191 signed request preview
├── seo/Breadcrumbs.tsx Structured breadcrumb nav for SEO
├── ui/
│ ├── InfoTip.tsx CSS-only tooltip with `?` icon
│ ├── PageHint.tsx Strong page subtitle
│ ├── Pagination.tsx Reusable pagination control
│ └── TradeAlertBanner.tsx Fixed-position WS alert banner (trade_alert events).
│ Auto-dismisses after 10s. Filtered by wallet address.
└── lib/wsContext.tsx WebSocket singleton context (replaces WsProvider)
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
- One small global store —
store/dashboard.ts(Zustand,useDashboardStore). Holds cross-page UI/session state: selected post, chart asset/timeframe, wallet address, subscription + bot-readiness flags, masked HL key hint, and the live-price map. Everything else stays tree-local (state + props) — don't grow this store into a catch-all; page-specific data belongs in the page. - SWR-style cache in
lib/cache.tsfor the API-heavy pages. - WebSocket singleton via
WsProvider— used bySignalMonitorand 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 useoklch(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.activeis 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.tsxEXISTS intentionally as a permanent redirect to/en/macro— that's fine. If you find a stale/en/btchardcoded link anywhere else, it's a real bug — fix it. isZh = falsein metadata generators — i18n was shelved pre-launch. See "Stack quirks" above.- Multiple
proxy.tspatterns — Next.js 16 split middleware behaviour. Keep proxy logic in the singleproxy.tsat 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_messageacceptingint | strfor 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-foris 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.tsno longer deletesx-forwarded-for. It now reads the real client IP from the incomingx-forwarded-fororx-real-ipheader (set by Vercel/CDN) and sets it on the upstream request soslowapican distinguish individual clients. -
RemovedsignMessageAsyncinOpenPositionspolling deps (MEDIUM, FIXED 2026-05-29):signMessageAsyncandisZhfrom theuseEffectdependency array incomponents/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.assetwidened from'BTC' | 'ETH'tostring(BUG-14 now tracks the actually-traded asset which may be SOL/TRUMP/etc.).lib/useRealtimeData.ts:PriceMsg.assetandHandlers.onPricewidened tostring(BUG-08 expanded the WS price stream to all ASSET_MAP entries).store/dashboard.ts:livePriceschanged from{ BTC; ETH }toRecord<string, number | null>;setLivePriceparam widened tostring.lib/api.ts:getPricesasset param widened tostring.components/trades/TradeTable.tsx: hardcodedASSETS = ['all','BTC','ETH','SOL']replaced by a dynamicuseMemothat builds the asset list from the actual trade set — new perps (TRUMP, BNB, etc.) appear in the filter automatically.DashboardClient.tsx+TradesPageClient.tsx: removedisZhfromuseEffectdeps (compile-time constant; including it restarted effects on every render).
-
Interaction/button bugs sweep (UI-A..G, FIXED 2026-05-29):- A
OpenPositions.tsxclose-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
confirmCloseTradedistinguishes user-cancelled signature (isUserRejection) from real failure: cancellation closes the modal silently; real errors stay open in'err'state with the message. - C
SystemControl.flipAutoclaimsbusy=truebeforeawait 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 resetskeyState/keyErrso a previously-failed save's red error doesn't bleed into the new edit. - E Paper/Live
SwitchclearssubErrwhen toggled so a stale subscribe-error doesn't sit next to the new choice. - F
confirmSignkeeps a module-scope_activeCancel; a second call while a sheet is mounted resolves the first promise withfalseinstead of leaving its caller hanging forever. All 9 call sites verified to cleanly handle thefalsereturn (setBusy(false)/setLoadState('idle')). - G
OpenPositions.toggleGrowerrors now write to a dedicatedgrowErrstate with a 4-second auto-clear, instead of squatting in the panel-headererrbanner shared with the 15-s poll error.
- A
-
H2 Chart data race (FIXED 2026-06-01):cancelledflag added to the candlesuseEffect— stale BTC response can no longer overwrite ETH candles when asset is switched mid-fetch. -
M8 Duplicate React keys (FIXED 2026-06-01):onNewPostnow deduplicates bypost.idbefore prepending to the posts list. -
L2 fittedRef not reset on asset change (FIXED 2026-06-01):Chart view is re-fitted whenassetchanges, not only ontimeframechange. -
L4 invalid ARIA role (FIXED 2026-06-01):Ticker.tsxrole="marquee"replaced withrole="region". -
L5 ws:// mixed-content block (FIXED 2026-06-01):wsContext.tsxnow auto-upgrades towss://when served over HTTPS, instead of hard-codingws://. -
Data-consistency / UI sweep (FIXED 2026-06-09):- Overview headline counts (
DashboardClient) now come from server-side/posts-pagedtotals (getPostsPage(1,1,...)for global / truth / macro), not the 80-post first-paint slice. The local slice is only a pre-load fallback. Without this a 1108-post feed showed "80 tracked / —". - "Live" chip downgrades to "Delayed" when
/api/health/deepreportsis_leader === false(follower process runs no background tasks → data is frozen). Keyed ONLY on is_leader, notstatus:degraded, to avoid flapping on transient single-feed staleness. - KOL pages default to a 30d window (was 7d) everywhere —
kol/page.tsxSSR,KolPageClientkolDays, and the Overview KOL-intel card. KOL ingestion is daily/sparse so 7d was frequently empty (7d=0 / 30d=196). - KOL divergence "Xd ago" uses
post_at, notcreated_at(a backfill set created_at≈now and made old events look fresh). - Macro tone is derived from the backend
regime_label, not a re-thresholded score (±15 on the client vs ±20 on the server disagreed). - Trump page header count follows the active signal filter.
- Source labels:
blog(KOL),sma_reclaim/rsi_reversal/breakout(TradeTable + PostCards),adopted(TradeTable) registered — no more raw technical strings. - TradeTable resets source/asset filters that don't apply to the new wallet's trade set (clamp effect), so a stuck filter + vanished breakdown card can't hide the new history.
- OpenPositions has an in-page "Unlock positions" button (signs
view_user) — no forced detour to Settings. - BotConfigPanel: overnight schedules (22:00–02:00) are allowed (backend
already supports wrap-around); readiness reports NOT-ready for a Macro-only
wallet with no Telegram bound (since
/adoptneeds Telegram). - Cross-page selection: clicking a Recent-signals row clears
selectedDayPostsso the single-post detail actually shows.
- Overview headline counts (
Open / deferred (need interface change or DB migration):
- C3 Signed READ endpoints (
allow_replay=True) putts/sigin URL → access logs. Fix requires changing read endpoints to POST body (interface change + frontend update). - H4 KEK derived with single SHA-256, no salt/KDF. Fix requires re-encrypting all HL API keys (DB migration).
- M5
GET /telegram/{wallet}/statusis unauthenticated — exposeschat_id/tg_username. - M7 Rate limit bypassable via spoofed XFF — infrastructure-level fix (nginx/Cloudflare).
M9FIXED 2026-06-12:lib/cache.tskeeps a per-keysubscriberslist — every caller that hits a stale key during an in-flight revalidation registers itsonUpdate, and ALL are notified when the fetch resolves (previously only the caller that started the revalidation got fresh data).Funding Reversal tab Breakout MonitorRESOLVED 2026-06-12 (removed):SignalMonitorunmounted from the Funding tab — the backend scanner has been disabled for months (funding_signal._enabled=False, operator-only toggle) so the panel only ever showed "Paused / No signals yet". The backend's 5-min poll job was also unscheduled (backend/app/main.py); the/signal/*API routes remain. To revive: re-add theadd_job()AND remountSignalMonitorinMacroVibesPageClient.
How to verify changes locally
# 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.tsxhas 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 ANDlayout.tsxwhen renaming product features.app/sitemap.ts— sitemap.xml. Add new routes here.- OG image —
app/opengraph-image.tsxdynamically 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 inapp/api/*/positions.pyetc. - When backend adds a field (e.g.
BotTrade.released_at), this repo'slib/api.tstypes 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 inbackend/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 originNEXT_PUBLIC_SITE_URL— canonical site URL (used for OG, sitemap)NEXT_PUBLIC_WS_URL— WebSocket endpoint (usuallywss://api...)
Sibling repo
/Users/k/Public/trumpsignal/backend— Python/FastAPI backend, includes Hyperliquid integration, Telegram bot, AI scoring (DeepSeek), all signal scanners, the adoption/release flow. See its CLAUDE.md.