All reports
Technology by deep-research

Neuron MVP Technical Blueprint -- Synthesized Architecture from SQLite, TipTap, and Knowledge Graph Research

Neuron

Neuron MVP Technical Blueprint

Executive Summary

This blueprint synthesizes four prior research reports and extensive external benchmarking into an actionable architecture for Neuron’s MVP. It provides specific schema definitions, component architecture, performance expectations backed by independent benchmarks, and a phased implementation roadmap.

Prior research synthesized:

  1. Local-First SQLite Architecture Patterns (MOKA-350)
  2. TipTap 3.x + Graph Visualization (MOKA-345)
  3. PKM & Knowledge Graph Tools Landscape (MOKA-338)
  4. Apple Core AI & Foundation Models (MOKA-326)

MVP scope: Desktop note-taking app with wiki-links, knowledge graph visualization, full-text search, daily notes, and sidebar navigation. Local-first, AI-enhanced.

Go/No-Go Recommendation: GO for individual PKM MVP. The architecture choices (SQLite + TipTap + Sigma.js + Tauri) are validated by independent benchmarks and production adoption. The primary technical risk is team sync (CRDT), which should be deferred to post-MVP.


1. Should Moklabs Build This?

Verdict: GO for the individual desktop MVP.

Technical validation:

  • SQLite handles PKM-scale workloads (10K-100K notes) with sub-millisecond CRUD and <10ms FTS5 search (SQLite benchmarks; Phiresky)
  • TipTap has 33.6K GitHub stars and 13.5M monthly NPM downloads, making it the most adopted headless rich-text editor (GitHub)
  • Tauri apps start in <500ms with ~2.5MB installers vs Electron’s 1-2s startup and 85MB+ installers (Hopp, 2025; Levminer)
  • Sigma.js renders 100K edges with default styles and is the most production-ready React graph option (Sigma.js; MENUDO, 2025)

Technical risks that don’t block MVP:

  • CRDT team sync is unsolved at scale, but MVP is individual-first — no sync needed
  • On-device AI (Apple Foundation Models) is cutting-edge, but MVP entity extraction can use regex/heuristics first

2. What Specifically Would We Build?

2.1 Architecture Overview

+------------------------------------------------------+
|                    Tauri Shell                        |
|  +------------------------------------------------+  |
|  |              React Frontend (WebView)           |  |
|  |  +----------+ +-----------+ +--------------+   |  |
|  |  | Sidebar  | |  Editor   | | Graph View   |   |  |
|  |  | - tree   | |  (TipTap) | | (Sigma.js)   |   |  |
|  |  | - search | |  - wiki   | | - WebGL      |   |  |
|  |  | - daily  | |  - slash  | | - d3-force   |   |  |
|  |  | - recent | |  - blocks | | - Web Worker |   |  |
|  |  +----------+ +-----------+ +--------------+   |  |
|  +----------------------+-------------------------+   |
|                         | Tauri Commands (IPC)        |
|  +----------------------+-------------------------+   |
|  |              Rust Backend                       |  |
|  |  +----------+ +-----------+ +--------------+   |  |
|  |  | DB Layer | | AI Engine | | File Watcher |   |  |
|  |  | rusqlite | | (future)  | | (import)     |   |  |
|  |  +----------+ +-----------+ +--------------+   |  |
|  +------------------------------------------------+  |
+------------------------------------------------------+

Why Tauri over Electron:

MetricTauriElectronSource
Startup time<500ms1-2sHopp, 2025
Installer size~2.5MB~85MBLevminer
Idle memory30-40MB100-300MBRaftLabs, 2025
Market share35% YoY growth (2025)60% of cross-platform appsCodeology, 2025
Backend languageRustNode.js
Security modelIPC allowlistFull Node.js accessPeerlist

Tauri’s Rust backend enables direct rusqlite integration without FFI overhead, and its IPC allowlist model is more secure for a local-first app handling user knowledge.

2.2 Frontend Stack

ComponentLibraryRationaleAdoption Evidence
FrameworkReact 19Tauri default, TipTap first-class React supportDominant framework
EditorTipTap (ProseMirror)Headless, extensible, wiki-link via Mention33.6K GitHub stars, 13.5M NPM monthly downloads (GitHub)
Graph vizSigma.js 3.x + GraphologyWebGL renderer, handles 100K edges”Most production-ready option in 2024-2025” (MENUDO)
Graph layoutd3-force (Web Worker)Offload force computation; sync OK for <500 nodesSigma.js docs
StylingTailwind CSS 4Utility-first, rapid iteration
StateZustandLightweight, Tauri-friendly
RoutingTanStack RouterType-safe, file-based

