For detailed subsystem docs, see docs/index.md.
InferenceX App — Next.js 16 dashboard for ML inference benchmark data. DB-backed with Neon PostgreSQL, React Query for data fetching, D3.js for charts.
- Framework: Next.js 16 (App Router, Turbopack)
- Language: TypeScript (strict mode)
- Styling: Tailwind CSS 4 + shadcn/ui (Radix UI primitives)
- Charts: D3.js — shared library at
src/lib/d3-chart/, scatter/GPU/bar charts - Data: Neon DB → API routes (
/api/v1/*) → React Query hooks → Context providers - Deployment: Vercel with daily cron-triggered rebuilds
- Analytics: PostHog (
posthog-js) via@/lib/analytics— recommended on all interactive elements (autocapture provides baseline coverage)
pnpm install # Install dependencies
pnpm dev # Dev server with Turbopack (http://localhost:3000)
pnpm build # Production build
pnpm typecheck # TypeScript type checking (all packages)
pnpm lint # Lint with oxlint
pnpm lint:fix # Auto-fix lint issues
pnpm fmt # Format check with oxfmt
pnpm fmt:fix # Auto-fix formatting
pnpm test:unit # Vitest unit tests
pnpm test:e2e # Cypress E2E testspackages/
├── app/ # Next.js frontend (@semianalysisai/inferencex-app)
│ ├── content/blog/ # MDX blog posts (frontmatter + content)
│ └── src/
│ ├── app/ # Pages, layouts, API routes (/api/v1/*)
│ │ └── blog/ # Blog list + [slug] post pages, OG image generation
│ ├── components/ # Tab sections: inference/, evaluation/, historical-trends/,
│ │ # throughput-calculator/, reliability/, gpu-specs/, blog/, ui/
│ ├── hooks/api/ # React Query hooks (use-benchmarks, use-availability, etc.)
│ └── lib/ # Utilities, constants, d3-chart/, chart-utils, blog, data-mappings
├── constants/ # Shared constants (GPU keys, model mappings, SEO)
└── db/ # DB layer, ETL, migrations, queries, ingest scripts
Path alias: @/* → packages/app/src/
Frontend → React Query hooks (src/hooks/api/) → /api/v1/* routes → Neon DB
API routes (packages/app/src/app/api/v1/):
benchmarks?model=X&date=YYYY-MM-DD— latest benchmark per (config, concurrency)benchmarks/history?model=X&gpu=Y— historical benchmark data for trend chartsworkflow-info?date=YYYY-MM-DD— runs, changelogs, configs for a dateavailability—Record<model, dates[]>reliability— rawReliabilityRow[]evaluations— rawEvalRow[]server-log— retrieve benchmark runtime logsinvalidate— invalidate API cache (admin)
API routes return raw DB data — no presentation logic. Frontend handles all transformations.
Static content routes (no DB):
/blog— blog listing (statically generated from MDX files incontent/blog/)/blog/[slug]— blog post page with MDX rendering and OG image generation/feed.xml— RSS 2.0 feed/llms.txt— LLM-readable site index/llms-full.txt— full article content for LLM ingestion/sitemap.xml— dynamic sitemap (includes blog posts)
- Linter: oxlint —
pnpm lint/pnpm lint:fix - Formatter: oxfmt —
pnpm fmt/pnpm fmt:fix - Type checking:
pnpm typecheck(tsc --noEmit, strict mode) - Node: 24.x
See .env.example. Key vars: GITHUB_TOKEN, DATABASE_READONLY_URL, DATABASE_WRITE_URL (admin only).
See Testing for full requirements, quality standards, and pre-commit checklist. Tests are mandatory — missing/low-quality tests are 🔴 BLOCKING on PR review.
All interactive elements should have track() from @/lib/analytics (autocapture provides baseline coverage).
Convention: [section]_[action] — e.g., latency_zoom_reset, calculator_bar_selected, tab_changed
Prefixes: latency_, interactivity_, gpu_timeseries_, inference_, calculator_, evaluation_, reliability_, tab_, selector_, blog_, social_
Order: inference → evaluation → historical → calculator → reliability → gpu-specs (defined in page-content.tsx VALID_TABS). Tab value = URL hash.
Any new feature that operates on inference or evaluation chart data must also work for unofficial run overlays — not just the official run rendering path. The overlay path is a separate code branch (overlayData, processedOverlayData, overlayRooflines, activeOverlayHwTypes, overlayRunColor/overlayRunIndex from @/lib/overlay-run-style, useUnofficialRun() from @/components/unofficial-run-provider) that is easy to forget — features that only handle the official path silently degrade for users who load an unofficial run via ?unofficialrun=….
When adding a chart feature (toggle, label, overlay, filter, export, share-link param, tooltip enrichment, …):
- Implement it for both official and overlay data paths. Use
overlayRunColor(runIndex)for overlay strokes / labels so they match the legend swatches; do not reuse the hw-derived color helper (getCssColor(resolveColor(hw))) for overlay items. - Respect overlay visibility filters:
activeOverlayHwTypes(hw toggles) and any per-run dismissal inunofficialRunInfos. Don't draw overlay items the user has hidden. - Verify it manually with an unofficial run loaded — paste a
?unofficialrun=<github-actions-run-id>URL and confirm the new feature renders for overlay rooflines / points / rows, animates with zoom, and survives a per-run dismiss. - Add at least one E2E or unit test that exercises the overlay path. The mock helper
createMockUnofficialRunContext(cypress/support/mock-data.ts) and thecypress/e2e/inference-chart.cy.tsoverlay setup are good starting points. - Note overlay support explicitly in the PR description so reviewers can verify it ("works for both official runs and
?unofficialrun=overlays — verified at ").
If the feature genuinely cannot apply to overlays (e.g., it depends on data only ingested for official runs), say so explicitly in code comments and the PR description. Default to "must support overlays."
- D3 scatter plot:
src/components/inference/ui/ScatterGraph.tsx - D3 GPU graph:
src/components/inference/ui/GPUGraph.tsx - Chart layout/errors:
src/components/inference/ui/ChartDisplay.tsx - Shared D3 library:
src/lib/d3-chart/(setup, axes, grid, watermark, layers)
- State:
src/components/inference/InferenceContext.tsx - Controls:
src/components/inference/ui/ChartControls.tsx - Filter logic:
src/components/inference/hooks/useChartData.ts
- Register in
src/lib/chart-utils.ts:Y_AXIS_METRICS,calculateRoofline,computeAllRooflines,markRooflinePoints - Add TS types: optional field in
InferenceData, add toYAxisMetricKey, addChartDefinitionfields - Add chart config:
src/components/inference/inference-chart-config.json - Add Y-axis dropdown:
ChartControls.tsx - Add subtitle/disclaimer in
ChartDisplay.tsxif metric depends on assumed constants - Add disagg caveat banner in
ChartDisplay.tsxfor per-GPU or per-MW metrics (animated amberborder-l-2banner pattern) - Expose in UI state:
InferenceContext.tsx
- Create
packages/app/content/blog/<slug>.mdxwith frontmatter:title,subtitle,date(required),tags,modifiedDate(optional) - Write content using Markdown + custom MDX components (
Figure,Blur) - No code changes needed — the post automatically appears in the blog list, sitemap, RSS feed, llms.txt, and gets a generated OG image
See Blog for content format, available MDX components, and design details.
- Blog library (posts, headings, reading time):
src/lib/blog.ts - Blog list page:
src/app/blog/page.tsx - Blog post page:
src/app/blog/[slug]/page.tsx - MDX components:
src/components/blog/mdx-components.tsx - TOC sidebar:
src/components/blog/blog-toc.tsx - OG image generation:
src/app/blog/[slug]/og-image-render.tsx - RSS feed:
src/app/feed.xml/route.ts - SEO constants:
packages/constants/src/seo.ts
First ask for the PR / GitHub Actions run URL — see Adding Entities for the full workflow. Never ask other questions before getting the URL.
page-content.tsx: Add toVALID_TABS, addTabsTrigger(desktop),SelectItem(mobile),TabsContent- Create a per-section context provider (see
InferenceContext.tsx,EvaluationContext.tsxfor patterns) - Use
ChartLegendwithvariant="sidebar", sorted byHW_REGISTRYsort order, default expanded - Analytics: all interactive elements use
track()with{tabname}_prefix
Workflow for a periodic dep bump. Branch: chore/bump-deps-YYYY-MM-DD. Commit each step separately so failures are easy to bisect.
-
Bump versions:
pnpm taze -I -r latest(interactive, all workspaces). Approve what you want, skip what you don't. -
Resolve install errors:
ERR_PNPM_IGNORED_BUILDSafter a pnpm major bump means newallowBuildsentries inpnpm-workspace.yamlwere left as placeholder strings — set them totrue(orfalseif you don't want the build script to run).- pnpm 11 moved
pnpm.overridesfrompackage.jsontopnpm-workspace.yaml. Overrides left inpackage.jsonare silently ignored. Migrate them.
-
Audit security:
pnpm security(runspnpm audit && audit-ci). For each remaining vulnerability, add a targeted override inpnpm-workspace.yaml:overrides: <pkg>@<vulnerable-range>: '>=<min-patched-version>'
- Use the lowest patched version (e.g.
>=8.5.10, not>=8.5.14). pnpm resolves to the highest available that satisfies the constraint, so we automatically get the latest patch — and the override doesn't go stale when 8.5.15 ships. - Use the narrow
<vulnerable-range>selector (not bare<pkg>:) so the override only fires on vulnerable resolutions and doesn't disturb pins already on safe versions. - Verify minimum set: drop any override that doesn't map to a current advisory. Test by removing it and re-running
pnpm security.
- Use the lowest patched version (e.g.
-
Fix lint/format:
pnpm lint:fix && pnpm fmt:fix. New rules from oxlint version bumps may not have autofixers (e.g.require-unicode-regexp,unicorn/no-negated-condition) — fix manually. For mechanical bulk changes, delegate to a subagent and verify withpnpm typecheck. -
Final check:
pnpm lint && pnpm fmt && pnpm typecheck && pnpm securityall pass. Pre-commit hook reruns these.
Detailed design rationale (the "why" and "how", not the "what") lives in docs/:
- Index — index of all docs MUST ALWAYS READ IN CASE OF RELEVANT INFORMATION
- Architecture — Client-first design, hash routing, caching, color system
- D3 Charts — 4-effect architecture, zoom refs, tooltip lifecycle
- Data Pipeline — DB schema reasoning, ETL design, spline interpolation
- Pitfalls — Token type bugs, schema evolution, stale closures, zoom loss
- GPU Specs — Topology invariants, unit conventions, hardware gotchas
- TCO Calculator — Interpolation, composite keys, cost matrix
- Adding Entities — Checklists for adding models, GPUs, precisions, sequences, frameworks
- Testing — Requirements, quality standards, pre-commit checklist
- Data Transforms — BenchmarkRow → AggDataEntry → InferenceData pipeline, hardware key construction, derived metrics
- State Ownership — Context provider state map, availability filtering cascade, comparison dates, URL params
- Blog — MDX content system, SEO features, TOC sidebar, reading progress, analytics events
All Claude AI workflows are dispatched from a single trigger word @claude. The next word selects the mode:
@claude(or@claude <anything>) — implementation with Playwright MCP. Triggered by mentioning in issues/comments. Full code implementation + browser testing. Createsclaude/issue-{N}-*branches. Must verify charts render real data (no "No data available").@claude chrome— implementation with Chrome DevTools MCP instead of Playwright. Preferred when you need deeper debugging (network requests, console messages, JS evaluation).@claude review— code review only. Also auto-runs on PR open/sync. Flags: bugs, security, breaking changes, missing tests (🔴 BLOCKING), low-quality tests (🔴 BLOCKING). Ignores: style, naming, docs.