← Library EQUILIBRIUM equilibrium-system.com

EQUILIBRIUM UI KIT Figma

EQUILIBRIUM UI KIT / FIGMA DOCUMENT. Now let's do it as a real UI document under Figma, and not just a description. You can give it to the designer or assemble it yourself. We give:

EQUILIBRIUM UI KIT / FIGMA DOCUMENT

FILE STRUCTURE FIGMA

Create file: EQUILIBRIUM / INTERFACE V1

Pages (pages):

PAGE 00 — FOUNDATIONS

Color system

Main:

Status:

Surfaces:

Typography

Effects

PAGE 01 — COMPONENTS

Node (node). Dimensions:

  • S: 16px
  • M: 32px
  • L: 64px

Properties:

  • Color = Type
  • size = effect

Status:

  • normal
  • hovered
  • active
  • overloaded (pulsation)

HyperEdge (hyperrib). It's not a line.

Visual:

  • Soft cloud
  • gradient
  • blur

Options

  • Weak
  • Active
  • Congested
  • conflict

Flow Line

  • thin line
  • motion animation
  • direction

Panel (card)

  • Used on the right.

Size

  • width: 360–420px

Contains:

  • Title
  • Data Blocks
  • buttons

Button

Types:

  • Primary
  • Secondary
  • Ghost

Mode Switch. Switch:

  • Overview
  • Diagnostics
  • Scenario
  • Action

Timeline

  • horizontal scale
  • draggable
  • Event Marks

PAGE 02 — SCREENS

SCREEN 1 — GLOBAL DASHBOARD

Frame 1440 × 900 (desktop)

Layout

TOP BAR (64px)


LEFT PANEL (240px) | CENTER GRAPH | RIGHT PANEL (400px)


BOTTOM TIMELINE (120px)

TOP BAR

Left → Right:

  • Logo (EQUILIBRIUM)
  • Search
  • Level selector
  • Mode switch
  • AI button
  • Settings

LEFT PANEL

Sections:

  • Node types
  • Types of Connections
  • Status

Use the checkboxes

CENTER — GRAPH AREA

This is the main frame.

Inside:

  • Nodes (Auto Layout disabled)
  • HyperEdges (under layer)
  • Flows (top layer animation) > Use absolute positioning

RIGHT PANEL

Condition 1 — Node Vie

  • Title
  • Type
  • Metrics
  • Connections
  • Buttons

Condition 2 — Edge View

  • Meaning
  • Participants
  • Resources
  • Effect
  • Metrics

BOTTOM — TIMELINE

  • Time Line
  • Event Points
  • drag

PAGE 03 — FLOWS

Flow 1 — Node selection

Click →

→ backlight

→ Opening Panel

Flow 2 — Selection of hyperribs

Click →

→ Protocol Disclosure

Flow 3 — Creating a connection

  • Select Node
  • Press .
  • Select Type
  • fill in the fields

Flow 4 — Diagnostics

Switching mode →

→ problem highlighting

Flow 5 — Script

Parameter change →

→ graph update

START COMPONENTS (minimum)

If you want to quickly:

Do it:

  • Node (1 component)
  • Panel
  • Graph area
  • Top bar

That's enough for the first prototype.

CRITICAL RULES (remember)

  • Don't overload the screen.
  • Don't make small elements
  • Don't make complicated forms.

3 main principles:

Simplicity

> Readability

Feeling of a living system




Stack MVP

MVP

We go into the code so that we get the skeleton of MVP, not a set of beautiful words. Below we give:

  • stack,
  • structure of the project,
  • data models,
  • front to React,
  • st.,
  • API,
  • Neo4j-scheme,
  • The first working scenario.

For the visual graph, it is now reasonable to take React Flow; the current version uses the ?xyflow/react package, and custom nodes there are made with conventional React components. For the graph database, this naturally lies under the official neo4j-driver. 

Stack MVP

Frontend

  • Next.js / React
  • TypeScript
  • @xyflow/react
  • Zustand

Backend

  • Next.js Route Handlers or Node/Express
  • TypeScript
  • Neo4j
  • neo4j-driver

Why so

  • React Flow is already sharpened under node-based UI.  
  • Neo4j is well suited because we need nodes, connections, relationship types and graph queries. The official JS driver is directly supported.  


Project structure

equilibrium-mvp/

├─ app/

│  ├─ page.tsx

│  ├─ api/

│  │  ├─ graph/route.ts

│  │  ├─ node/route.ts

│  │  └─ edge/route.ts

├─ components/

│  ├─ graph/

│  │  ├─ EquilibriumCanvas.tsx

│  │  ├─ nodes/

│  │  │  ├─ MeaningNode.tsx

│  │  │  ├─ ActorNode.tsx

│  │  │  └─ EffectNode.tsx

│  │  ├─ panels/

│  │  │  ├─ LeftFilters.tsx

│  │  │  ├─ RightInspector.tsx

│  │  │  └─ BottomTimeline.tsx

│  │  └─ TopBar.tsx

├─ lib/

│  ├─ neo4j.ts

│  ├─ mapper.ts

│  └─ constants.ts

├─ store/

│  └─ equilibrium.store.ts

├─ types/

│  └─ equilibrium.ts

├─ cypher/

│  ├─ schema.cypher

│  ├─ seed.cypher

│  └─ queries.cypher

├─ package.json

└─ .env.local


Data types

Start not with 12 types, but with 6. Otherwise you'll drown.

types/equilibrium.ts

export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'process'

  | 'resource'

  | 'norm'

  | 'effect';


export type EQEdgeType =

  | 'intent'

  | 'cooperation'

  | 'production'

  | 'feedback'

  | 'evolution';


export type EQState =

  | 'harmony'

  | 'tension'

  | 'crisis'

  | 'growth'

  | 'transformation'

  | 'unknown';


export interface EQMetrics {

  trust?: number;

  activity?: number;

  load?: number;

  influence?: number;

  stability?: number;

  ethics?: number;

}


export interface EQNodeData {

  label: string;

  nodeType: EQNodeType;

  description?: string;

  state: EQState;

  metrics: EQMetrics;

  tags?: string[];

}


export interface EQNode {

  id: string;

  type: EQNodeType;

  position: { x: number; y: number };

  data: EQNodeData;

}


export interface EQEdgeData {

  label: string;

  edgeType: EQEdgeType;

  meaning?: string;

  process?: string;

  norm?: string;

  metric?: string;

  ethics?: string;

  intensity?: number;

  risk?: number;

  state: EQState;

}


export interface EQEdge {

  id: string;

  source: string;

