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:
Ben Stull
2026-05-24 04:31:11 -07:00
commit 779ba6db59
42 changed files with 10385 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
// RFCView.jsx — §9.4 super-draft view (and a stub for active RFCs).
//
// Slice 1 ships read-only body rendering: the breadcrumb names the
// entry, the body renders via marked. The discuss-vs-contribute toggle,
// per-branch chat, change-card panel, and breadcrumb dropdown all land
// in Slice 2 per §8.
import { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import { marked } from 'marked'
import { getRFC } from '../api'
export default function RFCView() {
const { slug } = useParams()
const [entry, setEntry] = useState(null)
const [error, setError] = useState(null)
useEffect(() => {
setEntry(null); setError(null)
getRFC(slug).then(setEntry).catch(err => setError(err.message))
}, [slug])
if (error) return <div className="entry-view"><p>Error: {error}</p></div>
if (!entry) return <div className="entry-view">Loading</div>
const stateClass = entry.state === 'active' ? 'active' : ''
return (
<article className="entry-view">
<div className={`entry-state-banner ${stateClass}`}>
{entry.state === 'super-draft' ? 'Super-draft' : (entry.id || 'Active')}
</div>
<h1 className="entry-title">{entry.title}</h1>
<div className="entry-meta">
<span>{entry.slug}</span>
{entry.proposed_by && <> · proposed by <strong>{entry.proposed_by}</strong></>}
{entry.proposed_at && <> · {entry.proposed_at}</>}
{entry.tags.length > 0 && (
<div style={{ marginTop: 6 }}>
{entry.tags.map(t => <span key={t} className="entry-tag">{t}</span>)}
</div>
)}
</div>
{entry.state === 'active' && (
<div className="entry-state-banner" style={{ background: '#fffbeb', borderColor: '#fde68a', color: '#92400e' }}>
The active-RFC view (editor, branches, chat) lands in Slice 2.
The body below is the canonical main-branch text.
</div>
)}
<div
className="entry-body"
dangerouslySetInnerHTML={{ __html: marked.parse(entry.body || '') }}
/>
</article>
)
}