Files
zeekay 438b8644f3 rewards: /leaderboard and /bounty on the one points ledger
Every page that names points reads the cloud's rewards: the board
(GET /v1/rewards), the reader's standing, badges and history
(/v1/rewards/me), the listing (/v1/rewards/optin) and the rules table
(/v1/rewards/rules), snapshotted at build into
lib/data/rewards-rules.json and asked again when a page opens.  The
hard-coded RULES tiles, POINTS, the client-side score and
lib/hanzo/board.ts are gone; /beta and /referral draw the same Rules and
board and link to /leaderboard.

/leaderboard is the home: tabs by source and period, Your standing,
badges earned and locked, the rules, token milestones and levels, Link
GitHub through the existing per-person connection (the page completes
the grant GitHub returns), and Share your rank with a leaderboard card.
/bounty prints the contribution kinds and cap, three steps, the open
issues labelled bounty or good first issue in Hanzo's orgs (searched at
build with a token only), My contributions and the rules.  A star now
counts for the linked GitHub account, so Earn more offers Link GitHub.

The cards, nav, footer, llms.txt and sitemap carry both pages; /founder
no longer says points go nowhere.

Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
2026-10-27 01:56:45 -07:00

56 lines
1.8 KiB
JavaScript

#!/usr/bin/env node
// What `next build` reads, regenerated before it runs.
//
// The first group each write files of their own and read none the others
// write, so they run at once: one after another they were 23 s of every
// deploy, most of it waiting on the network. The last four read what the
// first group wrote (the open bounties are searched in the orgs the rewards
// rules name, the route tree's policy joins the catalog, and keep-stamps
// compares every snapshot), so they run after it, in order.
//
// Every script runs to its end before the build is refused, so one failure
// does not hide another.
import { spawn } from 'node:child_process'
import { existsSync } from 'node:fs'
const AT_ONCE = [
'gen-gui-css',
'rooms-assets',
'sync-mark',
'brands',
'zen',
'kai',
'coding',
'tools',
'sync-catalog',
'sync-models',
'sync-papers',
'sync-blog',
'sync-oss',
'sync-rewards',
'sync-releases',
'sync-menu',
]
const AFTER = ['sync-bounties', 'sync-rooms', 'sync-pages', 'keep-stamps']
// A script is TypeScript (`.ts`, run by Node's own type stripping) or a `.mjs` not yet converted.
const run = (name: string) =>
new Promise<string | null>((done) => {
spawn(process.execPath, [existsSync(`scripts/${name}.ts`) ? `scripts/${name}.ts` : `scripts/${name}.mjs`], { stdio: 'inherit' })
.on('error', (err) => done(`${name}: ${err.message}`))
.on('exit', (code, signal) => done(code === 0 ? null : `${name} exited ${signal ?? code}`))
})
const failed = (await Promise.all(AT_ONCE.map(run))).filter(Boolean)
if (failed.length) {
for (const f of failed) console.error(`prebuild: ${f}`)
process.exit(1)
}
for (const name of AFTER) {
const f = await run(name)
if (f) {
console.error(`prebuild: ${f}`)
process.exit(1)
}
}