Module 03·Developer Tools

Orrery · Notes and code in one folder

ReleasedExpertPublished 2026-09-06

A desktop markdown editor that treats a folder of notes and a folder of code as the same folder. Wikilinks, a knowledge graph that draws imports too, a real code editor, git, a terminal, PDFs you can edit. Plain files on disk, nothing converted.

Point Orrery at a folder. Your notes stay ordinary markdown on disk, joined by [[wikilinks]]. That folder usually holds code as well, so Orrery edits code properly, shows git diffs, and gives you a shell in the same window.

Note apps stop at notes. Code editors treat links between documents as text. In practice a folder of notes and a folder of code are the same folder.

The editor showing live preview of headings, wikilinks, a table, a highlight and KaTeX maths
Live preview. The file on disk is what you typed.

Writing

Formatting renders as you type. Headings size up, bold is bold, links show their label. The syntax markers come back the moment your cursor lands on them. Nothing is converted to a rich-text model and back, so the file on disk is what you typed.

Three modes on Ctrl+Shift+1 through 3: edit, hybrid, reading.

Reading mode with a displayed equation, a mermaid flowchart and a TypeScript code card
Reading mode. Diagrams and maths, drawn.

Tables tidy themselves. Mermaid fences become diagrams. $maths$ goes through KaTeX. A long code fence scrolls inside its own card instead of pushing the document around.


The whole folder as a map

The knowledge graph: 28 notes in four clusters, joined by 71 links
Notes and source files on one map.

The graph draws the vault. Size nodes by links, influence or bridging. Colour them by cluster or folder. A note you linked to but never wrote appears as a ghost, so the gaps show up next to the structure.

Code is on the map too. Notes point at each other with wikilinks, source files point at each other with imports. Both are drawn, so a note that links [[pane]] reaches pane.ts. Imports resolve only to files inside the vault: a dependency on react is real, but it is not part of this folder.


Code, git and a terminal

A side-by-side diff with a minimap, source control in the sidebar, and a terminal
Two real editors, side by side. Both editable.

Git is built in. Stage, unstage, discard, commit, change bars in the gutter. A diff opens as a tab holding two real editors with syntax highlighting, undo and selection. The working-tree side saves with Ctrl+S.

The commit graph draws every branch. Click a commit for the files it touched, then a file for that commit's diff against its parent.

A TypeScript file with syntax highlighting, a minimap, folding and git change bars
143 languages. Folding, minimap, multiple cursors.

Language servers are used where you already have them: hover types, completion, diagnostics, go-to-definition. Orrery bundles none of them.


Files that are not text

A PDF opens as a document: pages, selectable text, find, an outline, thumbnails. Vault search reads inside them. [[paper.pdf#page=12]] opens at that page. You can also change one: highlight, draw, fill a form, rearrange pages, or retype a line in the document's own font. Nothing is written until you save.

A .csv opens as a grid you can sort and filter without touching the file. A .db opens as its tables, read-only twice over: the file is opened read-only, and anything that is not a question is refused before it reaches the database.


Reading a web page you did not write

An HTML file being read, with the bar reporting one diagram drawn and two equations typeset
The bar says what it did, and what it held back.

An HTML file opens as source. The Read switch renders it, and this is the one place Orrery displays untrusted markup as markup, so the terms are strict.

The page runs in a sandboxed frame under a policy that starts by refusing everything. Its stylesheet and pictures load from one folder, over a scheme that resolves paths in the main process, so a page asking for ~/.ssh/id_rsa gets nothing. Nothing loads from the internet until you ask. The page's own code does not run until you ask. Code fetched from the internet never runs, at any setting: loading a picture from someone's server tells them you opened their file, and running their code hands them the page you are reading.

Two things it draws that a browser would need the page's scripts for: mermaid diagrams and TeX. Both rendered by Orrery from the page's own source, before anything has run.


Built to be argued with

71Klines of strict TS
Source
1,498
Unit tests
440across 57 specs
End-to-end
68one test file each
Pure core modules
2817 dark, 11 light
Themes
143
Languages
75
Commands
679lines, optional
Rust sidecar

Layers do not cross. core/ is pure logic that runs in a bare Node process with no Electron, no DOM and no editor, which is why its tests need no build step. main and renderer never import each other; they meet at one file that declares every IPC channel, validated with zod at the boundary. A test enforces all of it, so a layer violation turns a test red rather than becoming a habit.

Document text lives in CodeMirror state rather than in React, so no large string travels through a render on a keystroke. Saves go through a temporary file and check the mtime first, so a write cannot silently discard a change made outside the editor.

Because it is counted as coverage. Several bugs here were found by breaking a fix and watching the test stay green. One case: a whole file of "it ignores X" assertions passed because jsdom delivers postMessage with event.source set to null, so the code rejected everything, including the case that was supposed to work. The test that caught it was the one asserting the positive.


Where the time actually went

Every performance bug in this project had an obvious cause that turned out to be wrong. Measuring first was worth more than any optimisation.

▶Three that were measured before they were fixed

Opening a huge folder. Suspected the graph and analytics. It was a serial directory walk producing 70 MB of JSON, and a recursive watcher that had not finished after sixty seconds.

Start-up with many tabs open. Suspected serial file reads and store churn. The reads were 75 ms of 4,564 ms and the store updates were 6 ms. The cause was one line setting the active tab on every pass of a loop, so the visible pane displayed every restored document in turn before settling on the one you wanted.

Start-up freezing for fifteen seconds. Suspected the file walk. The walk was 493 ms. resolveImport was rebuilding a set of every path in the vault, per import, which on a 3,700-file repository is hundreds of millions of string operations. Indexed once: 15,155 ms to 1,775 ms.

StackElectron 43 · React 19 · TypeScript strict · CodeMirror 6 · Zustand
Layerscore → shared → main / preload / renderer, enforced by a test
Renderercontext isolation, sandbox, webSecurity on, no Node
Untrusted HTMLown scheme, own policy per document, disk access confined to a folder
SearchTypeScript, with an optional Rust sidecar: 34 ms against 543 ms
PlatformsLinux (.deb, AppImage). macOS and Windows targets exist, unbuilt
LicenceMIT

Get it

→ →