Files
hanzo.ai/lib/hanzo/board.ts
zeekay 7a8b778644 beta: the leaderboard is the cloud's board, and naming yourself on it is your choice
Rows come from GET /v1/leaderboard/beta: a name only for a person who listed,
a builder for the rest, the reader's own row below the top, the count on the
waitlist. The listing choice is GET and PUT /v1/leaderboard/optin. Until the
cloud answers a board, only the reader's own row is drawn; no floor of one
point, no plan badge the server did not send.

Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
2026-10-26 01:50:54 -07:00

91 lines
2.9 KiB
TypeScript

/**
* THE BETA BOARD: everyone on the hanzo.ai/beta waitlist, ranked by the one score
* the cloud keeps (GET /v1/leaderboard/beta), and whether the reader's own row
* carries their name (GET and PUT /v1/leaderboard/optin). A row is named only for a
* person who chose to be listed; everyone else is a builder, and still counts.
*/
import { api } from '@hanzo/app/lib/api'
import { scope } from '@hanzo/app/lib/session'
/** One ranked record. `tokens` is a range; `tokensUsed` is exact on a named row and the reader's own. */
export interface Row {
rank: number
name: string | null
you: boolean
score: number
referrals: number
qualified: number
tokens: string
tokensUsed?: number
applied: string
}
export interface Board {
rows: Row[]
you: Row | null
of: number
/** False when the cloud could not count token use or referrals, and no score has them then. */
tokens: boolean
referrals: boolean
}
export interface Listing {
listed: boolean
handle: string
canSet: boolean
}
const json = { 'Content-Type': 'application/json' }
/** The top of the waitlist and the reader's own row; null while the cloud has no board to answer. */
export async function board(limit = 25): Promise<Board | null> {
const headers = scope()
if (!headers.Authorization) return null
try {
const res = await fetch(`${api()}/v1/leaderboard/beta?limit=${limit}`, { headers })
if (!res.ok) return null
const body = (await res.json()) as Partial<Board>
return {
rows: Array.isArray(body.rows) ? body.rows : [],
you: body.you ?? null,
of: typeof body.of === 'number' ? body.of : 0,
tokens: body.tokens !== false,
referrals: body.referrals !== false,
}
} catch {
return null
}
}
const own = (raw: unknown): Listing | null => {
const u = (raw as { user?: Partial<Listing> } | null)?.user
if (!u) return null
return { listed: u.listed === true, handle: typeof u.handle === 'string' ? u.handle : '', canSet: u.canSet === true }
}
/** Whether the reader's row carries their name. */
export async function listing(): Promise<Listing | null> {
const headers = scope()
if (!headers.Authorization) return null
try {
const res = await fetch(`${api()}/v1/leaderboard/optin`, { headers })
return res.ok ? own(await res.json()) : null
} catch {
return null
}
}
/** List the reader's row under a handle (their username when none is given), or take the name off. */
export async function list(listed: boolean, handle = ''): Promise<Listing | null> {
const headers = scope()
if (!headers.Authorization) return null
try {
const res = await fetch(`${api()}/v1/leaderboard/optin`, { method: 'PUT', headers: { ...json, ...headers }, body: JSON.stringify({ listed, handle }) })
if (!res.ok) return null
const body = (await res.json()) as Partial<Listing>
return { listed: body.listed === true, handle: typeof body.handle === 'string' ? body.handle : '', canSet: true }
} catch {
return null
}
}