v0.19.0 frontend: /docs/* route tree + flyout nav + sessions browser

Reorganizes the /docs surface from a single DOCS.md route into a hub
with a left-side flyout nav and three new sub-routes for the on-site
sessions browser (roadmap item #30):

  /docs                      → redirect to /docs/user-guide
  /docs/user-guide           → existing DOCS.md content
  /docs/sessions             → redirect to /docs/sessions/about
  /docs/sessions/about       → README.md of ohm-session-history
  /docs/sessions/<NNNN>      → per-session transcript index
  /docs/sessions/<NNNN>/<f>  → per-transcript view

The flyout is a persistent left sidebar on desktop and a slide-out
drawer on mobile (toggled by a ☰ button in the docs header). Its
session list is driven by the /api/docs/sessions/manifest fetch —
loading shows a skeleton; 502 shows an inline retry; empty manifest
shows only the "About" row.

Each sub-route owns its own empty-state / error handling:
  - 404 transcripts render "This transcript isn't published yet"
    with a link back to the parent session index, no JS crash.
  - 502 (gitea unreachable) renders a retry button.
  - Manifest 404 is mapped to {} server-side so the flyout renders
    cleanly with no error banner when no sessions are published yet.

Analytics (per SPEC §21):
  - new EVENTS.DOC_VIEWED ("Doc Viewed") fires on each sub-route
    mount with `section`: 'user-guide' | 'sessions/about' |
    'sessions/<NNNN>' | 'sessions/<NNNN>/<filename>'.
  - every interactive nav element carries aria-label +
    data-amp-track-name so autocapture rows are readable.

The existing v0.14.0 Docs.jsx component is dropped — its content
moved verbatim into DocsUserGuide.jsx; the new layout subsumes the
back-button + signed-out home affordances it used to carry. All
four new sub-routes reuse the existing MarkdownPreview renderer
(marked + mermaid lazy-load) so no second markdown library lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-28 09:07:20 -07:00
parent 39e57706d9
commit 822f4266f6
11 changed files with 864 additions and 56 deletions
+29 -4
View File
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import { Routes, Route, Link, useLocation, useNavigate } from 'react-router-dom'
import { Routes, Route, Link, Navigate, useLocation, useNavigate } from 'react-router-dom'
import { getMe, subscribeToNotifications } from './api'
import { anonymize, EVENTS, identify, track } from './lib/analytics'
import Catalog from './components/Catalog.jsx'
@@ -12,7 +12,11 @@ import Landing from './components/Landing.jsx'
import Login from './components/Login.jsx'
import BetaPending from './components/BetaPending.jsx'
import Philosophy from './components/Philosophy.jsx'
import Docs from './components/Docs.jsx'
import DocsLayout from './components/DocsLayout.jsx'
import DocsUserGuide from './components/DocsUserGuide.jsx'
import DocsSessionsAbout from './components/DocsSessionsAbout.jsx'
import DocsSessionIndex from './components/DocsSessionIndex.jsx'
import DocsSessionTranscript from './components/DocsSessionTranscript.jsx'
import NotificationSettings from './components/NotificationSettings.jsx'
import Admin from './components/Admin.jsx'
import AcceptInvitation from './components/AcceptInvitation.jsx'
@@ -233,7 +237,13 @@ export default function App() {
itself establishes the session on success. */}
<Route path="/invites/claim" element={<InviteClaim />} />
<Route path="/philosophy" element={<PhilosophyWithSidebar viewer={viewer} />} />
<Route path="/docs" element={<DocsWithSidebar viewer={viewer} />} />
{/* v0.19.0 / roadmap item #30 — /docs/* is a hub with sub-nav.
The bare /docs path redirects to the user guide; sessions
browser lives at /docs/sessions/*. See DocsLayout.jsx
for the flyout shape and CHANGELOG v0.19.0 for the
upgrade path. */}
<Route path="/docs" element={<Navigate to="/docs/user-guide" replace />} />
<Route path="/docs/*" element={<DocsWithSidebar viewer={viewer} />} />
{/* §14.5 / §14.6: cookie-consent companions to /philosophy.
Available to anonymous and authenticated viewers alike. */}
<Route path="/privacy" element={<PolicyShell><Privacy /></PolicyShell>} />
@@ -303,9 +313,24 @@ function PhilosophyWithSidebar({ viewer }) {
}
function DocsWithSidebar({ viewer }) {
// v0.19.0 / roadmap item #30 — the `/docs/*` surface is a flyout
// shell with sub-routes. The shell (sidebar + content area) is the
// DocsLayout outlet host; the sub-routes mount their respective
// pages into the outlet. Bare `/docs/sessions` redirects to the
// sessions about page so deep-linkers and the flyout's "Sessions"
// header both land somewhere coherent.
return (
<main className="chrome-pane">
<Docs authenticated={!!viewer} />
<Routes>
<Route element={<DocsLayout authenticated={!!viewer} />}>
<Route index element={<Navigate to="user-guide" replace />} />
<Route path="user-guide" element={<DocsUserGuide />} />
<Route path="sessions" element={<Navigate to="about" replace />} />
<Route path="sessions/about" element={<DocsSessionsAbout />} />
<Route path="sessions/:nnnn" element={<DocsSessionIndex />} />
<Route path="sessions/:nnnn/:filename" element={<DocsSessionTranscript />} />
</Route>
</Routes>
</main>
)
}