  target: string;

  type: 'default' | 'hyper';

  data: EQEdgeData;

}


export interface GraphPayload {

  nodes: EQNode[];

  edges: EQEdge[];

}

Zustand store

store/equilibrium.store.ts

'use client';


import { create } from 'zustand';

import type { GraphPayload, EQNode, EQEdge, EQState, EQNodeType, EQEdgeType } from '@/types/equilibrium';


interface FiltersState {

  nodeTypes: EQNodeType[];

  edgeTypes: EQEdgeType[];

  states: EQState[];

}


interface EquilibriumStore {

  graph: GraphPayload;

  selectedNode: EQNode | null;

  selectedEdge: EQEdge | null;

  filters: FiltersState;


  setGraph: (graph: GraphPayload) => void;

  selectNode: (node: EQNode | null) => void;

  selectEdge: (edge: EQEdge | null) => void;

  updateFilters: (filters: Partial<FiltersState>) => void;

  upsertNode: (node: EQNode) => void;

  upsertEdge: (edge: EQEdge) => void;

}


export const useEquilibriumStore = create<EquilibriumStore>((set) => ({

  graph: { nodes: [], edges: [] },

  selectedNode: null,

  selectedEdge: null,

  filters: {

    nodeTypes: ['meaning', 'actor', 'process', 'resource', 'norm', 'effect'],

    edgeTypes: ['intent', 'cooperation', 'production', 'feedback', 'evolution'],

    states: ['harmony', 'tension', 'crisis', 'growth', 'transformation', 'unknown'],

  },


  setGraph: (graph) => set({ graph }),

  selectNode: (node) => set({ selectedNode: node, selectedEdge: null }),

  selectEdge: (edge) => set({ selectedEdge: edge, selectedNode: null }),

  updateFilters: (filters) =>

    set((state) => ({

      filters: { ...state.filters, ...filters },

    })),


  upsertNode: (node) =>

    set((state) => {

      const exists = state.graph.nodes.some((n) => n.id === node.id);

      return {

        graph: {

          ...state.graph,

          nodes: exists

            ? state.graph.nodes.map((n) => (n.id === node.id ? node : n))

            : [...state.graph.nodes, node],

        },

      };

    }),


  upsertEdge: (edge) =>

    set((state) => {

      const exists = state.graph.edges.some((e) => e.id === edge.id);

      return {

        graph: {

          ...state.graph,

          edges: exists

            ? state.graph.edges.map((e) => (e.id === edge.id ? edge : e))

            : [...state.graph.edges, edge],

        },

      };

    }),

}));


Graph Component

React Flow renders node, edge, and handles interaction via callbacks like onNodesChange. That's exactly what we need for the base canvas. 

components/graph/EquilibriumCanvas.tsx


'use client';


import { useEffect, useMemo } from 'react';

import {

  Background,

  Controls,

  MiniMap,

  ReactFlow,

  ReactFlowProvider,

  addEdge,

  useEdgesState,

  useNodesState,

  Connection,

  Edge,

  NodeMouseHandler,

  EdgeMouseHandler,

} from '@xyflow/react';

import '@xyflow/react/dist/style.css';


import { useEquilibriumStore } from '@/store/equilibrium.store';

import MeaningNode from './nodes/MeaningNode';

import ActorNode from './nodes/ActorNode';

import EffectNode from './nodes/EffectNode';


const nodeTypes = {

  meaning: MeaningNode,

  actor: ActorNode,

  effect: EffectNode,

};


function CanvasInner() {

  const { graph, selectNode, selectEdge } = useEquilibriumStore();


  const initialNodes = useMemo(() => graph.nodes, [graph.nodes]);

  const initialEdges = useMemo(

    () =>

      graph.edges.map((e) => ({

        ...e,

        animated: e.data.state === 'growth',

        style: {

          strokeWidth: 2,

        },

      })),

    [graph.edges]

  );


  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);

  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);


  useEffect(() => setNodes(initialNodes), [initialNodes, setNodes]);

  useEffect(() => setEdges(initialEdges), [initialEdges, setEdges]);


  const onConnect = (connection: Connection) => {

    setEdges((eds) =>

      addEdge(

        {

          ...connection,

          type: 'default',

          data: {

            label: 'New relation',

            edgeType: 'cooperation',

            state: 'unknown',

          },

        },

        eds

      )

    );

  };


  const onNodeClick: NodeMouseHandler = (_, node) => {

    selectNode(node as any);

  };


  const onEdgeClick: EdgeMouseHandler = (_, edge) => {

    selectEdge(edge as any);

  };


  return (

    <div style={{ width: '100%', height: '100%' }}>

      <ReactFlow

        nodes={nodes}

        edges={edges as Edge[]}

        onNodesChange={onNodesChange}

        onEdgesChange={onEdgesChange}

        onConnect={onConnect}

        onNodeClick={onNodeClick}

        onEdgeClick={onEdgeClick}

        nodeTypes={nodeTypes}

        fitView

      >

        <MiniMap />

        <Controls />

        <Background gap={20} />

      </ReactFlow>

    </div>

  );

}


export default function EquilibriumCanvas() {

  return (

    <ReactFlowProvider>

      <CanvasInner />

    </ReactFlowProvider>

  );

}

Custom Nodes

In React Flow, custom nodes are the usual React components that are transferred to nodeTypes. This is an officially supported path. 

components/graph/nodes/MeaningNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function MeaningNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(123, 97, 255, 0.22)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{ fontSize: 12, opacity: 0.7 }}>Meaning</div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Ethics: ?data.metrics?.ethics ?? '-'

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

components/graph/nodes/ActorNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function ActorNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(77, 163, 255, 0.18)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{ fontSize: 12, opacity: 0.7 }}>Subject</div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Trust: ?data.metrics?.trust ?? '-'

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

components/graph/nodes/EffectNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function EffectNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 180,

        borderRadius: 16,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: '0 0 24px rgba(61, 220, 151, 0.18)',

      }}

    >

      <Handle type="target" position={Position.Left} />

      <div style={{ fontSize: 12, opacity: 0.7 }}>Effect</div>

      <div style={{ fontSize: 16, fontWeight: 700 }}>{data.label}</div>

      <div style={{ fontSize: 12, marginTop: 8 }}>

        Stability: ?data.metrics?.stability ?? '-'

      </div>

      <Handle type="source" position={Position.Right} />

    </div>

  );

}

Main page

app/page.tsx

'use client';


import { useEffect } from 'react';

import EquilibriumCanvas from '@/components/graph/EquilibriumCanvas';

