Repository navigation
feat(bounties): drive the Bounty Board from Tabularium - #35
Conversation
The board is now built from approved and shipped requests on Tabularium instead of a hand-edited list: fetch-app-data maps them onto pluginBounties.json, keeping the committed copy when the registry is down or predates request review. Claimed bounties show as in progress until their plugin is listed. In the browser the board refreshes votes and claims with one call to the registry, and signed-in visitors can upvote, claim or release a bounty and create a repository scaffolded by @tabularis/create-plugin from the bounty modal. Signed-out actions go through the Tabularium sign-in and are replayed on return. Refs #34
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Code Review SummaryStatus: 5 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (6 files)
Fix these issues in Kilo Cloud Previous Review Summaries (2 snapshots, latest commit dfe1153)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit dfe1153)Status: No Issues Found | Recommendation: Merge Files Reviewed (1 file)
Previous review (commit 79b9fde)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (19 files)
Detailed FindingsWARNING: if (body.requests.some((request) => !request.status || !Array.isArray(request.claimers))) return null;If the registry returns 100 requests and just one is missing const validRequests = body.requests.filter((r) => r.status && Array.isArray(r.claimers));
if (validRequests.length !== body.requests.length) {
console.warn(`${body.requests.length - validRequests.length} malformed requests dropped`);
}
for (const request of validRequests) bySlug.set(request.slug, request);SUGGESTION: return kind === 'theme'
? `npx --package @tabularis/create-plugin tabularis-theme scaffold ${slug} --dir ${slug} --min-runtime-version <version>`
: `npm create @tabularis/plugin@latest ${slug}`;The Notes
Reviewed by free · Input: 401.9K · Output: 11.9K · Cached: 1.7M |
| if (!res.ok) return null; | ||
| const body = (await res.json()) as {total: number; requests: LiveRequest[]}; | ||
| // A registry without request review has no status or claimers: keep the build data. | ||
| if (body.requests.some((request) => !request.status || !Array.isArray(request.claimers))) return null; |
There was a problem hiding this comment.
WARNING: fetchLiveRequests discards all live data if any single request is malformed
If the registry returns 100 requests and just one is missing status or claimers, the entire live dataset is discarded and the UI falls back to stale build-time data. Consider filtering out malformed entries instead:
| if (body.requests.some((request) => !request.status || !Array.isArray(request.claimers))) return null; | |
| const validRequests = body.requests.filter((r) => r.status && Array.isArray(r.claimers)); | |
| if (validRequests.length !== body.requests.length) { | |
| console.warn(`${body.requests.length - validRequests.length} malformed requests dropped`); | |
| } | |
| for (const request of validRequests) bySlug.set(request.slug, request); |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
There was a problem hiding this comment.
Fixed in dfe1153: malformed entries are skipped (with a console warning) and the rest of the live data is kept. Against a registry that predates request review every entry is skipped, so the board still falls back to the build data.
| /** Local alternative to the hosted scaffold, for any forge. */ | ||
| export function localScaffoldCommand(slug: string, kind: string | null): string { | ||
| return kind === 'theme' | ||
| ? `npx --package @tabularis/create-plugin tabularis-theme scaffold ${slug} --dir ${slug} --min-runtime-version <version>` |
There was a problem hiding this comment.
SUGGESTION: localScaffoldCommand shows literal <version> placeholder in user-facing command
The <version> placeholder renders literally in the UI. Consider using a clearer placeholder like --min-runtime-version 0.x.x or --min-runtime-version <VERSION> with angle brackets, or document that users must replace it.
| ? `npx --package @tabularis/create-plugin tabularis-theme scaffold ${slug} --dir ${slug} --min-runtime-version <version>` | |
| return kind === 'theme' | |
| ? `npx --package @tabularis/create-plugin tabularis-theme scaffold ${slug} --dir ${slug} --min-runtime-version 0.x.x` | |
| : `npm create @tabularis/plugin@latest ${slug}`; |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
There was a problem hiding this comment.
Fixed in dfe1153: the local theme command is prefilled with the current Tabularis version (APP_VERSION) instead of a placeholder.
Skip only the live requests without review data instead of discarding the whole response, and prefill the local theme scaffold command with the current Tabularis version instead of a <version> placeholder. Refs #34
First snapshot generated from registry.tabularis.dev/api/requests, with the request ids the board needs to vote and claim. Refs #34
| `${TABULARIUM_URL}/api/requests?status=approved,fulfilled&limit=100&page=${page}`, | ||
| {credentials: 'include'}, | ||
| ); | ||
| if (!res.ok) return null; |
There was a problem hiding this comment.
WARNING: fetchLiveRequests returns null on any HTTP error, discarding all previously fetched pages
If a request for page 3 fails (e.g., transient network error, rate limit), the function returns null immediately, losing data from pages 1-2. The UI then falls back to stale build-time data. Consider returning partial data or at least logging the error:
| if (!res.ok) return null; | |
| if (!res.ok) { | |
| console.warn(`fetchLiveRequests: page ${page} failed with ${res.status}`); | |
| return bySlug.size > 0 ? bySlug : null; | |
| } |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| if (requests.length >= res.total || res.requests.length === 0) break; | ||
| } | ||
| // A registry that predates request review returns no status: nothing to build from. | ||
| if (requests.some((request) => !request.status)) { |
There was a problem hiding this comment.
WARNING: buildBounties throws if any request lacks status, failing the entire build
The check requests.some((request) => !request.status) throws an error if even a single request is missing a status. The comment says "A registry that predates request review returns no status" — implying ALL requests would lack status. But if the registry has mixed old/new data, this fails unnecessarily. Filter instead of throwing:
| if (requests.some((request) => !request.status)) { | |
| // A registry that predates request review returns no status: filter those out. | |
| const validRequests = requests.filter((request) => request.status); | |
| if (validRequests.length === 0) { | |
| throw new Error('the registry does not expose request review status yet'); | |
| } | |
| return { | |
| constellationIds: committed.constellationIds, | |
| bounties: validRequests.map((request) => toBounty(request, previous.get(request.slug))), | |
| }; |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| function toBounty(request, previous) { | ||
| const b = request.bounty ?? {}; | ||
| const h = hashOf(request.slug); | ||
| const boardStatus = b.boardStatus ?? previous?.boardStatus ?? 'open'; |
There was a problem hiding this comment.
SUGGESTION: boardStatus from registry is not validated against allowed values
b.boardStatus is used directly without checking if it's a valid BOARD_STATUS value (most-wanted, open, scoped, coming-soon). An invalid value from the registry would propagate to the UI and could cause the status badge to render incorrectly or crash. Add validation with a fallback:
| const boardStatus = b.boardStatus ?? previous?.boardStatus ?? 'open'; | |
| const allowedBoardStatus = ['most-wanted', 'open', 'scoped', 'coming-soon']; | |
| const boardStatus = allowedBoardStatus.includes(b.boardStatus) ? b.boardStatus : (previous?.boardStatus ?? 'open'); |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| ...bounty, | ||
| requestId: live.id, | ||
| upvotes: live.upvotes, | ||
| claimers: live.claimers as BountyClaimer[], |
There was a problem hiding this comment.
SUGGESTION: Unsafe type assertion live.claimers as BountyClaimer[]
The shapes currently match, but a type guard would be safer for future-proofing against registry API changes. Consider adding a runtime check or using a type predicate:
| claimers: live.claimers as BountyClaimer[], | |
| claimers: (live.claimers as BountyClaimer[]).map(c => ({ username: c.username ?? null, displayName: c.displayName, avatarUrl: c.avatarUrl ?? null, repoUrl: c.repoUrl ?? null })), |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| export async function fetchLiveRequests(): Promise<Map<string, LiveRequest> | null> { | ||
| try { | ||
| const bySlug = new Map<string, LiveRequest>(); | ||
| for (let page = 1; page <= 10; page += 1) { |
There was a problem hiding this comment.
SUGGESTION: Live fetch limit (10 pages) differs from build-time fetch (50 pages)
fetchLiveRequests fetches up to 10 pages (1,000 requests) while buildBounties fetches up to 50 pages (5,000 requests). If the registry grows beyond 1,000 approved/fulfilled requests, live data will be incomplete. Align the limits for consistency.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
Closes #34. Depends on TabularisDB/tabularium#29.
What changes
The bounty board is built from Tabularium plugin requests instead of a hand-edited JSON file.
approvedrequest with no claims. The label isbounty.boardStatus.claimed)approvedrequest that has at least one claimfulfilledrequest: its plugin is listed in the registryBuild time
scripts/fetch-app-data.mjs:GET /api/requests?status=approved,fulfilledontosrc/data/pluginBounties.json.src/data/pluginBounties.json: converted to the new shape, matching whatimport:bountieswrites.claimedbecomesscoped, shipped bounties are linked to their plugin, andclaimUrlis removed.In the browser
useLiveBounties:votedByMeandclaimedByMe.POST /api/requests/:id/scaffold.workflowscope yet, a "Grant access" button links to the re-authorization page.npm create @tabularis/plugin@latest <slug>) is always shown as an alternative.CLAUDE.mddocuments the data flow and the rebuild webhook.Deploy order
bun run import:bountiesagainst production.request.approved|rejected|claimed|unclaimed|fulfilled) pointing at the Vercel deploy hook.Testing
pnpm build: passes and builds/plugins/bounties(656 static pages). The board kept the committed copy, because production doesn't support request review yet.tsc: no new errors. The only errors are the existing ones undernotes/.buildBounties()against it. It produced 31 bounties with request ids, and their states and metadata were preserved.ALLOWED_ORIGINS.