3bc8fe92af
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>
234 lines
12 KiB
Markdown
234 lines
12 KiB
Markdown
# Build notes
|
||
|
||
The slicing plan for the v1 build, the current state of the codebase,
|
||
and the next slice's brief.
|
||
|
||
## The slicing plan
|
||
|
||
Eight slices carry §§1–15 of [`SPEC.md`](../SPEC.md) end-to-end. The
|
||
build does not extend the spec; spec corrections during the build are
|
||
rare and surgical and live in the appropriate numbered section per
|
||
§19.3's working agreement.
|
||
|
||
1. **Repository scaffolding + propose-to-super-draft vertical.** The
|
||
chokepoint that every Git operation flows through (§1 bot wrapper),
|
||
the §4 cache machinery (webhook + reconciler), the §5 schema, Gitea
|
||
OAuth + user provisioning, the minimal §7 catalog, and one
|
||
end-to-end vertical: propose → idea PR → merge → super-draft view.
|
||
2. **The active-RFC view per §8 in full.** Editor, branch creation,
|
||
per-branch chat with AI participation (the §18 `<change>` protocol),
|
||
the change-card panel, accept/decline/edit, manual-edit flushes,
|
||
sub-threads, flags, DiffView.
|
||
3. **The PR flow per §10.** Open, review surface (diff + compressed
|
||
chat), the §10.3 seen-cursor, §10.4 review threads, merge,
|
||
post-merge, §10.9 conflict resolution.
|
||
4. **Super-draft body editing per §9.5 + §9.6.** Meta-repo edit
|
||
branches as the unit of work; everything from §8 inherits.
|
||
5. **Graduation per §13.** The dialog, the five-step transactional
|
||
sequence, rollback, the pre-graduation history affordance.
|
||
6. **Notifications per §15.** Last, because every other surface
|
||
produces signals the inbox receives — notification correctness
|
||
depends on the producers being in place first.
|
||
7. **The §14 chrome.** Landing page polish, the `/philosophy` route,
|
||
the persistent About link.
|
||
8. **Hardening.** End-to-end tests, dev/prod deployment shape,
|
||
the §12 30/90 branch-hygiene timers.
|
||
|
||
## State of the codebase
|
||
|
||
### Slice 1 — shipped
|
||
|
||
The repository scaffolding (`backend/`, `frontend/`, `scripts/`,
|
||
`docs/`), the §5 schema as numbered migrations under
|
||
`backend/migrations/`, the §1 bot wrapper (`app/bot.py`) that is the
|
||
single chokepoint every Git write flows through, Gitea OAuth and the
|
||
§6.1 user-provisioning row in `users`, the §4.1 webhook receiver and
|
||
the §4.1 periodic reconciler (both writing to the cache; user actions
|
||
never do), the §7 left pane (catalog list, search, sort, state-filter
|
||
chips, pending-ideas disclosure), and one end-to-end vertical: propose
|
||
→ idea PR opens → owner merges → super-draft appears in the catalog →
|
||
super-draft view renders the body.
|
||
|
||
### Slice 2 — shipped
|
||
|
||
The §8 active-RFC view in full. The bot wrapper grew per-RFC-repo
|
||
write operations — branch cut from main, accept-change commit with
|
||
the structured `original`/`proposed`/`reason` body and trailers,
|
||
manual-edit flush, and a `ensure_rfc_repo_seed` seam Slice 5's
|
||
graduation will eventually replace. The §4 cache now mirrors per-RFC
|
||
repos via a new `refresh_rfc_repo` path; the webhook receiver
|
||
dispatches on `repository.full_name` so per-RFC events refresh just
|
||
that repo, and the reconciler sweeps every active entry. The §18
|
||
carryovers landed as `backend/app/providers.py` (the multi-provider
|
||
abstraction, unchanged from the prototype) and `backend/app/chat.py`
|
||
(an adapter that runs the provider's streaming interface against
|
||
`thread_messages` rows, parses `<change>` blocks, and materializes
|
||
`changes` rows per §8.14). The §17 endpoints owned by Slice 2 — the
|
||
`branches/<branch>/*` and `threads/<thread_id>/*` families — live in
|
||
`backend/app/api_branches.py`, mounted alongside Slice 1's routes via
|
||
`api.make_router`. On the frontend, `RFCView.jsx` was rebuilt as the
|
||
§8 three-column surface; `Editor.jsx`, `ChatPanel.jsx`,
|
||
`ChangePanel.jsx`, `PromptBar.jsx`, `SelectionTooltip.jsx`,
|
||
`DiffView.jsx`, `ModelPicker.jsx`, and `modelStyles.js` were lifted
|
||
from the prototype and adapted to the canonical `threads` /
|
||
`thread_messages` / `changes` shape rather than the prototype's
|
||
global session_id. The §18 carryovers explicitly preserved: SSE
|
||
streaming with base64-encoded chunks, Tiptap + ProseMirror plugin for
|
||
the paragraph-margin gutter accent, the prompt-bar selection-quote
|
||
machinery, the model picker.
|
||
|
||
The §17 endpoints exercised so far:
|
||
|
||
| Method | Path | § |
|
||
| ------ | -------------------------------------- | ------- |
|
||
| GET | `/api/auth/me` | §6 |
|
||
| GET | `/api/rfcs` | §7, §17 |
|
||
| GET | `/api/rfcs/{slug}` | §17 |
|
||
| GET | `/api/proposals` | §17 |
|
||
| GET | `/api/proposals/{pr_number}` | §17 |
|
||
| POST | `/api/rfcs/propose` | §9.1 |
|
||
| POST | `/api/proposals/{pr_number}/merge` | §9.3 |
|
||
| POST | `/api/proposals/{pr_number}/decline` | §9.3 |
|
||
| POST | `/api/proposals/{pr_number}/withdraw` | §9.3 |
|
||
| POST | `/api/webhooks/gitea` | §4.1 |
|
||
| GET | `/auth/login` / `/auth/callback` / `/auth/logout` | §18 |
|
||
| GET | `/api/models` | §18 |
|
||
| GET | `/api/rfcs/{slug}/main` | §8.1, §8.2, §17 |
|
||
| GET | `/api/rfcs/{slug}/branches/{branch}` | §8.4, §17 |
|
||
| POST | `/api/rfcs/{slug}/branches/main/promote-to-branch` | §8.14, §17 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/changes/{id}/accept` | §8.9, §17 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/changes/{id}/decline` | §8.9, §17 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/changes/{id}/reask` | §8.11, §17 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/manual-flush` | §8.11, §17 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/visibility` | §11.1, §17 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/grants` | §6.4, §17 |
|
||
| DELETE | `/api/rfcs/{slug}/branches/{branch}/grants/{login}` | §6.4 |
|
||
| GET | `/api/rfcs/{slug}/branches/{branch}/threads` | §8.12, §17 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/threads` | §8.12, §8.13 |
|
||
| GET | `/api/rfcs/{slug}/branches/{branch}/threads/{id}/messages` | §8.12 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/threads/{id}/messages` | §8.12 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/threads/{id}/resolve` | §8.12 |
|
||
| POST | `/api/rfcs/{slug}/branches/{branch}/threads/{id}/chat` | §18 |
|
||
|
||
Slice 2 ships covered by `backend/tests/test_rfc_view_vertical.py` —
|
||
the FakeGitea simulator from Slice 1 grew per-RFC-repo support (PUT
|
||
contents, POST `orgs/{org}/repos`, `seed_rfc_repo`), and a new test
|
||
file walks the §8 vertical end-to-end: 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.
|
||
|
||
### What's deferred from Slice 2
|
||
|
||
These were in the §8 spec but lean on infrastructure later slices
|
||
build, so they were scoped out of this slice without altering the
|
||
spec:
|
||
|
||
- **Super-draft body editing on the meta repo (§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" gesture to cut against the
|
||
meta repo. The Slice 2 RFCView renders a placeholder for
|
||
super-draft entries pointing at Slice 4.
|
||
- **The §10.4 review threads on PRs.** `thread_kind='review'` is in
|
||
the schema and the threads endpoints honor it generically, but the
|
||
PR-page surface where review threads anchor to diff hunks lands
|
||
with Slice 3.
|
||
- **DiffView's full reconstruction from `changes` history.** Slice 2
|
||
renders the editor's current HTML (which carries the
|
||
session-local tracked-change markup from the accepts that happened
|
||
in this session) into DiffView; rebuilding the full accepted-change
|
||
markup from `changes` for a returning contributor needs a render
|
||
pipeline DiffView doesn't yet own. The current behavior matches
|
||
§8.10's "session-local" framing exactly; the §19.2 "persistent
|
||
accepted-change markup" topic is the durable extension when
|
||
evidence demands it.
|
||
- **The §10.6 PR-side commit / chat reconciliation.** Manual-edit
|
||
flushes drop a system-author message into branch chat per §10.6
|
||
in Slice 2, but the PR-side seen-cursor that uses the marker
|
||
ships with Slice 3.
|
||
- **Branch-name path conversion for slashes.** The auto-generated
|
||
branch name in Slice 2 is `<login>-draft-<hex>` (no slash) so the
|
||
FastAPI `{branch}` path segment matches without `{branch:path}`.
|
||
Users can still rename to a slashed name, but the routes will
|
||
404 on read; the proper fix is `{branch:path}` everywhere, which
|
||
lands cleanly when Slice 3 makes the same change to the PR routes
|
||
(PR numbers don't have this problem, but resolving the routing
|
||
shape once across both surfaces is the right hop).
|
||
|
||
## Environment notes
|
||
|
||
- **Python 3.13.** Earlier 3.11+ should also work; 3.13 is what the
|
||
build session ran on.
|
||
- **Node 20+** for the frontend.
|
||
- **Local Gitea on port 3000.** Anything that exposes the Gitea v1
|
||
REST API works. If you tunnel Gitea elsewhere (e.g. a container,
|
||
a Codespace), re-run `scripts/seed_meta_repo.py` so the webhook
|
||
re-registers against the right `APP_URL`.
|
||
|
||
## Conventions
|
||
|
||
- **Bot writes only via `app/bot.py`.** If a module wants to call
|
||
`app/gitea.py`'s write methods directly, the spec is right and
|
||
the module is wrong — the wrapper is the chokepoint that makes
|
||
the §6.5 `On-behalf-of:` trailer and the §6 authorization both
|
||
consistent.
|
||
- **Cache writes only from `app/cache.py`.** User actions trigger
|
||
Git operations via the bot; the cache learns about them when the
|
||
webhook arrives (or the next reconciler sweep), and never before.
|
||
This invariant is what makes §4's "Git is truth" claim hold
|
||
operationally.
|
||
- **Spec corrections during the build are rare and surgical.** When
|
||
running code reveals the spec was wrong at a structural level (per
|
||
§19.3's working agreement), the correction lands in the appropriate
|
||
numbered section with a brief note explaining what running code
|
||
revealed. Spec extensions during the build are not in scope —
|
||
they accumulate in §19.2.
|
||
- **§16 stays deferred.** Body full-text search, per-RFC model
|
||
picker, funder role, persistent accepted-change markup, slug
|
||
renames — these are not shipped in any slice. They earn their own
|
||
topic sessions when use surfaces evidence they matter.
|
||
|
||
## Next slice
|
||
|
||
**Slice 3: the PR flow per §10.**
|
||
|
||
§8 settled the within-branch surface; §10 settles the bridge between
|
||
a branch and main. The work covers the `Open PR` affordance from
|
||
§10.1 (with the §11.3 universal-public confirmation when the branch
|
||
is private), the §10.2 AI-drafted creation modal (title +
|
||
description from the diff plus the branch chat), the §10.3 review
|
||
page (three-column, diff in the center, compressed conversation
|
||
right, per-user seen-cursor accenting new hunks and new messages),
|
||
the §10.4 `thread_kind='review'` threads anchored to diff hunks
|
||
inline in branch chat, §10.5 merge (no-fast-forward, preserving the
|
||
per-acceptance commits), §10.6 update-after-open (commits and chat
|
||
arriving on the open PR, the manual-flush system message that
|
||
already lands per Slice 2), §10.7 post-merge (`Merged` banner,
|
||
chat read-only, 90-day deletion timer starts), §10.8 withdraw, and
|
||
§10.9 conflict-replay with the resolution-branch path. The shared
|
||
seen-cursor mechanism in §15.7 (the `pr_seen` and
|
||
`branch_chat_seen` cursors are in the schema already; Slice 3 wires
|
||
the advance-on-view reconciler).
|
||
|
||
The carryovers Slice 3 inherits — none new from the prototype; the
|
||
prototype's `PRModal.jsx` had a one-shot PR-creation flow that the
|
||
spec's §10 expanded considerably. The `backend/app/bot.py` operations
|
||
Slice 3 needs are: `open_pr`, `merge_pr` (style='merge' to preserve
|
||
the per-accepted-change commit nodes per §10.5), `close_pr` (for
|
||
withdraw), and the resolution-branch replay sequence from §10.9 —
|
||
which is structurally a `cut_branch_from_main` plus a series of
|
||
`commit_accepted_change` calls plus an `open_pr`.
|
||
|
||
The frontend needs a `PRView.jsx` sibling to `RFCView.jsx` that
|
||
inherits the §8.1 three-column shape but renders the diff instead
|
||
of the editor. The route is `/rfc/<slug>/prs/<n>`.
|
||
|
||
The next build session should read `SPEC.md`, `README.md`, and
|
||
`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.
|