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
+95 -80
View File
@@ -2405,91 +2405,91 @@ surface. With Topic 13 folded in, the structural surface is
complete. What follows is no longer "topics that block specifying
v1" but "topics to address during or shortly after the v1 build."
### 19.1 Next slice: super-draft body editing
### 19.1 Next slice: graduation per §13
Slice 3 of the build has landed. The §10 PR flow is wired
end-to-end against the local Gitea — the §10.1 `Open PR`
affordance on a branch with the §11.3 universal-public flip when
the source branch is private, the §10.2 AI-drafted modal pulling
title and description from the diff plus the branch chat (with a
deterministic stub when no provider is configured), and the §10.3
PR review page mounted at `/rfc/<slug>/pr/<n>` that inherits the
§8.1 three-column shape and renders a unified/split diff in the
center against a compressed conversation on the right that
interleaves chat, flag, and review threads with visual
distinction. The §10.3 per-user seen-cursor advances on every
visit and accents new commits and new messages on the next; stale
tabs cannot roll the cursor backward. §10.4 review threads
materialize as `thread_kind='review'` `anchor_kind='range'` rows
on the branch chat, surfaced inline with the AI conversation but
distinguished by header badge. §10.5 merge runs the bot through
Gitea's `style='merge'` no-fast-forward path with an
`On-behalf-of:` trailer on the merge commit, preserving the §8.6
per-acceptance commits as reachable nodes in main's history.
§10.6 update-after-open falls out of Slice 2's existing
per-accept-and-per-flush push paths plus the new diff re-render on
every PR view. §10.7 post-merge renders the PR read-only with a
`Merged` banner; §10.8 withdraw collapses the PR to read-only with
a `Withdrawn` banner, distinguishing the user gesture from a
generic Gitea close via the audit log. §10.9 conflict-replay
surfaces a `Start resolution branch` affordance from the conflict
banner when Gitea reports the PR as unmergeable, cuts a fresh
branch off main's tip via the bot, replays the original branch's
accepted AI changes onto the resolution branch — applying each one
whose `original` text still locates exactly once, surfacing the
rest as stale-pending changes the contributor can re-anchor — and
opens a new PR whose `Supersedes:` trailer the cache parses on the
resolution PR's merge to auto-close the original.
Slice 4 of the build has landed. Super-draft body editing per §9.5
runs end-to-end against the local Gitea — the §9.4 super-draft view
replaces the Slice 2 placeholder and renders through the same
`RFCView.jsx` surface as an active RFC, dispatched on `entry.state`.
The §9.5 `Start Contributing` gesture cuts a meta-repo edit branch
via `POST /api/rfcs/<slug>/start-edit-branch`, re-anchors pending
main-scoped `changes` rows, and lands the contributor in contribute
mode on the new branch. From there everything in §8 — chat, AI
participation, accept/decline/edit, manual-edit flushes, range and
paragraph sub-threads, flags, DiffView, stale-change handling —
reaches the super-draft surface through the same routes Slice 2
shipped, with the dispatch sitting in `api_branches.py`'s helpers:
when `cached_rfcs.state = 'super-draft'`, the bot writes to the
meta repo and the file is `rfcs/<slug>.md` (the body wrapped in
frontmatter); when `state = 'active'`, it writes to the per-RFC
repo and the file is `RFC.md`. The body extracted from the entry's
frontmatter envelope is what the editor and the diff see; the
serializer re-wraps on every commit. The §10 PR flow against
meta-repo edit branches falls out structurally unchanged, with
`pr_kind='meta_body_edit'` distinguishing the cache row — the
§10.3 review page, the §10.4 review threads, the §10.5 merge, the
§10.8 withdraw, and the §10.9 conflict-replay path all dispatch the
same way. §9.7's visibility and contribute grants on edit branches
reuse the Slice 2 `branch_visibility` / `branch_contribute_grants`
machinery, keyed on the meta repo. The §9.5 metadata pane lands as
`POST /api/rfcs/<slug>/metadata` — title and tag edits open a
small meta-repo PR via the bot's new `open_metadata_pr` primitive;
slug renames remain deferred per §9.5 and the §19.2 candidate. The
§9.5 unclaimed-merge gate — only app admins/owners can merge a
body-edit PR until §13.1's claim runs — falls out of the existing
`_can_merge` rule against an empty `owners_json` / `arbiters_json`.
The §10 endpoints live in `backend/app/api_prs.py`, mounted
alongside the Slice 1 and 2 routers. The bot grew
`open_branch_pr`, `merge_branch_pr`, `withdraw_branch_pr`,
`cut_resolution_branch`, and `commit_replay_change`. The §5
schema grew `cached_prs.superseded_by_pr_number`,
`cached_prs.merge_commit_sha`, and a `pr_resolution_branches` join
table that records resolution-branch parentage. On the frontend,
the `Open PR` button landed on `RFCView.jsx`'s branch view,
opening `PRModal.jsx`; `PRView.jsx` is the §10.3 page in full.
The two §17 routes Slice 4 added — `start-edit-branch` and
`metadata`live in `backend/app/api_branches.py`. The bot grew
`open_metadata_pr`. The §4 cache grew `refresh_meta_branches`
which 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. The §5 schema needed no migration —
the super-draft scoping note already settled that the existing
tables carry both cases. On the frontend, `RFCView.jsx`'s
super-draft placeholder is replaced by the full editor surface;
the `BranchDropdown` renders `canonical body` as the first
position per §9.4; a `MetadataPaneModal` opens from the breadcrumb
actions for viewers holding super-draft edit authority.
Slice 3 ships covered by `backend/tests/test_pr_flow_vertical.py`
nine integration tests against an extended FakeGitea that grew
PR mergeability tracking via per-branch base snapshots,
no-fast-forward merge behavior, and a `mergeable` field on PR
responses. The tests cover opening with the §11.3 flip and the
§10.9 one-PR-per-branch refusal, the AI draft, the three-column
payload shape, seen-cursor advance with stale-tab protection,
review-thread posting, arbiter-only merge, contributor withdraw
with the `withdrawn` state distinct from generic `closed`,
anonymous read 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 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 on read, accept and manual
flush both preserving the frontmatter envelope, the body-edit PR's
`pr_kind='meta_body_edit'` cache shape, the full cut-accept-open-
merge loop with the §9.5 admin-only unclaimed-merge gate, the
metadata pane PR cycle, the canonical-body branch (`main` for
super-drafts) refusing contribute writes, and the metadata pane
permission gate refusing plain contributors. The full Slices 14
test suite is 35/35 green.
**Slice 4 is super-draft body editing per §9.5 + §9.6.** The
unit of work is the meta-repo edit branch — `edit/<slug>/<auto-
name>` per §9.5 — and almost everything from §8 falls out
structurally unchanged once `<slug>` resolves to a super-draft
entry and `<branch>` names a meta-repo branch rather than a
per-RFC-repo branch, per the §5 super-draft scoping note and §17's
single-dispatch rule. The §9.5 `Start Contributing` gesture on a
super-draft cuts a meta-repo edit branch via the bot and
re-anchors pending main-scoped `changes` rows. The §9.6 chat-and-
threads surface inherits the existing `threads` /
`thread_messages` shape. The §9.7 visibility and contribute grants
on edit branches reuse the Slice 2 machinery, keyed on the meta
repo. The metadata pane from §9.5 lands as
`POST /api/rfcs/{slug}/metadata` — title and tag edits as small
meta-repo PRs via the bot. Slug renames remain deferred per §9.5
and the §19.2 candidate entry. The PR flow against meta-repo
edits is structurally identical to the active-RFC PR flow Slice 3
shipped and falls out from the same dispatch; the graduation flow
from §13 stays deferred to Slice 5.
**Slice 5 is graduation per §13.** The five-step transactional
sequence flips a super-draft to active: validate the dialog's
`id`/`repo`/`owners` inputs against the catalog and Gitea, create
the per-RFC repo via `bot.ensure_rfc_repo_seed` (which Slice 2
added as a forward-looking seam), copy the body from the entry's
frontmatter envelope into the new repo's `RFC.md` on main, strip
the body from the meta-repo entry and fill the `id` / `repo` /
`graduated_at` / `graduated_by` frontmatter fields, and migrate
the chat per §13.4 — the whole-doc main thread and the canonical-
body view's range/paragraph sub-threads re-anchor onto the new
RFC's main thread; edit-branch chats stay attached to their
original `branch_name` on the meta repo per §9.8, surfaced by the
pre-graduation history affordance on the new RFC view. The §9.8
precondition gate — open body-edit PRs block graduation — is
enforced before the bot starts the sequence, so the §13.3 rollback
complexity does not grow. The Graduate dialog opens a stream
handle for the §17 SSE progress endpoint and renders the step
stack from `pending → running → done/failed` transitions, with a
trailing `rollback` step's events if any earlier step fails.
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 next build session should read `SPEC.md`, `README.md`,
`docs/DEV.md`, and this §19.1 entry 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.
### 19.2 Candidate topics for sessions after the v1 build lands
@@ -2673,6 +2673,21 @@ binding.
the §12 hygiene timer that fires the deletion. Slice 8
("Hardening") owns the §12 30/90 timers as a whole; calling out
here so the dependency is explicit.
- **In-app merge for metadata PRs.** Slice 4's metadata pane opens
a meta-repo PR per §9.5; the merge surface for those PRs is the
Gitea web UI for now, because `api_prs.py`'s merge endpoint is
scoped to body-changing PRs (`rfc_branch` and `meta_body_edit`).
A small follow-on adds a `prs/<n>/merge`-shaped path that handles
`meta_metadata` kinds — likely a tiny variant since there's no
diff-rendered review surface to inherit. Defer-able until usage
shows admins finding the Gitea round-trip annoying.
- **Cache-rebuild discovery of meta-repo edit branches.** Slice 4's
`refresh_meta_branches` scans every meta-repo branch and filters
by name prefix (`edit-` / `edit/`) to discover super-draft edit
branches. The reconciler hits this on every sweep, so it's
cheap, but a dedicated index on `cached_branches.branch_name`
would shorten the join-against-`cached_rfcs`-state for very
large super-draft fleets. Trivial; defer until the cost shows up.
- **Body full-text search.** When the time comes.
Topic 13 (notifications) is settled and folded into §5 (the