- Wrap synchronous setState calls in useEffect with startTransition to avoid cascading renders - Convert nested SortIcon components to renderSortIcon helper functions - Replace all `any` types with proper interfaces (OverviewData, TrendPoint, RankItem) - Remove unused formatTokens import in logs page - Add no-any rule to CLAUDE.md
3.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@AGENTS.md
Commands
npm run dev # Start dev server (localhost:3000)
npm run build # Production build (standalone output)
npm run start # Start production server
npm run lint # ESLint (eslint-config-next/core-web-vitals + typescript)
Docker deployment: port 8019, docker-compose up with .env.production, external network sinobridge.
Architecture
Next.js 16 App Router analytics dashboard for an API gateway. React 19, TypeScript strict mode, Tailwind CSS 4, Recharts 3, PostgreSQL.
TypeScript: Never use any type. Always define proper interfaces/types for all data structures. Use unknown with type narrowing if the type is truly unknown.
Three Global Contexts (wrapped in components/ClientProviders.tsx)
- ThemeProvider (
lib/theme.tsx) — light/dark/system, localStoragetheme, supports iframe embedding via?theme=query param and postMessage sync - I18nProvider (
lib/i18n.tsx) — zh/en, localStoragelocale, all translation keys in a singletranslationsobject - TimeRangeProvider (
lib/time-range-context.tsx) — today/7d/30d/all/custom, localStoragetime-range, exposesgetEffectiveRange()returning{ start?, end? }unix timestamps (seconds)
Pages consume these via useTheme(), useI18n(), useTimeRange(). The TimeRangeSelector component reads from context (no props).
Data Flow
Pages call getEffectiveRange() → buildQuery("/api/...", { start, end }) → API route → lib/queries.ts SQL → PostgreSQL → JSON response → Recharts/tables.
Database Layer (lib/db.ts, lib/queries.ts)
pgpool (max 10 connections), env varPG_CONNECTION_STRING- All queries filter
type = 2, timezoneAsia/Shanghai - Real model name resolution:
COALESCE(other::jsonb->>'upstream_model_name', model_name) - Quota to USD:
quota / 500000 - Parameterized queries with
$Npositional params
API Routes (all GET, query-param driven)
| Route | Key Params | Returns |
|---|---|---|
/api/overview |
start, end | Aggregate stats (calls, tokens, users, models, channels) |
/api/trends |
start, end, granularity (day/week/month) | Time-series array |
/api/rankings |
start, end, type (user/model/channel), limit | Ranked items |
/api/detail/[type]/[id] |
start, end | Stats + breakdown (models or users) |
/api/logs |
start, end, page, page_size, username, model, token_name | Paginated logs |
/api/aggregation |
start, end | All users aggregated (top 500) |
Styling
CSS variables in globals.css — dark theme default (cyan #00e5ff accent), light theme (blue #0284c7). Key classes: .glass (glassmorphic card), .gradient-text, .btn-accent, .input-glass, .row-glow. Portal page (/portal) has its own extensive animation system.
Path Alias
@/* maps to project root (tsconfig).
Fonts
Outfit (sans, --font-geist-sans) and JetBrains Mono (mono, --font-geist-mono) via next/font/google.