Slice 2: the §8 active-RFC view in full

Per the §19.1 brief: the three-column shape (§8.1) opens on main
in discuss mode (§8.2), supports the §8.3 discuss-vs-contribute
flip on non-main branches, hosts §8.4's per-branch chat with AI
participation (§18's <change> protocol → §8.14 changes rows), the
§8.8 change-card panel with §8.9 accept/decline/edit-before-accept,
the §8.10 tracked-change markup + DiffView toggle, the §8.11
manual-edit flushes with the stale-change mechanic, the §8.12
range and paragraph sub-threads, the §8.13 flag affordance, and
the §8.14 discuss-mode buffer.

Backend: bot.py grew per-RFC-repo write ops (cut_branch_from_main,
commit_accepted_change with the structured original/proposed/reason
body and Change-Id + Source-Message-Id + On-behalf-of trailers,
commit_manual_flush, ensure_rfc_repo_seed). cache.py grew
refresh_rfc_repo and the webhook dispatches on repository.full_name.
providers.py and chat.py port the §18 carryovers — multi-provider
LLM abstraction and SSE-streaming chat against the §5 threads /
thread_messages / changes schema. api_branches.py mounts the §17
branches/<branch>/* and threads/<thread_id>/* routes with the §6
/ §11 permission checks inline.

Frontend: RFCView.jsx rebuilt as the §8 surface; Editor.jsx,
ChatPanel.jsx, ChangePanel.jsx, PromptBar.jsx, SelectionTooltip.jsx,
DiffView.jsx, ModelPicker.jsx, modelStyles.js lifted from the
prototype and adapted to the canonical schema.

Covered by `backend/tests/test_rfc_view_vertical.py` — eleven new
integration tests against an extended FakeGitea (PUT contents,
POST orgs/{org}/repos, seed_rfc_repo): main-view read,
promote-to-branch, accept (with and without edit-before-accept),
decline, manual flush + system message, flag creation, visibility
flip, anonymous read-but-no-contribute, stale-change refusal, and
the chat-streaming path with a fake provider injected. The 5
Slice 1 tests continue to pass alongside.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-24 04:35:14 -07:00
parent 779ba6db59
commit 3bc8fe92af
24 changed files with 5433 additions and 151 deletions
+132 -51
View File
@@ -2405,63 +2405,109 @@ 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 active-RFC view in full
### 19.1 Next slice: the PR flow
Slice 1 of the build has landed. The repository scaffolding
(`backend/`, `frontend/`, `scripts/`, `docs/`) is in place; the §5
canonical app tables exist as numbered SQLite migrations with the
§4 cache mirror beside them; the §1 bot wrapper is the single
chokepoint every Git write flows through, with the §6.5
`On-behalf-of:` trailer applied uniformly and an `actions` row
recorded; Gitea OAuth provisions a `users` row on first sign-in
with role resolved from `OWNER_GITEA_LOGIN`; the §4.1 webhook
receiver and the periodic reconciler both write to the cache and
neither user actions nor the API do; the §7 left pane (catalog
with search, sort, state-filter chips, pending-ideas disclosure,
"+ Propose New RFC" button) renders against `GET /api/rfcs` and
`GET /api/proposals`; and the end-to-end propose-to-super-draft
vertical works: propose modal opens the idea PR, owner merges from
the pending-idea view, webhook (or reconciler sweep) updates the
cache, the catalog crossfades the super-draft in, and the
super-draft view renders the body. The vertical is covered by
integration tests against an in-process Gitea simulator.
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.
Several §9 affordances that depend on infrastructure that has not
yet been built were deferred from Slice 1 to Slice 2 — they are
not new candidate topics, only delivery sequencing:
Several §8 / §10 affordances were deferred from Slice 2 to later
slices — they're not new candidate topics, only delivery sequencing:
- The §9.1 propose modal's AI-suggested tags and the §9.2
AI-drafted PR description — the AI surface lands with chat.
- The §9.3 two-step composer-then-preview decline dialog —
shipped as a single-step required-comment input in Slice 1, with
the preview-and-confirm ceremony pulled into the existing §19.2
"pending-idea view's interaction design (remainder)" topic
alongside the merge-confirmation ceremony.
- The §9.3 pre-merge chat thread on a pending-idea view and its
migration to the super-draft on merge — depends on the per-RFC
/ per-branch chat infrastructure Slice 2 builds.
- **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 2 is the active-RFC view per §8 in full.** The view
inherits the three-column shape (§8.1), opens on `main` in
discuss mode by default (§8.2), supports the §8.3
discuss-vs-contribute mode flip on non-main branches, hosts §8.4's
per-branch chat with AI participation (the §18 `<change>`
protocol parsing into `changes` rows per §8.6), the §8.8
change-card panel with §8.9's accept / decline /
edit-before-accept resolution, the §8.10 tracked-change markup
and DiffView toggle, the §8.11 manual-edit flushes, the §8.12
range and paragraph sub-threads, the §8.13 flag affordance, and
the §8.14 discuss-mode buffer. The carryover assets — the Tiptap
configuration, the SelectionTooltip, the `<change>` parser, the
prompt-bar selection-quote machinery, the multi-provider LLM
abstraction, the SSE streaming — are present in working form in
the prototype at
`/Users/benstull/projects/wiggleverse/rfc-app-prototype/` and
should be lifted directly per §18.
**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.
The next build session should read `SPEC.md`, `README.md`, and
`docs/DEV.md` and pick up Slice 2 cleanly without re-briefing.
The working agreement in §19.3 carries forward: implement the
`docs/DEV.md` and pick up Slice 3 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.
@@ -2570,6 +2616,41 @@ binding.
topic once the cost of "the cache thinks the bot proposed
everything pre-app" becomes concrete. Touches §4.1 (the
reconciler's job description) and §15.9 (the attribution rule).
- **Branch-name path routing.** Slice 2's `branches/<branch>`
endpoints use FastAPI's default `{branch}` path-segment matcher,
which refuses slashes. The Slice 2 auto-generated branch name
steered around this with `<login>-draft-<hex>`, but a user who
renames to a slashed name will 404 on read. The fix is to convert
every `branches/<branch>` route to `{branch:path}` with the
understanding that ordering matters (more-specific routes like
`branches/main/promote-to-branch` must register first). Surfaced
by Slice 2's testing; defer-able until a user actually wants a
slashed branch name.
- **Markdown round-trip fidelity in the editor.** Slice 2's manual-
flush converts the Tiptap document to text via `editor.getText()`,
which discards markdown structure on round-trip (lists become
flat lines, headings lose their `#`, links collapse to their text
content). A faithful HTML-to-markdown serializer — or switching
the on-disk format to a structured one the editor owns natively
— earns its own session once usage shows where the loss bites.
Touches §8.6 (commit unit) and §8.11 (the manual-edit card's
diff fidelity).
- **The chat feed's per-thread filter affordances.** §8.12 commits a
top-of-chat disclosure that lists open threads with anchor previews
and per-thread filter affordances. Slice 2 wired the disclosure
counts; the filter that collapses the feed down to a single
thread, and the per-thread "Reply" affordance that posts back into
a specific thread from the unified feed, are the natural follow-on.
Small scope, defer-able until the feed grows busy enough to
warrant.
- **Cross-branch source-message labelling.** §8.14's data-model rule
permits a `changes` row whose `source_message_id` points at a
message in main's chat — the row's `branch_name` was mutated from
`main` to the new branch on promote-to-branch, but the message
reference stays. Slice 2's frontend doesn't yet label these as
"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.
- **Body full-text search.** When the time comes.
Topic 13 (notifications) is settled and folded into §5 (the