Counter-argument: Why not Obsidian’s CodeMirror? CodeMirror is code-editor-first and requires more work for rich-text note-taking. TipTap (ProseMirror) is document-editor-first, with built-in Mention extension for wiki-links and better support for block-level content. The trade-off: TipTap’s learning curve is real (“not plug-and-play” per reviews — Product Hunt, 2026), but its flexibility justifies the investment.


3. SQLite Schema (Production-Ready)

3.1 Core Tables

-- Notes (primary content)
CREATE TABLE notes (
    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    title TEXT NOT NULL DEFAULT '',
    content TEXT NOT NULL DEFAULT '',  -- TipTap JSON or Markdown
    content_format TEXT NOT NULL DEFAULT 'tiptap',  -- 'tiptap' | 'markdown'
    is_daily_note INTEGER NOT NULL DEFAULT 0,
    daily_date TEXT,  -- ISO date for daily notes (YYYY-MM-DD)
    parent_id TEXT REFERENCES notes(id) ON DELETE SET NULL,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT NOT NULL DEFAULT (datetime('now')),
    archived_at TEXT,
    word_count INTEGER NOT NULL DEFAULT 0
);

CREATE UNIQUE INDEX idx_notes_daily ON notes(daily_date) WHERE is_daily_note = 1;
CREATE INDEX idx_notes_updated ON notes(updated_at DESC);
CREATE INDEX idx_notes_parent ON notes(parent_id) WHERE parent_id IS NOT NULL;

-- Full-text search (FTS5 with external content)
CREATE VIRTUAL TABLE notes_fts USING fts5(
    title, content,
    content=notes,
    content_rowid=rowid,
    tokenize='porter unicode61 remove_diacritics 2'
);

-- FTS sync triggers
CREATE TRIGGER notes_ai AFTER INSERT ON notes BEGIN
    INSERT INTO notes_fts(rowid, title, content) VALUES (new.rowid, new.title, new.content);
END;
CREATE TRIGGER notes_ad AFTER DELETE ON notes BEGIN
    INSERT INTO notes_fts(notes_fts, rowid, title, content) VALUES('delete', old.rowid, old.title, old.content);
END;
CREATE TRIGGER notes_au AFTER UPDATE ON notes BEGIN
    INSERT INTO notes_fts(notes_fts, rowid, title, content) VALUES('delete', old.rowid, old.title, old.content);
    INSERT INTO notes_fts(rowid, title, content) VALUES (new.rowid, new.title, new.content);
END;

3.2 Knowledge Graph Tables

-- Wiki-links (note-to-note edges)
CREATE TABLE links (
    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 for preview
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    PRIMARY KEY (source_id, target_id)
);

CREATE INDEX idx_links_target ON links(target_id);  -- backlink queries

