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:
+79
-17
@@ -8,6 +8,9 @@ import PRView from './components/PRView.jsx'
|
||||
import ProposalView from './components/ProposalView.jsx'
|
||||
import ProposeModal from './components/ProposeModal.jsx'
|
||||
import Landing from './components/Landing.jsx'
|
||||
import Philosophy from './components/Philosophy.jsx'
|
||||
import NotificationSettings from './components/NotificationSettings.jsx'
|
||||
import Admin from './components/Admin.jsx'
|
||||
import ToastHost, { showToast } from './components/ToastHost.jsx'
|
||||
import './App.css'
|
||||
|
||||
@@ -65,8 +68,16 @@ export default function App() {
|
||||
return <div className="boot">Loading…</div>
|
||||
}
|
||||
|
||||
// §14.2: the philosophy route is reachable by anonymous visitors too.
|
||||
// Resolve it before the authentication gate so a signed-out reader
|
||||
// who follows the §14.1 landing link does not get bounced to sign-in.
|
||||
if (!me?.authenticated) {
|
||||
return <Landing />
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/philosophy" element={<Philosophy authenticated={false} />} />
|
||||
<Route path="*" element={<Landing />} />
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -76,6 +87,21 @@ export default function App() {
|
||||
<Link to="/">Wiggleverse RFCs</Link>
|
||||
</div>
|
||||
<div className="header-right">
|
||||
{/* §14.3: the persistent About link. One word, no badge, no
|
||||
state — visible from every authenticated screen so a
|
||||
contributor mid-PR who wonders why a conversation is
|
||||
public can reach the answer in two clicks. */}
|
||||
<Link to="/philosophy" className="header-about" title="Why this exists (§14)">
|
||||
About
|
||||
</Link>
|
||||
<Link to="/settings/notifications" className="header-settings" title="Notification settings (§15)">
|
||||
Settings
|
||||
</Link>
|
||||
{(me.user.role === 'owner' || me.user.role === 'admin') && (
|
||||
<Link to="/admin" className="header-admin" title="Admin home base">
|
||||
Admin
|
||||
</Link>
|
||||
)}
|
||||
<button
|
||||
className="inbox-trigger"
|
||||
onClick={() => setInboxOpen(o => !o)}
|
||||
@@ -92,18 +118,27 @@ export default function App() {
|
||||
</div>
|
||||
</header>
|
||||
<div className="app-body">
|
||||
<Catalog
|
||||
onProposeRFC={() => setProposeOpen(true)}
|
||||
version={catalogVersion}
|
||||
/>
|
||||
<main className="main-pane">
|
||||
<Routes>
|
||||
<Route path="/" element={<Welcome viewer={me.user} />} />
|
||||
<Route path="/rfc/:slug" element={<RFCView viewer={me.user} />} />
|
||||
<Route path="/rfc/:slug/pr/:prNumber" element={<PRView viewer={me.user} />} />
|
||||
<Route path="/proposals/:prNumber" element={<ProposalView viewer={me.user} onChange={() => setCatalogVersion(v => v + 1)} />} />
|
||||
</Routes>
|
||||
</main>
|
||||
<Routes>
|
||||
<Route path="/philosophy" element={<PhilosophyWithSidebar viewer={me.user} />} />
|
||||
<Route path="/settings/notifications" element={<NotificationSettingsWithSidebar viewer={me.user} />} />
|
||||
<Route path="/admin/*" element={<AdminWithSidebar viewer={me.user} />} />
|
||||
<Route path="*" element={
|
||||
<>
|
||||
<Catalog
|
||||
onProposeRFC={() => setProposeOpen(true)}
|
||||
version={catalogVersion}
|
||||
/>
|
||||
<main className="main-pane">
|
||||
<Routes>
|
||||
<Route path="/" element={<Welcome viewer={me.user} />} />
|
||||
<Route path="/rfc/:slug" element={<RFCView viewer={me.user} />} />
|
||||
<Route path="/rfc/:slug/pr/:prNumber" element={<PRView viewer={me.user} />} />
|
||||
<Route path="/proposals/:prNumber" element={<ProposalView viewer={me.user} onChange={() => setCatalogVersion(v => v + 1)} />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</>
|
||||
} />
|
||||
</Routes>
|
||||
</div>
|
||||
{proposeOpen && (
|
||||
<ProposeModal
|
||||
@@ -123,6 +158,34 @@ export default function App() {
|
||||
)
|
||||
}
|
||||
|
||||
function PhilosophyWithSidebar() {
|
||||
// The chrome surfaces (§14.2 philosophy, §15 settings, §6/§17 admin)
|
||||
// all use the full app body — no catalog left pane, no propose modal.
|
||||
// The header carries the navigation back; the body is a single
|
||||
// reading surface.
|
||||
return (
|
||||
<main className="chrome-pane">
|
||||
<Philosophy authenticated={true} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function NotificationSettingsWithSidebar({ viewer }) {
|
||||
return (
|
||||
<main className="chrome-pane">
|
||||
<NotificationSettings viewer={viewer} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function AdminWithSidebar({ viewer }) {
|
||||
return (
|
||||
<main className="chrome-pane">
|
||||
<Admin viewer={viewer} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function Welcome({ viewer }) {
|
||||
return (
|
||||
<div className="welcome">
|
||||
@@ -134,10 +197,9 @@ function Welcome({ viewer }) {
|
||||
idea PR against the meta repository.
|
||||
</p>
|
||||
<p>
|
||||
Slice 1 of the build is in place: propose → idea PR → owner merges →
|
||||
super-draft appears in the catalog → super-draft view renders. The
|
||||
revision flow, per-branch chat, AI participation, and the PR surface
|
||||
land in subsequent slices.
|
||||
Wondering why a conversation is public, why graduation costs what it
|
||||
does, or why the model is in the chat? <Link to="/philosophy">Read the
|
||||
philosophy</Link>.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user