Slice 1: scaffolding + propose-to-super-draft vertical
Brings the §1 bot wrapper, the §4 cache (webhook + reconciler), the §5 schema (six numbered migrations), Gitea OAuth + §6 user provisioning, the §7 catalog left pane, and the propose-to-merge vertical: propose modal opens an idea PR against the meta repo, an owner merges from the pending-idea view, the cache picks it up via webhook or reconciler sweep, and the catalog renders the new super-draft. Per §1 the bot is the only Git writer; every commit, branch creation, and PR merge carries the §6.5 On-behalf-of: trailer and an `actions` audit row. Per §4 the cache is never written from a user action — it's webhook+reconciler only. Covered by `backend/tests/test_propose_vertical.py` against an in-process Gitea simulator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
// api.js — every backend call lives here.
|
||||
//
|
||||
// All write requests pass {credentials: 'include'} implicitly because
|
||||
// the dev proxy and the production deploy serve the API from the same
|
||||
// origin as the frontend. If you split origins later, change here.
|
||||
|
||||
async function jsonOrThrow(res) {
|
||||
if (!res.ok) {
|
||||
let detail = ''
|
||||
try {
|
||||
const body = await res.json()
|
||||
detail = body.detail || JSON.stringify(body)
|
||||
} catch {
|
||||
detail = await res.text()
|
||||
}
|
||||
const error = new Error(detail || `HTTP ${res.status}`)
|
||||
error.status = res.status
|
||||
throw error
|
||||
}
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function getMe() {
|
||||
const res = await fetch('/api/auth/me')
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function listRFCs() {
|
||||
return jsonOrThrow(await fetch('/api/rfcs'))
|
||||
}
|
||||
|
||||
export async function getRFC(slug) {
|
||||
return jsonOrThrow(await fetch(`/api/rfcs/${slug}`))
|
||||
}
|
||||
|
||||
export async function listProposals() {
|
||||
return jsonOrThrow(await fetch('/api/proposals'))
|
||||
}
|
||||
|
||||
export async function getProposal(prNumber) {
|
||||
return jsonOrThrow(await fetch(`/api/proposals/${prNumber}`))
|
||||
}
|
||||
|
||||
export async function proposeRFC({ title, slug, pitch, tags }) {
|
||||
const res = await fetch('/api/rfcs/propose', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title, slug, pitch, tags: tags || [] }),
|
||||
})
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function mergeProposal(prNumber) {
|
||||
const res = await fetch(`/api/proposals/${prNumber}/merge`, { method: 'POST' })
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function declineProposal(prNumber, comment) {
|
||||
const res = await fetch(`/api/proposals/${prNumber}/decline`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ comment }),
|
||||
})
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
|
||||
export async function withdrawProposal(prNumber) {
|
||||
const res = await fetch(`/api/proposals/${prNumber}/withdraw`, { method: 'POST' })
|
||||
return jsonOrThrow(res)
|
||||
}
|
||||
Reference in New Issue
Block a user