Edit the page. Keep the file.
Noto is a Markdown editor that edits the rendered document and keeps the file byte for byte. You type into headings, tables, task lists, math and fenced code directly; there is no separate preview pane and no read-only source island that you have to leave the document to edit. When you save, every block you did not touch is written back exactly as it was found, including its line endings and whatever whitespace it happened to have.
It runs on macOS, Windows and Linux, and it is built with TypeScript, Electron, React and ProseMirror.
Downloads, guides and the project's direction live at roobli.github.io/Noto.docs.
Noto is usable and under active development. The current line is 0.0.2-alpha; 0.0.1 was the first release with downloadable builds. The honest summary of where it stands is worth reading before you depend on it.
All three platforms are now verified the same way: the packaged application is built and then driven through its real interface, and the whole suite of 237 packaged tests passes on macOS, Windows and Linux on every change, alongside about 1,200 unit tests. Windows was unproven until the run that proved it, which is also the run that found two faults nobody could have read out of the code: every plugin came up needing recovery because a durability step that only POSIX has was being treated as a failure, and the remote control refused the token its own Settings pane had shown, because the file's permissions mean nothing there and the token was being replaced on every read.
Which is the honest summary: it is exercised most on macOS, where it is used daily against a vault of about seven thousand notes, and the other two are proven by machine rather than by living in them.
The performance story is likewise mixed rather than a clean win, and the numbers are below.
Builds for macOS, Windows and Linux are on the
releases page. Each release carries
one download per platform, listed below as of v0.0.2-alpha.113.
First open: until we ship Apple notarization and a Windows code-signing
certificate, each OS warns once on a fresh download. That is expected — not a
broken build. Follow docs/install.md (short, per platform).
macOS one-liner after unzip (from the folder that contains Noto.app):
xattr -cr Noto.app && open Noto.appOr Right-click → Open. Prefer that over double-click the first time.
Packaged builds can check GitHub Releases from Settings → Updates. Stable
(default) follows formal releases only; Testing includes alphas. See
docs/architecture/updates.md.
| Platform | Download |
|---|---|
| macOS, Apple silicon | Noto-<version>-macos-arm64.zip: unzip and move Noto.app to Applications |
| Windows, x64 | NotoSetup-<version>.exe |
| Linux, Debian and Ubuntu, x64 | noto_<version>_amd64.deb |
| Linux, Fedora and openSUSE, x64 | noto-<version>-1.x86_64.rpm |
Intel Macs are not supported by current releases: the macOS build is Apple silicon only, and Rosetta runs Intel apps on Apple silicon, not the other way round.
You need Node 22 and pnpm 11. Newer Node majors are rejected on purpose, since the packaging path is pinned to the runtime it was verified against.
pnpm install
node node_modules/electron/install.js # see the note below
pnpm startThe second line fetches Electron's own binary. The published electron package
declares a postinstall script, but the copy pnpm 11 installs arrives without
a scripts field, so nothing runs it and node_modules/electron/dist never
appears. Without that directory, packaging fails and the app cannot launch.
pnpm rebuild electron does not help, because from pnpm's side there is no
build to run.
Open a file with Cmd+O, or open a folder with Cmd+Alt+O to get the
workspace tree. Open notes sit behind a quiet Recent strip in the status bar
rather than a classic tab bar. The rail on the left holds Files and Outline by
default; Links (graph / related) stays off until you turn it on in Settings →
Appearance. The one control at the top left opens and closes the rail, and
Cmd+Shift+L and Cmd+Shift+O open it directly on the view they name.
Cmd+F finds, Cmd+Alt+F finds and replaces, Cmd+K opens the command
palette, and Cmd+, opens Settings (and closes it again) — a full-page shell with
categories, where plugins are turned on under the Plugins section. On Windows and Linux, read
Control for Command.
The title bar carries the filename and nothing else that is not an action you
can take right now: Save appears when there is something to save and is absent
when there is not. The gear is the single Settings entry; Plugins is reached
from Settings' left nav, not a second top-bar icon. What the chrome is for, and
why it is this quiet, is written down in
docs/design/chrome.md.
Settings carries the document's typography, so text size, line height and
line width are yours to set rather than fixed: the width is a character count,
not a pixel width, so it holds as the size changes. Saving automatically is
there too, off by default and debounced against typing, and it refuses in
exactly the cases the Save button refuses. A custom stylesheet can be pointed at
any absolute path and wins over the built-in theme, so :root { --accent: … }
is enough to retheme the app without editing it.
Cmd+P is quick open: type part of a note's name or its path and it ranks the
whole folder by how well it matches and by how often and how recently you open
it, so an empty box already shows the few notes you probably want. Enter
opens the note; Alt+Enter writes a [[wiki link]] to it at the caret instead.
Wiki links render inline wherever they appear, and Cmd+click follows one.
The links are decorations rather than a node type, so they cannot reach the
saved bytes: [[a note]] is ordinary text in the file, exactly as you typed it.
Cmd+Shift+F searches inside the notes rather than across their names, and
Tab switches between the two without leaving the box. A content result shows
the lines it was found on, and opening one lands on the match with the find bar
already carrying the query. The scan reads the folder on demand rather than
keeping an index: on a vault of 7,066 notes and 82.5 MB it takes about 1.3
seconds cold and 274 ms once the operating system has the files cached, which is
what the adaptive debounce is for.
The open folder names itself on the tree's first row; an ellipsis on that
row holds what acts on the folder: open another, reveal it in the file manager,
refresh, and the folders you opened before. Cmd+Shift+R reveals the current
note instead.
Cmd+/ opens Source Code Mode: the whole note as the Markdown it is saved as,
the way Typora's Command-slash does. Cmd+Alt+/ still toggles source for the
single block under the caret, which is the escape hatch when you want to fix
one paragraph, one table or one fence without leaving the rest rendered.
Markdown input rules work as you type: # for a heading, - for a list,
``` for a code fence. The syntax markers for the block you are editing
appear while you are in it and fold away when you leave, so the document stays
readable without hiding what it is made of.
This is the property the rest of the design is arranged around. Most editors
that render Markdown round-trip it through a serializer, so opening and saving
a file rewrites parts of it you never touched: a list marker changes, emphasis
switches from _ to *, a table's padding is normalized, trailing whitespace
disappears.
Noto parses the file into blocks, records the exact source bytes of each one along with its origin and hash, and on save writes back the recorded bytes for every block that was not edited. Only blocks you actually changed are serialized. A document you open and save without touching is identical to the byte.
The same block record is what detects an external change: if the file on disk no longer matches what was accepted, the save is refused and you are offered a copy rather than an overwrite.
Measured by the benchmark workflow on a packaged
build of main at acb3d66, 29 September 2026, on a GitHub-hosted macos-14
runner, a virtual Apple silicon Mac. It opens four generated documents through
the workspace, the way the menu or the file tree does, types in the middle of
each and saves. Open and save are timed by the harness driving the app, a
keystroke inside the page, from the edit to the next frame. Median of five
runs; the run.
| document | bytes | blocks | open | keystroke | save |
|---|---|---|---|---|---|
| small | 66,061 | 338 | 224 ms | 14.2 ms | 150 ms |
| medium | 524,952 | 2,742 | 604 ms | 11.5 ms | 261 ms |
| large | 2,097,661 | 10,982 | 1,733 ms | 24.5 ms | 734 ms |
| huge | 8,389,427 | 43,970 | 10,026 ms | 85.7 ms | 3,146 ms |
Up to half a megabyte a note opens in under a second and a keystroke stays inside one 60 Hz frame. At 2 MB opening takes under two seconds, a keystroke about a frame and a half, and a save under one. At 8 MB opening takes ten seconds and a keystroke about five frames, which is the part still to improve.
These are runs on a shared virtual machine, and two runs of one build have
differed by up to 41%, so compare them only with other runs of the workflow.
Every tag gets the same run, so a release's numbers are in its run summary.
Method, corpus, profiles and the history of each fix are in
docs/performance/measurements.md.
Measured on a desk Mac with a clock inside each application, and with the
parser Noto used before @roobli/md became the default in
v0.0.2-alpha.112. It has not been repeated with the current engine, which
needs a Mac with Typora installed.
| document | bytes | Noto | Typora |
|---|---|---|---|
| small | 66,061 | 268 ms | 282 ms |
| medium | 524,952 | 903 ms | 343 ms |
| large | 2,097,661 | 4,017 ms | never loaded |
| huge | 8,389,427 | 22,467 ms | never loaded |
At 66 KB the two were level. At 525 KB Typora was 2.6 times faster. At 2 MB and above Typora did not load the document at all: its editor still reported an empty document after three minutes, which was checked three separate ways before being written down.
Plugins are explicit: each one declares the capabilities it wants, and the main process brokers every one of them. A renderer plugin can decorate or transform the document only if it asked for that; a service plugin gets a filesystem grant scoped to a path, revocable, and denied everywhere else. Nothing is ambient.
Four plugins ship in the build. Title Shift promotes and demotes heading levels, and Markdown Padding applies CJK spacing rules; both are ports of the author's Typora plugins. Semantic Focus and Fixture Reader are bundled examples, one per runtime kind, kept so that a plugin author has a working plugin of each shape to read and so the capability broker has something that exercises it.
Writing one is described in docs/plugins.md, which also says plainly where the model stops today: plugins ship inside the application, and the sandboxed runtime for third-party code is built but not yet opened. Theming, which needs no plugin at all, is one CSS file named in Settings; docs/theming.md lists what it can reach.
pnpm verify # typecheck and the unit suite
pnpm package:e2e # package the variant the end-to-end suite drives
pnpm test:e2e # run that suite against the packaged app
pnpm make:release # installers for the host platformpnpm make:release produces a zip on macOS, a Squirrel installer on Windows,
and deb and rpm packages on Linux. Signing is read from the environment, so an
unsigned local build and a signed release build take the same path: set
NOTO_APPLE_SIGNING_IDENTITY, and APPLE_ID, APPLE_PASSWORD and
APPLE_TEAM_ID to notarize, or WINDOWS_CERTIFICATE_FILE and
WINDOWS_CERTIFICATE_PASSWORD on Windows. With none of them set the build
still succeeds and is simply unsigned, which is what a contributor without
certificates needs.
The end-to-end suite runs against a packaged application rather than a development server, because the things most likely to break in an Electron app are exactly the things a development server does not exercise: the preload bridge, the fuses, what is inside the asar, and the application menu.
The main process owns the file: reading, block records, saving, recovery, the workspace, recent files and settings. The renderer owns the editor and never touches the filesystem. A separate service process handles plugin filesystem access, so a plugin's file reads are not running inside the window. The preload bridge exposes four narrow, versioned APIs and validates every message on both sides.
The Markdown pipeline defaults to @roobli/md (escape hatch:
NOTO_MARKDOWN_ENGINE=micromark) with GFM, math and frontmatter, mapped onto a
ProseMirror schema that keeps each block's original source alongside its
rendered form. That mapping is what makes both byte-exact saving and per-block
source mode possible from one representation.
Further reading lives in docs/: the performance measurements, the Linux
verification record, and a review of what the release build asks the operating
system for.
AGPL-3.0-only. See LICENSE.
Third-party dependency licenses are inventoried in
resources/provenance/THIRD_PARTY_NOTICES.md, regenerated with
pnpm license:inventory.
Typora is a behavior reference only. Noto contains no Typora code, markup, assets, strings, themes or private protocols.


