Project

12 min read

← Digest

2026-06-21

INTRODUCING NEURON: A LOCAL-FIRST KNOWLEDGE WORKSPACE

How I designed Neuron around plain files, isolated process boundaries, and file-driven surfaces — JSON Canvas boards, Notion-style databases, and sandboxed HTMX views — on an architecture built to evolve.

ElectronReactTypeScriptCodeMirror 6MDXJSON CanvasHTMXLocal-first

The Thesis Neuron is a desktop knowledge workspace for Markdown and MDX. The starting principle is deliberately simple: a user's notes should remain ordinary files they can inspect, move, sync, version, or open without Neuron.

That makes local .md and .mdx files the source of truth. The application adds a fast workspace, live rendering, knowledge connections, plugins, and AI integrations around those files without turning the content into an opaque database.

Neuron in Use

Product Evidence

Neuron split editor with a live Properties panel beside raw MDX

Split editor with live properties

Neuron reading mode showing editable properties and an MDX callout

Reading mode & MDX components

Neuron Notion-style database rendered from a .db file

Notion-style .db databases

Neuron JSON Canvas board linking Markdown cards and databases

JSON Canvas boards

Architecture Built to Evolve The desktop shell is not one large application with filesystem, network, editor, and rendering concerns mixed together. Neuron separates them by privilege and responsibility.

The Electron main process owns filesystem access, repository scanning, settings persistence, and credential-safe network or AI requests. A lean preload bridge exposes a restricted, typed IPC contract. The React renderer stays focused on interaction, CodeMirror editing, Markdown syntax, and MDX component evaluation.

Local-first Process Architecture

System View

Markdown + MDXplain filesMain processfilesystem / settingsAI + networkPreload bridgetyped IPCReact rendererCodeMirror 6MDX runtimePrivileged boundarycredentials stay hereContribution registryplugins stay modularSmall contracts keep heavy work away from rendering

The trade-off is additional contracts and IPC coordination. I accepted that cost because the alternative couples interface code to host privileges, makes performance failures harder to isolate, and gives every future feature a path into the core.

File-Driven Surfaces A repository is just an ordinary folder, so the interesting question is what a file *becomes* when you open it. Neuron keeps a small surface registry that maps a file extension to the component that replaces the default Markdown editor — the file on disk stays plain text or plain JSON, but the editor it opens into is purpose-built.

A .canvas file opens as a JSON Canvas infinite whiteboard in the Obsidian-compatible format: undo/redo, multi-select, alignment, and Markdown cards with per-node styling. A .db file opens as a Notion-style typed database — colored select tags, filter and sort — backed by sql.js, stored as inspectable JSON with atomic writes and live reload when the file changes underneath it. A .nhtml file opens as an HTMX view: user-authored plain HTML plus bundled htmx, rendered inside a sandboxed webview against a capability-scoped, token-authenticated loopback API.

That last surface is the one I hardened most. The view server is loopback-only on an ephemeral port, hands each view a per-session token, and enforces default-deny capability manifests with glob path policies — so a local dashboard can read exactly what it declares and nothing else. It is Electron-free and carries its own test suite.

Architectural Pillar: The Zero-Trust Runtime The renderer is treated as an untrusted presentation surface. It has no direct Node.js capability, no environment-variable access, and no general filesystem API. Privileged integrations such as AI providers and network fetches stay in the main process, where credentials can remain outside the frontend bundle and outside ordinary IPC payloads.

Credential and Capability Boundary

Zero-trust Runtime

Sandboxed / untrusted zoneMDX contentpluginsReactrendererno Node APIsPrivileged zoneMain processallowlistedhandlersFilesystemscoped accessCredentialsuserData memoryTyped IPCbridgeRenderer code cannot bypass the mapped capability surface

The preload bridge exposes only mapped operations with typed inputs and outputs. This does not make arbitrary content harmless, but it sharply reduces the capability available to a compromised renderer: an MDX snippet cannot invent a shell command or request an unmapped filesystem operation.

That micro-surface is a maintenance decision as much as a security decision. Every privileged action has one auditable handler and one explicit contract instead of being scattered through UI components.

Performance and Scale Without a Database Keeping plain files as the data tier avoids database lock-in and migration overhead, but it moves indexing responsibility into the application. A naive implementation would repeatedly scan every note for tags, graph edges, and wikilinks.

