Slice 4: super-draft body editing per §9.5 + §9.6

The §17 routing-collapse rule lands in api_branches.py and
api_prs.py — every branches/<branch>/... and prs/<n>/... route
dispatches on the entry's state to pick the right Gitea repo, and
the body extracted from the entry's frontmatter envelope is what
the editor and the diff see. The bot grows open_metadata_pr;
cache grows refresh_meta_branches. Two §17 routes added:
start-edit-branch and metadata. The §9.4 super-draft view replaces
RFCView.jsx's Slice 2 placeholder; a metadata pane modal opens
from the breadcrumb. Branch naming uses edit-<slug>-<6hex> to
dodge the §19.2 path-routing candidate while preserving §9.5's
structural shape.

Covered by tests/test_super_draft_vertical.py (10 tests). The
full Slices 1-4 suite is 35/35 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-24 15:43:21 -07:00
parent a2bf89e90b
commit 4565a6cb95
10 changed files with 1558 additions and 344 deletions
+115 -42
View File
@@ -186,6 +186,63 @@ posting, arbiter-only merge, contributor withdraw with the
of a public PR, and the full §10.9 conflict-replay path including
the auto-close of the original PR on the resolution PR's merge.
### Slice 4 — shipped
Super-draft body editing per §9.5 + §9.6 + §9.7. The §17 routing-collapse
rule landed in `backend/app/api_branches.py` and `backend/app/api_prs.py`
— every `branches/<branch>/...` and `prs/<n>/...` route now dispatches
on the entry's state to pick the right Gitea repo, and the body
extracted from the entry's frontmatter envelope is what the editor and
the diff see. The bot wrapper grew `open_metadata_pr`; the rest of the
bot's methods already accepted owner/repo arguments and worked against
the meta repo without change. The §4 cache learned about meta-repo edit
branches via a new `refresh_meta_branches` pass that mirrors
`edit-<slug>-<6hex>` branches into `cached_branches` and synthesizes a
per-slug `main` row so the §10.1 has-commits-ahead check works
uniformly across active and super-draft surfaces. The §5 schema needed
no migration — the super-draft scoping note already settled that the
existing tables carry both cases.
The two §17 routes Slice 4 added:
| Method | Path | § |
| ------ | -------------------------------------- | ------- |
| POST | `/api/rfcs/{slug}/start-edit-branch` | §9.5 |
| POST | `/api/rfcs/{slug}/metadata` | §9.5 |
Everything else from the §8 vertical (chat, accept, decline, manual
flush, threads, flags, visibility, grants, the SSE chat stream) and the
§10 PR flow (open, draft, review, merge, withdraw, conflict-replay)
reaches super-drafts through the same routes Slices 2 and 3 shipped —
no per-state forks at the API surface.
The branch-naming choice: §9.5 names the structural shape
`edit/<slug>/<auto-name>`, but FastAPI's default `{branch}` path matcher
refuses slashes (the §19.2 path-routing candidate). Slice 4 picked
`edit-<slug>-<6hex>` — same dash-separated shape Slice 2 used for
`<login>-draft-<6hex>`. Metadata-pane PRs use the parallel
`metadata-<slug>-<6hex>` form. The cache parsers in `app/cache.py`
recognize both the dashed and slashed prefixes so a future routing-fix
slice can flip back without a data migration.
On the frontend, `RFCView.jsx`'s super-draft placeholder was replaced
by the full editor surface — same component, dispatched on
`entry.state`. The `BranchDropdown` renders `canonical body` as the
first position when the entry is a super-draft, per §9.4. A new
`MetadataPaneModal` opens from the breadcrumb actions when the viewer
holds super-draft edit authority per §9.5 (until §13.1's claim runs,
that's app admins/owners only).
Slice 4 ships covered by `backend/tests/test_super_draft_vertical.py`
ten integration tests against the FakeGitea, covering main-view read,
start-edit-branch, body extraction from the envelope on read, accept
preserving the frontmatter on write, manual flush through the envelope,
the body-edit PR's `pr_kind='meta_body_edit'` shape, the full
cut-accept-open-merge loop with the §9.5 unclaimed-merge gate
(admin/owner only), the metadata pane PR cycle, the canonical-body
branch (`main` for super-drafts) being read-only, and the metadata pane
permission gate.
### What's deferred from Slice 2
These were in the §8 spec but lean on infrastructure later slices
@@ -259,51 +316,67 @@ spec:
## Next slice
**Slice 4: super-draft body editing per §9.5 + §9.6.**
**Slice 5: graduation per §13.**
The §8 within-branch surface and the §10 bridge to main now ship for
active RFCs; the same mechanics still need to reach super-draft
entries on the meta repo. Slice 4's unit of work is the meta-repo
edit branch — `edit/<slug>/<auto-name>` per §9.5 — and the
structural claim is that almost everything from §8 falls out
unchanged once `<slug>` resolves to a super-draft entry and
`<branch>` names a meta-repo branch rather than a per-RFC-repo
branch (see the §5 super-draft scoping note).
A super-draft becomes an active RFC through the §13 graduation
sequence — the dialog (§13.2), the five-step transactional sequence
with rollback (§13.3), the chat-follows-the-work migration (§13.4),
the pre-graduation history affordance for the new RFC view (§9.8),
and the precondition gate that refuses to graduate while body-edit
PRs are open (§9.8 / §13.3).
What Slice 4 owns specifically:
Slice 4 left this clean: the §9.5 metadata pane, the body-edit PR
flow, and the active-RFC PR flow all converge on the same dispatch.
Graduation is the act that flips an entry's state from `super-draft`
to `active`, creates the per-RFC repo via `bot.ensure_rfc_repo_seed`
(which Slice 2 added as a forward-looking seam), copies the body
from the frontmatter envelope into the new repo's `RFC.md`, strips
the body field from the meta-repo entry, mints the integer ID and
fills the `repo`/`graduated_at`/`graduated_by` fields, and migrates
the whole-doc main thread's chat to the new RFC's `branch_name=null`
thread per §13.4.
- §9.5's `Start Contributing` on a super-draft cutting an
`edit/<slug>/<auto-name>` branch on the meta repo via the bot,
re-anchoring pending `changes` rows from `main` to the new branch
the way `promote-to-branch` does for active RFCs.
- §9.6's chat-and-threads surface scoped to the super-draft and to
edit branches, sharing the §5 `threads`/`thread_messages` shape.
- §9.7's visibility and contribute grants on edit branches — the
same `branch_visibility` / `branch_contribute_grants` machinery
that Slice 2 wired, now keyed on the meta repo.
- The metadata pane from §9.5 — title and tag edits as small
meta-repo PRs via `POST /api/rfcs/{slug}/metadata`. Slug renames
remain deferred per §9.5 / §19.2.
- The §17 routing collapse the spec calls for: the
`branches/<branch>/...` endpoint family already exists; Slice 4's
job is the dispatch in `api_branches.py` that recognizes a
super-draft slug and routes to the meta repo on every read and
write. `RFCView.jsx`'s super-draft placeholder is replaced by the
full editor surface.
What Slice 5 owns specifically:
What Slice 4 does NOT own: the §10 PR flow against the meta repo's
super-draft edits is structurally identical to the active-RFC PR
flow Slice 3 just shipped, and falls out from the same dispatch.
The graduation flow from §13 stays deferred to Slice 5.
- The §13.2 Graduate dialog — three fields (integer ID, repo name,
initial owners), the inline-validation endpoint
`GET /api/rfcs/{slug}/graduate/check`, the blocking-PRs popover
via `GET /api/rfcs/{slug}/blocking-prs`, and the merge-actor set
per §13's authority rules.
- The §13.3 transactional sequence — five steps emitted as an SSE
stream via `GET /api/rfcs/{slug}/graduate/progress`, with each
step's `pending → running → done/failed` transitions surfacing in
the dialog, and a trailing `rollback` step if any earlier step
fails. The bot grows `graduate` plus the rollback primitives the
sequence needs.
- The §13.4 chat migration — the whole-doc main thread on the
super-draft (`rfc_slug=<slug>`, `branch_name='main'`) re-anchors
onto the new RFC's main thread; range and paragraph sub-threads
on the canonical-body view migrate too per §9.8's clarification.
Edit-branch chats stay attached to their original `branch_name`
on the meta repo per §9.8 — no data movement, surfaced by the
pre-graduation history affordance.
- The §9.8 pre-graduation history affordance on the new RFC view —
the slug remains the canonical key per §2.3, so the query is a
straightforward lookup of `threads` and `changes` rows where
`rfc_slug = <slug>` and `branch_name` begins with the meta-repo
edit prefix.
The carryovers Slice 4 inherits — none new from the prototype;
every §8 / §10 surface already exists. The work is dispatch glue
plus a small number of routes that need the meta-repo path
(`branches/edit/<slug>/<auto-name>` cuts).
What Slice 5 does NOT own:
The next build session should read `SPEC.md`, `README.md`, and
`docs/DEV.md` and pick up Slice 4 cleanly without re-briefing. The
working agreement in §19.3 continues to apply: implement the slice,
correct the spec only where running code reveals it was wrong at a
structural level, accumulate new candidate topics in §19.2, do not
extend the spec beyond what the slice requires.
- The §15 notification surface (still Slice 6).
- The §14 chrome polish (still Slice 7).
- The §12 30/90 branch-hygiene timers (still Slice 8).
The carryovers Slice 5 inherits — the `ensure_rfc_repo_seed`
primitive Slice 2 added, the body-edit-PR precondition gate
(checked against the same `cached_prs` shape Slice 4 wired), and
the existing `actions` audit-log shape for the rollback record.
The next build session should read `SPEC.md`, `README.md`,
`docs/DEV.md`, and `SPEC.md`'s §19.1 and pick up Slice 5 cleanly
without re-briefing. The working agreement in §19.3 continues to
apply: implement the slice, correct the spec only where running
code reveals it was wrong at a structural level, accumulate new
candidate topics in §19.2, do not extend the spec beyond what the
slice requires.