# 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.4.0 — 2026-05-27 **Minor — no operator action required beyond rebuild + restart.** The proposer of a new RFC is now the implicit first owner of its super- draft entry, set automatically at propose time from the session user. The §13.1 claim flow remains available for *additional* owners. No schema changes, no env-var changes, no API-shape changes; only newly proposed RFCs receive the auto-owner — existing super-drafts whose `owners:` is empty are unaffected and can still be claimed via §13.1 as before. This is a §19.3 rule-2 spec correction: `SPEC.md` §9.1, §9.2, and §13.1 are updated to reflect the new shape. ### Changed - **`POST /api/rfcs/propose`** (`backend/app/api.py`) now sets `Entry.owners = [user.gitea_login]` when constructing the new super-draft entry, instead of `owners=[]`. The session's `gitea_login` is the canonical source; the endpoint never accepted an owner field from the request payload and still doesn't. - **`SPEC.md` §9.1** narrowed: the "no proposed-owner or working- group fields" sentence becomes a proposer-owner-auto / working- group-deferred split, with a §19.3 rule-2 note. - **`SPEC.md` §9.2** frontmatter shape: `owners: []` → `owners: []`, with a §19.3 rule-2 note. - **`SPEC.md` §13.1** reframed: claim flow is now a graduation-time broadening for additional owners, not a precondition for the proposer's own RFC. The §13.1 / §13.2 / §13.3 graduation pipeline itself is unchanged — the "at least one owner" precondition for graduation still holds and is now satisfied by default. ### Upgrade steps (from 0.3.0) 1. The deployment **MUST** rebuild and restart per the routine deploy steps. No `.env` changes, no schema/migration changes, no API-shape changes. 2. Operators **SHOULD** note that newly proposed RFCs after the upgrade carry the proposer in `owners:` automatically. Existing super-drafts with empty `owners:` are not migrated; they remain claimable via the §13.1 flow exactly as before. No deployment- side data action is required. 3. Deployments **MAY** communicate the UX shift to active proposers (the "Claim ownership" affordance no longer applies to your own newly proposed RFC), but the affordance simply hides on RFCs the viewer already owns, so no operator-side action is required. ## 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 `
` 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` `` 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.