1185397a0e
Roadmap item #3. An RFC's main view now carries a discussion surface distinct from PR comments and from branch chat. The substrate is the existing threads/thread_messages tables — rows with branch_name IS NULL scope to the RFC's main view; the schema already permitted that shape, v0.5.0 is the first build to write it. Five new endpoints under /api/rfcs/<slug>/discussion/..., a new RFCDiscussionPanel right-column component used when branchParam === main, SPEC §10.10 settling discussion-vs-contribution, and §17 listing the new routes. Notification routing reuses the existing chat_message_in_participated_thread / chat_reply_to_my_message event kinds with branch_name=null on the fan-out row; a distinct event_kind is a §19.2 candidate. Anonymous viewers can read; writes require contributor — v0.6.0's item #4 will harden adjacent gates. No schema migration; minor bump, no operator action required beyond rebuild and restart. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
337 lines
16 KiB
Markdown
337 lines
16 KiB
Markdown
# Changelog
|
|
|
|
The binding policy for what each kind of version bump means and what
|
|
the changelog has to carry is in [`SPEC.md` §20](./SPEC.md). The
|
|
practical recipe downstream deployments follow when reading this file
|
|
is in [`docs/DEPLOYMENTS.md`](./docs/DEPLOYMENTS.md).
|
|
|
|
The canonical version is the `VERSION` file at the repo root;
|
|
`frontend/package.json#version` mirrors it. Deployments pin to a
|
|
specific framework version in their own `.rfc-app-version` file
|
|
(per §20.5).
|
|
|
|
While the framework is pre-1.0, minor-version bumps may introduce
|
|
breaking changes; the entry below each such bump documents the
|
|
upgrade steps a deployment must apply.
|
|
|
|
Upgrade-steps blocks use the [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119)
|
|
/ [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174) normative-
|
|
language convention per `SPEC.md` §20.4. **MUST** / **SHALL** steps
|
|
are required; **SHOULD** steps are recommended (the framework's
|
|
default and tested path); **MAY** steps are optional. Upgrades that
|
|
skip versions are the composition of each intervening adjacent
|
|
release's steps in order — no A-to-B path is pre-computed beyond
|
|
that.
|
|
|
|
## 0.5.0 — 2026-05-27
|
|
|
|
**Minor — no operator action required.** This release wires the
|
|
PR-less per-RFC discussion surface (roadmap item #3, SPEC §10.10).
|
|
An RFC's main view now carries a discussion panel distinct from PR
|
|
comments and branch chat; contribution — proposing edits the document
|
|
will land — still requires opening a PR via the §10.1 affordance.
|
|
The substrate is the existing `threads` / `thread_messages` tables;
|
|
rows with `threads.branch_name IS NULL` scope to the RFC's main view.
|
|
No schema migration is required.
|
|
|
|
### Added
|
|
|
|
- **PR-less discussion endpoints** (`backend/app/api_discussion.py`)
|
|
mounted at `/api/rfcs/<slug>/discussion/...`:
|
|
- `GET /api/rfcs/<slug>/discussion/threads` — list threads where
|
|
`threads.branch_name IS NULL`. Anonymous-readable per the v0.3.0
|
|
contract; the default whole-doc chat thread is materialized
|
|
lazily on first read.
|
|
- `POST /api/rfcs/<slug>/discussion/threads` — open a new
|
|
discussion thread (`thread_kind='chat'`, `anchor_kind='whole-doc'`,
|
|
`branch_name=NULL`). Body: optional `label`, optional first
|
|
`message`. Requires contributor role.
|
|
- `GET /api/rfcs/<slug>/discussion/threads/<thread_id>/messages`
|
|
— read messages on a discussion thread. Anonymous-readable.
|
|
- `POST /api/rfcs/<slug>/discussion/threads/<thread_id>/messages`
|
|
— post a message. Body: `text`, optional `quote`. Requires
|
|
contributor role.
|
|
- `POST /api/rfcs/<slug>/discussion/threads/<thread_id>/resolve`
|
|
— resolve a discussion thread. Permission per §10.10: thread
|
|
creator, RFC owner / arbiter, or app admin / owner.
|
|
- **`RFCDiscussionPanel.jsx`** — the right-column surface on the RFC
|
|
view when the viewer is on `main`. Composer requires sign-in;
|
|
Cmd/Ctrl+Enter sends. Multiple threads surface as pill-shaped
|
|
tabs above the message feed. A "New thread" affordance opens a
|
|
fresh thread on the same RFC.
|
|
- **SPEC `§10.10` PR-less discussion vs. contribution** — settles
|
|
the distinction between discussion (RFC-scoped, no PR, both
|
|
anonymous-readable and contributor-writeable) and contribution
|
|
(still requires a PR via §10.1). Also extends `§5`'s `threads`
|
|
table commentary so the null-branch interpretation is documented
|
|
as actively used rather than reserved scaffold.
|
|
- **SPEC `§17`** — lists the five new `discussion/...` endpoints
|
|
in the illustrative table.
|
|
|
|
### Changed
|
|
|
|
- **`backend/app/chat.py`** — `_fan_out_chat` now passes
|
|
`branch_name` straight through to the notify chokepoint instead of
|
|
coercing `None` to `"main"`. The notifications row carries the
|
|
null through, which preserves the §15.7 reconciler's keying on
|
|
`(rfc_slug, branch_name)` for the eventual discussion-side
|
|
chat-seen advance (deferred to a §19.2 candidate). Existing
|
|
branch-scoped chat continues to pass non-null branch names; the
|
|
change is invisible to that path.
|
|
- **`backend/app/notify.py`** — `fan_out_chat_message`'s
|
|
`branch_name` parameter is now typed `str | None` to match the
|
|
v0.5.0 PR-less shape. Routing rules are unchanged; the inbox prose
|
|
renders identically whether the chat lives on a branch or on the
|
|
RFC's discussion surface, which is the right honest signal.
|
|
- **`RFCView.jsx`** — the right-column panel is now conditional:
|
|
when `branchParam === 'main'`, render `RFCDiscussionPanel`
|
|
(the new PR-less surface); otherwise render the existing
|
|
`ChatPanel` (branch chat unchanged).
|
|
|
|
### §19.2 candidates surfaced
|
|
|
|
- PR-less discussion: range / paragraph anchors (the data model
|
|
permits them; the UI is the deferred part).
|
|
- PR-less discussion: distinct notification `event_kind`s
|
|
(`open_rfc_discussion_thread`, etc.) if usage shows contributors
|
|
want to filter discussion-vs-branch in the §15.2 inbox.
|
|
- PR-less discussion: chat-seen cursor closing the §15.7
|
|
reconciliation loop for the new surface.
|
|
- PR-less discussion: AI participant invocation (discussion-only,
|
|
no `<change>` block side-effects).
|
|
- PR-less discussion: anonymous-read polish to match the v0.6.0
|
|
write-gate hardening (item #4).
|
|
|
|
### Upgrade steps (from 0.4.0)
|
|
|
|
1. The deployment **MUST** rebuild the frontend (`npm install &&
|
|
npm run build`) so `RFCDiscussionPanel.jsx` ships in the bundle.
|
|
No new env vars; existing `frontend/.env` is sufficient.
|
|
2. The deployment **MUST** restart the backend so the new
|
|
`api_discussion` router mounts. No schema migration runs — the
|
|
`threads` table already supports `branch_name IS NULL` per §5,
|
|
and the v0.5.0 build is the first to write rows in that shape.
|
|
3. The deployment **MAY** announce the new surface to its
|
|
contributors: the RFC view's main page now carries a discussion
|
|
panel below the document. Existing branch chat and PR review
|
|
surfaces are unchanged.
|
|
4. The deployment **SHOULD NOT** expect a hardening of the
|
|
anonymous-write gate in v0.5.0 — that lands in v0.6.0 (item #4).
|
|
v0.5.0's write paths already refuse anonymous posts, so no
|
|
pre-emptive operator action is needed.
|
|
|
|
## 0.3.0 — 2026-05-27
|
|
|
|
**Minor — operator action required if a deployment wants to enable the
|
|
private-beta gate; no action required to stay open.** This release adds
|
|
an email allowlist that, when populated, restricts OAuth sign-in to the
|
|
listed emails while keeping all read paths public. Anonymous visitors
|
|
now see the full app (catalog, RFC bodies, public branch conversations)
|
|
in read-only mode instead of the §14.1 landing-page wall.
|
|
|
|
### Added
|
|
|
|
- **`allowed_emails` table** (`backend/migrations/011_allowlist.sql`).
|
|
Empty list = gate off (any successful OAuth provisions a user, as
|
|
before). Any rows present = gate on (only listed emails, plus
|
|
users already grandfathered by `gitea_id`, may sign in).
|
|
- **Admin → Allowlist tab** at `/admin/allowlist`. Add/remove emails,
|
|
see who added each row and when. Status banner shows whether the
|
|
gate is currently active.
|
|
- **`/beta-pending` page** shown after a rejected OAuth callback. Free-
|
|
text invite-contact line is configurable via the new
|
|
`VITE_BETA_CONTACT` env var (optional; falls back to a generic line).
|
|
- **Beta chips** next to the Discuss/Contribute mode toggle, the Sign
|
|
in link, and the header Sign-in button so anonymous viewers see
|
|
immediately what is gated.
|
|
- **Anonymous read mode** in the React app: the §14.1 Landing page is
|
|
preserved at `/welcome` for deployments that want to link to it, but
|
|
the default route now renders the full app shell with write
|
|
affordances hidden behind a sign-in CTA.
|
|
|
|
### Changed
|
|
|
|
- **`/auth/callback`** now consults `auth.is_allowed_sign_in()` after
|
|
fetching the Gitea profile. Rejected sign-ins clear the OAuth state
|
|
and redirect to `/beta-pending`; the session is not populated.
|
|
- **`Catalog`** receives a `viewer` prop. Anonymous viewers see "Sign
|
|
in to propose (Beta)" instead of "+ Propose New RFC".
|
|
- **`PhilosophyWithSidebar`** now reads `authenticated` from the
|
|
current viewer instead of hardcoded `true`.
|
|
|
|
### Fixed
|
|
|
|
- **Single-finger scroll on the `/philosophy` page** (and any other
|
|
`.chrome-pane`-hosted view: `/admin/*`, `/settings/notifications`)
|
|
was broken on iOS Safari. The `.app` container used `height: 100vh`,
|
|
which on iOS measures the URL-bar-hidden ("largest") viewport — so
|
|
`.app` overflowed what's actually visible. Combined with the
|
|
`body { overflow: hidden }` in `index.css`, this meant single-finger
|
|
touches on the visible area were consumed by the (blocked) page-
|
|
level scroll attempt rather than reaching the nested `.chrome-pane`
|
|
scroll. Two-finger touches bypassed the page-level layer and
|
|
one-finger then worked once the URL bar had collapsed. Switched
|
|
`.app` to `height: 100dvh` (dynamic viewport — adjusts as the URL
|
|
bar shows/hides), with `100vh` retained as a fallback for browsers
|
|
predating iOS 15.4 / Chrome 108.
|
|
|
|
### Upgrade steps (from 0.2.3)
|
|
|
|
1. The deployment **MUST** rebuild the frontend with the new
|
|
`VITE_BETA_CONTACT` env var optionally set in `frontend/.env` (it
|
|
is OK to leave it blank — the `/beta-pending` page falls back to
|
|
a generic line).
|
|
2. The deployment **MUST** restart the backend so migration
|
|
`011_allowlist.sql` runs. No data loss; the new table starts
|
|
empty, which keeps the gate off and preserves existing behavior.
|
|
3. To **enable** the private-beta gate, the deployment operator
|
|
**SHOULD** sign in once (so their `users` row exists and they
|
|
grandfather in by `gitea_id`), then open `/admin/allowlist` and
|
|
add the first invited email. The first row added turns the gate
|
|
on for any user not yet in `users`.
|
|
4. To **stay open**, do nothing — leave `allowed_emails` empty and
|
|
the deployment behaves exactly as 0.2.3.
|
|
5. The deployment **MAY** customise its `/beta-pending` contact line
|
|
by setting `VITE_BETA_CONTACT` (an email, a URL, or a short
|
|
instruction) before the frontend build. Unset is fine.
|
|
|
|
## 0.2.3 — 2026-05-26
|
|
|
|
**Patch — no operator action required.** Rebuild and restart per the
|
|
routine deploy steps; no `.env` changes, no schema changes, no
|
|
behavior changes a deployment would notice in steady state beyond
|
|
the new endpoint below.
|
|
|
|
### Added
|
|
|
|
- **`GET /api/health`** — an unauthenticated probe returning JSON
|
|
`{version, status}` for ops tooling. `version` is the running
|
|
framework version (the contents of `VERSION` per §20.1, read at
|
|
process start and cached as a module-level constant in
|
|
`backend/app/health.py`); `status` is `"ok"` with HTTP 200 in
|
|
v1. The `"degraded"` / HTTP 503 path stays reserved in the
|
|
response shape so a later release can wire real degradation
|
|
conditions without breaking the contract. The version-match
|
|
check is the structural value — a deploy-control-panel polling
|
|
the endpoint after `systemctl restart` catches the failure mode
|
|
where a restart did not pick up the new code. See `SPEC.md` §17
|
|
and the §19.2 settlement.
|
|
|
|
### Upgrade steps (from 0.2.2)
|
|
|
|
1. The deployment **MAY** configure its monitoring (Pingdom,
|
|
Healthchecks.io, the flotilla deploy control panel, etc.) to
|
|
probe `/api/health` and compare the returned `version` against
|
|
the tag last deployed. The endpoint is unauthenticated by
|
|
design — no PII in the payload, no session required.
|
|
|
|
## 0.2.2 — 2026-05-26
|
|
|
|
**Patch — no operator action required.** Rebuild the frontend and
|
|
restart per the routine deploy steps; no `.env` changes, no schema
|
|
changes, no behavior changes a deployment would notice in steady
|
|
state beyond the fix below.
|
|
|
|
### Fixed
|
|
|
|
- **Mermaid blocks rendered as raw code on `/philosophy`.**
|
|
`frontend/src/components/Philosophy.jsx` parsed PHILOSOPHY.md with
|
|
the bare `marked` import and `dangerouslySetInnerHTML`, so
|
|
```` ```mermaid ```` fences fell through as `<pre>` blocks rather
|
|
than rendered diagrams. Swapped to `MarkdownPreview`, which already
|
|
carries the lazy mermaid loader + SVG render path used by the RFC
|
|
body view, so the philosophy surface now renders mermaid the same
|
|
way RFC bodies do. Pre-existing gap, surfaced when a deployment
|
|
authored a mermaid block in its `PHILOSOPHY_PATH` override.
|
|
|
|
## 0.2.1 — 2026-05-26
|
|
|
|
**Patch — no operator action required.** Rebuild the frontend and
|
|
restart per the routine deploy steps; no `.env` changes, no schema
|
|
changes, no behavior changes a deployment would notice in steady
|
|
state.
|
|
|
|
### Fixed
|
|
|
|
- **PR view blank page (React #310).** `frontend/src/components/PRView.jsx`
|
|
declared its `threadsByKind` `useMemo` *after* the early-return
|
|
guard for the loading state (`if (!pr) return …`). On first mount
|
|
`pr` was null so the early return fired and 14 hooks were called;
|
|
on the second render `pr` was populated and execution reached the
|
|
`useMemo`, calling 15 hooks — violating the Rules of Hooks and
|
|
unmounting the page subtree (blank screen). The `useMemo` is now
|
|
declared above the early returns with optional-chaining on `pr`,
|
|
keeping the hook count stable between the loading and loaded
|
|
renders. Pre-existing bug in the post-Contribute-rewrite PR view;
|
|
surfaced in production by 0.2.0's graduation merge race fix
|
|
enabling the workflow that opens this code path.
|
|
|
|
## 0.2.0 — 2026-05-26
|
|
|
|
**Breaking config change.** The frontend now requires
|
|
`VITE_APP_NAME` to be set at build time. `npm run build` fails with a
|
|
clear message if it is missing. There is no default; every deployment
|
|
names itself.
|
|
|
|
### Upgrade steps (from 0.1.0)
|
|
|
|
1. The deployment **MUST** create a `frontend/.env` file before
|
|
building. See `frontend/.env.example` for the contract.
|
|
2. The deployment **MUST** set `VITE_APP_NAME` in `frontend/.env` to
|
|
the user-visible name it wants to ship — the string used as the
|
|
browser tab title, the header brand, and the landing H1. The
|
|
build will fail loudly if this is unset or blank.
|
|
3. The deployment **MUST** rebuild the frontend (`npm install && npm
|
|
run build`) and redeploy the resulting bundle. The previous
|
|
bundle does not read `VITE_APP_NAME`.
|
|
4. The deployment **SHOULD** verify the name appears correctly in
|
|
the browser tab and the header after redeploy before bumping its
|
|
`.rfc-app-version` pin to `0.2.0`.
|
|
5. The deployment **MAY**, in the same upgrade, take the opportunity
|
|
to retire any local overrides it was using to brand the
|
|
pre-0.2.0 hardcoded strings — those overrides are now obsolete.
|
|
6. The deployment **MUST NOT** continue to expect graduation step 4
|
|
to fail on Gitea's "Please try again later" race; that path is
|
|
now handled by `wait_for_mergeable` + bounded retry. Any local
|
|
workaround retrying graduation at the deployment level is now
|
|
redundant and **SHOULD** be removed.
|
|
|
|
### Fixed
|
|
|
|
- **Graduation merge race.** §13.3 step 4 (`merge_pr`) used to call
|
|
Gitea's merge endpoint the instant step 3 returned, which races
|
|
Gitea's background mergeability computation and produces the
|
|
`405 "Please try again later"` response. Step 4 now waits for the
|
|
`mergeable` field to become non-null (bounded by a 30s timeout) and
|
|
retries the merge call up to three times on the transient response.
|
|
See `backend/app/gitea.py#wait_for_mergeable` and
|
|
`backend/app/bot.py#_merge_with_retry`.
|
|
|
|
### Changed
|
|
|
|
- `frontend/src/App.jsx` header brand reads `VITE_APP_NAME` instead of
|
|
a hardcoded string.
|
|
- `frontend/src/components/Landing.jsx` H1 reads `VITE_APP_NAME`
|
|
instead of a hardcoded string. The pitch / deck / attribution copy
|
|
in this file is still deployment-specific and tracked as a follow-up
|
|
for extraction into deployment config.
|
|
- `frontend/index.html` `<title>` is rewritten at build time via Vite's
|
|
HTML transform.
|
|
|
|
### Added
|
|
|
|
- `frontend/.env.example` documenting the new required variable.
|
|
- Top-level `VERSION` file and this `CHANGELOG.md` as the canonical
|
|
release log.
|
|
- `SPEC.md` §20 (versioning and downstream deployments) as the
|
|
binding policy for the framework/deployment relationship.
|
|
- `docs/DEPLOYMENTS.md` as the practical guide for building on
|
|
rfc-app and upgrading existing deployments to new versions.
|
|
- `CLAUDE.md` at the repo root capturing the separation-of-concerns
|
|
rule for working sessions.
|
|
|
|
## 0.1.0 — v1 build
|
|
|
|
Initial release. See `docs/DEV.md` for the slicing plan and build
|
|
history.
|