Repository navigation
feat(ui): add public /apps leaderboard page - #2170
Conversation
Lists apps using LLM Gateway ranked by tokens processed, aggregated from the log.source field. Public endpoint at /public/apps. Seed data expanded to cover popular coding agents (Claude Code, Cursor, Cline, Codex, OpenCode, Aider, Continue, Windsurf, Roo, Kilo, Zed, Bolt, v0, Lovable, Autohand, SoulForge, OpenClaw, n8n) so the page has realistic ranks. Page features a top-3 podium, top-12 grid, long-tail list, and a DevPass upsell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (4)
📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughAdds a public apps aggregation API (/public/apps), a Next.js page that fetches and renders ranked app usage with search/filter UI and upsell content, plus updated DB seed logic to produce weighted agent activity for the public listing. ChangesPublic Apps Leaderboard
Sequence DiagramsequenceDiagram
participant Browser
participant NextPage as Next.js Page
participant APIServer as API Server
participant Database
participant AppsGrid as AppsGrid Component
Browser->>NextPage: Request /apps
NextPage->>APIServer: GET /public/apps?limit=200
APIServer->>Database: Aggregate globalSourceStats by source
Database-->>APIServer: grouped rows (tokens, requests, lastUsedAt)
APIServer-->>NextPage: JSON { apps, totalApps, totalTokens, totalRequests }
NextPage->>NextPage: compute totals & format numbers
NextPage->>AppsGrid: pass apps[]
AppsGrid->>AppsGrid: enrich (getAppMetadata), filter, rank
AppsGrid-->>NextPage: render podium, grid, list
NextPage-->>Browser: HTML (hero, grid, upsell)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
apps/ui/src/app/apps/page.tsx (1)
39-50: ⚡ Quick win
formatBigNumberduplicatesformatTokensinapps-grid.tsx.Both functions are character-for-character identical (B/M/K suffix thresholds, same
Intl.NumberFormatfallback). Extract to a shared utility (e.g.,apps/ui/src/lib/format.ts) and import in both files.♻️ Proposed refactor
Create
apps/ui/src/lib/format.ts:const numberFormatter = new Intl.NumberFormat("en-US"); export function formatTokens(n: number): string { if (n >= 1_000_000_000) { return `${(n / 1_000_000_000).toFixed(2)}B`; } if (n >= 1_000_000) { return `${(n / 1_000_000).toFixed(2)}M`; } if (n >= 1_000) { return `${(n / 1_000).toFixed(1)}K`; } return numberFormatter.format(n); }Then in both
page.tsxandapps-grid.tsx:-function formatBigNumber(n: number): string { … } // page.tsx -function formatTokens(n: number): string { … } // apps-grid.tsx +import { formatTokens } from "@/lib/format";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/ui/src/app/apps/page.tsx` around lines 39 - 50, formatBigNumber in page.tsx is duplicated by formatTokens in apps-grid.tsx; extract the shared logic into a single exported utility (e.g., export function formatTokens / formatBigNumber in a new shared module) that defines the common numberFormatter and the B/M/K rules, then replace both local implementations by importing and calling that shared function from page.tsx and apps-grid.tsx (update references to formatBigNumber or formatTokens to the unified export).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/src/routes/public-apps.ts`:
- Around line 50-63: The query in public-apps.ts aggregates the entire
tables.log (select/groupBy/orderBy on tables.log.source) causing full table
scans; fix by 1) restricting the query with a date-range filter on
tables.log.createdAt (e.g., only include rows where createdAt >= now() -
interval '90 days') so the GROUP BY and aggregates operate on a bounded window,
2) add an index on source in your DB schema (e.g., in packages/db/src/schema.ts
add an index named like log_source_idx on table.source) so GROUP BY/ORDER BY can
use an index scan, and 3) return a Cache-Control header from this
unauthenticated route so CDN/proxies can cache responses (set appropriate
max-age/stale-while-revalidate) to avoid repeated expensive scans when clients
hit /public/apps?limit=...
In `@apps/ui/src/app/apps/page.tsx`:
- Around line 164-170: Replace the plain anchor used for internal navigation
with Next's Link: import Link from 'next/link' at the top of page.tsx and change
the <a href="/docs" ...>...</a> to <Link href="/docs" className="group
inline-flex items-center gap-1.5 text-sm font-medium text-foreground
hover:text-blue-500">Read the docs <ArrowRight .../></Link>, leaving the
ArrowRight component and className intact so client-side routing, prefetching,
and scroll restoration are used.
In `@apps/ui/src/components/apps/apps-grid.tsx`:
- Line 393: The ternary assigning listApps is redundant because both branches
use filtered.slice(12); replace the expression with a single direct assignment
(set listApps = filtered.slice(12)) and remove the conditional isFiltered ? ...
: ...; update the declaration that currently references listApps, isFiltered,
and filtered.slice(12) to the simplified form so the code is clearer and avoids
the dead ternary.
- Around line 400-406: The search <input> in apps-grid.tsx currently only uses a
placeholder and lacks an accessible name; add an aria-label (e.g.,
aria-label="Search apps") or associate it with a visible/visually-hidden <label>
so the input has an accessible name; update the input element where
value={query} and onChange={(e) => setQuery(e.target.value)} to include the
aria-label attribute (or label id via aria-labelledby) to satisfy WCAG.
- Around line 77-117: The AppLogo component uses the React namespace in its prop
type (React.FC<React.SVGProps<SVGSVGElement>>) but React isn't imported; add an
isolated type import like `import type React from "react";` alongside the
existing imports (e.g., the same import line that brings in useMemo/useState) so
the Icon prop and SVG prop types resolve for the AppLogo function and related
JSX.
In `@apps/ui/src/components/apps/devpass-upsell.tsx`:
- Around line 16-24: The file uses the React namespace in the APP_LOGOS type
declaration (React.FC<React.SVGProps<SVGSVGElement>>) but never imports React;
add a type-only import (import type React from "react") at the top of the file
so the TS compiler/IDE can resolve React for the APP_LOGOS declaration and
similar type references.
---
Nitpick comments:
In `@apps/ui/src/app/apps/page.tsx`:
- Around line 39-50: formatBigNumber in page.tsx is duplicated by formatTokens
in apps-grid.tsx; extract the shared logic into a single exported utility (e.g.,
export function formatTokens / formatBigNumber in a new shared module) that
defines the common numberFormatter and the B/M/K rules, then replace both local
implementations by importing and calling that shared function from page.tsx and
apps-grid.tsx (update references to formatBigNumber or formatTokens to the
unified export).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 48c5142e-8ba5-4c5b-b039-a5691a10703e
⛔ Files ignored due to path filters (1)
apps/ui/src/lib/api/v1.d.tsis excluded by!**/v1.d.ts
📒 Files selected for processing (7)
apps/api/src/index.tsapps/api/src/routes/public-apps.tsapps/ui/src/app/apps/page.tsxapps/ui/src/components/apps/app-metadata.tsapps/ui/src/components/apps/apps-grid.tsxapps/ui/src/components/apps/devpass-upsell.tsxpackages/db/src/seed.ts
steebchen-bot
left a comment
There was a problem hiding this comment.
won't work in prod
Switch /public/apps endpoint from raw log scan to the aggregated globalSourceStats table. Filter out the literal 'unknown' source (NULL log.source rows that the aggregator buckets together) so the /apps page only lists named apps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
/appspage that lists tools and coding agents using LLM Gateway, ranked by tokens processed (similar to openrouter.ai/apps)GET /public/appsthat groupslog.sourceand sumstotal_tokensDesign notes
Aesthetic intentionally matches the existing landing-page DNA in
apps/ui/src/components/landing/*:font-displayheadlines,AnimatedGroup blur-slidereveals, atmospheric blue radial blobs, gradient hairline separators, singleShimmerButtonprimary CTA. No purple-gradient AI-template tropes.The DevPass upsell uses logo-stack social proof from the leaderboard above (Claude Code, Cursor, Cline, OpenCode) and loss-aversion framing ("Stop juggling nine separate subscriptions") rather than generic feature bullets.
Test plan
pnpm run setupto reseed; visithttp://localhost:3002/appsGET /public/appsreturns aggregated stats without authGET /public/apps?limit=5respects the limit🤖 Generated with Claude Code
Summary by CodeRabbit