Ben Stull 4565a6cb95 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>
2026-05-24 15:43:21 -07:00
2026-05-24 05:10:50 -07:00

RFC App

A single-process FastAPI + SQLite + React + Vite + Tiptap app that materializes the Wiggleverse RFC framework specified in SPEC.md. The framework's mission lives in PHILOSOPHY.md; the spec is the binding contract; this README is how to bring the app up against a local Gitea instance and exercise the slice the build session has shipped so far.

The implementation is in progress. See docs/DEV.md for the slicing plan and the current state.

What the app expects to talk to

  • A Gitea instance at GITEA_URL. The instance hosts the meta repository and (eventually) one repository per graduated RFC.
  • A bot service account in that Gitea, with a personal access token in GITEA_BOT_TOKEN. Per §1 the bot is the only writer in the system — every commit, branch, and PR the app produces flows through one wrapper that applies the §6.5 On-behalf-of: trailer and records a row in the actions audit log.
  • An OAuth2 application registered against that Gitea, with the callback URL set to {APP_URL}/auth/callback. Real human users authenticate via Gitea OAuth (the §18 carryover); the app reads their Gitea profile, provisions a row in users, and layers §6's app-owned permission model on top.

Local bring-up

The shortest path from a clean checkout to a working app is:

1. Stand up a local Gitea

Anything that exposes the Gitea REST API works. The fastest path is Docker:

docker run -d --name gitea \
  -p 3000:3000 -p 222:22 \
  -v gitea-data:/data \
  gitea/gitea:1.21

Open http://localhost:3000, walk through the install wizard (SQLite, default port), and create your owner-zero account.

2. Create the bot service account

In Gitea, sign in as your owner account and Site Administration → User Accounts → Create User Account. Give it a name like rfc-bot and an email. Then sign in as the bot, open Settings → Applications → Generate New Token, and grant it the write:repository, write:user, and write:admin scopes (admin is needed because the bot will create per-RFC repos on graduation; in v1 you can scope down to repo and org if you want to defer admin until Slice 5).

Copy the token; you will paste it into .env.

3. Create the org that will host the meta repo

The seed script creates the meta repo inside an org. Create the org (e.g. wiggleverse) in Gitea and add rfc-bot to it as an Owner.

4. Register the OAuth2 application

In Gitea: Site Administration → Integrations → OAuth2 Applications → Create. Name it whatever you like, set the redirect URI to http://localhost:8000/auth/callback. Copy the client id and client secret — they go into .env.

5. Configure the app

cd backend
cp .env.example .env
$EDITOR .env    # fill in every variable

Required values:

Variable What it is
GITEA_URL Base URL of the Gitea instance (no trailing slash).
GITEA_BOT_USER The bot account's login.
GITEA_BOT_TOKEN The bot account's access token.
GITEA_ORG The org that owns the meta repo.
META_REPO The meta repo's name (default meta).
OAUTH_CLIENT_ID From the OAuth app you registered.
OAUTH_CLIENT_SECRET Likewise.
APP_URL The URL the app is reachable at locally.
SECRET_KEY A long random string for cookie signing.
OWNER_GITEA_LOGIN Your owner-zero Gitea login — gets the owner role on first sign-in.
GITEA_WEBHOOK_SECRET A shared secret for the §4.1 webhook signature.

The LLM-provider settings (ENABLED_MODELS, ANTHROPIC_API_KEY, etc.) are not exercised by Slice 1 but are wired through config.py so the next slice can pick them up.

6. Install dependencies

Backend:

cd backend
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

Frontend:

cd ../frontend
npm install

7. Seed the meta repo

The seed script creates wiggleverse/meta if it does not exist, populates it with PHILOSOPHY.md, README.md, CONTRIBUTING.md, the regenerate-index workflow placeholder, and an empty rfcs/ directory, and registers the Gitea webhook the app needs:

cd backend
.venv/bin/python ../scripts/seed_meta_repo.py

Re-running is safe — every step is upsert-shaped.

8. Run the app

In two terminals:

# Terminal 1 — backend
cd backend
.venv/bin/uvicorn app.main:app --reload --port 8000
# Terminal 2 — frontend
cd frontend
npm run dev

Open http://localhost:5173. Sign in with your owner-zero Gitea account. The catalog should appear empty; the + Propose New RFC button at the bottom opens the propose modal.

What slice 1 lets you do

End-to-end: propose a new RFC → an idea PR opens against the meta repo → an owner merges from the pending-idea view → the super-draft appears in the catalog → opening it renders the body.

This exercises the §4 cache (webhook + reconciler), the §6 permission model (the owner-only merge button, the contributor-only propose modal), the §1 bot wrapper (every Git write goes through it, every commit and PR carries the On-behalf-of: trailer), and the §9 propose-merge-render path.

Out of scope for slice 1: the active-RFC view (§8), per-branch chat, AI participation, the change-card panel, PRs against per-RFC repos, graduation, notifications, the landing page's full polish. Those slices are listed in docs/DEV.md.

Verifying it worked

After bring-up:

  • http://localhost:8000/docs lists the API routes the build session has wired so far.
  • sqlite3 backend/data/rfc-app.db .schema shows the §5 schema.
  • gitea ls /api/v1/repos/wiggleverse/meta/contents/rfcs after a proposal merges should show one new <slug>.md.

Seeding an active RFC for §8 testing

Slice 2 (the active-RFC view per §8) needs an entry whose state is active and whose per-RFC repo exists. Slice 5's graduation flow will land the proper path; until then, scripts/seed_test_rfc.py is the dev shortcut. Sign in once via OAuth so a users row exists, then:

cd backend && .venv/bin/python ../scripts/seed_test_rfc.py \
    --slug open-human-model \
    --title "Open Human Model"

The script creates wiggleverse/rfc-NNNN-<slug>, seeds RFC.md on main, registers the webhook, and graduates the meta entry. The §8 surface at /rfc/<slug> then has something real to render.

Troubleshooting

  • The catalog stays empty after a merge. Check that the webhook is reaching the app: the reconciler runs every five minutes and will catch up, but a missing or misconfigured webhook is the most common reason for sub-second freshness to fail. The seed script registers the webhook for you; if you bring up Gitea on a different host (e.g. a Codespace, a tunnel), re-run the seed against the new APP_URL.
  • OAuth callback errors. The redirect URI in Gitea has to match APP_URL exactly, including the protocol and port.
  • The bot can't merge. The bot needs Maintainer or Owner on the meta repo (membership in GITEA_ORG as Owner gives it both).

Where to read further

  • SPEC.md — the binding contract. Every load-bearing decision is there.
  • PHILOSOPHY.md — why this framework exists. The spec's decisions answer to it.
  • docs/DEV.md — the build's slicing plan, the current state, and the next slice's brief.
  • deploy/DEPLOY.md — single-host production deployment behind nginx + Let's Encrypt.
S
Description
Open-source RFC platform software
Readme 589 KiB
Languages
Python 66.2%
JavaScript 21.9%
HTML 5.9%
CSS 5.8%
Shell 0.2%