-- Entities (extracted from note content)
CREATE TABLE entities (
    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    name TEXT NOT NULL,
    entity_type TEXT NOT NULL,  -- 'person', 'company', 'project', 'concept', 'place'
    canonical_name TEXT,  -- normalized for dedup
    metadata TEXT,  -- JSON blob for extra attributes
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE UNIQUE INDEX idx_entities_canonical ON entities(canonical_name, entity_type);

-- Note-entity associations
CREATE TABLE note_entities (
    note_id TEXT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
    entity_id TEXT NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
    confidence REAL NOT NULL DEFAULT 1.0,
    status TEXT NOT NULL DEFAULT 'auto',  -- 'auto', 'accepted', 'dismissed'
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    PRIMARY KEY (note_id, entity_id)
);

-- Entity-entity relations (semantic graph)
CREATE TABLE entity_relations (
    source_entity_id TEXT NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
    target_entity_id TEXT NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
    relation_type TEXT NOT NULL,  -- 'works_at', 'related_to', 'part_of'
    confidence REAL NOT NULL DEFAULT 1.0,
    source_note_id TEXT REFERENCES notes(id) ON DELETE SET NULL,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    PRIMARY KEY (source_entity_id, target_entity_id, relation_type)
);

-- Tags
CREATE TABLE tags (
    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
    name TEXT NOT NULL UNIQUE,
    color TEXT,
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE note_tags (
    note_id TEXT NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
    tag_id TEXT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
    PRIMARY KEY (note_id, tag_id)
);

3.3 PRAGMA Configuration

PRAGMA journal_mode = WAL;          -- concurrent reads during writes
PRAGMA synchronous = NORMAL;         -- balance safety/speed (desktop = battery OK)
PRAGMA foreign_keys = ON;
PRAGMA cache_size = -64000;          -- 64MB page cache (desktop can afford it)
PRAGMA mmap_size = 268435456;        -- 256MB memory-mapped I/O
PRAGMA temp_store = MEMORY;
PRAGMA optimize;                     -- run on app startup

Benchmark justification for WAL mode: In concurrent workloads (4 writers, 8 readers, 10K operations each), WAL mode achieves 4,800-5,100 ops/second vs 1,200-1,400 in standard journal mode — a 3-4x improvement (DEV Community). For a single-user desktop app, WAL is overkill but provides safety for future multi-threaded access patterns.

Important limitation: WAL mode allows only one writer at a time. If concurrent background tasks (entity extraction, link parsing) need to write, they must be serialized or batched. For a single-user desktop app, this is not a bottleneck (SQLite WAL docs).


4. Performance Expectations (Independently Benchmarked)

OperationDatasetExpected LatencySource
Note CRUDAny< 1msSQLite benchmarks — SQLite achieves 2.72ms for SELECT; CRUD on single rows is sub-ms
FTS5 search10K notes< 10msFTS5 benchmarks — FTS5 achieves 140ms on 1M records; 10K is ~100x smaller
FTS5 search1M records~140msFTS5 comparison — 30% faster than FTS3 (200ms)
2-hop graph query10K notes, 50K links< 5msRecursive CTE on indexed tables; SQLite handles 100K SELECTs/s (Phiresky)
Graph rendering<500 nodesSync (instant)Sigma.js ForceAtlas2 runs synchronously for <500 nodes (Sigma.js docs)
Graph rendering500-5K nodes~200ms (Web Worker)d3-force in Web Worker; Sigma.js handles up to 100K edges (Sigma.js)
Graph rendering5K+ nodes with iconsDegradedSigma.js struggles with 5K+ nodes with icons (Ogma comparison)
Entity extractionSingle note< 50ms (regex)Local heuristic extraction; no network latency
Batched writesTransaction50K inserts/sNiharDaily, 2025

Key insight from real-world Elasticsearch replacement: A team replacing Elasticsearch with SQLite FTS5 achieved median latency in single-digit milliseconds — validating FTS5 as sufficient for PKM-scale search (Medium, 2025).

Counter-argument: When SQLite is NOT enough. SQLite lags PostgreSQL in complex JOINs and aggregations (Medium, 2025). If Neuron’s knowledge graph grows to enterprise scale (100K+ entities with multi-hop traversals), SQLite’s single-writer limitation and lack of graph-native query optimizations may become bottlenecks. Mitigation: keep SQLite for MVP; evaluate DuckDB or embedded graph DB if team/enterprise scale demands it.


5. TipTap Editor Configuration

Based on TipTap adoption data (33.6K GitHub stars, 13.5M monthly NPM downloads — GitHub) and the 2025 Liveblocks analysis calling it “the most well-rounded choice” (Liveblocks, 2025):

import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extension-placeholder'
import { Mention } from '@tiptap/extension-mention'
import TaskList from '@tiptap/extension-task-list'
import TaskItem from '@tiptap/extension-task-item'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'

const editor = new Editor({
  extensions: [
    StarterKit.configure({
      heading: { levels: [1, 2, 3] },
      codeBlock: false,  // replaced by CodeBlockLowlight
    }),
    Placeholder.configure({
      placeholder: 'Start writing, or type [[ to link a note...',
    }),
    // Wiki-links via Mention extension
    Mention.configure({
      HTMLAttributes: { class: 'wiki-link' },
      renderLabel: ({ node }) => `[[${node.attrs.label}]]`,
      suggestion: wikiLinkSuggestion,  // custom suggestion config
    }),
    TaskList,
    TaskItem.configure({ nested: true }),
    CodeBlockLowlight.configure({ lowlight }),
  ],
})

Wiki-link suggestion handler:

const wikiLinkSuggestion = {
  char: '[[',
  items: async ({ query }: { query: string }) => {
    // Call Tauri command to search notes via FTS5
    return invoke('search_notes', { query, limit: 10 })
  },
  render: () => wikiLinkPopupRenderer,  // React popup component
}

6. Graph Visualization

Based on Sigma.js 3.x performance analysis and React Sigma production readiness (MENUDO, 2025):

// graph-worker.ts (Web Worker for >500 nodes)
import { forceSimulation, forceLink, forceManyBody, forceCenter } from 'd3-force'

self.onmessage = ({ data: { nodes, edges } }) => {
  const simulation = forceSimulation(nodes)
    .force('link', forceLink(edges).id(d => d.id).distance(80))
    .force('charge', forceManyBody().strength(-200))
    .force('center', forceCenter(0, 0))
    .on('tick', () => {
      self.postMessage({ type: 'tick', nodes: simulation.nodes() })
    })
    .on('end', () => {
      self.postMessage({ type: 'done', nodes: simulation.nodes() })
    })
}
// GraphView.tsx
import Graph from 'graphology'
import Sigma from 'sigma'

function GraphView({ noteId }: { noteId: string }) {
  const containerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const graphData = await invoke('get_note_graph', {
      noteId,
      depth: 2,  // 2-hop neighborhood
    })

    const graph = new Graph()
    graphData.nodes.forEach(n => graph.addNode(n.id, {
      label: n.title,
      x: Math.random(),
      y: Math.random(),
      size: Math.min(5 + n.backlinks * 2, 20),  // size by connectivity
      color: n.id === noteId ? '#3b82f6' : '#64748b',
    }))
    graphData.edges.forEach(e => graph.addEdge(e.source, e.target))

    // Apply force layout via Web Worker (for >500 nodes)
    const worker = new Worker(new URL('./graph-worker.ts', import.meta.url))
    worker.postMessage({ nodes: graphData.nodes, edges: graphData.edges })
    worker.onmessage = ({ data }) => {
      if (data.type === 'tick' || data.type === 'done') {
        data.nodes.forEach(n => {
          graph.setNodeAttribute(n.id, 'x', n.x)
          graph.setNodeAttribute(n.id, 'y', n.y)
        })
      }
    }

    const renderer = new Sigma(graph, containerRef.current!)
    return () => { renderer.kill(); worker.terminate() }
  }, [noteId])

  return <div ref={containerRef} className="w-full h-full" />
}

