Case study · Open source

Parallelepiped

A desktop notes application for Windows that works on a plain folder of Markdown files. Notes link to each other with [[wikilinks]], show their backlinks, are searchable in full text and can be explored as an interactive 3D graph in which every note is a neuron. Written in Go and TypeScript with Wails and published under the MIT licence.

Role
Sole author
Stack
Go · Wails · TypeScript · SQLite
Licence
MIT
Source
Public on GitHub
A fictional demo vault: following links, writing a link with autocompletion, searching, then rotating and zooming the 3D graph in the dark and light themes.

Overview

Knowledge kept as linked notes is only useful if the links can be seen and followed. Parallelepiped opens any folder of Markdown files as a vault and keeps the files themselves as the only source of truth: there is no proprietary format and no import step. On top of them it builds an index of links, tags and text, and uses it for backlinks, search and a 3D graph that can be rotated, zoomed and hovered to light up a note's connections. It can also read a code repository and generate a linked map of its modules, so a project without documentation can be explored the same way. It is an original personal project, not a fork of an existing application.

My contribution

I am the sole author. I designed the module boundaries and wrote the Go backend — vault, notes, index, file watcher and the structural import — with the SQLite schema and its migrations; the TypeScript frontend with the editor, the context column and the 3D graph, including the neuron geometry and its rendering optimisations; the light and dark themes and the app icon; and the Makefile, tests and architecture documentation.

The project exercises two things I care about: a layered modular monolith applied outside the HTTP world, and interactive graphics that stay fast without giving up how they look.

Architecture

One executable. The frontend runs in the system's WebView2 and calls the Go backend through Wails bindings. The backend is a modular monolith: five business modules, each split into handler, service, model, repository and DTO, with cross-cutting concerns in a separate platform layer. Modules talk to each other only through small interfaces defined by the consumer.

How Parallelepiped is organised The TypeScript frontend — editor, context column and 3D graph — calls the Go backend through Wails bindings. The backend has five modules: vault, notes, index, watcher and brain. Notes are read and written as Markdown files, which are the source of truth. The index module keeps links, tags and a full-text index in SQLite, which can be rebuilt from the files. The watcher observes the folder, updates the index when files change outside the app and notifies the frontend with an event. FRONTEND · TS Editor CodeMirror 6, [[link]] autocompletion Context column mini-map, backlinks, outgoing links, tags 3D graph three.js, force layout neurons and fibres, rendered on demand GO · MODULAR MONOLITH handler → service → repository, per module vault open, recent, tree notes read, save, trash index links, tags, FTS5 watcher fsnotify, debounce brain code → linked notes platform errors catalogue, validation, database, logging, config, desktop bridge Markdown files the vault folder source of truth SQLite index links, backlinks, tags, FTS5 a cache, rebuilt from the files Wails Dashed: the watcher sees outside edits, reindexes and sends vault:changed to the frontend
Drawn from the project's architecture document and the internal/modules, internal/platform and frontend/src directories.

Code layout

  • main.go is the composition root: it builds the repositories, services and handlers and binds them to Wails.
  • internal/modules holds vault, notes, index, watcher and brain, each with handler, service, model, repository and DTO.
  • internal/platform holds the error catalogue, field validation, database, logging and the desktop bridge.
  • migrations/ keeps the versioned index schema at the root, embedded in the binary.
  • frontend/src separates the views (tree, tabs, editor, context, graph) from the API client and the shared colour and theme modules.

Stack

  • Backend: Go with Wails v2, a pure-Go SQLite driver with FTS5, and fsnotify for the folder watcher.
  • Frontend: TypeScript and Vite, CodeMirror 6 for the editor.
  • Graph: 3d-force-graph on three.js, with custom neuron geometry and a bloom pass in the dark theme.
  • Interface: an original layout with a light and a dark theme, folder colours shared by every view.
  • Delivery: a single executable built from a Makefile, with an optional NSIS installer.

Engineering decisions

Three decisions that shaped the project, each with the alternative considered and what it costs.

1. The files are the source of truth; the database is a cache

