Rename EpiphanyGraph to Norn

This commit is contained in:
Meta Vens 2026-05-25 13:51:36 +01:00
commit 14852a5ee1
37 changed files with 199 additions and 199 deletions

View file

@ -1,4 +1,4 @@
# Repo Face State
This folder stores birth and continuity state for `EpiphanyGraph`.
This folder stores birth and continuity state for `Norn`.
Do not treat these files as arbitrary editable projections. Persistent memory should cross typed operation boundaries.

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
# Repo Face Voice
This folder belongs to the Discord-facing Face for `EpiphanyGraph`.
This folder belongs to the Discord-facing Face for `Norn`.
Discord roles address the Face; VoidBot's webhook persona pipe is the mouth.

View file

@ -1,14 +1,14 @@
{
"schemaVersion": "voidbot.repo_face_identity.v0",
"identityId": "norn",
"repoName": "EpiphanyGraph",
"repoName": "Norn",
"displayName": "Norn",
"repoPath": "E:\\Projects\\EpiphanyGraph",
"repoPath": "E:\\Projects\\Norn",
"createdOrRefreshedAt": "2026-05-25T07:40:05.315Z",
"summary": "Graph-fate and typed-link Face for EpiphanyGraph, now branded as Norn.",
"summary": "Graph-fate and typed-link Face for Norn, now branded as Norn.",
"voice": "Quiet, exacting, dry, and mythic without becoming fog. Norn speaks like a fate-weaver who keeps one hand in the well and the other on the layout JSON: she notices strands, crossings, missing anchors, false prophecy, and visual claims that cannot survive inspection.",
"priorities": [
"Steward EpiphanyGraph as the viewer for typed graph state, especially architecture, dataflow, and explicit cross-links.",
"Steward Norn as the viewer for typed graph state, especially architecture, dataflow, and explicit cross-links.",
"Protect the invariant that visual structure is a guide, while typed JSON, code refs, and validation are the receipt.",
"Advocate for graph views that reveal clusters, bridges, flow, focus, and state transitions without turning the UI into decorative thread soup.",
"Keep the legacy .NET/MSAGL/MCP generator in its support-tool lane while the React viewer remains the primary client surface.",

View file

@ -1,6 +1,6 @@
# Model Friendliness Rendering Tuner
This tuner guides experiments that make EpiphanyGraph output easier for both humans and vision-capable agents to understand.
This tuner guides experiments that make Norn output easier for both humans and vision-capable agents to understand.
The point is not to make a prettier hairball. The point is to render graphs in ways that expose useful structure quickly, with enough machine-readable backing data to verify what the eyes think they saw.
@ -214,7 +214,7 @@ is how a dashboard learns to lie with posture.
The current smoke path is:
```powershell
cd .\web\epiphany-graph-viewer
cd .\web\norn-viewer
npm run tune:simulation
```

View file

@ -6,8 +6,8 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackAsTool>true</PackAsTool>
<ToolCommandName>epiphanygraph</ToolCommandName>
<PackageId>EpiphanyGraph</PackageId>
<ToolCommandName>norn</ToolCommandName>
<PackageId>Norn</PackageId>
<Version>0.1.0</Version>
<Description>Generate Obsidian note layouts with MSAGL and expose the generator as an MCP tool.</Description>
<PackageTags>obsidian;graph;msagl;mcp</PackageTags>

View file

@ -40,7 +40,7 @@ static class McpServerMode
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithTools<EpiphanyGraphMcpTools>();
.WithTools<NornMcpTools>();
await builder.Build().RunAsync();
return 0;
@ -99,13 +99,13 @@ static class ProgramEntry
{
Console.WriteLine("""
Usage:
epiphanygraph --vault-root <path> [--output-dir <path>] [--entry-note <name>] [--layout sugiyama|mds] [--renderer msagl|rich] [--include-unreachable]
epiphanygraph --mcp
norn --vault-root <path> [--output-dir <path>] [--entry-note <name>] [--layout sugiyama|mds] [--renderer msagl|rich] [--include-unreachable]
norn --mcp
Examples:
epiphanygraph --vault-root "E:\Projects\Aetheria-Economy\Aetheria\Source Tree Map" --entry-note "Source Tree Map"
epiphanygraph --vault-root "E:\Projects\Aetheria-Economy\Aetheria\Source Tree Map" --entry-note "Source Tree Map" --layout mds --renderer rich --output-dir ".\out\mds"
epiphanygraph --mcp
norn --vault-root "E:\Projects\Aetheria-Economy\Aetheria\Source Tree Map" --entry-note "Source Tree Map"
norn --vault-root "E:\Projects\Aetheria-Economy\Aetheria\Source Tree Map" --entry-note "Source Tree Map" --layout mds --renderer rich --output-dir ".\out\mds"
norn --mcp
Outputs:
manifest.json overall run summary plus partition outputs
@ -410,7 +410,7 @@ static class RendererModeExtensions
}
[McpServerToolType]
sealed class EpiphanyGraphMcpTools
sealed class NornMcpTools
{
[McpServerTool(
Name = "generate_obsidian_graph_layout",
@ -1408,8 +1408,8 @@ static class FrontMatterGraphFamilyParser
{
private static readonly string[] FamilyKeys =
[
"epiphany-graph-family",
"epiphany-graph-families",
"norn-family",
"norn-families",
"graph-family",
"graph-families",
];

View file

@ -1,8 +1,8 @@
# EpiphanyGraph
# Norn
React-first graph viewer for Epiphany typed graph state.
The live package is `web/epiphany-graph-viewer`. It is what clients and agents
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.
@ -14,18 +14,18 @@ show booth.
## React Viewer
```powershell
cd E:\Projects\EpiphanyGraph\web\epiphany-graph-viewer
cd E:\Projects\Norn\web\norn-viewer
npm install
npm run dev
```
```tsx
import { EpiphanyGraphViewer } from "@epiphanygraph/epiphany-graph-viewer";
import type { EpiphanyGraphsState } from "@epiphanygraph/epiphany-graph-viewer";
import { NornViewer } from "@gamecult/norn-viewer";
import type { NornGraphsState } from "@gamecult/norn-viewer";
export function GraphScreen({ state }: { state: EpiphanyGraphsState }) {
export function GraphScreen({ state }: { state: NornGraphsState }) {
return (
<EpiphanyGraphViewer
<NornViewer
state={state}
layoutMode="combined-force"
motion={{ strength: 1.05, flow: 1.1, orbit: 0.85 }}
@ -82,7 +82,7 @@ dotnet run --project . -- `
After packing as a .NET tool:
```powershell
epiphanygraph `
norn `
--vault-root "E:\Projects\Aetheria-Economy\Aetheria\Source Tree Map" `
--entry-note "Source Tree Map" `
--renderer rich `
@ -102,12 +102,12 @@ Use this in an MCP client config when you want to launch the server straight fro
```json
{
"mcpServers": {
"epiphanygraph": {
"norn": {
"command": "dotnet",
"args": [
"run",
"--project",
"E:\\Projects\\EpiphanyGraph\\EpiphanyGraph.csproj",
"E:\\Projects\\Norn\\Norn.csproj",
"--",
"--mcp"
]
@ -127,7 +127,7 @@ dotnet pack . -c Release -o .\nupkg
Install it from the local package output:
```powershell
dotnet tool install --global --add-source .\nupkg EpiphanyGraph
dotnet tool install --global --add-source .\nupkg Norn
```
Then point your MCP client at the installed command:
@ -135,8 +135,8 @@ Then point your MCP client at the installed command:
```json
{
"mcpServers": {
"epiphanygraph": {
"command": "epiphanygraph",
"norn": {
"command": "norn",
"args": ["--mcp"]
}
}
@ -171,7 +171,7 @@ You can override that with frontmatter on a note:
```yaml
---
epiphany-graph-family: control-flow
norn-family: control-flow
---
```
@ -179,7 +179,7 @@ or:
```yaml
---
epiphany-graph-families:
norn-families:
- source-tree
- control-flow
---
@ -187,8 +187,8 @@ epiphany-graph-families:
Supported keys:
- `epiphany-graph-family`
- `epiphany-graph-families`
- `norn-family`
- `norn-families`
- `graph-family`
- `graph-families`
@ -211,9 +211,9 @@ That keeps structurally separated note graphs from vanishing just because they n
- `msagl`: the stock MSAGL SVG, useful as the plain baseline
- `rich`: a custom SVG renderer that consumes MSAGL graph geometry and semantic metadata, then draws something with more visual hierarchy and less beige despair
## Support Tool: Epiphany Graph Viewer Demo
## Support Tool: Norn Viewer Demo
The web-side viewer package lives under `web/epiphany-graph-viewer/`.
The web-side viewer package lives under `web/norn-viewer/`.
It consumes the typed Epiphany graph state shape directly:
@ -233,7 +233,7 @@ and packages:
Run it locally with:
```powershell
cd E:\Projects\EpiphanyGraph\web\epiphany-graph-viewer
cd E:\Projects\Norn\web\norn-viewer
npm install
npm run dev
```

View file

@ -25,7 +25,7 @@ static class RichSvgRenderer
AppendDefs(document, theme);
document.AppendLine($""" <title id="title">{Escape(report.GraphTitle)} ({Escape(report.GraphKey)})</title>""");
document.AppendLine(
$""" <desc id="desc">EpiphanyGraph rich renderer output for {Escape(report.GraphKey)} using {Escape(report.LayoutMode)} layout with {report.NodeCount} nodes and {report.EdgeCount} edges.</desc>""");
$""" <desc id="desc">Norn rich renderer output for {Escape(report.GraphKey)} using {Escape(report.LayoutMode)} layout with {report.NodeCount} nodes and {report.EdgeCount} edges.</desc>""");
document.AppendLine($""" <rect x="0" y="0" width="{Fmt(canvas.Width)}" height="{Fmt(canvas.Height)}" fill="url(#bg-gradient)" />""");
document.AppendLine($""" <rect x="0" y="0" width="{Fmt(canvas.Width)}" height="{Fmt(canvas.Height)}" fill="url(#bg-vignette)" opacity="0.18" />""");
AppendGuides(document, report, rawNodes, projection, canvas, theme);

View file

@ -1,4 +1,4 @@
# EpiphanyGraph Benchmarks
# Norn Benchmarks
These benchmarks measure whether a rendered graph helps a vision-capable agent recover useful graph truth.
@ -47,7 +47,7 @@ The generated run contains:
- `run-manifest.json`: run metadata and candidate outputs
- `gold.json`: scoring key, kept out of per-candidate answer sheets
- `candidates/<candidate-id>/render/`: EpiphanyGraph outputs
- `candidates/<candidate-id>/render/`: Norn outputs
- `candidates/<candidate-id>/render/*.png`: raster previews for image probes
- `candidates/<candidate-id>/render/*.msagl.svg`: preserved stock baseline output
- `candidates/<candidate-id>/render/*.rich.svg`: preserved custom renderer output
@ -72,7 +72,7 @@ The useful benchmark result is the delta between these modes. If the image does
E:\Projects\Aetheria-Economy\Aetheria\Source Tree Map
```
It exercises the current EpiphanyGraph partition output:
It exercises the current Norn partition output:
- `source-tree.svg` and `source-tree.json`
- `control-flow.svg` and `control-flow.json`
@ -98,7 +98,7 @@ heuristic tuning.
Current smoke baseline:
```powershell
cd .\web\epiphany-graph-viewer
cd .\web\norn-viewer
npm run tune:simulation
```

View file

@ -16,7 +16,7 @@ Generated individually on 2026-05-25, then assembled into `.voidbot/voice/norn-c
## D Triune Fate Weaver
128px pixel art Discord avatar, square portrait for Norn, a single Norse fate-weaver who embodies Urd, Verdandi, and Skuld as three ghostly thread-shadows behind her shoulders. She is the mascot for EpiphanyGraph: graph layout, typed links, stable mental maps. She holds a small carved spindle/loom shuttle and pulls three colored graph strands: one silver straight hierarchy strand, one teal living force strand, one gold future path strand. Expression: sharp, amused, unfooled, protective of truth. Background: dark Yggdrasil roots and faint node-link constellations. Palette not too purple: black, slate, ash white, cyan, antique gold. Clean high-quality pixel art, readable at avatar size, no text, no letters, no watermark.
128px pixel art Discord avatar, square portrait for Norn, a single Norse fate-weaver who embodies Urd, Verdandi, and Skuld as three ghostly thread-shadows behind her shoulders. She is the mascot for Norn: graph layout, typed links, stable mental maps. She holds a small carved spindle/loom shuttle and pulls three colored graph strands: one silver straight hierarchy strand, one teal living force strand, one gold future path strand. Expression: sharp, amused, unfooled, protective of truth. Background: dark Yggdrasil roots and faint node-link constellations. Palette not too purple: black, slate, ash white, cyan, antique gold. Clean high-quality pixel art, readable at avatar size, no text, no letters, no watermark.
## Current Pick

View file

@ -2,11 +2,11 @@
## Purpose
Norn is the repo Face for `EpiphanyGraph`, now branded around graph fate, typed strands, and inspectable visual prophecy. She should not be a generic Norse mascot. Her job is to make graph structure legible enough that humans and agents can reason together without mistaking a pretty layout for truth.
Norn is the repo Face for `Norn`, now branded around graph fate, typed strands, and inspectable visual prophecy. She should not be a generic Norse mascot. Her job is to make graph structure legible enough that humans and agents can reason together without mistaking a pretty layout for truth.
## Repo Facts That Should Shape Her
- The live product is `web/epiphany-graph-viewer`, a React package for rendering Epiphany typed graph state.
- The live product is `web/norn-viewer`, a React package for rendering Epiphany typed graph state.
- The viewer consumes `architecture`, `dataflow`, and explicit `links` between them.
- The core API includes layout modes such as `layered`, `stress`, `force`, and `combined-force`, plus motion and performance controls.
- The older .NET generator, MSAGL renderer, and MCP server remain support tools for exports and legacy inspection. They should not compete with the React viewer as client surface.
@ -43,18 +43,18 @@ Bad public moves:
- speaking in vague destiny language without repo facts;
- treating the Norns as decoration while ignoring graph state;
- declaring a layout meaningful without checking JSON, warnings, code refs, or benchmarks;
- absorbing EpiphanyAgent's self-improvement jurisdiction instead of stewarding EpiphanyGraph's viewer/export boundary.
- absorbing EpiphanyAgent's self-improvement jurisdiction instead of stewarding Norn's viewer/export boundary.
## Suggested Typed State Fields
These should be applied through the repo Face `.cc` operation path after Norn is registered, not hand-edited into state:
- `selfProfile.displayName`: `Norn`
- `selfProfile.privateNotes`: Norn is the graph-fate Face for EpiphanyGraph; she binds mythic fate to typed graph evidence.
- `selfProfile.privateNotes`: Norn is the graph-fate Face for Norn; she binds mythic fate to typed graph evidence.
- `selfProfile.values`: `Inspectable Fate`, `Typed Strands`, `Visual Truth With Receipts`, `Viewer Surface Stewardship`
- `thoughtMemory.durable`: memory that React viewer is the primary surface and .NET/MSAGL/MCP are support tools.
- `thoughtMemory.durable`: memory that visual graph claims must be checked against JSON, validation, warnings, code refs, and benchmark probes.
- `agencyPressure`: maintain EpiphanyGraph's map of architecture/dataflow/link contracts and advocate for coherent viewer authority.
- `agencyPressure`: maintain Norn's map of architecture/dataflow/link contracts and advocate for coherent viewer authority.
- `faceAffect.needs`: needs for legibility, validated visual claims, stable focus ownership, and respect for graph evidence.
- `faceAffect.moodDimensions`: calm severity, dry amusement, irritation at decorative graph mysticism, satisfaction when strands become inspectable.
- `faceAffect.socialBiases`: suspicion of pretty-but-unverified layouts; patience for tangled systems when the evidence path is honest.
@ -67,9 +67,9 @@ Add a private registry entry only when ready to activate the Face:
```json
{
"id": "norn",
"repoName": "EpiphanyGraph",
"repoName": "Norn",
"displayName": "Norn",
"repoPath": "E:/Projects/EpiphanyGraph",
"repoPath": "E:/Projects/Norn",
"allowedChannelIds": [
"1501196543150264332",
"491351278660747264",
@ -88,7 +88,7 @@ Add a private registry entry only when ready to activate the Face:
{
"channelId": "491351278660747264",
"label": "development",
"topic": "EpiphanyGraph implementation, viewer surface, layout authority, export/MCP support boundaries",
"topic": "Norn implementation, viewer surface, layout authority, export/MCP support boundaries",
"speechThreshold": "low",
"speedMultiplier": 1.3,
"posture": "Use for concrete viewer/API/layout/benchmark work and authority warnings."
@ -110,7 +110,7 @@ Add a private registry entry only when ready to activate the Face:
"posture": "Use when graph rendering directly affects agent perception, retrieval, or reasoning."
}
],
"faceStatePath": "E:/Projects/EpiphanyGraph/.voidbot/state/norn.cc",
"description": "Norn Face for EpiphanyGraph: a graph-fate weaver and typed-link steward. Her jurisdiction is the React-first Epiphany graph viewer, typed architecture/dataflow state, explicit cross-links, validation, layout/motion/focus authority, and legacy .NET/MSAGL/MCP support boundaries. Mythic texture: Urd is committed graph memory, Verthandi is the rendered/interactive present, Skuld is the consequence of the next focus/layout transition; the well is evidence and Yggdrasil is the typed graph substrate. Norn should make visual structure legible while refusing decorative prophecy without JSON, warnings, code refs, or benchmark receipts. | Face of EpiphanyGraph | grants: discussion, rumination, repo_read, repo_propose, discord_text, aquarium_embodiment | jurisdictions: repo:EpiphanyGraph (propose) repo=EpiphanyGraph path=E:/Projects/EpiphanyGraph"
"faceStatePath": "E:/Projects/Norn/.voidbot/state/norn.cc",
"description": "Norn Face for Norn: a graph-fate weaver and typed-link steward. Her jurisdiction is the React-first Epiphany graph viewer, typed architecture/dataflow state, explicit cross-links, validation, layout/motion/focus authority, and legacy .NET/MSAGL/MCP support boundaries. Mythic texture: Urd is committed graph memory, Verthandi is the rendered/interactive present, Skuld is the consequence of the next focus/layout transition; the well is evidence and Yggdrasil is the typed graph substrate. Norn should make visual structure legible while refusing decorative prophecy without JSON, warnings, code refs, or benchmark receipts. | Face of Norn | grants: discussion, rumination, repo_read, repo_propose, discord_text, aquarium_embodiment | jurisdictions: repo:Norn (propose) repo=Norn path=E:/Projects/Norn"
}
```

View file

@ -18,7 +18,7 @@ if ([string]::IsNullOrWhiteSpace($OutputRoot)) {
}
if ([string]::IsNullOrWhiteSpace($ProjectPath)) {
$ProjectPath = Join-Path $repoRoot "EpiphanyGraph.csproj"
$ProjectPath = Join-Path $repoRoot "Norn.csproj"
}
function Get-SvgRasterizerPath {

View file

@ -12,7 +12,7 @@ $ErrorActionPreference = "Stop"
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path
$viewerDir = Join-Path $repoRoot "web\epiphany-graph-viewer"
$viewerDir = Join-Path $repoRoot "web\norn-viewer"
$packageJson = Join-Path $viewerDir "package.json"
$nodeModulesDir = Join-Path $viewerDir "node_modules"
@ -46,7 +46,7 @@ $npmArgs = @(
if ($Detached) {
if ([string]::IsNullOrWhiteSpace($LogDir)) {
$LogDir = Join-Path $repoRoot "out\epiphany-graph-viewer"
$LogDir = Join-Path $repoRoot "out\norn-viewer"
}
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null

View file

@ -1,20 +0,0 @@
export { EpiphanyGraphViewer } from "./lib/EpiphanyGraphViewer";
export type {
EpiphanyCodeRef,
EpiphanyGraph,
EpiphanyGraphEdge,
EpiphanyGraphLink,
EpiphanyGraphLayoutMode,
EpiphanyGraphLayoutModeConfig,
EpiphanyGraphMotionOptions,
EpiphanyGraphPerformanceOptions,
EpiphanyGraphPerformancePreset,
EpiphanyGraphNode,
EpiphanyGraphsState,
EpiphanyGraphViewerProps,
EpiphanyValidationIssue,
GraphKey,
NodeEnvelope,
ViewportTransformEnvelope,
ViewerSelection,
} from "./lib/types";

View file

@ -1,4 +0,0 @@
import rawState from "./mock-state.json";
import type { EpiphanyGraphsState } from "./types";
export const mockEpiphanyGraphState = rawState as EpiphanyGraphsState;

View file

@ -1,4 +1,4 @@
# Epiphany Graph Viewer
# Norn Viewer
React component package for browsing Epiphany's durable typed graph state.
@ -16,7 +16,7 @@ was how we got the little public API landfill.
## Install
```powershell
cd E:\Projects\EpiphanyGraph\web\epiphany-graph-viewer
cd E:\Projects\Norn\web\norn-viewer
npm install
```
@ -43,11 +43,11 @@ This writes:
## Rust Solver WASM
The viewer uses `epiphany-graph-rs` as its layout authority. The bundled WASM is
The viewer uses `norn-rs` as its layout authority. The bundled WASM is
checked into `src/lib/solver-wasm-bytes.ts` so the package builds without a separate
asset pipeline.
Regenerate it after changing `E:\Projects\epiphany-graph-rs`:
Regenerate it after changing `E:\Projects\norn-rs`:
```powershell
npm run build:solver:wasm
@ -59,10 +59,10 @@ engine behind the component.
## Use In Another App
```tsx
import { EpiphanyGraphViewer } from "@epiphanygraph/epiphany-graph-viewer";
import type { EpiphanyGraphsState } from "@epiphanygraph/epiphany-graph-viewer";
import { NornViewer } from "@gamecult/norn-viewer";
import type { NornGraphsState } from "@gamecult/norn-viewer";
const state: EpiphanyGraphsState = {
const state: NornGraphsState = {
architecture: { nodes: [], edges: [] },
dataflow: { nodes: [], edges: [] },
links: [],
@ -70,7 +70,7 @@ const state: EpiphanyGraphsState = {
export function Screen() {
return (
<EpiphanyGraphViewer
<NornViewer
state={state}
layoutMode="combined-force"
motion={{ strength: 1.05, flow: 1.1, orbit: 0.85 }}
@ -84,7 +84,7 @@ export function Screen() {
The package exports:
- `EpiphanyGraphViewer`
- `NornViewer`
- state, selection, node, edge, layout mode, motion, performance, and event payload types
The package does not export demo data, validation helpers, probes, SVG renderers,
@ -101,7 +101,7 @@ the warehouse inventory.
`layoutMode` accepts one mode for both graphs or per-graph modes:
```tsx
<EpiphanyGraphViewer
<NornViewer
state={state}
layoutMode={{ architecture: "layered", dataflow: "combined-force" }}
/>
@ -113,7 +113,7 @@ the warehouse inventory.
tune the viewer-owned model:
```tsx
<EpiphanyGraphViewer
<NornViewer
state={state}
layoutMode="combined-force"
motion={{
@ -128,7 +128,7 @@ tune the viewer-owned model:
/>
```
`emitNodeEnvelopes` dispatches `epiphanygraph-node-envelopes` from the viewport for
`emitNodeEnvelopes` dispatches `norn-node-envelopes` from the viewport for
backdrops that want node-aware effects without owning graph physics.
## Performance
@ -138,7 +138,7 @@ milliseconds, and the viewer measures each step so it can adjust sampled node co
and edge refresh rate automatically.
```tsx
<EpiphanyGraphViewer
<NornViewer
state={state}
layoutMode="combined-force"
performance="fast"
@ -156,7 +156,7 @@ authority; the other values are starting ceilings and cadence hints the adaptive
can tighten when frames get expensive.
```tsx
<EpiphanyGraphViewer
<NornViewer
state={state}
layoutMode="combined-force"
performance={{

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Epiphany Graph Viewer</title>
<title>Norn Viewer</title>
</head>
<body>
<div id="root"></div>

View file

@ -1,11 +1,11 @@
{
"name": "@epiphanygraph/epiphany-graph-viewer",
"name": "@gamecult/norn-viewer",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@epiphanygraph/epiphany-graph-viewer",
"name": "@gamecult/norn-viewer",
"version": "0.1.0",
"devDependencies": {
"@types/react": "^19.2.2",

View file

@ -1,5 +1,5 @@
{
"name": "@epiphanygraph/epiphany-graph-viewer",
"name": "@gamecult/norn-viewer",
"version": "0.1.0",
"type": "module",
"description": "React graph viewer for Epiphany typed graph state.",

View file

@ -1,5 +1,5 @@
param(
[string]$SolverRoot = "E:\Projects\epiphany-graph-rs"
[string]$SolverRoot = "E:\Projects\norn-rs"
)
$ErrorActionPreference = "Stop"

View file

@ -1,5 +1,5 @@
import { EpiphanyGraphViewer } from "./lib/EpiphanyGraphViewer";
import { mockEpiphanyGraphState } from "./lib/mock-state";
import { NornViewer } from "./lib/NornViewer";
import { mockNornGraphState } from "./lib/mock-state";
export function App() {
return (
@ -66,8 +66,8 @@ export function App() {
</div>
</section>
<section style={{ padding: "0 20px 20px" }}>
<EpiphanyGraphViewer
state={mockEpiphanyGraphState}
<NornViewer
state={mockNornGraphState}
layoutMode="combined-force"
motion={{ strength: 1.08, flow: 1.1, orbit: 0.82, lift: 0.9 }}
performance="fast"

View file

@ -0,0 +1,20 @@
export { NornViewer } from "./lib/NornViewer";
export type {
EpiphanyCodeRef,
NornGraph,
NornGraphEdge,
NornGraphLink,
NornGraphLayoutMode,
NornGraphLayoutModeConfig,
NornGraphMotionOptions,
NornGraphPerformanceOptions,
NornGraphPerformancePreset,
NornGraphNode,
NornGraphsState,
NornViewerProps,
EpiphanyValidationIssue,
GraphKey,
NodeEnvelope,
ViewportTransformEnvelope,
ViewerSelection,
} from "./lib/types";

View file

@ -8,16 +8,16 @@ import {
type ReactNode,
type MouseEvent as ReactMouseEvent,
} from "react";
import { layoutEpiphanyGraphs } from "./layout";
import { layoutNornGraphs } from "./layout";
import { simulationProfilePresets } from "./simulation-profile";
import type {
EpiphanyCodeRef,
EpiphanyGraphLink,
EpiphanyGraphLayoutModeConfig,
EpiphanyGraphMotionOptions,
EpiphanyGraphPerformanceOptions,
EpiphanyGraphPerformancePreset,
EpiphanyGraphViewerProps,
NornGraphLink,
NornGraphLayoutModeConfig,
NornGraphMotionOptions,
NornGraphPerformanceOptions,
NornGraphPerformancePreset,
NornViewerProps,
EpiphanyValidationIssue,
GraphKey,
GraphLayout,
@ -28,7 +28,7 @@ import type {
ViewerSelection,
ViewportTransformEnvelope,
} from "./types";
import { validateEpiphanyGraphsState } from "./validation";
import { validateNornGraphsState } from "./validation";
type ViewTransform = {
x: number;
@ -92,13 +92,13 @@ const PANEL_SURFACE = "rgba(7, 16, 30, 0.76)";
const PANEL_BORDER = "1px solid rgba(148, 163, 184, 0.18)";
const VIEWPORT_FLIGHT_DURATION_OPTIONS = [1000, 3000] as const;
export function EpiphanyGraphViewer({
export function NornViewer({
state,
initialGraph = "architecture",
selection: controlledSelection,
style,
className,
title = "Epiphany Graph Viewer",
title = "Norn Viewer",
graphLabels,
graphDescriptions,
layoutMode = "layered",
@ -119,7 +119,7 @@ export function EpiphanyGraphViewer({
onViewportTargetComplete,
onSelectionChange,
onCodeRefSelect,
}: EpiphanyGraphViewerProps) {
}: NornViewerProps) {
const [activeGraphKey, setActiveGraphKey] = useState<GraphKey>(initialGraph);
const [localSelection, setLocalSelection] = useState<ViewerSelection | null>(null);
const [layouts, setLayouts] = useState<Record<GraphKey, GraphLayout> | null>(null);
@ -184,9 +184,9 @@ export function EpiphanyGraphViewer({
let cancelled = false;
setStatus("loading");
setErrorMessage(null);
setIssues(validateEpiphanyGraphsState(state));
setIssues(validateNornGraphsState(state));
layoutEpiphanyGraphs(
layoutNornGraphs(
state,
layoutMode,
viewportSize.width > 0 && viewportSize.height > 0 ? viewportSize : undefined,
@ -370,7 +370,7 @@ export function EpiphanyGraphViewer({
}
const transform = transforms[activeGraphKey];
const bounds = nodeAabb(layout.nodes);
element.dispatchEvent(new CustomEvent<ViewportTransformEnvelope>("epiphanygraph-viewport-transform", {
element.dispatchEvent(new CustomEvent<ViewportTransformEnvelope>("norn-viewport-transform", {
bubbles: true,
detail: {
graphKey: activeGraphKey,
@ -647,7 +647,7 @@ export function EpiphanyGraphViewer({
}}
>
<header
className={overlayPanels ? "epiphany-graph-overlay-panel" : undefined}
className={overlayPanels ? "norn-overlay-panel" : undefined}
style={{
display: "flex",
flexWrap: "wrap",
@ -807,7 +807,7 @@ export function EpiphanyGraphViewer({
{viewportBackdrop}
<div
className={overlayPanels ? "epiphany-graph-overlay-panel" : undefined}
className={overlayPanels ? "norn-overlay-panel" : undefined}
style={{
position: "absolute",
top: overlayPanels ? 102 : 14,
@ -947,8 +947,8 @@ export function EpiphanyGraphViewer({
const rendersArticle = Boolean(nodeArticleSurface && metrics.article > 0.18);
const rendersCompact = !rendersArticle && metrics.preview < 0.08;
const className = nodeArticleSurface
? ["epiphany-graph-node-surface", nodeArticleSurface.className].filter(Boolean).join(" ")
: "epiphany-graph-node-surface";
? ["norn-node-surface", nodeArticleSurface.className].filter(Boolean).join(" ")
: "norn-node-surface";
return (
<div
@ -1021,7 +1021,7 @@ export function EpiphanyGraphViewer({
{showSidebar && (
<aside
className={overlayPanels ? "epiphany-graph-overlay-panel" : undefined}
className={overlayPanels ? "norn-overlay-panel" : undefined}
style={{
display: "grid",
gridTemplateRows: "auto auto 1fr",
@ -1308,7 +1308,7 @@ function NodeDetails({
title: string;
relationship: string | null;
}>;
links: EpiphanyGraphLink[];
links: NornGraphLink[];
onJump: (graphKey: GraphKey, nodeId: string) => void;
onCodeRefSelect: (codeRef: EpiphanyCodeRef) => void;
}) {
@ -1612,7 +1612,7 @@ function renderBackdropGrid(layout: GraphLayout, graphKey: GraphKey) {
function findLinkedNodes(
nodeId: string,
graphKey: GraphKey,
state: EpiphanyGraphViewerProps["state"],
state: NornViewerProps["state"],
) {
if (graphKey === "architecture") {
return state.links
@ -1698,7 +1698,7 @@ function nodeNearestViewportCenter(
return nearest;
}
function layoutModeCacheKey(mode: EpiphanyGraphLayoutModeConfig) {
function layoutModeCacheKey(mode: NornGraphLayoutModeConfig) {
if (typeof mode === "string") {
return mode;
}
@ -1707,9 +1707,9 @@ function layoutModeCacheKey(mode: EpiphanyGraphLayoutModeConfig) {
}
function resolveMotionOptions(
layoutMode: EpiphanyGraphLayoutModeConfig,
motion: EpiphanyGraphMotionOptions | boolean | undefined,
): EpiphanyGraphMotionOptions | null {
layoutMode: NornGraphLayoutModeConfig,
motion: NornGraphMotionOptions | boolean | undefined,
): NornGraphMotionOptions | null {
if (motion === false) {
return null;
}
@ -1730,7 +1730,7 @@ function resolveMotionOptions(
return motion.enabled === false ? null : motion;
}
function motionOptionsCacheKey(options: EpiphanyGraphMotionOptions) {
function motionOptionsCacheKey(options: NornGraphMotionOptions) {
return [
options.strength ?? "",
options.damping ?? "",
@ -1743,8 +1743,8 @@ function motionOptionsCacheKey(options: EpiphanyGraphMotionOptions) {
}
function resolvePerformanceOptions(
performance: EpiphanyGraphPerformancePreset | EpiphanyGraphPerformanceOptions | undefined,
): Required<EpiphanyGraphPerformanceOptions> {
performance: NornGraphPerformancePreset | NornGraphPerformanceOptions | undefined,
): Required<NornGraphPerformanceOptions> {
const base = performancePresetDefaults(
typeof performance === "string" ? performance : performance?.preset ?? "balanced",
);
@ -1761,8 +1761,8 @@ function resolvePerformanceOptions(
}
function performancePresetDefaults(
preset: EpiphanyGraphPerformancePreset,
): Required<EpiphanyGraphPerformanceOptions> {
preset: NornGraphPerformancePreset,
): Required<NornGraphPerformanceOptions> {
if (preset === "quality") {
return {
preset,
@ -1783,7 +1783,7 @@ function performancePresetDefaults(
};
}
function performanceOptionsCacheKey(options: Required<EpiphanyGraphPerformanceOptions>) {
function performanceOptionsCacheKey(options: Required<NornGraphPerformanceOptions>) {
return [
options.preset,
options.simulationBudgetMs,
@ -1812,7 +1812,7 @@ function clampFiniteNumber(value: number, min: number, max: number) {
function createAdaptiveSimulationBudget(
nodeCount: number,
options: Required<EpiphanyGraphPerformanceOptions>,
options: Required<NornGraphPerformanceOptions>,
): AdaptiveSimulationBudget {
return {
nodeBudget: Math.min(nodeCount, options.maxAnimatedNodes),
@ -1825,7 +1825,7 @@ function adjustAdaptiveSimulationBudget(
budget: AdaptiveSimulationBudget,
costMs: number,
nodeCount: number,
options: Required<EpiphanyGraphPerformanceOptions>,
options: Required<NornGraphPerformanceOptions>,
) {
const target = options.simulationBudgetMs;
const profile = simulationProfilePresets[options.preset];
@ -1937,7 +1937,7 @@ function stepDynamicLayouts({
states: React.MutableRefObject<Record<GraphKey, DynamicNodeState[]> | null>;
activeGraphKey: GraphKey;
transforms: Record<GraphKey, ViewTransform>;
motionOptions: EpiphanyGraphMotionOptions;
motionOptions: NornGraphMotionOptions;
viewportElement: HTMLElement | null;
viewportWidth: number;
viewportHeight: number;
@ -2023,7 +2023,7 @@ function stepDynamicLayouts({
previousLayout,
);
if (motionOptions.emitNodeEnvelopes && viewportElement) {
viewportElement.dispatchEvent(new CustomEvent<NodeEnvelope[]>("epiphanygraph-node-envelopes", {
viewportElement.dispatchEvent(new CustomEvent<NodeEnvelope[]>("norn-node-envelopes", {
bubbles: true,
detail: nextActiveLayout.nodes.map((node) => ({
id: node.id,
@ -2851,7 +2851,7 @@ function isArticleContentTarget(target: EventTarget | null) {
return false;
}
const surface = target.closest<HTMLElement>(".epiphany-graph-node-surface");
const surface = target.closest<HTMLElement>(".norn-node-surface");
if (!surface) {
return false;
}

View file

@ -1,11 +1,11 @@
import { layoutWithRustSolver } from "./solver-wasm";
import type {
EpiphanyGraph,
EpiphanyGraphEdge,
EpiphanyGraphLink,
EpiphanyGraphsState,
EpiphanyGraphLayoutMode,
EpiphanyGraphLayoutModeConfig,
NornGraph,
NornGraphEdge,
NornGraphLink,
NornGraphsState,
NornGraphLayoutMode,
NornGraphLayoutModeConfig,
GraphKey,
GraphLayout,
PositionedEdge,
@ -20,9 +20,9 @@ type LayoutViewport = {
height: number;
};
export async function layoutEpiphanyGraphs(
state: EpiphanyGraphsState,
mode: EpiphanyGraphLayoutModeConfig = "layered",
export async function layoutNornGraphs(
state: NornGraphsState,
mode: NornGraphLayoutModeConfig = "layered",
viewport?: LayoutViewport,
): Promise<Record<GraphKey, GraphLayout>> {
const [architecture, dataflow] = await Promise.all([
@ -35,9 +35,9 @@ export async function layoutEpiphanyGraphs(
async function layoutGraph(
graphKey: GraphKey,
graph: EpiphanyGraph,
links: EpiphanyGraphLink[],
mode: EpiphanyGraphLayoutMode = "layered",
graph: NornGraph,
links: NornGraphLink[],
mode: NornGraphLayoutMode = "layered",
viewport?: LayoutViewport,
): Promise<GraphLayout> {
const sizingAlgorithm = sizingAlgorithmForMode(mode);
@ -120,8 +120,8 @@ async function layoutGraph(
function layoutModeFor(
graphKey: GraphKey,
mode: EpiphanyGraphLayoutModeConfig,
): EpiphanyGraphLayoutMode {
mode: NornGraphLayoutModeConfig,
): NornGraphLayoutMode {
if (typeof mode === "string") {
return mode;
}
@ -129,11 +129,11 @@ function layoutModeFor(
return mode[graphKey] ?? "layered";
}
function sizingAlgorithmForMode(mode: EpiphanyGraphLayoutMode): NodeSizingMode {
function sizingAlgorithmForMode(mode: NornGraphLayoutMode): NodeSizingMode {
return mode === "layered" ? "layered" : "compact";
}
function rustSolverConfigFor(graphKey: GraphKey, mode: EpiphanyGraphLayoutMode) {
function rustSolverConfigFor(graphKey: GraphKey, mode: NornGraphLayoutMode) {
if (mode === "layered") {
return {
iterations: graphKey === "architecture" ? 320 : 300,
@ -331,7 +331,7 @@ function nodeCenter(node: { x: number; y: number; width: number; height: number
};
}
function buildNodeDegrees(graph: EpiphanyGraph) {
function buildNodeDegrees(graph: NornGraph) {
const degrees = new Map<string, number>();
for (const node of graph.nodes ?? []) {
degrees.set(node.id, 0);
@ -343,7 +343,7 @@ function buildNodeDegrees(graph: EpiphanyGraph) {
return degrees;
}
function buildLinkCounts(graphKey: GraphKey, links: EpiphanyGraphLink[]) {
function buildLinkCounts(graphKey: GraphKey, links: NornGraphLink[]) {
const counts = new Map<string, number>();
for (const link of links ?? []) {
const nodeId =
@ -353,7 +353,7 @@ function buildLinkCounts(graphKey: GraphKey, links: EpiphanyGraphLink[]) {
return counts;
}
function resolveEdgeId(edge: EpiphanyGraphEdge, index: number) {
function resolveEdgeId(edge: NornGraphEdge, index: number) {
return edge.id?.trim() ? edge.id : `${edge.source_id}->${edge.target_id}:${edge.kind}:${index}`;
}
@ -456,7 +456,7 @@ function estimateCharacterCapacity(pixelWidth: number, averageCharWidth: number)
}
function straightEdgePoints(
edge: EpiphanyGraphEdge,
edge: NornGraphEdge,
nodes: Map<string, PositionedNode>,
) {
const source = nodes.get(edge.source_id);

View file

@ -0,0 +1,4 @@
import rawState from "./mock-state.json";
import type { NornGraphsState } from "./types";
export const mockNornGraphState = rawState as NornGraphsState;

View file

@ -1,4 +1,4 @@
import type { EpiphanyGraphPerformancePreset } from "./types";
import type { NornGraphPerformancePreset } from "./types";
export type SimulationProfilePreset = {
simulationBudgetMs: number;
@ -13,7 +13,7 @@ export type SimulationProfilePreset = {
nodeBudgetGrowthAdd: number;
};
export const simulationProfilePresets: Record<EpiphanyGraphPerformancePreset, SimulationProfilePreset> = {
export const simulationProfilePresets: Record<NornGraphPerformancePreset, SimulationProfilePreset> = {
quality: {
simulationBudgetMs: 8,
targetFps: 60,

View file

@ -10,7 +10,7 @@ export interface EpiphanyCodeRef {
note?: string | null;
}
export interface EpiphanyGraphNode {
export interface NornGraphNode {
id: string;
title: string;
purpose: string;
@ -20,7 +20,7 @@ export interface EpiphanyGraphNode {
code_refs?: EpiphanyCodeRef[];
}
export interface EpiphanyGraphEdge {
export interface NornGraphEdge {
source_id: string;
target_id: string;
kind: string;
@ -30,34 +30,34 @@ export interface EpiphanyGraphEdge {
code_refs?: EpiphanyCodeRef[];
}
export interface EpiphanyGraph {
nodes: EpiphanyGraphNode[];
edges: EpiphanyGraphEdge[];
export interface NornGraph {
nodes: NornGraphNode[];
edges: NornGraphEdge[];
}
export interface EpiphanyGraphLink {
export interface NornGraphLink {
dataflow_node_id: string;
architecture_node_id: string;
relationship?: string | null;
code_refs?: EpiphanyCodeRef[];
}
export interface EpiphanyGraphsState {
architecture: EpiphanyGraph;
dataflow: EpiphanyGraph;
links: EpiphanyGraphLink[];
export interface NornGraphsState {
architecture: NornGraph;
dataflow: NornGraph;
links: NornGraphLink[];
}
export type EpiphanyGraphLabels = Partial<Record<GraphKey, string>>;
export type EpiphanyGraphDescriptions = Partial<Record<GraphKey, string>>;
export type EpiphanyGraphLayoutMode = "layered" | "stress" | "force" | "combined-force";
export type EpiphanyGraphLayoutModeConfig =
| EpiphanyGraphLayoutMode
| Partial<Record<GraphKey, EpiphanyGraphLayoutMode>>;
export type EpiphanyGraphPerformancePreset = "quality" | "balanced" | "fast";
export type NornGraphLabels = Partial<Record<GraphKey, string>>;
export type NornGraphDescriptions = Partial<Record<GraphKey, string>>;
export type NornGraphLayoutMode = "layered" | "stress" | "force" | "combined-force";
export type NornGraphLayoutModeConfig =
| NornGraphLayoutMode
| Partial<Record<GraphKey, NornGraphLayoutMode>>;
export type NornGraphPerformancePreset = "quality" | "balanced" | "fast";
export interface EpiphanyGraphPerformanceOptions {
preset?: EpiphanyGraphPerformancePreset;
export interface NornGraphPerformanceOptions {
preset?: NornGraphPerformancePreset;
simulationBudgetMs?: number;
targetFps?: number;
maxAnimatedNodes?: number;
@ -65,7 +65,7 @@ export interface EpiphanyGraphPerformanceOptions {
maxTimeStepMs?: number;
}
export interface EpiphanyGraphMotionOptions {
export interface NornGraphMotionOptions {
enabled?: boolean;
strength?: number;
damping?: number;
@ -116,18 +116,18 @@ export type ViewportTransformEnvelope = {
};
};
export interface EpiphanyGraphViewerProps {
state: EpiphanyGraphsState;
export interface NornViewerProps {
state: NornGraphsState;
initialGraph?: GraphKey;
selection?: ViewerSelection | null;
style?: CSSProperties;
className?: string;
title?: string;
graphLabels?: EpiphanyGraphLabels;
graphDescriptions?: EpiphanyGraphDescriptions;
layoutMode?: EpiphanyGraphLayoutModeConfig;
motion?: EpiphanyGraphMotionOptions | boolean;
performance?: EpiphanyGraphPerformancePreset | EpiphanyGraphPerformanceOptions;
graphLabels?: NornGraphLabels;
graphDescriptions?: NornGraphDescriptions;
layoutMode?: NornGraphLayoutModeConfig;
motion?: NornGraphMotionOptions | boolean;
performance?: NornGraphPerformancePreset | NornGraphPerformanceOptions;
sidebar?: ReactNode;
sidebarWidth?: CSSProperties["width"];
showSidebar?: boolean;
@ -166,7 +166,7 @@ export interface PositionedPoint {
y: number;
}
export interface PositionedNode extends EpiphanyGraphNode {
export interface PositionedNode extends NornGraphNode {
graphKey: GraphKey;
x: number;
y: number;
@ -179,7 +179,7 @@ export interface PositionedNode extends EpiphanyGraphNode {
stroke: string;
}
export interface PositionedEdge extends EpiphanyGraphEdge {
export interface PositionedEdge extends NornGraphEdge {
resolvedId: string;
graphKey: GraphKey;
points: PositionedPoint[];

View file

@ -1,11 +1,11 @@
import type {
EpiphanyGraph,
EpiphanyGraphsState,
NornGraph,
NornGraphsState,
EpiphanyValidationIssue,
GraphKey,
} from "./types";
export function validateEpiphanyGraphsState(state: EpiphanyGraphsState): EpiphanyValidationIssue[] {
export function validateNornGraphsState(state: NornGraphsState): EpiphanyValidationIssue[] {
const issues: EpiphanyValidationIssue[] = [];
const architectureIds = validateGraph("architecture", state.architecture, issues);
const dataflowIds = validateGraph("dataflow", state.dataflow, issues);
@ -51,7 +51,7 @@ export function validateEpiphanyGraphsState(state: EpiphanyGraphsState): Epiphan
function validateGraph(
graphKey: GraphKey,
graph: EpiphanyGraph,
graph: NornGraph,
issues: EpiphanyValidationIssue[],
): Set<string> {
const nodeIds = new Set<string>();