Performance ceiling: Sigma.js handles 100K edges with default styles but struggles at 5K+ nodes with custom icons (Ogma comparison). For Neuron MVP, the 2-hop neighborhood query will typically return 50-200 nodes, well within comfortable range. If users accumulate 10K+ notes with dense linking, consider implementing virtual viewport rendering or LOD (level-of-detail) for the full graph view.


7. Sidebar & Navigation

7.1 Sidebar Sections

+---------------------+
|  Quick Search        |  <- FTS5 search bar
+---------------------+
|  Today               |  <- Auto-created daily note
|  Yesterday           |
|  2 days ago          |
+---------------------+
|  Favorites           |  <- Pinned notes
+---------------------+
|  All Notes           |  <- Tree view, sorted by updated_at
|    +- Project X      |
|    +- Meeting Notes  |
|    +- ...            |
+---------------------+
|  Tags                |  <- Tag cloud / list
+---------------------+
|  Graph               |  <- Full graph view toggle
+---------------------+

7.2 Daily Notes (Rust Backend)

#[tauri::command]
async fn get_or_create_daily_note(db: State<'_, DbPool>) -> Result<Note, String> {
    let today = chrono::Local::now().format("%Y-%m-%d").to_string();
    let conn = db.get().map_err(|e| e.to_string())?;

    let existing = conn.query_row(
        "SELECT id, title, content FROM notes WHERE is_daily_note = 1 AND daily_date = ?1",
        [&today],
        |row| Ok(Note { id: row.get(0)?, title: row.get(1)?, content: row.get(2)? }),
    );

    match existing {
        Ok(note) => Ok(note),
        Err(_) => {
            let id = generate_id();
            let title = chrono::Local::now().format("%A, %B %d, %Y").to_string();
            conn.execute(
                "INSERT INTO notes (id, title, content, is_daily_note, daily_date) VALUES (?1, ?2, '', 1, ?3)",
                [&id, &title, &today],
            ).map_err(|e| e.to_string())?;
            Ok(Note { id, title, content: String::new() })
        }
    }
}