Decision:
notes are ordinary .md files written atomically (temporary file, then rename). The SQLite index of links, tags and text lives outside the vault and can be deleted and rebuilt from the files at any time; if an index update fails after a save, the save stands and the failure is logged.
Alternative:
store the notes in the database and export Markdown on demand.
Reason:
the user's notes stay readable and editable with any tool, under any version control, without the app. Because the index is derived and single-user, its migrations can safely run when it is opened — a deliberate exception to the usual rule, documented in the architecture notes.
Limitation:
the index can briefly disagree with the disk, and anything outside the files — such as the layout of the graph — is not stored with the notes.

2. Recognise the app's own writes by content hash, not by a time window

Decision:
the watcher waits for each path to be quiet for 300 ms, then reads it from disk. It keeps the hash of the content it last indexed; when the disk holds exactly that content, the event came from the app itself and is dropped.
Alternative:
ignore every event for a short time after the app saves a note.
Reason:
a time window either swallows a real external edit made just after a save or lets the app's own save trigger a reindex. Comparing content has no window: an outside edit is always picked up, and the app's own saves never cause extra work.
Limitation:
removed or renamed folders, and lost events, still fall back to rebuilding the whole vault index.

3. Make the graph faster without changing a pixel

Decision:
the graph renders on demand — while the layout settles, the camera moves or the pointer is active — and pauses after 1.5 seconds of inactivity. Hovering a neuron restyles only the fibres that touch it and the one hovered before, updating their meshes in place.
Alternative:
simplify the scene — merge the fibres into flat lines or drop the tube geometry — to cut the cost.
Reason:
the visual style is the point of the feature, and a simplified version was rejected as soon as it was seen. The library re-created every link on each hover; restyling only the affected ones took a hover on a vault of about 860 links from roughly 150 ms to 7 ms, and the result was checked to be identical to the library's own output.
Limitation:
the optimisation depends on internals of the graph library, so upgrading it needs that equivalence check repeated.

Quality, safety and cost

Everything below is in the public repository and can be checked against the source. The measured figures come from a single development machine and are not a guarantee for other hardware.

Tests

  • 13 Go test files with 49 test functions across the models, services and repositories of every module.
  • They cover link parsing, the index queries, the watcher's debounce and write ledger, and the structural import's analysis and re-import rules.
  • make check runs go vet, the Go tests and the TypeScript type check.

Safety of the user's notes

  • Deleting a note moves it to a .trash folder inside the vault, with the date in the name; the app never deletes a file permanently.
  • The structural import only overwrites notes it generated itself, marked in their front matter; the “Minhas anotações” (my notes) section of each generated note survives every re-import.
  • Errors cross to the frontend in one envelope with a code and field messages; internal details stay in the log.

Footprint

  • A single executable of about 18 MB, with no runtime to install on Windows 11, which ships WebView2.
  • The graph stops drawing while it is idle or hidden, and draws at the screen's native resolution without supersampling.
  • Hovering a neuron costs about 7 ms on a vault of about 860 links.

Results and limitations

What exists today is a working application: tabs and an editor with link autocompletion; a context column with a 2D mini-map of the note's neighbourhood, backlinks, outgoing links and tags; full-text search; folder colours shared by every view; a 3D graph with a graph-only mode; light and dark themes; an index kept in step with edits made outside the app; and a structural import that turns a code repository into linked notes inside its own docs/ folder.

Stated limitations

  • Only Windows 11 has been verified. Wails can target other systems, but no other platform has been built or tested.
  • Renaming a note does not rewrite the links that point to it, and there is no rendered live preview yet; both are next on the roadmap, with sandboxed WebAssembly plugins after them.
  • The structural import is static. It reads packages, exported symbols and imports for Go and relative imports for TypeScript and JavaScript; it does not understand what the code does.
  • The executable is not code-signed, so SmartScreen may warn on first run.
  • There is no real-world usage data. This is a personal project; I am not claiming users, downloads or adoption.

Explore

The repository is public and includes the README with the build instructions, the architecture document, and the contributing guide, code of conduct and security policy.

The repository documentation and the application interface are in Portuguese. The video and screenshots use a fictional demo vault.