Neuron instead tracks file signatures and refreshes only changed nodes in an in-memory metadata cache. Parsing and workspace mapping run asynchronously away from the typing path. The renderer receives lightweight results rather than performing repository-wide work itself.

Incremental Workspace Index

Scale Without a Database

MarkdownrepositoryFile signaturesdetect changednodes onlyAsync parsemetadata +wikilinksIn-memory cachesmall incrementalupdatesGraph layoutpre-computedTag lookupcachedWikilink resolvecachedThe renderer receives lightweight results while indexing stays off the typing path

The trade-off is that the cache is disposable and must be reconstructed from files. That is intentional: the cache optimizes reads, but it never becomes a second source of truth. Corruption recovery is deletion and rebuild, not data migration.

Bug: Compounding Caret Displacement Symptom: clicking below a custom React widget placed the caret on the wrong text line, and the displacement accumulated farther down the document.

Root cause: widget margins collapsed outside their standard block wrappers. CodeMirror's height map measured each wrapper without the escaped vertical space, so its internal document geometry diverged from the DOM.

Debugging the Height Map

Before / After

Before: collapsed marginRendered MDX widgetMargin escapes wrapperCodeMirror measures too little heightAfter: flow-root boundaryRendered MDX widgetMargin contained inside wrapperdisplay: flow-rootCorrect geometry restores caret-to-line alignment

Resolution: display: flow-root establishes a block formatting context around each widget and contains its margins. A second remeasurement after document fonts become ready removes smaller font-metric drift. The durable fix restored the layout contract instead of adding another timing observer.

Bug: Unclosed MDX Tag Parsing Crash Symptom: a self-closing component followed by prose on the same line, such as <Badge /> and trailing text, was treated as an opening block tag and crashed the parser while it searched for a closing tag.

Root cause: the first parser classified the entire line by its ending. It did not distinguish one valid leading component from the ordinary Markdown that followed it.

Defensive MDX Parsing

Decision Flow

Input line<Badge /> proseAnchored matchself-closing tag+ trailing textSplit safelycomponentremainderRender widgetin live previewReturn proseto MarkdownParse the smallest valid unit, then hand the rest back

Resolution: an anchored expression isolates the complete self-closing component first and captures the remainder separately. The component becomes a live widget; trailing prose returns to the standard Markdown stream. The parser now claims only the smallest syntax unit it can prove it owns.

Long-Term Sustainability Desktop applications accumulate debt quickly when integrations weave themselves through editor state. Neuron uses contribution registries so AI providers, workspace tools, MDX components, and distribution concerns depend on contracts rather than private document-engine internals.

Replacement-cost Map

Long-term Sustainability

Document enginefiles / editor / indexstable coreAI providersClaude / OllamaWorkspace toolsjournal / graphMDX componentscustom renderersDistributionbuilder / CIContribution contractsReplace an integration without rewriting the document model

This keeps replacement cost local. A model provider, renderer, or tool can change without rewriting the file model. Runtime and build dependencies are also kept at the boundary where they are used, making upgrades and security remediation less likely to destabilize the editor core.

From Project to Product A desktop workspace is only useful if people can install it. Version tags trigger GitHub Actions builds, and electron-builder produces native artifacts for Windows, macOS, and Linux. Releases use the repository-scoped GitHub token, so the delivery workflow does not require a hardcoded publishing credential.

Release Surface

v1.4.1

Windows

NSIS + portable + appx

macOS

DMG + ZIP / x64 + arm64

Linux

AppImage + Debian

3

Desktop platforms

1

Tagged release pipeline

The GitHub Pages download surface points users toward the latest release, while the release itself carries the platform-specific packages. What shipped as v1.0.0 is now under active development at v1.4.1, and every release has grown by adding a surface rather than rewriting the core.

What I Learned Neuron reinforced a pattern I keep seeing in systems work: difficult bugs often live at boundaries. The caret bug was a disagreement between CSS layout and CodeMirror's height model. The MDX crash was a disagreement between line-level parsing and component-level syntax. The distribution problem was a boundary between a static website and native desktop artifacts.

The best fixes made those boundaries explicit. Local files own the data. The main process owns privilege. IPC owns communication. The renderer owns interaction. Registries own extension. Release automation owns delivery.

That clarity is what lets Neuron grow without asking users to surrender control of their knowledge.

← All PostsPortfolio →