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
+62
View File
@@ -478,6 +478,68 @@ export async function advanceChatSeen(slug, branch, lastSeenMessageId) {
}))
}
// ---------------------------------------------------------------------------
// §14.2 / Slice 7: PHILOSOPHY.md surface
// ---------------------------------------------------------------------------
export async function getPhilosophy() {
return jsonOrThrow(await fetch('/api/philosophy'))
}
// ---------------------------------------------------------------------------
// Slice 7: admin neighborhood (§17 admin/* + user search for the §15.8 mute
// typeahead).
// ---------------------------------------------------------------------------
export async function listAdminUsers() {
return jsonOrThrow(await fetch('/api/admin/users'))
}
export async function setUserRole(userId, role) {
return jsonOrThrow(await fetch(`/api/admin/users/${userId}/role`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ role }),
}))
}
export async function setUserMute(userId, muted) {
return jsonOrThrow(await fetch(`/api/admin/users/${userId}/mute`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ muted }),
}))
}
export async function listAuditLog({ actionKind, actorUserId, rfcSlug, beforeId, limit } = {}) {
const params = new URLSearchParams()
if (actionKind) params.set('action_kind', actionKind)
if (actorUserId != null) params.set('actor_user_id', actorUserId)
if (rfcSlug) params.set('rfc_slug', rfcSlug)
if (beforeId != null) params.set('before_id', beforeId)
if (limit != null) params.set('limit', limit)
const qs = params.toString()
return jsonOrThrow(await fetch(`/api/admin/audit${qs ? `?${qs}` : ''}`))
}
export async function listPermissionEvents({ beforeId, limit } = {}) {
const params = new URLSearchParams()
if (beforeId != null) params.set('before_id', beforeId)
if (limit != null) params.set('limit', limit)
const qs = params.toString()
return jsonOrThrow(await fetch(`/api/admin/permission-events${qs ? `?${qs}` : ''}`))
}
export async function listGraduationQueue() {
return jsonOrThrow(await fetch('/api/admin/graduation-queue'))
}
export async function searchUsers(q) {
const params = new URLSearchParams()
if (q) params.set('q', q)
return jsonOrThrow(await fetch(`/api/users/search?${params}`))
}
// SSE subscription helper. Returns a close() function. The handler
// surface mirrors §15.3: a snapshot event on open, then per-notification
// `notification` events, plus `read` events when another tab marks a row.