import { useEquilibriumStore } from '@/store/equilibrium.store';


export default function HomePage() {

  const { setGraph, selectedNode, selectedEdge } = useEquilibriumStore();


  useEffect(() => {

    fetch('/api/graph')

      .then((r) => r.json())

      .then(setGraph)

      .catch(console.error);

  }, [setGraph]);


  return (

    <main

      style={{

        display: 'grid',

        gridTemplateColumns: '240px 1fr 400px',

        gridTemplateRows: '64px 1fr 120px',

        height: '100vh',

        background: '#0B0F1A',

        color: 'white',

      }}

    >

      <header style={{ gridColumn: '1 / 4', borderBottom: '1px solid #1F2633', display: 'flex', alignItems: 'center', padding: '0 16px' }}>

        EQUILIBRIUM

      </header>


      <aside style={{ borderRight: '1px solid #1F2633', padding: 16 }}>

        <h3>Filters</h3>

        <div>Nodes, links, states</div>

      </aside>


      <section style={{ position: 'relative' }}>

        <EquilibriumCanvas />

      </section>


      <aside style={{ borderLeft: '1px solid #1F2633', padding: 16 }}>

        !selectedNode && !selectedEdge && <div>Choose node or link</div>


        {selectedNode && (

          <>

            <h3>{selectedNode.data.label}</h3>

            <p>Type: {selectedNode.data.nodeType}</p>

            <p>Status: {selectedNode.data.state}</p>

            <pre style={{ whiteSpace: 'pre-wrap' }}>

              {JSON.stringify(selectedNode.data.metrics, null, 2)}

            </pre>

          </>

        )}


        {selectedEdge && (

          <>

            <h3>{selectedEdge.data.label}</h3>

            <p>Type: {selectedEdge.data.edgeType}</p>

            <p>Status: {selectedEdge.data.state}</p>

            <p>Meaning: ?selectedEdge.data.meaning || '-'}</p>

            <p>Ethics: ?selectedEdge.data.ethics || '-'}</p>

          </>

        )}

      </aside>


      <footer style={{ gridColumn: '1 / 4', borderTop: '1px solid #1F2633', padding: 16 }}>

        Timeline / Simulation

      </footer>

    </main>

  );

}


Neo4j connection

The official driver is connected via neo4j.driver(...); the documentation also recommends explicitly specifying the target base for performance. 

lib/neo4j.ts

import neo4j from 'neo4j-driver';


const uri = process.env.NEO4J_URI!;

const username = process.env.NEO4J_USERNAME!;

const password = process.env.NEO4J_PASSWORD!;

const database = process.env.NEO4J_DATABASE || 'neo4j';


export const driver = neo4j.driver(uri, neo4j.auth.basic(username, password));


export { database };

API Read Count

app/api/graph/route.ts

import { NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function GET() {

  const query = `

    MATCH (n:EQNode)

    OPTIONAL MATCH (n)-[r:RELATES_TO]->(m:EQNode)

    RETURN collect(DISTINCT {

      id: n.id,

      type: n.nodeType,

      position: { x: n.x, y: n.y },

      data: {

        label: n.label,

        nodeType: n.nodeType,

        description: n.description,

        state: n.state,

        metrics: {

          trust: n.trust,

          activity: n.activity,

          load: n.load,

          influence: n.influence,

          stability: n.stability,

          ethics: n.ethics

        },

        tags: n.tags

      }

    }) AS nodes,

    collect(DISTINCT CASE WHEN r IS NULL THEN NULL ELSE {

      id: r.id,

      source: startNode(r).id,

      target: endNode(r).id,

      type: 'default',

      data: {

        label: r.label,

        edgeType: r.edgeType,

        meaning: r.meaning,

        process: r.process,

        norm: r.norm,

        metric: r.metric,

        ethics: r.ethics,

        intensity: r.intensity,

        risk: r.risk,

        state: r.state

      }

    } END) AS edges

  `;


  const result = await driver.executeQuery(query, {}, { database });

  const record = result.records[0];


  const nodes = record.get('nodes');

  const edges = record.get('edges').filter(Boolean);


  return NextResponse.json({ nodes, edges });

}

Creating a node

app/api/node/route.ts

import { NextRequest, NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function POST(req: NextRequest) {

  const body = await req.json();


  const query = `

    CREATE (n:EQNode {

      id: $id,

      label: $label,

      nodeType: $nodeType,

      description: $description,

      state: $state,

      x: $x,

      y: $y,

      trust: $trust,

      activity: $activity,

      load: $load,

      influence: $influence,

      stability: $stability,

      ethics: $ethics,

      tags: $tags

    })

    RETURN n

  `;


  await driver.executeQuery(

    query,

    {

      id: body.id,

      label: body.data.label,

      nodeType: body.data.nodeType,

      description: body.data.description || '',

      state: body.data.state,

      x: body.position.x,

      y: body.position.y,

      trust: body.data.metrics?.trust ?? null,

      activity: body.data.metrics?.activity ?? null,

      load: body.data.metrics?.load ?? null,

      influence: body.data.metrics?.influence ?? null,

      stability: body.data.metrics?.stability ?? null,

      ethics: body.data.metrics?.ethics ?? null,

      tags: body.data.tags ?? [],

    },

    { database }

  );


  return NextResponse.json({ ok: true });

}

Creating a connection

app/api/edge/route.ts

import { NextRequest, NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function POST(req: NextRequest) {

  const body = await req.json();


  const query = `

    MATCH (a:EQNode {id: $source})

    MATCH (b:EQNode {id: $target})

    CREATE (a)-[r:RELATES_TO {

      id: $id,

      label: $label,

      edgeType: $edgeType,

      meaning: $meaning,

      process: $process,

      norm: $norm,

      metric: $metric,

      ethics: $ethics,

      intensity: $intensity,

      risk: $risk,

      state: $state

    }]->(b)

    RETURN r

  `;


  await driver.executeQuery(

    query,

    {

      id: body.id,

      source: body.source,

      target: body.target,

      label: body.data.label,

      edgeType: body.data.edgeType,

      meaning: body.data.meaning || '',

      process: body.data.process || '',

      norm: body.data.norm || '',

      metric: body.data.metric || '',

      ethics: body.data.ethics || '',

      intensity: body.data.intensity ?? null,

      risk: body.data.risk ?? null,

      state: body.data.state,

    },

    { database }

  );


  return NextResponse.json({ ok: true });

}

Cypher-scheme

cypher/schema.cypher

CREATE CONSTRAINT eqnode_id IF NOT EXISTS

FOR (n:EQNode)REQUIRE n.id IS UNIQUE;

cypher/seed.cypher

CREATE (:EQNode {

  id: 'meaning-1',

  label: 'Development of competences',

  nodeType: 'meaning',

  state: 'growth',

  x: 100,

  y: 120,

  ethics: 0.9

});


CREATE (:EQNode {

  id: 'actor-1',

  label: 'Cooperative FENIX',

  nodeType: 'actor',

  state: 'harmony',

  x: 420,

  y: 180,

  trust: 0.82,

  activity: 0.77

});


CREATE (:EQNode {

  id: 'effect-1',

  label: 'Employment growth',

  nodeType: 'effect',

  state: 'growth',

  x: 760,

  y: 200,

  stability: 0.71

});


MATCH (a:EQNode {id: 'meaning-1'})

MATCH (b:EQNode {id: 'actor-1'})

CREATE (a)-[:RELATES_TO {

  id: 'edge-1',

  label: 'Intention',

  edgeType: 'intent',

  meaning: 'Start the educational circuit',

  ethics: 'Availability',

  state: 'growth',

  intensity: 0.8

}]->(b);


MATCH (a:EQNode {id: 'actor-1'})

MATCH (b:EQNode {id: 'effect-1'})

CREATE (a)-[:RELATES_TO {

  id: 'edge-2',

  label: 'Feedback',

  edgeType: 'feedback',

  meaning: 'Assessment of the result of the program',

  metric: 'Employment Index',

  state: 'harmony',

  intensity: 0.7

}]->(b);

Example JSON for front

  "nodes": [

    {

      "id": "meaning-1",

      "type": "meaning",

      "position": { "x": 100, "y": 120 },

      "data": {

        "label": "Development of competences",

        "nodeType": "meaning",

        "state": "growth",

        "metrics": { "ethics": 0.9 }

      }

    },

    {

      "id": "actor-1",

      "type": "actor",

      "position": { "x": 420, "y": 180 },

      "data": {

        "label": "Cooperative FENIX",

        "nodeType": "actor",

        "state": "harmony",

        "metrics": { "trust": 0.82, "activity": 0.77 }

      }

    }

  ],

  "edges": [

    {

      "id": "edge-1",

      "source": "meaning-1",

      "target": "actor-1",

      "type": "default",

      "data": {

        "label": "Intention",

        "edgeType": "intent",

        "meaning": "Starting the educational circuit",

        "state": "growth",

        "intensity": 0.8

      }

    }

  ]

}

What to do with the “hyper-ribs” really

Here's an important point. React Flow works with regular edge between two nodes. This is his basic model as a flowgraph UI. So for MVP do this:

Option A is practical

  • store “hyperrebro” as a separate node such as process or protocol
  • Connecting participants with regular Edges

That is:

Meaning -> ProtocolHub -> Actor

Resource -> ProtocolHub

Norm -> ProtocolHub

ProtocolHub -> Effect

It's an honest decision. It does not break the UI and allows you to show a multidimensional connection.

Type for this

export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'process'

  | 'resource'

  | 'norm'

  | 'effect'

  | 'protocol';

And then the “hyperrebro” at the data level is a protocol node with a set of input/output relationships. It's the best start.

The first working scenario

Don't spray. Do one scenario:

Scenario: “Cooperative Education”

Nodes:

  • Meaning: competence development
  • Subject: Cooperative
  • Resource: fund
  • Regulation: regulation
  • Protocol: educational program
  • Effect: Employment growth

Connections:

  • meaning → protocol
  • actor → protocol
  • resource → protocol
  • norm → protocol
  • protocol → effect

This is MVP, which can be shown.

Commands to run

package.json

Dependency

{

  "dependencies": {

    "@xyflow/react": "^12",

    "neo4j-driver": "^6",

    "next": "^15",

    "react": "^19",

    "react-dom": "^19",

    "zustand": "^5"

  },

  "devDependencies": {

    "@types/node": "^22",

    "@types/react": "^19",

    "typescript": "^5"

  }

}

The ?xyflow/react package is the current name for React Flow, and the Neo4j JS driver is now in the current 6.x branch. 

.env.local

NEO4J_URI=neo4j://localhost

NEO4J_USERNAME=neo4j

NEO4J_PASSWORD=your_password

NEO4J_DATABASE=neo4j

What will you have after that?

After implementing this framework, you will have:

  • The interactive graph,
  • the castes,
  • right panel inspector,
  • download data from Neo4j,
  • creating nodes and connections through API,
  • basis for scenario, diagnosis and simulation.

This is no longer a concept. This is a living product.


Below I give the already assembled frame of the system: what is each module, what are the custom nodes, how to arrange an interactive graph, and code that can be immediately put in MVP. For React Flow, the current package is ?xyflow/react; custom nodes there are made as normal React components, transmitted via nodeTypes, and connection points are specified via <Handle />. For Neo4j, it is now normal to use the official JS driver and driver.executeQuery() with parameters and an explicit indication of the base. 

Definitions of system modules

We recommend that the first working version capture 10 modules.

Graph Core

heart of the system. Stores nodes, connections, user choices, modes, filters, layout, and interface events. What he does:

  • keeps the current state of the graph
  • Updates nodes and connections
  • Tells UI what is selected
  • Repainting

Node Registry

Registry of node types. What he does:

  • binds node.type → React component
  • Specifies how to render meaning, actor, resource, protocol, and so on
  • allows you to expand the system without rewriting the canvas

Edge Registry. Register of connections. What he does:

  • Defines the visuals and behavior of connections
  • sets styles for intent, cooperation, feedback, evolution
  • later may include custom ribs with signatures and risk indicators

Protocol Engine

This is the key module EQUILIBRIUM. What he does:

  • Collects multi-dimensional action from multiple entities
  • actually implements the "hyperrebro" as a separate protocol node
  • connects meaning, actors, resources, norms and effects into one circuit

For MVP, this is the best path, because React Flow works by its nature with conventional edges between two nodes; a multidimensional connection is more convenient to imagine as a separate hub node. 

Inspector Panel. Right panel. What he does:

  • shows the passport of the selected node
  • shows the passport of the selected connection
  • allows you to edit attributes
  • displays metrics, risks, ethics, load

Filter & Views. Left panel. What he does:

  • filters by node types
  • Filters by state
  • filters by types of links
  • switches modes: review, diagnosis, scenario, action

Timeline & Events. Bottom panel. What he does:

  • Shows events in time
  • Allows you to lose count status
  • In the future, it will simulate scenarios.

Diagnostics Engine. Diagnostics of the system. What he does:

  • looking for overloaded nodes
  • Finds conflict links
  • Highlighting the crisis areas
  • calculates “holes” in the contour: for example, there is a process, but there is no norm

Persistence Layer. Layer of storage.. What he does:

  • reads and writes the graph in Neo4j
  • Saves node positions
  • preserves metrics and states
  • gives JSON to the front

AI/Rule Layer. Layer of recommendations. What he does:

  • offers new connections
  • Warns of risks
  • the Simple Heuristics
  • explains why the system marks the node as a crisis

Types of system nodes

You do not need 30 types to start. It needs 7.

export type EQNodeType =

  | 'meaning' // meaning

  | 'actor' // subject

  | 'resource' // resource

  | 'norm' // norm

  | 'effect' // effect

  | 'protocol' // hyperrebro as hub node

  | 'metric'; // metric


Meaning. Meaning, mission, value. Answers the question: “Why?”

Actor. Person, organization, agent, co-operative. Answers the question: “who”.

Resource. Money, fund, data, energy, time. Answers the question: “at the expense of what.”

Norm. Regulations, rules, contract, law. Answers the question: “What are the limitations?”

Effect Result. Answers the question: “What happened?”

Protocol. Central working module. This is the implementation of multidimensional communication. He gathers around himself the other entities.

Metric. Result meter: confidence index, sustainability index, employment index.

Types of Connections

export type EQEdgeType =

  | 'intent'

  | 'input'

  | 'constraint'

  | 'output'

  | 'feedback'

  | 'evolution';

Meaning:

  • intent - meaning is included in the protocol
  • input — resource or actor participates in the protocol
  • contrast - the norm limits the protocol
  • output - the protocol creates an effect
  • feedback - the effect affects back
  • evolution — change of state in time

Basic types of data

types/equilibrium.ts

export type EQState =

  | 'harmony'

  | 'tension'

  | 'crisis'

  | 'growth'

  | 'transformation'

  | 'unknown';


export type EQNodeType =

  | 'meaning'

  | 'actor'

  | 'resource'

  | 'norm'

  | 'effect'

  | 'protocol'

  | 'metric';


export type EQEdgeType =

  | 'intent'

  | 'input'

  | 'constraint'

  | 'output'

  | 'feedback'

  | 'evolution';


export interface EQMetrics {

  trust?: number;

  load?: number;

  activity?: number;

  influence?: number;

  stability?: number;

  ethics?: number;

  value?: number;

}


export interface EQNodeData {

  label: string;

  nodeType: EQNodeType;

  description?: string;

  state: EQState;

  metrics: EQMetrics;

  tags?: string[];

}


export interface EQNode {

  id: string;

  type: EQNodeType;

  position: { x: number; y: number };

  data: EQNodeData;

}


export interface EQEdgeData {

  label: string;

  edgeType: EQEdgeType;

  state: EQState;

  meaning?: string;

  risk?: number;

  intensity?: number;

}


export interface EQEdge {

  id: string;

  source: string;

  target: string;

  type?: string;

  data: EQEdgeData;

}


export interface EQGraph {

  nodes: EQNode[];

  edges: EQEdge[];

}

Custom nodes: description

In React Flow, a custom node is a normal React component that receives NodeProps. A node can be made with multiple Handles, with interactive controls and its own UI. This is an officially supported model. Below is a set of nodes that is already similar to the system.

MeaningNode. Meaningful knot. Usually small, bright, with one entrance and one exit.

ActorNode. Subject Node. Shows trust, activity and load.

ResourceNode. Resource node. Usually compact, with an indicator of availability.

NormNode. Normative knot. Shows the type of restriction.

EffectNode. Node result. Shows influence and sustainability.

MetricNode. Measurement node. It is used as an evaluation indicator.

ProtocolNode. The most important. Central hub. It has several connection points:

  • The Left Meaning
  • The actors from above
  • Bottom line resources
  • Right Effects
  • separate handle for standards
  • separate handle for metrics

Basic Common Node Container

components/graph/nodes/NodeShell.tsx

'use client';


import { ReactNode } from 'react';


type Props = {

  title: string;

  subtitle: string;

  children?: ReactNode;

  glow?: string;

};


export default function NodeShell({ title, subtitle, children, glow }: Props) {

  return (

    <div

      style={{

        minWidth: 190,

        borderRadius: 18,

        padding: 12,

        background: '#121826',

        border: '1px solid #1F2633',

        boxShadow: glow || '0 0 18px rgba(255,255,255,0.08)',

        color: '#fff',

      }}

    >

      <div

        className="drag-handle"

        style={{

          fontSize: 11,

          opacity: 0.65,

          marginBottom: 6,

          cursor: 'grab',

        }}

      >

        {subtitle}

      </div>

      <div style={{ fontWeight: 700, fontSize: 15 }}>{title}</div>

      <div style={{ marginTop: 10 }}>{children}</div>

    </div>


Custom Nodes: Code

MeaningNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function MeaningNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="The Meaning"

      glow="0 0 24px rgba(123, 97, 255, 0.24)"

    >

      <div style={{ fontSize: 12, opacity: 0.85 }}>

        Ethics: ?data.metrics?.ethics ?? '-'

      </div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

ActorNode.tsx


'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function ActorNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Subject"

      glow="0 0 24px rgba(77, 163, 255, 0.20)"

    >

      <div style={{ fontSize: 12 }}>Trust: ?data.metrics?.trust ?? '-'</div>

      <div style={{ fontSize: 12 }}>Activity: ?data.metrics?.activity ?? '-'</div>

      <div style={{ fontSize: 12 }}>Load: ?data.metrics?.load ?? '-'</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

ResourceNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function ResourceNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Resource"

      glow="0 0 24px rgba(255, 200, 87, 0.18)"

    >

      <div style={{ fontSize: 12 }}>Availability: ?data.metrics?.value ?? '-'</div>

      <div style={{ fontSize: 12 }}>Stability: ?data.metrics?.stability ?? '-'</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

NormNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function NormNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Norma"

      glow="0 0 24px rgba(255,255,255,0.12)"

    >

      <div style={{ fontSize: 12 }}>{data.description || 'Rules/Restrictions'</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

EffectNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function EffectNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="effect"

      glow="0 0 24px rgba(61, 220, 151, 0.22)"

    >

      <div style={{ fontSize: 12 }}>Influence: ?data.metrics?.influence ?? '-'</div>

      <div style={{ fontSize: 12 }}>Sustainability: ?data.metrics?.stable ?? '-'</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

}

MetricNode.tsx

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';

import NodeShell from './NodeShell';


export default function MetricNode({ data }: NodeProps) {

  return (

    <NodeShell

      title={data.label}

      subtitle="Metrica"

      glow="0 0 24px rgba(80, 180, 255, 0.16)"

    >

      <div style={{ fontSize: 12 }}>Value: ?data.metrics?.value ?? '-'</div>


      <Handle type="target" position={Position.Left} id="in" />

      <Handle type="source" position={Position.Right} id="out" />

    </NodeShell>

  );

ProtocolNode.tsx. It's the main node.

'use client';


import { Handle, Position, NodeProps } from '@xyflow/react';


export default function ProtocolNode({ data }: NodeProps) {

  return (

    <div

      style={{

        minWidth: 260,

        borderRadius: 20,

        padding: 14,

        background: '#161d2d',

        border: '1px solid #2a3550',

        boxShadow: '0 0 28px rgba(123, 97, 255, 0.28)',

        color: '#fff',

      }}

    >

      <div className="drag-handle" style={{ fontSize: 11, opacity: 0.65, marginBottom: 6 }}>

        Protocol / Hyperrebro

      </div>


      <div style={{ fontSize: 16, fontWeight: 800 }}>{data.label}</div>


      <div style={{ marginTop: 10, fontSize: 12, opacity: 0.9 }}>

        || 'Multidimensional contour of action'

      </div>


      <div

        style={{

          display: 'grid',

          gridTemplateColumns: '1fr 1fr',

          gap: 6,

          marginTop: 10,

          fontSize: 12,

        }}

      >

        <div>Ethics: .data.metrics?.ethics ?? '-'Zqqq</div>

        <div>Load: ?? '-'-'--</div>

        <div>Stability: ?data.metrics?.stability ?? '-'QQ</div>

        <div>Activity: ??? '-'-'--</div>

      </div>


      <Handle type="target" position={Position.Left} id="meaning" style={{ top: '20%' }} />

      <Handle type="target" position={Position.Top} id="actors" style={{ left: '35%' }} />

      <Handle type="target" position={Position.Bottom} id="resources" style={{ left: '35%' }} />

      <Handle type="target" position={Position.Left} id="norms" style={{ top: '75%' }} />


      <Handle type="source" position={Position.Right} id="effects" style={{ top: '35%' }} />

      <Handle type="source" position={Position.Right} id="metrics" style={{ top: '75%' }} />

    </div>

  );


A node with multiple Handles is fully consistent with the React Flow model, where you can have as many source/target connections points as you want in custom nodes. 

Registry of Custom Nodes

components/graph/nodes/index.ts

import MeaningNode from './MeaningNode';

import ActorNode from './ActorNode';

import ResourceNode from './ResourceNode';

import NormNode from './NormNode';

import EffectNode from './EffectNode';

import MetricNode from './MetricNode';

import ProtocolNode from './ProtocolNode';


export const nodeTypes = {

  meaning: MeaningNode,

  actor: ActorNode,

  resource: ResourceNode,

  norm: NormNode,

  effect: EffectNode,

  metric: MetricNode,

  protocol: ProtocolNode,

};

Interactive graph: canvas code

<ReactFlow /> is the main component that renders nodes and edges, handles interactions, and works with state. This is the official centerpiece of the library. 

components/graph/EquilibriumCanvas.tsx


'use client';


import { useEffect, useMemo, useState } from 'react';

import {

  ReactFlow,

  ReactFlowProvider,

  Background,

  MiniMap,

  Controls,

  addEdge,

  useNodesState,

  useEdgesState,

  Connection,

  Edge,

  NodeMouseHandler,

  EdgeMouseHandler,

  BackgroundVariant,

} from '@xyflow/react';

import '@xyflow/react/dist/style.css';


import { nodeTypes } from './nodes';

import { EQGraph } from '@/types/equilibrium';


type Props = {

  graph: EQGraph;

  onSelectNode?: (node: any) => void;

  onSelectEdge?: (edge: any) => void;

};


function edgeStyleByState(state?: string) {

  switch (state) {

    case 'growth':

      return { stroke: '#4DA3FF', strokeWidth: 2.5 };

    case 'crisis':

      return { stroke: '#FF4D4F', strokeWidth: 2.5 };

    case 'harmony':

      return { stroke: '#3DDC97', strokeWidth: 2.5 };

    case 'tension':

      return { stroke: '#FFC857', strokeWidth: 2.5 };

    default:

      return { stroke: '#76839a', strokeWidth: 1.8 };

  }

}


function CanvasInner({ graph, onSelectNode, onSelectEdge }: Props) {

  const initialNodes = useMemo(() => graph.nodes, [graph.nodes]);


  const initialEdges = useMemo(

    () =>

      graph.edges.map((e) => ({

        ...e,

        animated: e.data.state === 'growth' || e.data.edgeType === 'feedback',

        style: edgeStyleByState(e.data.state),

        label: e.data.label,

      })),

    [graph.edges]

  );


  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);

  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges as Edge[]);

  const [selectedId, setSelectedId] = useState<string | null>(null);


  useEffect(() => setNodes(initialNodes), [initialNodes, setNodes]);

  useEffect(() => setEdges(initialEdges as Edge[]), [initialEdges, setEdges]);


  const onConnect = (connection: Connection) => {

    setEdges((eds) =>

      addEdge(

        {

          ...connection,

          type: 'smoothstep',

          data: {

            label: 'New connection',

            edgeType: 'input',

            state: 'unknown',

          },

          style: edgeStyleByState('unknown'),

        },

        eds

      )

    );

  };


  const handleNodeClick: NodeMouseHandler = (_, node) => {

    setSelectedId(node.id);

    onSelectNode?.(node);

  };


  const handleEdgeClick: EdgeMouseHandler = (_, edge) => {

    setSelectedId(edge.id);

    onSelectEdge?.(edge);

  };


  return (

    <div style={{ width: '100%', height: '100%' }}>

      <ReactFlow

        nodes={nodes.map((n) => ({

          ...n,

          selected: n.id === selectedId,

          dragHandle: '.drag-handle',

        }))}

        edges={edges}

        nodeTypes={nodeTypes}

        onNodesChange={onNodesChange}

        onEdgesChange={onEdgesChange}

        onConnect={onConnect}

        onNodeClick={handleNodeClick}

        onEdgeClick={handleEdgeClick}

        fitView

      >

        <MiniMap />

        <Controls />

        <Background variant={BackgroundVariant.Dots} gap={20} size={1} />

      </ReactFlow>

    </div>

  );

}


export default function EquilibriumCanvas(props: Props) {

  return (

    <ReactFlowProvider>

      <CanvasInner {...props} />

    </ReactFlowProvider>

  );

}

Example of an interactive screen

app/page.tsx

'use client';


import { useEffect, useState } from 'react';

import EquilibriumCanvas from '@/components/graph/EquilibriumCanvas';

import { EQGraph } from '@/types/equilibrium';


export default function HomePage() {

  const [graph, setGraph] = useState<EQGraph>({ nodes: [], edges: [] });

  const [selectedNode, setSelectedNode] = useState<any>(null);

  const [selectedEdge, setSelectedEdge] = useState<any>(null);


  useEffect(() => {

    fetch('/api/graph')

      .then((r) => r.json())

      .then(setGraph)

      .catch(console.error);

  }, []);


  return (

    <main

      style={{

        display: 'grid',

        gridTemplateColumns: '260px 1fr 400px',

        gridTemplateRows: '64px 1fr 120px',

        height: '100vh',

        background: '#0B0F1A',

        color: '#fff',

      }}

    >

      <header

        style={{

          gridColumn: '1 / 4',

          borderBottom: '1px solid #1F2633',

          display: 'flex',

          alignItems: 'center',

          padding: '0 16px',

          fontWeight: 800,

        }}

      >

        EQUILIBRIUM

      </header>


      <aside style={{ borderRight: '1px solid #1F2633', padding: 16 }}>

        <h3>Filters</h3>

        <div>Node types</div>

        <div>Status</div>

        <div>Link types</div>

      </aside>


      <section style={{ position: 'relative' }}>

        <EquilibriumCanvas

          graph={graph}

          onSelectNode={(node) => {

            setSelectedEdge(null);

            setSelectedNode(node);

          }}

          onSelectEdge={(edge) => {

            setSelectedNode(null);

            setSelectedEdge(edge);

          }}

        />

      </section>


      <aside style={{ borderLeft: '1px solid #1F2633', padding: 16, overflow: 'auto' }}>

        SelectNode & & !selectedEdge && <div>Select node or link</div>


        {selectedNode && (

          <>

            <h3>{selectedNode.data.label}</h3>

            <div>Type: {selectedNode.data.nodeType}</div>

            <div>Status: {selectedNode.data.state}</div>

            <div style={{ marginTop: 12, fontSize: 13, opacity: 0.9 }}>

              {selectedNode.data.description || 'No description'}

            </div>

            <pre style={{ marginTop: 12, fontSize: 12, whiteSpace: 'pre-wrap' }}>

              {JSON.stringify(selectedNode.data.metrics, null, 2)}

            </pre>

          </>

        )}


        {selectedEdge && (

          <>

            <h3>{selectedEdge.data.label}</h3>

            <div>Type: {selectedEdge.data.edgeType}</div>

            <div>Status: {selectedEdge.data.state}</div>

            <div>Meaning: ?selectedEdge.data.meaning || '-'}</div>

            <div>Risk: ? ? '-'-'--</div>

            <div>Intensity: ? '-'-'--</div>

          </>

        )}

      </aside>


      <footer

        style={{

          gridColumn: '1 / 4',

          borderTop: '1px solid #1F2633',

          padding: 16,

        }}

      >

        Timeline / Events / Simulation

      </footer>

    </main>

  );

}


Graph example for EQUILIBRIUM. This is no longer an abstraction, but a working scenario.

lib/mockGraph.ts

import { EQGraph } from '@/types/equilibrium';


export const mockGraph: EQGraph = {

  nodes: [

    {

      id: 'meaning-1',

      type: 'meaning',

      position: { x: 60, y: 220 },

      data: {

        label: 'Development of competences',

        nodeType: 'meaning',

        description: "The growth of the human potential of the region",

        state: 'growth',

        metrics: { ethics: 0.92 },

      },

    },

    {

      id: 'actor-1',

      type: 'actor',

      position: { x: 280, y: 60 },

      data: {

        label: 'Cooperative FENIX',

        nodeType: 'actor',

        state: 'harmony',

        metrics: { trust: 0.82, activity: 0.77, load: 0.42 },

      },

    },

    {

      id: 'resource-1',

      type: 'resource',

      position: { x: 280, y: 360 },

      data: {

        label: 'Educational Fund',

        nodeType: 'resource',

        state: 'harmony',

        metrics: { value: 0.76, stability: 0.66 },

      },

    },

    {

      id: 'norm-1',

      type: 'norm',

      position: { x: 280, y: 500 },

      data: {

        label: 'Program Regulations',

        nodeType: 'norm',

        description: 'Transparency of selection and accessibility of participation',

        state: 'harmony',

        metrics: {},

      },

    },

    {

      id: 'protocol-1',

      type: 'protocol',

      position: { x: 640, y: 230 },

      data: {

        label: 'Cooperative Education Protocol',

        nodeType: 'protocol',

        description: 'Contour: training → participation → employment',

        state: 'growth',

        metrics: {

          ethics: 0.9,

          load: 0.58,

          stability: 0.71,

          activity: 0.79,

        },

      },

    },

    {

      id: 'effect-1',

      type: 'effect',

      position: { x: 1020, y: 180 },

      data: {

        label: 'Employment growth',

        nodeType: 'effect',

        state: 'growth',

        metrics: { influence: 0.74, stability: 0.69 },

      },

    },

    {

      id: 'metric-1',

      type: 'metric',

      position: { x: 1020, y: 340 },

      data: {

        label: 'Employment Index',

        nodeType: 'metric',

        state: 'harmony',

        metrics: { value: 0.63 },

      },

    },

  ],

  edges: [

    {

      id: 'e1',

      source: 'meaning-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Intention',

        edgeType: 'intent',

        state: 'growth',

        meaning: 'The meaning is included in the protocol',

        intensity: 0.88,

      },

    },

    {

      id: 'e2',

      source: 'actor-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Participation',

        edgeType: 'input',

        state: 'harmony',

        meaning: 'Subject executes protocol',

        intensity: 0.72,

      },

    },

    {

      id: 'e3',

      source: 'resource-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Resource',

        edgeType: 'input',

        state: 'harmony',

        intensity: 0.68,

      },

    },

    {

      id: 'e4',

      source: 'norm-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Restriction',

        edgeType: 'constraint',

        state: 'harmony',

        meaning: 'Norm limits and directs',

      },

    },

    {

      id: 'e5',

      source: 'protocol-1',

      target: 'effect-1',

      type: 'smoothstep',

      data: {

        label: 'Result',

        edgeType: 'output',

        state: 'growth',

        intensity: 0.81,

      },

    },

    {

      id: 'e6',

      source: 'protocol-1',

      target: 'metric-1',

      type: 'smoothstep',

      data: {

        label: 'Measurement',

        edgeType: 'output',

        state: 'harmony',

      },

    },

    {

      id: 'e7',

      source: 'effect-1',

      target: 'protocol-1',

      type: 'smoothstep',

      data: {

        label: 'Feedback',

        edgeType: 'feedback',

        state: 'harmony',

        risk: 0.21,

      },

    },

  ],

};

