Slice 7: §14 chrome + settings and admin neighborhoods

§14.1 richer landing, §14.2 /philosophy route (disk-backed), §14.3
persistent About link. /settings/notifications surfaces Slice 6's
preferences/quiet-hours/mute/watches endpoints. /admin home base
consolidates role management, the §6.2 write-mute, the audit-log
viewer, the permission-events log, and the §13.2 graduation queue.

Backend: backend/app/philosophy.py, backend/app/api_admin.py (seven
admin endpoints + user-search), GET /api/users/me/notification-mutes.
Frontend: Landing.jsx (deck), Philosophy.jsx, NotificationSettings.jsx,
Admin.jsx, App.jsx routing for the chrome surfaces.

Tests: backend/tests/test_chrome_vertical.py — 13 cases. Full suite
75/75 green.

Spec corrections: §14.2 (PHILOSOPHY.md source is a deployment-time
decision), §17 (admin block extended to name the seven new endpoints
+ user-search and notification-mutes read). §19.1 rewritten for
Slice 8 hardening; §19.2 grew four candidates (owner succession,
mute-from-actor, the "Following since <date>" disclosure, audit-log
row prose).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-24 23:40:49 -07:00
parent f67d0aa0db
commit 060fa408a2
14 changed files with 2722 additions and 158 deletions
+132 -41
View File
@@ -29,10 +29,18 @@ rare and surgical and live in the appropriate numbered section per
6. **Notifications per §15.** Last, because every other surface
produces signals the inbox receives — notification correctness
depends on the producers being in place first.
7. **The §14 chrome.** Landing page polish, the `/philosophy` route,
the persistent About link.
7. **The §14 chrome + the settings and admin neighborhoods.**
Landing page polish, the `/philosophy` route, the persistent
About link; the `/settings/notifications` surface that exposes
Slice 6's preferences/quiet-hours/mute/watches endpoints; the
`/admin` home base that consolidates role management, the §6.2
write-mute, the audit-log viewer, and the §13.2 graduation-
readiness queue.
8. **Hardening.** End-to-end tests, dev/prod deployment shape,
the §12 30/90 branch-hygiene timers.
the §12 30/90 branch-hygiene timers, the §19.2 candidates that
cluster with deployment (branch-name path routing, cache
bootstrap from a pre-existing meta repo, in-app metadata-PR
merges, graduation rollback's branch cleanup).
## State of the codebase
@@ -280,6 +288,80 @@ adds the `email_opt_out_all` column to `users` for the bounce
webhook. Topic 13 settled the rest of the §5 surface before the
build started, so no further migrations were needed.
### Slice 7 — shipped
The §14 chrome, the §15 settings neighborhood, and the §6/§17 admin
home base — three surfaces over existing infrastructure.
§14.1's pre-login landing carries the title, the subtitle, the
short-form pitch from [`PHILOSOPHY.md`](../PHILOSOPHY.md), the
sign-in affordance, and a three-item deck that names what the
framework is. §14.2's `/philosophy` route reads `PHILOSOPHY.md`
through [`backend/app/philosophy.py`](../backend/app/philosophy.py)
(disk-backed, configurable via `PHILOSOPHY_PATH`; defaults to the
file at the project root) and renders with `marked`. §14.3's
persistent About link sits in the header alongside Settings (open
to everyone) and Admin (owner/admin only); the chrome's visual
budget stays tight per §14.4.
The notification-settings surface lives at `/settings/notifications`
([`NotificationSettings.jsx`](../frontend/src/components/NotificationSettings.jsx))
and is what the §15.4 email footer's `Manage all preferences` link
resolves to. Five sub-sections: the §15.4 per-category toggles
(with the `email_watched_churn` toggle permanently disabled and the
§15.4 refusal tooltip inline — naming the refusal is what keeps the
contract honest), the §15.5 digest cadence dropdown, the §15.8
quiet-hours editor (three inputs against
`Intl.supportedValuesOf('timeZone')` with all-three-or-clear
validation server-side), the §15.6 watches overview, and the
§15.8 per-user mute list with a typeahead add. Owners and admins
see the "cannot mute" prose inline per §15.8.
The admin home base lives at `/admin`
([`Admin.jsx`](../frontend/src/components/Admin.jsx)) as a four-
tab left-rail: Users (role management + §6.2 write-mute), Graduation
queue (§13.2-ready partition), Audit log (paged `actions` with
filter chips), and Permission events (paged `permission_events`).
Role-grant constraints land server-side in
[`backend/app/api_admin.py`](../backend/app/api_admin.py) per §6.1
— only owners may grant `owner`; owners cannot self-demote on the
role endpoint (the explicit succession path is a §19.2 candidate).
The §6.2 write-mute applies only to contributors; admins and owners
are not write-mutable. Every role and mute change writes a
`permission_events` row joined to `users` for the surface.
| Method | Path | § |
| ------ | --------------------------------------------- | ------- |
| GET | `/api/philosophy` | §14.2 |
| GET | `/api/admin/users` | §6.1 |
| POST | `/api/admin/users/{id}/role` | §6.1 |
| POST | `/api/admin/users/{id}/mute` | §6.2 |
| GET | `/api/admin/audit` | §6.5 |
| GET | `/api/admin/permission-events` | §6.5 |
| GET | `/api/admin/graduation-queue` | §13.2 |
| GET | `/api/users/me/notification-mutes` | §15.8 |
| GET | `/api/users/search` | §15.8 |
Slice 7 ships covered by
[`backend/tests/test_chrome_vertical.py`](../backend/tests/test_chrome_vertical.py) —
thirteen integration tests against the FakeGitea, covering the
philosophy route for anonymous and authenticated callers, the
§15.4 / §15.5 / §15.8 preferences round-trip (including the
permanent `email_watched_churn` refusal), the quiet-hours
all-or-nothing validation, the §15.8 mute add/list/unmute
round-trip, the user-search typeahead, the admin role and
write-mute round-trips with their `permission_events` audit, the
§6.1 refusal of owner-grant by non-owners, the audit-log filter
chips, the graduation-queue partition under both preconditions,
and the permission-events listing. The full Slices 17 test suite
is 75/75 green.
No schema migrations. The two surface-facing spec corrections —
§14.2 (PHILOSOPHY.md source is a deployment-time decision; v1 reads
from the app repo) and the §17 admin block (extended to name the
seven new endpoints) — are the only places the slice's running
code asked the spec to be more honest than it was.
### Slice 5 — shipped
Graduation per §13 in full. The §13.3 five-step transactional sequence
@@ -510,52 +592,61 @@ spec:
## Next slice
**Slice 7: the §14 chrome.**
**Slice 8: hardening — the last slice of the v1 build.**
With Slice 6 shipped, every structural and notification beat the
framework commits to is live: propose, claim, super-draft body
editing, the §10 PR flow against both repo shapes, graduation, and
the §15 inbox/email/digest stack. What remains for v1 is the chrome
that wraps the whole thing — the landing page that brings an
unauthenticated visitor in, the `/philosophy` route that surfaces
[`PHILOSOPHY.md`](../PHILOSOPHY.md) verbatim, the persistent About
link in the header per §14.3, plus the natural neighbors that
Slice 6 left as API-only and that §19.2 names as candidates:
With Slice 7 shipped, every structural beat the spec commits to is
live and every surface the framework exposes has chrome around it.
What remains is the hardening pass that lets a single-operator
deployment actually run end-to-end without hand-holding. Three
pieces hang together:
- **The notification-settings surface** — the actual UI for the
preferences/quiet-hours/mute endpoints Slice 6 wired. Topic 13
settled the schema and the per-category rules; the surface
where a contributor finds the per-category email toggles, the
digest cadence dropdown, the quiet-hours editor, the watches
overview, and the per-user mute list is the natural follow-on.
Likely lives at `/settings/notifications` (the link Slice 6's
emails already point at).
- **The admin neighborhood.** §19.2's "Admin surfaces" candidate.
Role management, the §6.2 app-wide write-mute, the audit-log
viewer, the graduation-readiness queue. Topics 12 and 13 both
expanded the admin's repertoire without giving it a centralized
home; Slice 7 picks the framing.
- **Landing page polish.** Slice 1 stood up a minimal landing for
the unauthenticated path; §14 commits a richer shape — what the
framework is, why it exists, what the visitor's first read should
be, and the sign-in affordance.
- **The `/philosophy` route.** [`PHILOSOPHY.md`](../PHILOSOPHY.md)
rendered inline, reachable from the header on every page, so the
reader can return to the framing without leaving the app.
- **The §12 30/90 branch-hygiene timers.** §11.5 names the branch
lifecycle (open → merged → 30d read-only → 90d deleted-by-bot,
with the per-user message-cursor preservation contract); §12
formalizes the policy. The wiring is a scheduled task next to
the existing `DigestScheduler` — same `run_tick` test-seam shape.
The §10.7 90-day deletion timer Slice 3 left explicitly deferred
lives here too. Touches `cache.Reconciler` (the natural place to
fire the hygiene sweep), `bot.delete_branch` (the §12 actuator,
not yet exercised), and the §19.2 cache-bootstrap topic if the
hygiene sweep also rebuilds branch state from Gitea after a
cache wipe.
- **An end-to-end smoke pass** over the working surfaces. Propose
→ super-draft → branch → PR → merge → graduate → active-RFC PR
→ notification → inbox → email — one or two `test_e2e_smoke.py`
cases that exercise the seams a per-slice test wouldn't. Plus
the §19.2 follow-ons the hardening pass is the natural place to
fold in: branch-name path routing (`{branch:path}` everywhere
with route-ordering discipline), cache bootstrap from a
pre-existing meta repo (the audit-log-first attribution shape
exercised against history the bot did not author), in-app merge
for metadata PRs, the graduation rollback's branch cleanup, and
the small Slice-2-onward follow-ons that are deferred until the
hardening pass demands them.
- **The dev/prod deployment shape.** `deploy/` already carries an
nginx vhost, a systemd unit, and a runbook stub. Slice 8 proves
the bring-up against a fresh host, settles the secret-material
handling (the existing `.env.example` plus the §15.4 SMTP
wiring), wires the §6 / §15.4 SMTP credentials, and lands the
README updates that take a new operator from `git clone` to a
signed-in browser.
What Slice 7 does NOT own:
What Slice 8 does NOT own:
- The §12 30/90 branch-hygiene timers (still Slice 8).
- New surfaces. The v1 surface is complete; the hardening pass is
about making what's there resilient, observable, and operable.
- The §16 deferred items.
- New §15 capabilities — Slice 6 shipped the surface; settings UI
is exposure of what's already there, not new behavior.
- The §19.2 candidate set as a whole — the hardening pass folds in
the candidates that naturally cluster with hygiene timers, cache
rebuild, and deployment; the rest stay queued for post-v1
sessions.
The carryovers Slice 7 inherits — the existing §14 spec text, the
§17 endpoint set including Slice 6's settings endpoints, and the
React Router layout already in place.
The carryovers Slice 8 inherits — the full §11.5 / §12 spec text,
the existing `cache.Reconciler` and `DigestScheduler` shape, the
deploy/ infrastructure, and the 75/75 green test suite.
The next build session should read `SPEC.md`, `README.md`,
`docs/DEV.md`, and `SPEC.md`'s §19.1 and pick up Slice 7 cleanly
`docs/DEV.md`, and `SPEC.md`'s §19.1 and pick up Slice 8 cleanly
without re-briefing. The working agreement in §19.3 continues to
apply: implement the slice, correct the spec only where running
code reveals it was wrong at a structural level, accumulate new