Files
trumpsignal-frontend/CLAUDE.md
T
k b2b96bb9c0 fix: Macro panel manage-only (not auto-open), Trump-only auto-trade copy, store docs
SystemControl was rendered as two per-system panels (Trump / Macro) that both
flipped the SAME global auto_trade, and its copy claimed a "qualifying Macro
Vibes signal auto-opens a trade". Both are wrong: bot_engine.process_post
early-returns for System 2, so the auto_trade gate only ever fires for Trump
(System 1). Macro Vibes is manage-only — it never auto-opens.

- Macro (`system="btc"`) panel: replaced the misleading Auto-Trade ON/OFF
  toggle with a manage-only explainer (open on HL → /adopt via Telegram → bot
  manages exits) and a note that the Trump-page switch has no effect here.
- Trump (`system="trump"`) panel: relabeled the switch "TRUMP (System 1)" and
  rewrote all copy/status lines to say Trump-only auto-open (was "GLOBAL
  (Trump + BTC)" / "both Trump & BTC").
- CLAUDE.md: corrected "No global store (no Redux/Zustand)" — the app DOES use
  a small Zustand store (store/dashboard.ts / useDashboardStore); documented
  what it holds and the guidance to keep it small.

Note (not a bug): dashboard adopt/release is intentionally Telegram-only —
there are no positions/adopt|hl|release client calls and the UI correctly
points to the Telegram /adopt command. No dashboard wiring is "broken".

tsc + next build clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:04:36 +08:00

14 KiB
Raw Blame History

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:
    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

  • One small global storestore/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.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:

  • Colorsoklch() 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-0106, 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

# 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 imageapp/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.