Neo4j: storage scheme

The official JS driver Neo4j recommends using parameterized queries, and executeQuery() itself creates a transaction. It is also useful to always explicitly specify a database to reduce overhead. 

lib/neo4j.ts

import neo4j from 'neo4j-driver';


const uri = process.env.NEO4J_URI!;

const username = process.env.NEO4J_USERNAME!;

const password = process.env.NEO4J_PASSWORD!;

const database = process.env.NEO4J_DATABASE || 'neo4j';


export const driver = neo4j.driver(uri, neo4j.auth.basic(username, password));

export { database };

cypher/schema.cypher

CREATE CONSTRAINT eqnode_id IF NOT EXISTS

FOR (n:EQNode)

REQUIRE n.id IS UNIQUE;

app/api/graph/route.ts

import { NextResponse } from 'next/server';

import { driver, database } from '@/lib/neo4j';


export async function GET() {

  const query = `

    MATCH (n:EQNode)

    OPTIONAL MATCH (n)-[r:RELATES_TO]->(m:EQNode)

    RETURN

      collect(DISTINCT {

        id: n.id,

        type: n.nodeType,

        position: { x: n.x, y: n.y },

        data: {

          label: n.label,

          nodeType: n.nodeType,

          description: n.description,

          state: n.state,

          metrics: {

            trust: n.trust,

            load: n.load,

            activity: n.activity,

            influence: n.influence,

            stability: n.stability,

            ethics: n.ethics,

            value: n.value

          }

        }

      }) AS nodes,

      collect(DISTINCT CASE WHEN r IS NULL THEN NULL ELSE {

        id: r.id,

        source: startNode(r).id,

        target: endNode(r).id,

        type: 'smoothstep',

        data: {

          label: r.label,

          edgeType: r.edgeType,

          state: r.state,

          meaning: r.meaning,

          risk: r.risk,

          intensity: r.intensity

        }

      } END) AS edges

  `;


  const result = await driver.executeQuery(query, {}, { database });

  const record = result.records[0];


  const nodes = record.get('nodes');

  const edges = record.get('edges').filter(Boolean);


  return NextResponse.json({ nodes, edges });

}


