React-first graph viewer for Epiphany typed graph state.
The live package is web/norn-viewer. It is what clients and agents
should reach for when they want to render architecture, dataflow, and typed
cross-links in a UI.
The older .NET generator, MSAGL SVG renderer, and MCP server still exist as support tools for note exports and legacy inspection. They are not competing client surfaces. Do not make consumers choose between engines like this repo is a sad little trade show booth.
cd E:\Projects\Norn\web\norn-viewer
npm install
npm run devimport { NornViewer } from "@gamecult/norn-viewer";
import type { NornGraphsState } from "@gamecult/norn-viewer";
export function GraphScreen({ state }: { state: NornGraphsState }) {
return (
<NornViewer
state={state}
layoutMode="combined-force"
motion={{ strength: 1.05, flow: 1.1, orbit: 0.85 }}
performance="fast"
/>
);
}The public viewer API is intentionally coarse:
layoutMode:layered,stress,force, orcombined-forcemotion: viewer-owned force tuning for Rust-solver combined-force layoutsperformance:quality,balanced,fast, or explicitsimulationBudgetMsrealtime knobsselection,focusSelection,expandedNode, and callbacks for app integration- typed state and event payloads
Norn owns graph layout, viewport motion, selection, focus, inspection, and the interaction boundary of expanded node articles. Consumers own graph meaning and article content. When an expanded article fills a node surface, Norn derives wheel and drag behavior from that surface's committed article geometry; consumers do not need product-specific classes or data attributes to make the article scrollable.
The support generator:
- scans markdown notes under a vault folder
- resolves Obsidian wikilinks and regular markdown note links
- optionally starts from an entry note and keeps only reachable notes
- classifies notes into
source-treeandcontrol-flowgraph families - lays out each family as its own graph partition
- reports cross-links that still jump between those families
- runs Microsoft Automatic Graph Layout
- writes:
manifest.jsonsource-tree.svgsource-tree.msagl.svgsource-tree.rich.svgsource-tree.jsoncontrol-flow.svgcontrol-flow.msagl.svgcontrol-flow.rich.svgcontrol-flow.jsoncross-links.jsonwarnings.txt
Run from source:
dotnet run --project . -- `
--vault-root "E:\Projects\Aetheria-Economy\Aetheria\Source Tree Map" `
--entry-note "Source Tree Map" `
--renderer rich `
--output-dir ".\out\source-tree-map"After packing as a .NET tool:
norn `
--vault-root "E:\Projects\Aetheria-Economy\Aetheria\Source Tree Map" `
--entry-note "Source Tree Map" `
--renderer rich `
--output-dir ".\out\source-tree-map"The MCP server exposes one tool:
generate_obsidian_graph_layout
Use this in an MCP client config when you want to launch the server straight from the repo:
{
"mcpServers": {
"norn": {
"command": "dotnet",
"args": [
"run",
"--project",
"E:\\Projects\\Norn\\Norn.csproj",
"--",
"--mcp"
]
}
}
}Build the package:
dotnet pack . -c Release -o .\nupkgInstall it from the local package output:
dotnet tool install --global --add-source .\nupkg NornThen point your MCP client at the installed command:
{
"mcpServers": {
"norn": {
"command": "norn",
"args": ["--mcp"]
}
}
}vaultRoot: path to the Obsidian vault folderentryNote: optional entry note name or relative note pathoutputDir: optional output directory, defaults to./outlayout:sugiyamaormdsrenderer:msaglorrichincludeUnreachable: keep notes that are not reachable from the entry note
The tool writes manifest.json, primary partition SVGs, raw .msagl.svg variants, custom .rich.svg variants, partition JSON files, cross-links.json, and warnings.txt, then returns:
- output paths for each partition
- seed note IDs used for traversal
- total note and edge counts
- per-partition node and edge counts
- warnings
- the resolved entry note ID, if one was used
By default, notes whose basename contains Control Flow land in the control-flow partition, and everything else lands in source-tree.
You can override that with frontmatter on a note:
---
norn-family: control-flow
---or:
---
norn-families:
- source-tree
- control-flow
---Supported keys:
norn-familynorn-familiesgraph-familygraph-families
If your chosen entryNote is itself one of the explicit family roots, the generator also seeds traversal from any sibling family roots it can find.
Source Tree Mapwill also pull inControl Flow MapControl Flow Mapwill also pull inSource Tree Map
That keeps structurally separated note graphs from vanishing just because they no longer link to each other directly.
sugiyama: layered, better for hierarchy-heavy note treesmds: force-ish spread, better when the graph is cross-linked chaos
mds is the closest MSAGL-native approximation to the Obsidian or Kamada-Kawai vibe. It is not a named Kamada-Kawai mode.
msagl: the stock MSAGL SVG, useful as the plain baselinerich: a custom SVG renderer that consumes MSAGL graph geometry and semantic metadata, then draws something with more visual hierarchy and less beige despair
The web-side viewer package lives under web/norn-viewer/.
It consumes the typed Epiphany graph state shape directly:
graphs.architecturegraphs.dataflowgraphs.links
and packages:
- Rust hybrid solver layout through bundled WASM
- zoom and pan
- zoom-gated label and detail pop-in
- node, edge, and code-ref inspection
- typed cross-link browsing between architecture and dataflow
- combined-force motion tuning for clients that want the lively overview
Run it locally with:
cd E:\Projects\Norn\web\norn-viewer
npm install
npm run dev- unresolved and ambiguous links are written to
warnings.txt - node IDs in the partition
.jsonfiles are relative note paths without.md - the current implementation estimates label sizes instead of using a platform text measurer, because this is a generator, not a cathedral
source-tree.svgandcontrol-flow.svgare the primary outputs for the selected renderer- raw MSAGL output is always preserved as
*.msagl.svg - the custom renderer currently emits
*.rich.svgfrom MSAGL geometry, not from post-processing the stock SVG