fix(§22/G-15): make the branch/edit/body subsystem three-tier aware (v0.53.0)
§22 migrated the READ/catalog path to the three-tier (project/collection)
model but the WRITE/branch/body subsystem still hardcoded the default
project's default collection — resolving every meta-resident entry to the
default content repo at rfcs/<slug>.md, ignoring the entry's project (its own
content repo) and collection (a <subfolder>/rfcs/ prefix). An entry outside
the default collection rendered a blank canonical body and every edit/PR/
body-write path hit the wrong file.
- New single resolver: projects.content_repo_for_collection() +
projects.entry_location(config, cid, slug) -> (org, repo, md_path)
(collection -> project -> content_repo + subfolder; falls back to the
default repo for a legacy/unknown collection).
- Every entry write/read path resolves via it: api_branches (body GET + all
branch write paths), api_prs (pr-draft/open/merge/withdraw/review/
resolution-branch), api_graduation (graduate/claim/retire/unretire +
orchestrator + state-flip), api.py mark-reviewed + idea-PR merge/decline/
withdraw + proposal preview, api_metadata. bot.open_metadata_pr and
bot.mark_entry_reviewed gained a file_path param. refresh_meta_branches,
the webhook corpus-refresh dispatch, and the hygiene branch-delete now
span every project's content repo, not just the default.
- New additive collection-scoped body-read routes:
GET /api/projects/{pid}/collections/{cid}/rfcs/{slug}/main and
.../branches/{branch} disambiguate a slug across collections (G-5) and read
the entry's own repo. Slug-only routes kept (now collection-aware via the
cached row). Frontend getRFCMain/getBranch take optional pid+cid; RFCView
threads them (mirrors the v0.52.1 entry-detail fix).
No migration, no config change. Existing default-collection entries
unaffected. Tests: backend resolver + collection-scoped branch/body + graduate-
in-subfolder write path; frontend api unit. backend 677 / frontend 60 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -221,8 +221,8 @@ export default function RFCView({ viewer }) {
|
||||
setSelection(null)
|
||||
setMode('discuss')
|
||||
|
||||
getRFCMain(slug).then(setMainView).catch(err => setError(err.message))
|
||||
getBranch(slug, branchParam)
|
||||
getRFCMain(slug, pid, cid).then(setMainView).catch(err => setError(err.message))
|
||||
getBranch(slug, branchParam, pid, cid)
|
||||
.then(view => {
|
||||
setBranchView(view)
|
||||
setEditorContent(view.body || '')
|
||||
@@ -231,7 +231,8 @@ export default function RFCView({ viewer }) {
|
||||
setChanges(view.changes || [])
|
||||
})
|
||||
.catch(err => setError(err.message))
|
||||
}, [slug, branchParam, entry])
|
||||
// §22/G-15: pid+cid scope the body reads; re-run if the collection changes.
|
||||
}, [slug, branchParam, entry, pid, cid])
|
||||
|
||||
// Load chat messages whenever the branch's main thread id resolves.
|
||||
useEffect(() => {
|
||||
@@ -266,7 +267,7 @@ export default function RFCView({ viewer }) {
|
||||
paragraphCount: manualPending?.paragraphCount || 1,
|
||||
})
|
||||
if (!res.noop) {
|
||||
const fresh = await getBranch(slug, branchParam)
|
||||
const fresh = await getBranch(slug, branchParam, pid, cid)
|
||||
setBranchView(fresh)
|
||||
setChanges(fresh.changes || [])
|
||||
setPreviewContent(fresh.body || '')
|
||||
@@ -408,7 +409,7 @@ export default function RFCView({ viewer }) {
|
||||
))
|
||||
}
|
||||
// Re-pull authoritative state: changes have been materialized server-side.
|
||||
const fresh = await getBranch(slug, branchParam)
|
||||
const fresh = await getBranch(slug, branchParam, pid, cid)
|
||||
setChanges(fresh.changes || [])
|
||||
// If we're in discuss mode and the new turn produced pending AI changes,
|
||||
// surface them as discuss-mode buffered count.
|
||||
@@ -447,7 +448,7 @@ export default function RFCView({ viewer }) {
|
||||
anchor_payload: { quote },
|
||||
label,
|
||||
})
|
||||
const fresh = await getBranch(slug, branchParam)
|
||||
const fresh = await getBranch(slug, branchParam, pid, cid)
|
||||
setBranchView(fresh)
|
||||
loadAllMessages(slug, branchParam, fresh.threads).then(setMessages)
|
||||
} catch (err) {
|
||||
@@ -463,7 +464,7 @@ export default function RFCView({ viewer }) {
|
||||
// body. The proper tracked-changes overlay lives in Phase 3's
|
||||
// preview pane; with CM6 as the source editor there is no HTML
|
||||
// surface to inject `<span class="tracked-*">` into.
|
||||
const fresh = await getBranch(slug, branchParam)
|
||||
const fresh = await getBranch(slug, branchParam, pid, cid)
|
||||
setBranchView(fresh)
|
||||
setChanges(fresh.changes || [])
|
||||
setEditorContent(fresh.body || '')
|
||||
@@ -477,7 +478,7 @@ export default function RFCView({ viewer }) {
|
||||
const handleDecline = useCallback(async (changeId) => {
|
||||
try {
|
||||
await apiDecline(slug, branchParam, changeId)
|
||||
const fresh = await getBranch(slug, branchParam)
|
||||
const fresh = await getBranch(slug, branchParam, pid, cid)
|
||||
setChanges(fresh.changes || [])
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
@@ -487,7 +488,7 @@ export default function RFCView({ viewer }) {
|
||||
const handleReask = useCallback(async (changeId) => {
|
||||
try {
|
||||
await reaskChange(slug, branchParam, changeId)
|
||||
const fresh = await getBranch(slug, branchParam)
|
||||
const fresh = await getBranch(slug, branchParam, pid, cid)
|
||||
setBranchView(fresh)
|
||||
setChanges(fresh.changes || [])
|
||||
loadAllMessages(slug, branchParam, fresh.threads).then(setMessages)
|
||||
@@ -499,7 +500,7 @@ export default function RFCView({ viewer }) {
|
||||
const handleResolveThread = useCallback(async (threadId) => {
|
||||
try {
|
||||
await resolveThread(slug, branchParam, threadId)
|
||||
const fresh = await getBranch(slug, branchParam)
|
||||
const fresh = await getBranch(slug, branchParam, pid, cid)
|
||||
setBranchView(fresh)
|
||||
loadAllMessages(slug, branchParam, fresh.threads).then(setMessages)
|
||||
} catch (err) {
|
||||
@@ -977,7 +978,7 @@ export default function RFCView({ viewer }) {
|
||||
current={branchView.visibility}
|
||||
onClose={() => setShowVisibility(false)}
|
||||
onSaved={async () => {
|
||||
const fresh = await getBranch(slug, branchParam)
|
||||
const fresh = await getBranch(slug, branchParam, pid, cid)
|
||||
setBranchView(fresh)
|
||||
setShowVisibility(false)
|
||||
}}
|
||||
@@ -993,7 +994,7 @@ export default function RFCView({ viewer }) {
|
||||
setShowGraduateDialog(false)
|
||||
// The catalog row and the RFC view now reflect `active`.
|
||||
getRFC(pid, slug, cid).then(setEntry).catch(() => {})
|
||||
getRFCMain(slug).then(setMainView).catch(() => {})
|
||||
getRFCMain(slug, pid, cid).then(setMainView).catch(() => {})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -1016,7 +1017,7 @@ export default function RFCView({ viewer }) {
|
||||
setShowMetadataPane(false)
|
||||
// Refresh main view so the new open PR surfaces in the
|
||||
// breadcrumb meta count immediately.
|
||||
getRFCMain(slug).then(setMainView).catch(() => {})
|
||||
getRFCMain(slug, pid, cid).then(setMainView).catch(() => {})
|
||||
navigate(entryPrPath(pid, slug, prNumber))
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user