Diagnosis: Basic rules. This is not AI, but a rigid logic. That's where we should start.

lib/diagnostics.ts

import { EQGraph } from '@/types/equilibrium';


export interface DiagnosticItem {

  type: 'warning' | 'error' | 'info';

  targetId: string;

  message: string;

}


export function runDiagnostics(graph: EQGraph): DiagnosticItem[] {

  const result: DiagnosticItem[] = [];


  for (const node of graph.nodes) {

    if (node.type === 'protocol') {

      const incoming = graph.edges.filter((e) => e.target === node.id);

      const outgoing = graph.edges.filter((e) => e.source === node.id);


      const hasMeaning = incoming.some((e) => e.data.edgeType === 'intent');

      const hasActor = incoming.some((e) => e.source.startsWith('actor-'));

      const hasResource = incoming.some((e) => e.source.startsWith('resource-'));

      const hasNorm = incoming.some((e) => e.data.edgeType === 'constraint');

      const hasOutput = outgoing.some((e) => e.data.edgeType === 'output');


      if (!hasMeaning) {

        result.push({

          type: 'error',

          targetId: node.id,

          message: 'The protocol has no semantic basis',

        });

      }


      if (!hasActor) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'The protocol has no executing entity',

        });

      }


      if (!hasResource) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'The protocol has no resource input',

        });

      }


      if (!hasNorm) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'The protocol has no regulatory limit',

        });

      }


      if (!hasOutput) {

        result.push({

          type: 'error',

          targetId: node.id,

          message: 'The protocol has no result',

        });

      }


      if ((node.data.metrics.load ?? 0) > 0.85) {

        result.push({

          type: 'warning',

          targetId: node.id,

          message: 'Protocol overloaded',

        });

      }

    }

  }


  return result;

}

