# 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.32.0 — 2026-06-01 **Minor — graduation's integer RFC number is now optional, and RFC owners + site owners can retire (soft-delete) RFCs. Schema migration `025_retired_state.sql` runs automatically on deploy; a frontend rebuild applies the UI.** Two changes to the §13 lifecycle: 1. **Optional number at graduation (§13.2/§13.3).** Graduation no longer hard-requires a valid `RFC-NNNN`. The Graduate dialog's integer-ID field is now optional: it is still pre-filled with the next free number as a *suggestion*, but the graduating owner may clear it and graduate with **no number**. When blank, the entry flips to `active` with `id: null` and the **slug remains the canonical identifier** (§2.3). `GET …/graduate/check` treats a blank id as valid (`ok:true`); `POST …/graduate` accepts a blank/absent `rfc_id` and only validates the `^RFC-\d{4,}$` regex + collision check when a number *is* supplied. The catalog and RFC view render number-less active entries by their slug/title (no "RFC-undefined"). RFC-0001's existing number is grandfathered — `cached_rfcs.rfc_id` was already nullable, so no data migration was needed for this part. 2. **Retire / un-retire — soft delete (§3, §3.1, §13.7).** A fourth entry state, `retired`, is added. An RFC's own owners (frontmatter) and site `owner`-role holders — **not** app admins — may retire an entry (`POST /api/rfcs//retire`); it flips to `retired` via an auto-merged meta-repo PR, leaving the body and every other field (including any integer id) intact. A retired entry is removed from **every** browsing surface: the catalog, the RFC/discussion/branch views (404), and link pickers/search. It is *not* hard-deleted — the entry stays in `rfcs/` (git is truth). Un-retire (`POST /api/rfcs//unretire`) restores the prior state and is **site-owner-only**, so a soft-delete is always recoverable by the operator but an RFC owner cannot reverse their own retirement. Site owners find retired entries via a new owner-gated admin surface (`GET /api/admin/retired-rfcs`, the "Retired" tab) and un-retire from there or by navigating directly to the entry. Upgrade steps: - **MUST** run the migration. `025_retired_state.sql` rebuilds `cached_rfcs` to widen its `state` CHECK constraint to include `retired` (SQLite cannot alter a CHECK in place). It runs automatically at startup via the forward-only migration runner; existing rows are preserved, and the table is reconstructible from Gitea by the reconciler regardless (§4). No operator action beyond a normal deploy. - **SHOULD** rebuild the frontend so the optional-id Graduate dialog, the Retire affordance, and the owner-only "Retired" admin tab are present. - No config or secret changes. ## 0.31.4 — 2026-06-01 **Patch — bug fix + UI polish: secondary buttons that were invisible on light surfaces now render legibly, and the RFC view's breadcrumb action bar is harmonized into one coherent control group. CSS-only (`frontend/src/App.css`); no schema, API, config, overlay, or secret change — a plain frontend rebuild applies it. No upgrade steps. Shipped from driver session 0059.0.** `.btn-link` was authored as a *dark-header* utility — white text on a translucent-white fill (`rgba(255,255,255,0.15)`), the established on-dark pattern for the app header's "Sign out". But the same class is reused on **light** surfaces: the RFC breadcrumb action bar (`RFCView.jsx`), the PR view's diff-mode toggle and "Edit title" control (`PRView.jsx`), the invitations and inbox modals, and the discussion panel. On those near-white backgrounds the buttons were white-on-white — present in the DOM, fully functional, but visually invisible. The reported symptom: on a super-draft's header, "Metadata", "Claim ownership", and "Invitations" looked *missing*, while the filled CTAs ("Start Contributing", "Graduate to RFC repo") rendered fine because their fill carried them. The fix is root-cause, not a per-site patch: - The base `.btn-link` rule is now a proper light-surface secondary button (white fill, hairline `--c-gray-300` border, `--c-gray-700` label, hover darkens both). This corrects every light-surface reuse at once. - The original translucent-on-dark treatment is preserved for the one legitimate dark-surface use via an `.app-header .btn-link` scope, so the header "Sign out" is unchanged. - The breadcrumb action bar (`.breadcrumb-actions`) normalizes every action — the discuss/contribute toggle, the filled CTAs, and the secondary buttons — to one height, radius, and type scale, so the row reads as a single intentional control group. The bar now `flex-wrap`s instead of clipping buttons off the right edge when the set is wide. - The diff-mode toggle's active option now reads as clearly selected (filled ink) rather than relying on a weight change alone. Smooth hover transitions and the keyboard focus ring were already provided globally by the v0.21.0 interaction-polish layer, so this change adds no new motion or focus rules — it only corrects resting-state color/contrast and harmonizes sizing. ## 0.31.3 — 2026-05-30 **Patch — admin Users tab: "Last seen" reads "Never" for unclaimed invites. Visual/logic only in `Admin.jsx`. A plain frontend rebuild applies it.** An admin-created invite row showed a real-looking "Last seen" timestamp identical to "Signed up," implying the invitee had visited when they hadn't. Cause: `users.last_seen_at` is `NOT NULL DEFAULT (datetime('now'))` (`migrations/001_users_and_audit.sql`) and the invite INSERT (`invites.py`) sets neither timestamp, so both default to the row-creation instant; `last_seen_at` only advances on a real authentication. Since an unclaimed invite has provably never authenticated (that unclaimed state is exactly what drives the "PENDING INVITE" badge), the Users tab now renders **"Never"** for the Last-seen cell of a pending-invite row instead of the misleading default. Signed-up (the invite-created date) is unchanged. Upgrade steps: none. **SHOULD** deploy as a normal code deploy. ## 0.31.2 — 2026-05-29 **Patch — landing (`/`) welcome panel spacing. Visual only: CSS in `App.css` (`.welcome`). A plain frontend rebuild applies it.** The welcome read-view was jammed against the catalog divider with no top offset and loose, uneven paragraph spacing. Root cause: `.main-pane` carries a bare `.main-pane { padding: 0; display: flex }` override (the §8 three-column RFC shell) that shadows the earlier padded read-view rule, so the pane provides no padding — and `.welcome` (just `max-width`) never compensated. The welcome surface now owns its own breathing room: 56px top / 48px side gutters, a capped 680px measure, a stronger `text-3xl` "Welcome." hero, and even `--space-8` paragraph rhythm at `--leading-relaxed`. Applies to both the signed-out and signed-in welcome (same `.welcome` class). Upgrade steps: none. **SHOULD** deploy as a normal code deploy. ## 0.31.1 — 2026-05-29 **Patch — admin Users tab + header UX polish. Visual only: CSS plus markup/structure in `Admin.jsx` (no API, schema, config, overlay, or secret change). A plain frontend rebuild applies it.** Two latent CSS defects fixed: - **`.invite-badge` had no rule.** The "(pending invite)" marker on admin-created-but-unclaimed user rows rendered as bare parenthetical text. It's now a quiet amber pill, consistent with the other status badges. - **`.btn-link-quiet` never reset native button chrome.** Used as a bare link-style `