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,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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user