# 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.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.