What is an "interactive graph" in fact

Not just a canvas with nodes, but 7 functions:

  • Drag
    Moving nodes.
  • Connect
    Create a new connection through hands.
  • Select
    Selecting a node or connection.
  • Inspect
    Displays the content on the right.
  • Filter
    Leave only the desired types on the screen.
  • Diagnose
    Highlight mistakes and risks.
  • Evolve
    Add a new protocol, resource, norm, metric.

React Flow is designed for node-based editors and interactive diagrams; the documentation separately describes the customization of nodes, hands, theming, and examples. 


What else you need to add to version 2

It's on the case, no jewelry. The following modules are required:

  • LeftFilters.tsx
  • RightInspector.tsx
  • BottomTimeline.tsx
  • CreateProtocolModal.tsx
  • DiagnosticsPanel.tsx

And the following improvements:

  • Maintaining node positions in the database
  • Form of creating protocol
  • Editing metrics
  • Layout algorithm
  • Highlighting Conflict Relationships
  • SSR-safe configuration if there is a server render; in React Flow for SSR separately there is NodeHandle when DOM is not yet measured.  

How to build it in MVP

Minimum stack:

  • Next.js
  • TypeScript
  • @xyflow/react
  • Neo4j
  • Zustand

Minimal entities:

  • 7 node types
  • 6 types of links
  • 1 Screen
  • 1 right panel
  • 1 Diagnostics
  • 1 script

Minimum scenario:

  • meaning
  • Subject
  • resource
  • Norm
  • Protocol
  • effect
  • metrics

This will look like a real operating panel, and not as a scheme “for presentation”.

The most important architectural conclusion

The hyperrebro in EQUILIBRIUM should not be attempted to be forcibly drawn as a magical super-line.

For a working interface, it must be done as a protocol node around which all participants in the process gather.

This decision:

  • Does not break React Flow,
  • normally stored in Neo4j,
  • easily diagnosed,
  • It is easy to expand,
  • It looks good in UI.

This is the best way to make the system real.

Further, it is logical to collect a full set of project files: package.json, tsconfig, app/page.tsx, all node components, API-routs, seed for Neo4j and CSS, so that it can be simply inserted into the repository and run.