7.3 Search (FTS5)

#[tauri::command]
async fn search_notes(db: State<'_, DbPool>, query: String, limit: u32) -> Result<Vec<SearchResult>, String> {
    let conn = db.get().map_err(|e| e.to_string())?;

    let mut stmt = conn.prepare(
        "SELECT n.id, n.title, snippet(notes_fts, 1, '<mark>', '</mark>', '...', 32) as snippet,
                rank
         FROM notes_fts
         JOIN notes n ON n.rowid = notes_fts.rowid
         WHERE notes_fts MATCH ?1
         ORDER BY rank
         LIMIT ?2"
    ).map_err(|e| e.to_string())?;

    let results = stmt.query_map(params![query, limit], |row| {
        Ok(SearchResult {
            id: row.get(0)?,
            title: row.get(1)?,
            snippet: row.get(2)?,
            rank: row.get(3)?,
        })
    }).map_err(|e| e.to_string())?
    .collect::<Result<Vec<_>, _>>()
    .map_err(|e| e.to_string())?;

    Ok(results)
}

8. Graph Queries (Rust Backend)

8.1 Neighborhood Query (2-hop)

#[tauri::command]
async fn get_note_graph(
    db: State<'_, DbPool>,
    note_id: String,
    depth: u32,
) -> Result<GraphData, String> {
    let conn = db.get().map_err(|e| e.to_string())?;

    let mut stmt = conn.prepare(
        "WITH RECURSIVE connected(note_id, depth) AS (
            SELECT ?1, 0
            UNION
            SELECT CASE
                WHEN l.source_id = c.note_id THEN l.target_id
                ELSE l.source_id
            END, c.depth + 1
            FROM connected c
            JOIN links l ON l.source_id = c.note_id OR l.target_id = c.note_id
            WHERE c.depth < ?2
        )
        SELECT DISTINCT n.id, n.title,
            (SELECT COUNT(*) FROM links WHERE target_id = n.id) as backlink_count
        FROM connected c
        JOIN notes n ON n.id = c.note_id"
    ).map_err(|e| e.to_string())?;

    let nodes: Vec<GraphNode> = stmt.query_map(params![note_id, depth], |row| {
        Ok(GraphNode {
            id: row.get(0)?,
            title: row.get(1)?,
            backlinks: row.get(2)?,
        })
    }).map_err(|e| e.to_string())?
    .collect::<Result<Vec<_>, _>>()
    .map_err(|e| e.to_string())?;

    let node_ids: Vec<String> = nodes.iter().map(|n| n.id.clone()).collect();
    let edges = get_edges_between(&conn, &node_ids)?;

    Ok(GraphData { nodes, edges })
}

9. Phased Implementation Roadmap

Phase 1: Foundation (Week 1-2)

Goal: Desktop App & Editor UX — core note-taking

TaskEffortDependencies
SQLite schema creation (all tables)1 dayNone
Tauri project setup (React + Tailwind)0.5 dayNone
Basic CRUD Tauri commands (notes)1 daySchema
TipTap editor with StarterKit1 dayTauri setup
Sidebar tree view (all notes, sorted)1 dayCRUD
Daily notes (auto-create on launch)0.5 dayCRUD
Basic styling and layout1 dayAll above

Deliverable: Working note-taking app with daily notes and sidebar.

Goal: Knowledge Graph Implementation — linking and discovery

