feat(docs): add a Diátaxis documentation section to the public site

A new public /docs area covering the whole platform, organized by Diátaxis:
tutorials (learning), how-to guides (tasks), reference (facts), and explanation
(the why). 29 pages, each grounded in the actual routes, actions, schema, and
UI — verified against the code and corrected for accuracy (e.g. admin-only vs
admin+board gating, where the permissions matrix actually lives, how Stripe
reconciliation matches).

Implementation:
- lib/docs/nav.ts is the single source of truth (categories + pages), driving
  the sidebar, static params, and prev/next.
- Content authored as content/docs/<category>/<slug>.md, read at build time
  (the routes are statically generated), rendered by <DocsMarkdown> — reuses the
  existing react-markdown/GFM stack, no new dependencies.
- (docs) route group with its own theme (docs.css) matching the public site's
  dark / monospace / lime aesthetic; sidebar with active-state, breadcrumb,
  prev/next, and a Diátaxis home. Public — signed-in users can read it too.
- "Docs" link added to the landing nav.

typecheck 0 · lint 0 errors · build ok (all /docs pages prerendered)
This commit is contained in:
Moheeb Zara
2026-08-05 00:42:42 -07:00
parent fc6adaf16e
commit 16229ae523
38 changed files with 2928 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { makeHeadingSlugger, isInDocumentHref } from '@/lib/markdown-anchors'
// Docs-brand markdown renderer. Unlike components/markdown.tsx (which is bound
// to the management app's theme tokens), this emits headings with stable anchor
// ids and otherwise leans on the `.docs-prose` stylesheet in docs.css so the
// output matches the public site's dark / monospace / lime aesthetic.
//
// Content here is author-trusted (checked-in .md), but we still never enable
// rehypeRaw, so any stray HTML stays inert.
export function DocsMarkdown({ content }: { content: string }) {
const slug = makeHeadingSlugger()
return (
<div className="docs-prose">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({ children }) => <h1 id={slug(children)}>{children}</h1>,
h2: ({ children }) => <h2 id={slug(children)}>{children}</h2>,
h3: ({ children }) => <h3 id={slug(children)}>{children}</h3>,
h4: ({ children }) => <h4 id={slug(children)}>{children}</h4>,
a: ({ href, children }) =>
isInDocumentHref(href) ? (
<a href={href}>{children}</a>
) : (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
),
}}
>
{content}
</ReactMarkdown>
</div>
)
}
+33
View File
@@ -0,0 +1,33 @@
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { DOC_CATEGORIES } from '@/lib/docs/nav'
export function DocsSidebar() {
const pathname = usePathname()
return (
<aside className="docs-sidebar" aria-label="Docs navigation">
{DOC_CATEGORIES.map(cat => (
<div key={cat.id} className="docs-sidebar-group">
<p className="docs-sidebar-group-label">{cat.label}</p>
{cat.pages.map(page => {
const href = `/docs/${cat.id}/${page.slug}`
const isActive = pathname === href
return (
<Link
key={page.slug}
href={href}
className={`docs-sidebar-link${isActive ? ' is-active' : ''}`}
aria-current={isActive ? 'page' : undefined}
>
{page.title}
</Link>
)
})}
</div>
))}
</aside>
)
}