feat(slice4): schema-driven metadata edit panel in RFCView (§22.4a PUC-1 §5.2)

MetadataFieldsPanel renders one control per declared field (enum→select,
tags→chips, text→input), read-only without contribute access, saving changed
values via saveEntryMeta (direct sidecar commit). Wired into the canonical
main view, gated on the collection declaring fields (INV-5). saveEntryMeta
API client added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-07 19:14:13 -07:00
parent 46c957cff5
commit abd17a6cc8
4 changed files with 227 additions and 1 deletions
+29 -1
View File
@@ -46,7 +46,9 @@ import GraduateDialog from './GraduateDialog.jsx'
import InvitationsModal from './InvitationsModal.jsx'
import { claimOwnership, retireRFC, unretireRFC } from '../api'
import { EVENTS, track } from '../lib/analytics'
import { entryPath, entryPrPath, useProjectId } from '../lib/entryPaths'
import { entryPath, entryPrPath, useProjectId, useCollectionId } from '../lib/entryPaths'
import { getCollection } from '../api'
import MetadataFieldsPanel from './MetadataFieldsPanel.jsx'
const MANUAL_IDLE_MS = 5 * 60 * 1000 // §8.6 idle window; exact value is impl detail.
const MANUAL_DEBOUNCE_MS = 800
@@ -70,10 +72,14 @@ export default function RFCView({ viewer }) {
const [searchParams, setSearchParams] = useSearchParams()
const navigate = useNavigate()
const pid = useProjectId()
const cid = useCollectionId()
const branchParam = searchParams.get('branch') || 'main'
const [entry, setEntry] = useState(null)
// §22.4a SLICE-4: the collection's metadata field schema (null when the
// collection declares none — the panel then renders nothing, INV-5).
const [collectionFields, setCollectionFields] = useState(null)
const [mainView, setMainView] = useState(null)
const [branchView, setBranchView] = useState(null)
const [error, setError] = useState(null)
@@ -142,6 +148,15 @@ export default function RFCView({ viewer }) {
.catch(() => {})
}, [slug, pid])
// §22.4a SLICE-4: load the collection's metadata field schema for the
// detail panel. Independent of the entry load; the panel reads the entry's
// own `meta` values + `can_edit_meta` capability.
useEffect(() => {
getCollection(pid, cid)
.then(c => setCollectionFields(c?.fields || null))
.catch(() => setCollectionFields(null))
}, [pid, cid])
// Per §9.4 / §17's routing-collapse rule, super-drafts render through
// the same surface as active RFCs — the bot, the chat, the change
// panel, and the PR flow all dispatch on the entry's state internally.
@@ -747,6 +762,19 @@ export default function RFCView({ viewer }) {
: <span style={{ color: '#999', fontStyle: 'italic' }}>Left blank by the proposer.</span>}
</div>
)}
{/* §22.4a SLICE-4 (PUC-1, UX §5.2): schema-driven metadata panel on
the canonical (main) view. Renders nothing when the collection
declares no `fields` (INV-5). */}
{branchParam === 'main' && collectionFields && (
<MetadataFieldsPanel
projectId={pid}
collectionId={cid}
slug={slug}
fields={collectionFields}
meta={entry.meta || {}}
canEdit={!!entry.can_edit_meta}
/>
)}
{inDiscuss && branchParam !== 'main' && (
<div className="discuss-mode-banner">
Discuss mode on <strong>{branchParam}</strong> chat freely;