TaskEffortDependencies
Wiki-link TipTap extension ([[ trigger)2 daysEditor
Link extraction on save (parse TipTap JSON for mentions)1 dayWiki-links
FTS5 search bar in sidebar1 daySchema
Backlinks panel (notes linking to current)1 dayLinks
Tags (create, assign, filter)1 daySchema
Note favorites / pinning0.5 dayCRUD

Deliverable: Connected knowledge base with search and backlinks.

Phase 3: Graph Visualization (Week 5-6)

Goal: Graph view and visual discovery

TaskEffortDependencies
Sigma.js graph view component2 daysLinks
d3-force layout in Web Worker1 dayGraph component
Node click -> navigate to note0.5 dayGraph + routing
Graph neighborhood query (recursive CTE)1 dayRust backend
Mini-graph in note sidebar (local context)1 dayGraph component
Graph styling (size by connectivity, colors)0.5 dayGraph

Deliverable: Interactive knowledge graph with navigation.

Phase 4: AI Enhancement (Week 7-8)

Goal: Knowledge Graph Engine & AI Pipeline

TaskEffortDependencies
Entity extraction on note save (regex NER v1)2 daysSchema
Auto-link suggestions (related notes via FTS5 similarity)1 dayFTS5 + entities
Entity panel in sidebar (people, projects, etc.)1 dayEntities
Auto-tag suggestions1 dayNER
Apple Foundation Models integration (future)3 daysCore AI SDK

Deliverable: AI-enhanced knowledge graph with auto-discovery.

Phase 5: Polish & Distribution (Week 9-10)

Goal: Desktop App Maturity

TaskEffortDependencies
Keyboard shortcuts (Cmd+K search, Cmd+N new note)1 dayAll
Theme support (light/dark)0.5 dayStyling
Import from Markdown folder1 dayCRUD
Export notes (Markdown, JSON)0.5 dayCRUD
Code signing + notarization (macOS)1 dayBuild
Auto-update via Tauri updater1 dayBuild

Deliverable: Polished, distributable desktop app.


10. Technical Decisions Summary

DecisionChoiceWhyIndependent Validation
ShellTauri 2.x<500ms startup, 2.5MB installer, Rust backendHopp benchmarks; 35% YoY adoption growth
StorageSQLite (rusqlite)Local-first, FTS5, proven at PKM scale100K SELECTs/s at multi-GB scale (Phiresky)
EditorTipTap (ProseMirror)Headless, extensible, wiki-link support33.6K stars, 13.5M monthly NPM downloads (GitHub)
Graph rendererSigma.js 3.x (WebGL)100K edges, lightweight”Most production-ready React option” (MENUDO, 2025)
Graph layoutd3-force in Web WorkerNon-blocking; sync for <500 nodesSigma.js docs
Content formatTipTap JSON (not Markdown)Preserves rich structure for graph extraction
IDsRandom hex (16 bytes)No UUID dependency, collision-safe at PKM scale
SearchFTS5 porter tokenizer30% faster than FTS3; single-digit ms latencyFTS5 benchmarks; Elasticsearch replacement
AI (future)Apple Foundation ModelsOn-device, privacy-first, free with macOS
Sync (future)cr-sqlite or SQLiteSyncCRDT-based, conflict-free, local-firstSQLiteSync; cr-sqlite

11. What Kills This Architecture? (Counter-Arguments)

11.1 TipTap JSON Lock-In

Risk: Storing content as TipTap JSON instead of Markdown creates vendor lock-in. If TipTap is abandoned or Neuron switches editors, migration is painful. Mitigation: TipTap JSON is a superset of ProseMirror’s doc model, which has multiple implementations. Additionally, implement Markdown export from day 1 (Phase 5) so users always have a portable copy.

11.2 SQLite Single-Writer Limitation

Risk: As AI features grow (entity extraction, auto-linking, semantic indexing), background write tasks may contend with user edits. Mitigation: Batch background writes into transactions. Use WAL mode’s concurrent-read capability to keep the UI responsive. For extreme cases, consider a separate SQLite database for AI-generated data (entities, relations) that syncs to the main DB periodically.

11.3 Sigma.js Performance Ceiling

Risk: At 5K+ nodes with icons, Sigma.js performance degrades (Ogma comparison). Power users with 10K+ notes and dense linking could hit this. Mitigation: Default to 2-hop neighborhood view (50-200 nodes typical). Implement LOD rendering for full graph view. The force-directed layout in Web Worker prevents main thread blocking regardless of node count.

11.4 Local-First Sync Complexity (Post-MVP)

Risk: CRDT-based sync is technically complex. cr-sqlite is maintained by a small team. SQLiteSync is new. Neither is battle-tested at scale. Analysis: This is the highest technical risk for the team features, but it does not block the individual MVP. Local-first apps in 2025 typically use Cloudflare Durable Objects or Turso (libSQL) as sync backends (DebugG.ai, 2025). Evaluate all options before committing. Mitigation: Build individual MVP first. Defer sync to Phase 6+. Consider hybrid architecture: local-first for writing, cloud for team graph aggregation (not full CRDT).


Sources

Architecture & Benchmarks

Tauri vs Electron

TipTap & Editor

Graph Visualization

Local-First & Sync

Prior Internal Research

Related Reports