All reports
by deep-research

tiptap graph visualization neuron mvp

Research: TipTap 3.x + Graph Visualization for Neuron Desktop MVP

MOKA-345 | Priority: High | Project: Research Date: 2026-03-20 Author: Deep Research Agent Supports: Neuron — AI-Powered Team Knowledge Graph


Executive Summary

Neuron’s MVP requires two core UI components: a rich-text editor with wiki-style [[backlinks]] and an interactive knowledge graph visualization. This report evaluates TipTap’s extension ecosystem for wiki-link support and compares graph visualization libraries for rendering 1K-10K node graphs in a Tauri desktop WebView.

Recommendations:

  1. Editor: TipTap with Mention extension configured for [[ trigger + custom WikiLink node type
  2. Graph: Sigma.js (2D WebGL) for the main graph view, with d3-force for layout computation in a Web Worker
  3. Backlink storage: SQLite links table with source/target + FTS5 for content search
  4. Architecture: Decouple layout computation (Web Worker) from rendering (WebGL) for smooth 60fps interaction

1.1 Architecture Overview

TipTap is built on ProseMirror, providing a headless, framework-agnostic rich-text editor. Key concepts for Neuron:

  • Extensions — Modular plugins (nodes, marks, functionality)
  • Node views — Custom React components rendered inline in the document
  • Suggestion utility — Powers autocomplete popups (mentions, slash commands, wiki-links)

The recommended approach uses TipTap’s built-in Mention extension with [[ as the trigger character:

import { Mention } from '@tiptap/extension-mention'
import { ReactRenderer } from '@tiptap/react'

const WikiLink = Mention.configure({
  HTMLAttributes: { class: 'wiki-link' },
  renderLabel: ({ node }) => `[[${node.attrs.label}]]`,
  suggestion: {
    char: '[[',
    items: async ({ query }) => {
      // Query SQLite for matching note titles
      return await searchNotes(query)
    },
    render: () => {
      // Custom React popup component
      let component: ReactRenderer
      return {
        onStart: (props) => {
          component = new ReactRenderer(WikiLinkPopup, { props, editor: props.editor })
        },
        onUpdate: (props) => component.updateProps(props),
        onExit: () => component.destroy(),
      }
    },
  },
})

1.3 Existing Community Extensions

ExtensionStatusNotes
tiptap-wikilink-extensionCommunity, not on npmBasic [[wiki-link]] support, installable from GitHub
@tiptap/extension-mentionOfficial, stableConfigurable trigger char, suggestion popup, customizable rendering
@tiptap/extension-linkOfficial, stableStandard hyperlinks (not wiki-style)

Recommendation: Use @tiptap/extension-mention (official, maintained) with custom configuration rather than the community wikilink extension (not on npm, limited maintenance). The Mention extension provides the full suggestion pipeline and is production-ready.

For full control, create a custom TipTap Node:

import { Node, mergeAttributes } from '@tiptap/core'

export const WikiLink = Node.create({
  name: 'wikiLink',
  group: 'inline',
  inline: true,
  atom: true, // Non-editable inline block

  addAttributes() {
    return {
      noteId: { default: null },
      label: { default: '' },
    }
  },

  parseHTML() {
    return [{ tag: 'span[data-wiki-link]' }]
  },

  renderHTML({ HTMLAttributes }) {
    return ['span', mergeAttributes(
      { 'data-wiki-link': '', class: 'wiki-link' },
      HTMLAttributes
    ), `[[${HTMLAttributes.label}]]`]
  },

  addNodeView() {
    return ReactNodeViewRenderer(WikiLinkComponent)
  },
})

1.5 TipTap Pro Extensions (Paid)

TipTap offers Pro extensions that may be relevant:

  • Collaboration (Hocuspocus) — Real-time CRDT sync (useful for future team features)
  • AI Autocompletion — Content generation inline
  • Comments — Inline annotations

For Neuron MVP, the free/open-source extensions are sufficient. Pro extensions become relevant when adding team collaboration.


2. Graph Visualization — Library Comparison

2.1 Library Matrix

LibraryRendererReactMax Nodes (smooth)3DBundle SizeLicense
Sigma.jsWebGL@sigma/react50K+ nodesNo~50KBMIT
ReagraphWebGL (Three.js)Native~10K nodesYes~200KBApache 2.0
react-force-graphCanvas/WebGLNative~10K (2D), ~5K (3D)Yes~150KBMIT
Cytoscape.jsCanvasreact-cytoscapejs~5K nodesNo~400KBMIT
D3-forceSVG/CanvasManual~2K (SVG), ~5K (Canvas)No~30KBISC
CosmographWebGL (GPU)Yes1M+ nodesNo~100KBCommercial

2.2 Performance Benchmarks (WebView Context)

Based on rendering technology research:

TechnologyLimit (smooth 60fps)Notes
SVG~2K nodes, 2K edgesDOM-based, good for small interactive graphs
Canvas~5K nodes, 5K edgesPixel-based, good balance of perf and interaction
WebGL~10K nodes, 11K edgesGPU-accelerated, best for large graphs
WebGL + GPU layout100K+Requires specialized libraries (Cosmograph, PIXI.js)

Tauri WebView considerations:

  • macOS (WKWebView): Excellent WebGL support, hardware acceleration default
  • Windows (WebView2/Chromium): Full WebGL 2.0 support
  • Linux (WebKitGTK): WebGL support varies by system; test with fallback
  • Known issue: Tauri has had WebGL context loss reports on some Windows configurations

Why Sigma.js for Neuron:

  1. Performance — WebGL rendering handles 50K+ nodes, far exceeding Neuron’s needs
  2. React bindings@sigma/react provides clean declarative API
  3. Lightweight — ~50KB vs 200KB+ for Three.js-based alternatives
  4. 2D focus — Knowledge graphs are inherently 2D; 3D adds visual complexity without UX benefit
  5. Edge rendering — Supports curved edges, labels, and custom node shapes
  6. Layout separation — Uses external layout algorithms (d3-force, graphology-layout-forceatlas2)

Architecture pattern:

┌────────────────────────────────────────────┐
│  Main Thread                               │
│  ┌──────────────┐  ┌───────────────────┐   │
│  │ Sigma.js     │  │ React UI          │   │
│  │ (WebGL       │  │ (TipTap editor,   │   │
│  │  rendering)  │  │  sidebar, search) │   │
│  └──────┬───────┘  └───────────────────┘   │
│         │                                   │
│         │ graph data (graphology)           │
│         │                                   │
│  ┌──────┴───────┐                          │
│  │ Graphology    │ ←── In-memory graph      │
│  │ (graph model) │     model                │
│  └──────┬───────┘                          │
│         │                                   │
└─────────┼──────────────────────────────────┘
          │ postMessage
┌─────────┼──────────────────────────────────┐
│  Web Worker                                │
│  ┌──────┴───────┐                          │
│  │ ForceAtlas2   │ ←── Layout computation   │
│  │ (graphology-  │     off main thread      │
│  │  layout)      │                          │
│  └──────────────┘                          │
└────────────────────────────────────────────┘

          │ IPC (Tauri commands)
┌─────────┼──────────────────────────────────┐
│  Rust Backend                              │
│  ┌──────┴───────┐  ┌───────────────────┐   │
│  │ SQLite        │  │ File watcher      │   │
│  │ (notes, links │  │ (markdown files)  │   │
│  │  FTS5 index)  │  │                   │   │
│  └──────────────┘  └───────────────────┘   │
└────────────────────────────────────────────┘

2.4 Alternative: Reagraph (If 3D Desired)

Reagraph provides out-of-the-box React graph visualization with:

  • 2D and 3D modes (Three.js/WebGL)
  • Force-directed, radial, and hierarchical layouts
  • Clustering and selection
  • ~10K node performance

Choose Reagraph over Sigma.js only if the team wants 3D graph exploration as a differentiator.


3.1 Schema Design

-- Notes table
CREATE TABLE notes (
  id TEXT PRIMARY KEY,           -- UUID or nanoid
  title TEXT NOT NULL,
  content TEXT,                  -- TipTap JSON or Markdown
  content_plain TEXT,            -- Plaintext for FTS
  created_at TEXT DEFAULT (datetime('now')),
  updated_at TEXT DEFAULT (datetime('now'))
);

-- Links table (bidirectional via query)
CREATE TABLE links (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  source_id TEXT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
  target_id TEXT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
  context TEXT,                  -- Surrounding text snippet
  position INTEGER,              -- Character offset in source document
  created_at TEXT DEFAULT (datetime('now')),
  UNIQUE(source_id, target_id, position)
);

-- Full-text search
CREATE VIRTUAL TABLE notes_fts USING fts5(
  title,
  content_plain,
  content='notes',
  content_rowid='rowid'
);

-- Indexes
CREATE INDEX idx_links_source ON links(source_id);
CREATE INDEX idx_links_target ON links(target_id);
CREATE INDEX idx_notes_title ON notes(title);
-- Get all backlinks to a note (who links TO this note)
SELECT n.id, n.title, l.context
FROM links l
JOIN notes n ON n.id = l.source_id
WHERE l.target_id = ?;

-- Get all outgoing links from a note
SELECT n.id, n.title, l.context
FROM links l
JOIN notes n ON n.id = l.target_id
WHERE l.source_id = ?;

-- Build graph data (all nodes and edges)
SELECT
  source_id || '→' || target_id AS edge_id,
  source_id,
  target_id
FROM links;

-- Search notes by title (for wiki-link autocomplete)
SELECT id, title FROM notes_fts WHERE title MATCH ? || '*' LIMIT 10;

-- Find orphan notes (no incoming or outgoing links)
SELECT n.id, n.title
FROM notes n
LEFT JOIN links l1 ON l1.source_id = n.id
LEFT JOIN links l2 ON l2.target_id = n.id
WHERE l1.id IS NULL AND l2.id IS NULL;

When a note is saved:

  1. Parse TipTap JSON document for wikiLink nodes
  2. Resolve noteId from title (create note stub if not found)
  3. Delete existing links for this source note
  4. Insert new links with context snippets
  5. Update FTS index
  6. Emit graph update event to frontend
async function extractLinks(noteId: string, doc: JSONContent): Promise<Link[]> {
  const links: Link[] = []

  function walk(node: JSONContent, path: number[] = []) {
    if (node.type === 'wikiLink' && node.attrs?.noteId) {
      links.push({
        sourceId: noteId,
        targetId: node.attrs.noteId,
        context: getContextSnippet(doc, path),
        position: getCharOffset(doc, path),
      })
    }
    node.content?.forEach((child, i) => walk(child, [...path, i]))
  }

  walk(doc)
  return links
}

4. UX Patterns from Obsidian / Logseq / Notion

4.1 Graph View UX Patterns

FeatureObsidianLogseqNotionNeuron (Recommended)
Graph layoutForce-directed (custom)Force-directedN/AForceAtlas2 (graphology)
RendererCanvas (custom)CanvasN/AWebGL (Sigma.js)
Node sizingBy link countBy link countN/ABy link count + recency
Node coloringBy folder/tagBy namespaceN/ABy tag/cluster
Edge displayLinesLinesN/ACurved lines + labels
Local graphYes (per-note)Yes (per-page)N/AYes (1-2 hop radius)
Global graphYes (entire vault)Yes (entire graph)N/AYes (with clustering)
FiltersTags, folders, orphansNamespaces, built-inN/ATags, date range, orphans
Search highlightNoNoN/AYes (highlight search results)
AnimationPhysics simulationPhysics simulationN/ASmooth transitions

4.2 Key UX Decisions for Neuron

  1. Local graph by default — Show 1-2 hop neighborhood of current note (less overwhelming than full graph)
  2. Progressive disclosure — Global graph as opt-in view with clustering for large vaults
  3. Node size = link count — Visual weight indicates importance
  4. Hover preview — Show note title + first paragraph on node hover
  5. Click to navigate — Clicking a node opens the note in the editor
  6. Bidirectional panel — Sidebar showing backlinks + outgoing links for current note
  7. Search-to-graph — Search results highlighted on the graph view

4.3 Obsidian’s Graph Architecture (Reference)

Obsidian uses:

  • Custom Canvas renderer (not a third-party library)
  • Force-directed layout with custom physics
  • JSON Canvas spec for spatial canvases (separate from graph view)
  • Plugins can extend graph (Neo4j Graph View, Extended Graph)
  • Performance: handles vaults of 10K+ notes

Neuron can match this with Sigma.js + Graphology while benefiting from:

  • WebGL (faster than Obsidian’s Canvas for large graphs)
  • Graphology’s rich algorithm library (centrality, community detection, PageRank)
  • ForceAtlas2 (better clustering than basic D3-force)

5. Implementation Roadmap for Neuron MVP

  1. Set up TipTap with StarterKit + Markdown extensions
  2. Implement WikiLink node (Mention extension with [[ trigger)
  3. SQLite schema: notes, links, FTS5
  4. Link extraction pipeline (save → parse → store links)
  5. Backlink sidebar panel

Phase 2: Graph View (Weeks 3-4)

  1. Integrate Sigma.js with @sigma/react
  2. Graphology model synced with SQLite link data
  3. ForceAtlas2 layout in Web Worker
  4. Local graph (per-note, 2-hop radius)
  5. Node hover preview + click navigation

Phase 3: Polish (Weeks 5-6)

  1. Global graph with clustering
  2. Tag-based node coloring
  3. Search-to-graph highlighting
  4. Graph filters (date range, orphans, tags)
  5. Keyboard shortcuts (Cmd+K for quick link, Cmd+G for graph)

NPM Dependencies

{
  "@tiptap/react": "^2.11",
  "@tiptap/starter-kit": "^2.11",
  "@tiptap/extension-mention": "^2.11",
  "@tiptap/extension-placeholder": "^2.11",
  "@sigma/react": "^1.0",
  "sigma": "^3.0",
  "graphology": "^0.25",
  "graphology-layout-forceatlas2": "^0.10",
  "graphology-communities-louvain": "^2.0",
  "graphology-metrics": "^2.0"
}

Rust Dependencies (Tauri Backend)

[dependencies]
rusqlite = { version = "0.32", features = ["bundled"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
notify = "7"  # File system watcher

6. Risk Assessment

RiskLikelihoodImpactMitigation
Sigma.js WebGL context loss on WindowsLow-MediumMediumImplement Canvas fallback; test on WebView2
TipTap Mention [[ double-char trigger edge casesMediumLowThorough testing; custom input rule as fallback
ForceAtlas2 slow for 10K+ nodesLowMediumWeb Worker + progressive loading; limit visible nodes
SQLite lock contention (concurrent reads/writes)LowMediumWAL mode; batch link updates
Graph layout jitter on data updatesMediumLowAnimate transitions; freeze layout on interaction

Sources