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
+273
View File
@@ -1218,3 +1218,276 @@
.toast.cat-personal-direct { border-left-color: #d97706; }
.toast.cat-structural { border-left-color: #2563eb; }
.toast.cat-churn { border-left-color: #6b7280; }
/* ── Slice 7: §14 chrome + /settings/notifications + /admin ──────────── */
/* Header chrome — the persistent §14.3 About link plus the Settings and
Admin entrypoints. The header's job is to be invisible until the user
reaches for it, so these read as quiet text links rather than buttons. */
.header-about, .header-settings, .header-admin {
color: #ddd; text-decoration: none;
font-size: 12px; letter-spacing: 0.02em;
padding: 4px 8px; border-radius: 4px;
}
.header-about:hover, .header-settings:hover, .header-admin:hover {
color: #fff; background: rgba(255,255,255,0.08);
}
.header-admin {
color: #fbbf24; /* admin link sits a notch warmer to signal authority */
}
/* /philosophy — the §14.2 read surface. The body inherits the
prototype's markdown styling; the header is a thin chrome strip. */
.chrome-pane {
flex: 1; min-width: 0; overflow: auto;
padding: 0; background: #fff;
}
.philosophy-page {
max-width: 760px; margin: 0 auto;
padding: 24px 32px 64px;
}
.philosophy-header {
display: flex; align-items: center; gap: 12px;
padding: 12px 0 18px;
border-bottom: 1px solid #f0f0ee;
margin-bottom: 28px;
}
.philosophy-back {
border: none; background: none; cursor: pointer;
color: #4b5563; font-size: 13px;
padding: 4px 8px; border-radius: 4px;
}
.philosophy-back:hover { background: #f3f4f6; color: #111; }
.philosophy-title {
font-size: 13px; color: #6b7280;
text-transform: uppercase; letter-spacing: 0.08em;
}
.philosophy-signin {
margin-left: auto;
font-size: 13px; color: #4b5563; text-decoration: none;
}
.philosophy-signin:hover { color: #111; text-decoration: underline; }
.philosophy-body h1 {
font-size: 28px; font-weight: 700; margin: 0 0 24px;
letter-spacing: -0.01em;
}
.philosophy-body h2 {
font-size: 19px; font-weight: 600; margin: 36px 0 12px;
color: #111;
}
.philosophy-body h3 {
font-size: 15px; font-weight: 600; margin: 24px 0 10px;
}
.philosophy-body p {
margin: 0 0 16px; line-height: 1.65; font-size: 15px; color: #1f2937;
}
.philosophy-body em { color: #4b5563; font-style: italic; }
.philosophy-body strong { color: #111; }
.philosophy-body ul, .philosophy-body ol {
margin: 0 0 20px; padding-left: 24px;
}
.philosophy-body li { margin-bottom: 8px; line-height: 1.6; }
.philosophy-body hr {
border: none; border-top: 1px solid #e5e7eb; margin: 32px 0;
}
.philosophy-body code {
background: #f3f4f6; padding: 1px 5px; border-radius: 3px;
font-size: 0.92em;
}
/* Richer landing page (§14.1) — adds the three-item deck under the
pitch. The .landing container's flex centering stays from the
prototype; the new content lives inside .landing-inner. */
.landing-inner {
max-width: 620px;
display: flex; flex-direction: column; align-items: center;
}
.landing-deck {
list-style: none; padding: 0;
margin: 48px 0 0; text-align: left;
display: flex; flex-direction: column; gap: 18px;
border-top: 1px solid #e5e7eb; padding-top: 32px;
max-width: 540px;
}
.landing-deck li {
font-size: 14px; line-height: 1.6; color: #374151;
}
.landing-deck strong { color: #111; }
/* /settings/notifications */
.settings-page {
max-width: 720px; margin: 0 auto;
padding: 24px 32px 64px;
}
.settings-header h1 {
margin: 0 0 6px; font-size: 24px; font-weight: 700; letter-spacing: -0.01em;
}
.settings-sub {
color: #6b7280; font-size: 13px; margin: 0 0 24px;
line-height: 1.5;
}
.settings-section {
border: 1px solid #e5e7eb; border-radius: 8px;
padding: 20px 24px; margin-bottom: 16px; background: #fff;
}
.settings-section h2 {
margin: 0 0 4px; font-size: 15px; font-weight: 600;
}
.settings-section-body { display: flex; flex-direction: column; gap: 12px; }
.settings-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.quiet-hours-row label {
display: flex; flex-direction: column; gap: 4px;
font-size: 12px; color: #6b7280;
}
.quiet-hours-row input, .quiet-hours-row select {
border: 1px solid #d1d5db; border-radius: 6px;
padding: 6px 10px; font-size: 13px;
background: white; color: #111;
}
.settings-note { font-size: 12px; color: #6b7280; margin: 0; }
.settings-note.warning { color: #b91c1c; }
.toggle-row {
display: flex; align-items: flex-start; gap: 12px;
cursor: pointer; padding: 10px 0;
border-top: 1px solid #f3f4f6;
}
.toggle-row:first-child { border-top: none; padding-top: 0; }
.toggle-row.disabled { cursor: not-allowed; opacity: 0.6; }
.toggle-row input[type=checkbox] { margin-top: 3px; }
.toggle-text { display: flex; flex-direction: column; gap: 2px; }
.toggle-label { font-size: 14px; font-weight: 500; color: #111; }
.toggle-desc { font-size: 12px; color: #6b7280; line-height: 1.5; }
.btn-primary {
background: #111; color: #fff; border: none;
padding: 7px 14px; border-radius: 6px; font-size: 13px; cursor: pointer;
}
.btn-primary:hover { background: #333; }
.btn-primary:disabled { background: #9ca3af; cursor: not-allowed; }
.btn-link-muted {
background: none; border: none; cursor: pointer;
color: #6b7280; font-size: 12px; padding: 4px 6px;
}
.btn-link-muted:hover { color: #111; text-decoration: underline; }
.settings-table, .admin-table {
width: 100%; border-collapse: collapse;
font-size: 13px;
}
.settings-table th, .admin-table th {
text-align: left; padding: 6px 8px;
font-size: 11px; text-transform: uppercase;
color: #6b7280; letter-spacing: 0.05em; font-weight: 600;
border-bottom: 1px solid #e5e7eb;
}
.settings-table td, .admin-table td {
padding: 8px 8px; border-bottom: 1px solid #f3f4f6;
}
.settings-table select { font-size: 13px; padding: 3px 6px; }
.set-by {
font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em;
padding: 2px 6px; border-radius: 4px; font-weight: 600;
}
.set-by-auto { background: #f3f4f6; color: #6b7280; }
.set-by-explicit { background: #dbeafe; color: #1e40af; }
.mutes-list { list-style: none; padding: 0; margin: 8px 0 0; }
.mutes-row {
display: flex; align-items: center; gap: 10px;
padding: 6px 8px; border-bottom: 1px solid #f3f4f6;
font-size: 13px;
}
.mute-handle { font-weight: 500; color: #111; }
.mute-when { font-size: 11px; margin-left: auto; }
.mute-typeahead { position: relative; }
.mute-typeahead input {
width: 100%; box-sizing: border-box;
border: 1px solid #d1d5db; border-radius: 6px;
padding: 7px 10px; font-size: 13px; outline: none;
}
.mute-typeahead input:focus { border-color: #111; }
.mute-typeahead-results {
position: absolute; top: 100%; left: 0; right: 0;
background: white; border: 1px solid #d1d5db; border-radius: 6px;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
margin-top: 4px; padding: 4px 0; z-index: 10;
list-style: none;
}
.mute-typeahead-results button {
display: flex; gap: 10px; align-items: center;
width: 100%; padding: 6px 10px;
background: none; border: none; text-align: left; cursor: pointer;
font-size: 13px;
}
.mute-typeahead-results button:hover { background: #f9fafb; }
/* /admin */
.admin-page {
display: flex; height: 100%;
width: 100%;
}
.admin-rail {
width: 220px; flex-shrink: 0;
background: #f9fafb; border-right: 1px solid #e5e7eb;
padding: 24px 16px;
}
.admin-rail h2 {
font-size: 13px; color: #6b7280;
text-transform: uppercase; letter-spacing: 0.08em;
margin: 0 0 12px;
}
.admin-rail ul { list-style: none; padding: 0; margin: 0 0 24px; }
.admin-rail li { margin-bottom: 2px; }
.admin-rail-link {
display: block; padding: 6px 10px;
font-size: 13px; color: #374151; text-decoration: none;
border-radius: 4px;
}
.admin-rail-link:hover { background: #f3f4f6; }
.admin-rail-link.active { background: #111; color: #fff; }
.admin-rail-note {
font-size: 11px; color: #6b7280; line-height: 1.5;
}
.admin-content {
flex: 1; min-width: 0;
padding: 28px 36px;
overflow: auto;
}
.admin-tab-header h2 {
margin: 0 0 4px; font-size: 18px; font-weight: 700;
}
.admin-tab-header p { margin: 0 0 24px; font-size: 13px; }
.admin-section-h {
font-size: 13px; text-transform: uppercase;
letter-spacing: 0.05em; color: #6b7280;
margin: 24px 0 8px;
}
.audit-filters {
display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap;
}
.audit-filters input, .audit-filters select {
border: 1px solid #d1d5db; border-radius: 6px;
padding: 5px 9px; font-size: 13px; background: white;
}
.audit-table code {
font-size: 11px; background: #f3f4f6;
padding: 1px 5px; border-radius: 3px;
}
.user-cell { display: flex; flex-direction: column; gap: 1px; }
.user-handle { font-weight: 500; color: #111; }
.mute-toggle {
display: inline-flex; align-items: center; gap: 6px;
font-size: 13px; cursor: pointer;
}
.grad-queue { list-style: none; padding: 0; margin: 8px 0 24px; }
.grad-queue li { padding: 8px 0; border-bottom: 1px solid #f3f4f6; }
.grad-queue-link { color: #111; text-decoration: none; font-size: 14px; }
.grad-queue-link:hover strong { text-decoration: underline; }
.muted { color: #6b7280; }
.error { color: #b91c1c; }