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:
@@ -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 1–4
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user