Slice 3: the PR flow
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2405,108 +2405,87 @@ 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: the PR flow
|
||||
### 19.1 Next slice: super-draft body editing
|
||||
|
||||
Slice 2 of the build has landed. The §8 active-RFC view is wired
|
||||
end-to-end against the local Gitea: the three-column shape (§8.1)
|
||||
inherits the §7 catalog on the left, hosts a Tiptap editor in the
|
||||
center with a breadcrumb dropdown listing main + open branches +
|
||||
open PRs, and surfaces per-branch chat plus the change-card panel
|
||||
on the right. Opening an active RFC lands on `main` in discuss mode
|
||||
per §8.2; `Start Contributing` on main calls the §17
|
||||
promote-to-branch endpoint to cut a new branch via the bot and
|
||||
re-anchors any pending `main`-scoped `changes` rows to it (§8.14).
|
||||
On a non-main branch the §8.3 discuss-vs-contribute toggle flips
|
||||
the editor between read-only and edit-enabled. The §18 carryovers
|
||||
landed in `backend/app/providers.py` and `backend/app/chat.py` and
|
||||
on the frontend as `Editor.jsx`, `ChatPanel.jsx`, `ChangePanel.jsx`,
|
||||
`PromptBar.jsx`, `SelectionTooltip.jsx`, `DiffView.jsx`, and
|
||||
`ModelPicker.jsx`. AI chat parses `<change>` blocks per §18 into
|
||||
`changes` rows with `state='pending'` per §8.14; accept runs the
|
||||
bot's per-accepted-change commit (§8.6) with the structured body
|
||||
and `Change-Id`, `Source-Message-Id`, and `On-behalf-of:` trailers;
|
||||
decline persists the row as evidence per §8.9; edit-before-accept
|
||||
preserves the AI's original text under an `AI proposed:` section
|
||||
of the commit body per §8.9. Manual edits flush as one commit per
|
||||
window with a system-author chat message landing per §10.6. The
|
||||
§8.10 tracked-change markup is session-local in the editor; DiffView
|
||||
is the read-only render of the same accepted changes. The §8.11
|
||||
stale-change machinery sets `changes.stale_since` when a manual
|
||||
edit changes the document such that a pending AI proposal's
|
||||
`original` no longer locates; the re-ask and force-apply paths are
|
||||
wired. The §8.12 range threads (via the selection tooltip) and the
|
||||
§8.13 flag threads (via the selection tooltip's flag tab) materialize
|
||||
as `threads` rows scoped to the branch; the chat feed renders them
|
||||
inline with the whole-doc default thread. The §11.1 visibility and
|
||||
§6.4 contribute grants are wired with the branch-creator /
|
||||
arbiter / admin set per §11.1, §11.2, §6.3. The §4 cache grew a
|
||||
`refresh_rfc_repo` path that the webhook dispatches per
|
||||
`repository.full_name` and the reconciler sweeps for every active
|
||||
entry. The vertical is covered by `backend/tests/test_rfc_view_vertical.py`
|
||||
— eleven integration tests against an extended FakeGitea that
|
||||
supports per-RFC repos.
|
||||
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.
|
||||
|
||||
Several §8 / §10 affordances were deferred from Slice 2 to later
|
||||
slices — they're not new candidate topics, only delivery sequencing:
|
||||
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.
|
||||
|
||||
- **Super-draft body editing on meta-repo edit branches (§9.5).**
|
||||
The `branches/<branch>` machinery is structurally general enough
|
||||
that meta-repo edit branches fall out of it once Slice 4 wires
|
||||
the super-draft view's "Start Contributing" to cut against the
|
||||
meta repo. The Slice 2 RFCView renders a placeholder for
|
||||
super-draft entries pointing at Slice 4.
|
||||
- **PR-anchored review threads (§10.4).** `thread_kind='review'` is
|
||||
in the §5 schema and the threads endpoints honor it generically,
|
||||
but the PR-page surface that anchors review threads to diff
|
||||
hunks lands with Slice 3.
|
||||
- **DiffView's full reconstruction from `changes` history.** Slice 2
|
||||
's DiffView renders the editor's current HTML, which carries the
|
||||
session-local tracked-change markup from accepts done in the
|
||||
current session. Rebuilding the markup for accepted changes
|
||||
earlier in branch history is the §19.2 "persistent
|
||||
accepted-change markup" topic; the §8.10 framing already commits
|
||||
the markup to session-local scope and points returning
|
||||
contributors at DiffView, which is the durable artifact.
|
||||
- **The §10.6 PR-side seen-cursor reconciliation.** Manual-edit
|
||||
flushes drop a system-author message per §10.6 in Slice 2, but
|
||||
the per-PR seen-cursor that uses the marker ships with Slice 3.
|
||||
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 3 is the PR flow per §10 in full.** Open a PR via the
|
||||
§10.1 affordance on a branch (with the §11.3 universal-public
|
||||
confirmation when the branch is private); the §10.2 AI-drafted
|
||||
creation modal pulls title and description from the diff plus the
|
||||
branch chat. The §10.3 PR review page inherits the §8.1
|
||||
three-column shape — catalog left, diff (unified or split) in the
|
||||
center, the compressed conversation plus the inline review-comment
|
||||
surface (§10.4) on the right. The §10.3 per-user seen-cursor
|
||||
mechanism accents new hunks and new conversation messages on the
|
||||
next visit. The §10.4 review comments materialize as
|
||||
`thread_kind='review'`, `anchor_kind='range'` threads anchored to
|
||||
the post-PR document state, surfaced inline with the AI chat
|
||||
visually distinguished. §10.5 merge writes a no-fast-forward merge
|
||||
commit preserving the per-accepted-change commit nodes from §8.6
|
||||
as individual reachable commits in main's history. §10.6 update-
|
||||
after-open re-renders the diff as new commits arrive (which they
|
||||
already do — Slice 2's manual-flush and accept-change paths both
|
||||
push immediately). §10.7 post-merge renders the PR read-only with
|
||||
a `Merged` banner and starts §12's 90-day deletion timer. §10.8
|
||||
withdraw closes the PR with the same read-only treatment. §10.9
|
||||
conflict-replay cuts a fresh resolution branch off main's tip,
|
||||
replays the source branch's diff (running the AI participant
|
||||
against unambiguous conflicts and surfacing the rest to the
|
||||
contributor), and opens a new PR with the original auto-closing on
|
||||
merge.
|
||||
|
||||
The carryover assets Slice 3 inherits: none new from the prototype
|
||||
beyond what Slice 2 already lifted. The prototype's `PRModal.jsx`
|
||||
was a one-shot submission flow; §10's PR creation modal is its
|
||||
descendant but the spec broadened the surface considerably. The
|
||||
seen-cursor advances are pure schema work — `pr_seen` and
|
||||
`branch_chat_seen` are in the §5 schema; Slice 3 wires the
|
||||
advance-on-view reconciler from §15.7.
|
||||
**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.
|
||||
|
||||
The next build session should read `SPEC.md`, `README.md`, and
|
||||
`docs/DEV.md` and pick up Slice 3 cleanly without re-briefing. The
|
||||
`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
|
||||
@@ -2651,6 +2630,49 @@ binding.
|
||||
"from a conversation on main" in the change panel; a small visual
|
||||
treatment is the natural follow-on. Surfaced by §8.14's data path
|
||||
going through Slice 2 for the first time.
|
||||
- **The PR-page diff renderer.** Slice 3 ships a line-level
|
||||
unified/split diff between branch and main RFC.md bodies,
|
||||
computed client-side from the two strings via a small LCS pass.
|
||||
Sufficient for the single-file v1 surface, but the §10.3
|
||||
per-hunk seen-cursor accent — distinct from the file-level
|
||||
accent Slice 3 wires — and the inline `<change>`-block
|
||||
attribution from `changes.commit_sha` are the natural follow-on.
|
||||
Earns a topic when a contributor's PR carries enough changes
|
||||
that a reviewer wants to scope review to one hunk at a time.
|
||||
Touches §10.3 (the per-hunk accent voice) and §10.4 (anchoring
|
||||
review threads to specific hunks rather than free-text quotes).
|
||||
- **The §10.2 modal's AI-drafted text when no provider is
|
||||
configured.** Slice 3 falls back to a deterministic stub
|
||||
(`Edits to <RFC title>` plus a character-count line) when the
|
||||
app has no LLM provider. The fallback is functional but does
|
||||
not produce spec-voice text. Per-RFC model availability (the
|
||||
first §19.2 candidate, on the funder-role topic) will need to
|
||||
settle the credential-delegation shape before this earns its
|
||||
own topic; until then, the stub is the right shape for the
|
||||
no-credential-available case.
|
||||
- **§10.9 replay AI participation.** Slice 3 implements the
|
||||
structural §10.9 path — fresh resolution branch off main, replay
|
||||
the accepted changes whose `original` text still locates exactly
|
||||
once, surface the rest as stale-pending changes on the
|
||||
resolution branch — but does not yet invoke the AI participant
|
||||
on the ambiguous conflicts to attempt a re-anchored proposal.
|
||||
The contributor re-anchors manually for now. The "AI runs
|
||||
against unambiguous conflicts" pass earns its own topic once
|
||||
conflicts happen often enough to design against; the §19.2
|
||||
"conflict-replay UX in detail" entry already names this.
|
||||
- **PR title and description sync with Gitea.** Slice 3's
|
||||
`POST /api/rfcs/.../prs/<n>/description` updates the cache row
|
||||
but does not mirror the edit back to Gitea via the issues
|
||||
endpoint. The PR page is the canonical surface for v1 and the
|
||||
cache is its source of truth, so the divergence is fine within
|
||||
the app — but anyone reading the PR directly on Gitea sees the
|
||||
pre-edit text. A small follow-on that propagates the edit
|
||||
through the bot wrapper closes the loop.
|
||||
- **The §10.7 90-day deletion timer wiring.** Slice 3 lands the
|
||||
PR-merged state and the read-only treatment but does not wire
|
||||
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.
|
||||
- **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