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

309 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
- **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.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-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
```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.