* docs(PBI-58): add developer manual chapters under docs/manual/ Adds a 7-file English-language manual targeted at new human contributors: index, overview, statuses & transitions (with mermaid state diagrams), git workflow, MCP integration, docker, and troubleshooting. The manual is the *map* — it cross-references existing runbooks/ADRs/architecture docs rather than duplicating their content. Regenerates docs/INDEX.md and validates with check-doc-links.mjs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(PBI-58): add markdown rendering deps + manual:build script Adds mermaid, rehype-slug, rehype-autolink-headings for the in-app /manual page. Wires manual:build into prebuild so production builds always regenerate the chapter TOC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(PBI-58): codegen script for in-app manual TOC scripts/build-manual.mjs walks docs/manual/, parses YAML front-matter, strips it from the body, and emits lib/manual.generated.ts with a typed ManualEntry[] containing slug, title, description, filePath, and the embedded markdown body. Pure Node 20, mirrors generate-docs-index.mjs. Inlining the markdown at build time keeps runtime serverless functions free of filesystem reads, which avoids whole-project NFT tracing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(PBI-58): /manual route renders developer manual chapters in-app Catch-all route at app/(app)/manual/[[...slug]]/page.tsx with generateStaticParams covering every TOC entry. Server-side MarkdownView uses react-markdown with remark-gfm, rehype-slug, and rehype-autolink-headings; mermaid code blocks are routed to a client-only MermaidBlock that dynamic-imports mermaid on mount. ManualSidebar (client) reads the typed TOC and highlights the active chapter via usePathname. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(PBI-58): add Manual link to main nav bar Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
1.8 KiB
TypeScript
73 lines
1.8 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useId, useRef, useState } from 'react'
|
|
|
|
type Props = {
|
|
source: string
|
|
}
|
|
|
|
let mermaidPromise: Promise<typeof import('mermaid').default> | null = null
|
|
|
|
function loadMermaid() {
|
|
if (!mermaidPromise) {
|
|
mermaidPromise = import('mermaid').then((mod) => {
|
|
const mermaid = mod.default
|
|
mermaid.initialize({
|
|
startOnLoad: false,
|
|
theme: 'default',
|
|
securityLevel: 'strict',
|
|
fontFamily: 'inherit',
|
|
})
|
|
return mermaid
|
|
})
|
|
}
|
|
return mermaidPromise
|
|
}
|
|
|
|
export function MermaidBlock({ source }: Props) {
|
|
const id = useId().replace(/[^a-zA-Z0-9]/g, '')
|
|
const containerRef = useRef<HTMLDivElement | null>(null)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
loadMermaid()
|
|
.then(async (mermaid) => {
|
|
if (cancelled) return
|
|
try {
|
|
const { svg } = await mermaid.render(`mermaid-${id}`, source)
|
|
if (cancelled) return
|
|
if (containerRef.current) containerRef.current.innerHTML = svg
|
|
setError(null)
|
|
} catch (err) {
|
|
if (cancelled) return
|
|
setError(err instanceof Error ? err.message : String(err))
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
if (cancelled) return
|
|
setError(err instanceof Error ? err.message : String(err))
|
|
})
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [id, source])
|
|
|
|
if (error) {
|
|
return (
|
|
<pre className="overflow-x-auto rounded-md bg-muted p-3 text-xs text-destructive">
|
|
<code>
|
|
{`Mermaid render failed: ${error}\n\n${source}`}
|
|
</code>
|
|
</pre>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className="my-4 flex justify-center overflow-x-auto rounded-md bg-muted p-4 [&_svg]:max-w-full"
|
|
aria-label="Diagram"
|
|
/>
|
|
)
|
|
}
|