Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What n0te is

n0te is a fast reader and quick editor for Obsidian notes and code, made for Omarchy. Press super n, type a few letters of a note’s name, and it’s open, set in your theme, in under 30 ms.

It’s for the times you’d rather not start Obsidian: checking a list, reading a note from the terminal, ticking off a task, fixing a line. Notes open as clean, readable pages. Press e on any paragraph, list item or heading and it turns back into markdown you can edit. esc turns it back into a page. There’s no save button, because n0te saves as you go and never leaves diff noise in your files.

What it does

  • Reads your vault as Obsidian does. It understands wikilinks, callouts, tasks, tags, tables, footnotes and frontmatter, and finds your vault through Obsidian’s own config.
  • Edits a block at a time. Only the block you’re editing shows its markdown; everything else stays a page. Blocks you don’t touch go back to disk byte for byte.
  • Keeps up with other apps. If Obsidian, Syncthing or git pull changes a note while it’s open, n0te merges the change into what you’re doing instead of overwriting it.
  • Opens code too. Any file opens with syntax highlighting in your theme’s colors, at a line if you ask (n0 src/main.rs:42).
  • Fits into Omarchy. One command binds super n, starts n0te at login and themes it. A theme switch repaints every open window.
  • Is keyboard-first. j and k step through blocks, ctrl k opens a palette of notes, headings and commands, and ctrl o goes back. The mouse works too.

What it isn’t

n0te is not a replacement for Obsidian. It has no graph, backlinks, plugins or full-text search across the vault, and it doesn’t try to. It reads and writes the same files, so you can use both.

Where to start

Note

n0te is at version 0.1. It’s used every day, and it’s careful with your files, but expect rough edges. It installs from a download for now; the source, an AUR package and a public issue tracker come when the repository goes public.

Install

n0te runs on Linux with GTK 4.20 or newer. It’s made for Omarchy (Hyprland with Omarchy’s Lua config), and works best there, but the reader and editor run on any GTK desktop. See Without Omarchy below.

Download

n0te comes as a download for x86_64 Linux, built on Arch: the two programs, an install script and the licenses.

curl -fLO https://n0te.dhin.dev/download/n0te-x86_64-linux.tar.gz
curl -fLO https://n0te.dhin.dev/download/SHA256SUMS
sha256sum -c SHA256SUMS
tar -xf n0te-x86_64-linux.tar.gz
./n0te/install.sh
n0te --setup

install.sh copies n0te and n0te-app into ~/.local/bin and adds the short name n0 next to them. It touches nothing else. To install somewhere else, set N0TE_BIN=/path/to/bin when you run it. ~/.local/bin must be on your PATH, which it is on Omarchy. n0te --setup then wires n0te into your session; see Set up Omarchy.

The binaries need GTK 4.20 or newer and glibc 2.39 or newer, which current Arch and Omarchy have. Check with pacman -Q gtk4 glibc. Other distributions work when theirs are at least that new.

Note

The source code and an AUR package are coming once the repository goes public. Until then, the download is the way to install n0te.

Set up Omarchy

n0te --setup            # super n, start at login, theme, completion
n0te --setup default    # the same, and .md files open in n0te

Setup says what it did, one line per step. It installs:

WhatWhere
super n opens n0te; the service starts at login~/.config/hypr/n0te.lua, loaded from hyprland.lua with one require line
The app entry (so n0te shows in the app launcher)~/.local/share/applications/n0te.desktop, unless the package installed one
The theme template, rendered into each Omarchy theme~/.config/omarchy/themed/n0te.css.tpl
Tab completion for n0te and n0 in bash~/.local/share/bash-completion/completions/, unless the package installed it
With default: n0te opens text/markdownxdg-mime; the app that had them is remembered

Running setup again is harmless. If you’ve changed the app entry, the template or the completion file, setup keeps yours. The settings page (ctrl , in n0te) switches the same things on and off one at a time. Paths follow XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME when they’re set.

The theme template takes effect at your next theme switch, when Omarchy renders it. Until then n0te reads the theme’s colors.toml directly, which looks nearly the same.

Without Omarchy

Everything except the Omarchy wiring works on any Linux desktop with GTK 4.20: reading, editing, the palette, the service.

  • Colors: without an Omarchy theme, n0te uses its own dark palette.
  • super n: setup skips it when there’s no ~/.config/hypr/hyprland.lua. Bind n0te to a key yourself. On Hyprland with a classic config, that’s bind = SUPER, N, exec, n0te.
  • Start at login: optional. Run n0te --service from your session’s autostart and even the first open is instant. Without it, the first n0 of a session starts the service itself.
  • A vault: n0te finds the vault Obsidian has open. Without Obsidian, set one in ~/.config/n0te/config.toml (vault = "/path/to/notes"). Without a vault, n0te still opens any file.

Fonts

n0te sets notes in iA Writer Quattro S, which Omarchy ships, and code in your monospace font (on Omarchy, the one you picked in its font menu). Elsewhere, install ttf-ia-writer from the AUR, or n0te falls back to IBM Plex Sans, then your default sans.

Updating

Download the new tarball and run its install.sh again, as above; your settings and setup stay as they are. Then stop the old service, which is still running the previous version:

n0 --quit

It saves every open note first. The next n0 starts the new version. n0te --version says which one you have.

Uninstall

n0te --setup remove     # undoes everything setup did, and gives .md files back
n0 --quit
rm ~/.local/bin/n0te ~/.local/bin/n0te-app ~/.local/bin/n0

--setup remove leaves the app entry, the theme template and the completion file if you’ve edited them; it always deletes ~/.config/hypr/n0te.lua, which is n0te’s own. Your settings stay in ~/.config/n0te/ until you delete them.

First steps

Five minutes with n0te, from the first open to your first edit.

Open a note

Press super n, or type n0 in a terminal. A window opens with the switcher: your most recently changed notes, and a box to type in.

Type a few letters of the note’s name. Matching is fuzzy, so lnch finds Projects/Acme/Launch plan, and letters that start a word count for more. ↑ and ↓ (or ctrl n and ctrl p) move through the list, Enter opens the note, and esc closes the window.

From a terminal you can name the note directly:

n0 "Launch plan"

n0 returns as soon as the window is up, so your shell is free while you read. The first n0 of a session takes about a third of a second, because it starts n0te’s service. After that, opens are close to instant.

Find your vault

n0te uses the vault Obsidian has open, or the one it opened last, straight from Obsidian’s own config. Nothing to set up. To use a different one, pick it in Settings (ctrl ,), or set vault in ~/.config/n0te/config.toml.

Without a vault, n0te still opens any file by path.

Read

The note opens as a page in your theme’s colors and iA Writer Quattro. Move with the keyboard:

  • j and k step to the next and previous block: a paragraph, a heading, a list item, a code block, a table.
  • space and b page down and up; gg and G go to the top and bottom.
  • tab lists the note’s headings; type to jump to one.
  • / finds text in the note; n and N step through the matches.

The status line at the bottom shows where you are: the file’s path on the left, and on the right something like READ · 812 words · 3/9 done · 4/31, which is the word count, the tasks done, and the block you’re on.

Click a link, or press Enter on a block that holds only a link. gd follows the first link in the current block. Wikilinks to notes that don’t exist yet are dimmed; following one creates the note, as in Obsidian.

ctrl o goes back and ctrl i goes forward, like a browser. So do alt ← and alt →, and your mouse’s back and forward buttons.

Edit

Press e (or Enter, or double-click). The block you’re on turns into its markdown, in a quiet box, and everything else stays a page. Type. Press esc and it’s a page again.

There’s nothing to save. n0te writes the file a second after you stop typing, and again when you press esc, switch windows or close. See Editing for the rest: new blocks with o, ticking tasks with x, moving list items, editing the whole note at once.

Do anything else

ctrl k (or :) opens the palette: notes, headings in this note, and every command, with its key next to it. If you forget a key, type what you want: uncheck, obsidian, wrap. ctrl p is the same palette with notes only.

Press q to close the window. Everything is already saved.

Next

Reading

n0te opens every note as a page: headings in their sizes, lists with hanging indents, tasks as boxes, callouts and code in boxes sized to the text column. The markdown stays out of sight until you edit a block.

Blocks

A note is a list of blocks: paragraphs, headings, list items (nested ones too), quotes, callouts, code blocks, tables, rules. Many keys act on the block you’re on, which is marked by a small tick in the margin.

KeyWhat it does
j k (or ↓ ↑)next / previous block
space b (or Page Down Page Up)page down / up
gg G (or Home End)top / bottom
ycopy the block as markdown
ffocus mode

Scrolling with the mouse or touchpad works as usual, and a click puts you on the block you clicked.

Focus mode

f dims everything except the block you’re on. Move with j and k and the light moves with you, which helps when you’re reading a long list one item at a time. f or esc turns it off. The status line says focus while it’s on.

Callouts

Callouts render in a box with a bold title, in their type’s color as Obsidian picks it: blue for note and info, cyan for tip, orange for warning, red for danger, green for success, and so on. A foldable callout (> [!note]- folded, > [!note]+ open) shows a ▸ or ▾ by its title; Enter on it, or a click on the title, opens or closes it.

Frontmatter

YAML frontmatter shows as one quiet line at the top, so a note with twelve properties doesn’t open on a wall of metadata. e on that line edits it like any other block.

Text size

ctrl + and ctrl - change the reading size in every window at once, from 70% to 240%. ctrl 0 goes back to 100%. The size is remembered.

Long lines and hard wraps

Prose that was hard-wrapped at about 80 columns, as many notes are, flows to the window’s width instead of breaking mid-sentence. Line breaks that are clearly deliberate stay, which matches Obsidian’s default reading view.

Code blocks inside notes wrap their long lines within the box. In code files, w turns wrapping on and off.

Hidden things

%%comments%% and <!-- HTML comments --> aren’t shown, as in Obsidian’s reading view. Done tasks can be folded away with h; see Checklists.

Reloading

n0te notices when the file changes on disk and reloads by itself, or merges if you’re editing (see Saving and changes on disk). r reloads by hand.

Finding your way

n0te doesn’t have a sidebar or a file tree. You get around by name: the switcher, the palette, links and history.

The palette

ctrl k (or :) opens the palette over the note. It lists three kinds of things:

  • Notes in your vault. With nothing typed, the most recently changed ones.
  • In this note: its headings, once you type.
  • Commands, each with its key, so the palette doubles as a reminder of what n0te can do.

Matching is fuzzy and favours letters at the start of words and in the file name. The best match is selected wherever it is in the list, so typing uncheck picks the command even if a note matches too.

KeyWhat it does
↓ ↑, tab shift tab, ctrl n ctrl pmove
Enteropen or run
escclose

ctrl p opens the palette with notes only, which is the switcher. It’s also what super n and a bare n0 show.

If no note has exactly the name you typed, a Create note row follows the notes. See New notes and templates.

The outline

tab opens the palette with this note’s headings, indented by level. Type to narrow, Enter to jump. The palette’s command is Outline: jump to a heading.

Find in a note

/ opens the find bar. Type, and every match lights up; the status line counts them (3 of 12). Enter closes the bar and keeps the matches, n and N step forward and back, and esc clears them.

There’s no search across the vault. For that, use Obsidian, or rg in a terminal and open the result with n0 file:line.

click a linkfollow it
Enter on a block that’s only a linkfollow it
gdfollow the first link in the block
  • Wikilinks resolve the way Obsidian resolves them: a bare name finds the note anywhere in the vault, preferring one next to the current note, then the shortest path. [[Note#Heading]] opens at the heading, [[#Heading]] jumps within this note, and [[Note|text]] shows its text.
  • Links to notes that don’t exist are dimmed. Following one creates the note, as in Obsidian.
  • Markdown links to local notes, like [the plan](Launch%20plan.md), work like wikilinks, #heading included.
  • Web, mail and obsidian:// links open in their app on the first click. Any other kind (file:, links into other apps) first shows where it goes in the status line, and opens on a second click within five seconds. Link text can hide where a link really goes; this way you always see it before anything runs.

Back and forward

Following a link opens the note in the same window. ctrl o goes back and ctrl i goes forward, each to the place you were, not just the note. So do alt ← and alt → and the mouse’s back and forward buttons.

Windows

Each window shows one note, and its history. From the command line, a note that’s already open isn’t opened twice: n0 NOTE and n0 --today bring its window forward instead. Inside a window, the switcher, the palette and links open the note in that window.

Notes open as regular windows that tile like any other, with your Omarchy window rules and transparency.

Editing

n0te edits one block at a time. The block you’re editing shows its markdown, and the rest of the note stays a page around it, so you never lose your place in a wall of syntax.

Edit a block

e, Enter or double-clickedit the block you’re on (or clicked)
escfinish: the block renders again
click another blockmove the edit there
↑ ↓ past the first or last linemove the edit to the block above or below

The block’s source appears in a quiet box. Syntax like **, [[ and - [ ] shows faintly, and the text keeps its rendered size and weight, so a heading still looks like a heading while you type. Only that block is re-read as you type, so editing stays fast in long notes.

Enter on a block that’s only a link follows it instead, and Enter on a foldable callout opens or closes it. e always edits.

Lists and quotes

While editing:

  • Enter continues a list or a quote: a new - , 1. , - [ ] or > line. On an empty item it ends the list instead.
  • tab nests a list item under the one above; shift tab moves it back out. Its sub-items come along.
  • alt ↑ and alt ↓ move the item, with its sub-items, above or below its neighbour.

alt ↑ and alt ↓ (or alt k and alt j) also work while reading, on the list item you’re on.

New blocks

o opens a new, empty block below the current one. In a list, it’s a new item at the same depth, after the current item’s sub-items: a task after a task, the next number after a numbered item.

Type [[ and a list of notes appears at the caret, filtered as you type, and named the way Obsidian names them: the shortest name that’s unique in the vault. [[# lists this note’s headings instead.

↑ ↓ choose, Enter or tab inserts the link, and esc dismisses the list.

Edit the whole note

ctrl e turns the whole note into its source, for changes that span many blocks: select all, delete, rewrite, paste in a draft. esc or ctrl e renders it again. The status line says SOURCE while you’re in it.

Select blocks

V (or v) starts a selection at the current block. j k gg G extend it, then:

ddelete the blocks
ycopy them as markdown
eedit them together, as one piece of source
xtick or untick every task in them
esccancel

A mouse selection works the same way, and it selects every block it touches, even if it’s a single word in one block: d then deletes the whole block and y copies its markdown. The status line says SELECT · 1 block when that’s what will happen. To copy just the selected text, use ctrl c.

Undo

uundo the last edit, even after esc
ctrl rredo
ctrl zwhile editing: undo typing

u undoes whole edits: one block edit, one tick, one o, one Uncheck all. It works as a merge, so if the note changed on disk since (Obsidian synced something in, say), those changes stay. History is kept per file for as long as n0te’s service runs, so it survives closing and reopening the note.

Saving

You never save. n0te writes the file a second after you stop typing, when you press esc, when its window loses focus, and when you close it. There’s no dialog and no “unsaved” state to worry about.

ctrl s, while reading or editing, saves at once, even when autosave is paused for a hold.

If a save fails (no permission, a full disk), the status line turns red and says why. Closing the window or leaving the note then takes a second try, and only that drops the change.

Start editing from the command line

n0 -e "Launch plan"      # edit at the end of the note
n0 -e +42 notes.md       # edit at line 42
n0 -e --today            # today's daily note

Leaving while editing

ctrl o, ctrl i, ctrl k, ctrl p and ctrl , finish the edit first, then do what they do. Nothing is lost.

Checklists

Tasks (- [ ] item) are where n0te earns its keep: open the list, tick, close, all in a few keystrokes.

Ticking

xtick or untick the task you’re on
click the boxthe same
V, then j / k, then xtick or untick several at once

Tasks are drawn as boxes. A done task’s box fills with your theme’s accent and its text is struck through. Ticking changes the box where it is: the page doesn’t re-render or move, so you can work down a long list with j x j x.

Every list item is its own block, nested ones too, so j and k stop on each task. (Inside a quote or a callout, the whole quote is one block, and x ticks its first task; click a box to tick another.)

The status line counts them: 12/25 done.

Hiding done tasks

h folds done tasks away, and h again brings them back. j and k step over hidden tasks. The status line says 12/25 done, hidden while they’re hidden.

To start every new window with done tasks hidden, switch on Hide done tasks in new windows in Settings.

Lists you run again and again

For a packing list, a release checklist, a weekly review: Uncheck all tasks in the palette (ctrl k, type uncheck) clears every box in the note. It’s one step, so a single u puts them all back.

Adding tasks

While editing a task, Enter starts the next one (- [ ] ), and Enter on an empty one ends the list. o on a task opens a new task below it, after its sub-tasks.

From a terminal, without opening a window:

n0 -t "buy milk"                 # to today's daily note
n0 -t "call the bank" errands    # to the note named errands

See Daily notes and quick capture.

What counts as a task

n0te knows [ ] (open) and [x] or [X] (done), which is what Obsidian writes. Custom statuses from plugins, like [-] or [>], show as plain text.

A tick changes exactly one character in the file, the one between the brackets, so a tick from n0te and an edit from Obsidian or a phone never get in each other’s way.

New notes and templates

Making a note

There are three ways to start a note:

  • From the switcher: type a name that matches no note, and pick Create note at the bottom of the list.
  • From a link: follow a link to a note that doesn’t exist yet (it’s dimmed), as in Obsidian.
  • From the command line: n0 "Trip to Lisbon". A name that matches no note opens the switcher with the name typed in, and Create note is the last row, already selected when nothing else matches.

The note goes where Obsidian would put it, following Obsidian’s Default location for new notes: the vault’s root, a chosen folder, or next to the current note. A name with a slash, like Projects/Acme/Retro, is a path in the vault. Characters Obsidian won’t allow in a file name (\ : * ? " < > |) become dashes.

Templates

If you use Obsidian’s Templates core plugin and its folder has templates in it, n0te asks how to start (otherwise the note simply starts blank):

  • Blank note: just the title, as # Trip to Lisbon.
  • One of your templates, from the templates folder you set in Obsidian.

The one you picked last time is already selected, so making a run of notes from one template is Enter, Enter, Enter.

n0te fills in Obsidian’s template variables: {{title}}, {{date}}, {{time}}, and formats like {{date:YYYY-MM-DD}}. An empty # heading gets the title too, and so does a heading that ends with a colon, like # Decision: .

Where the caret lands

The new note opens ready to type, at the first place in it that asks for something: an empty list item or label (like - [ ] or **Owner:** ), or an empty section, whichever comes first. If there’s nothing to fill in, you start typing at the end of the note, which for a blank note is just below the title.

Nothing is written until you type

The status line says NEW NOTE until the note is saved. If you leave without typing, no file is left behind, so a mistyped name costs nothing.

New files outside the vault

Anything that looks like a path opens as a new, empty file instead of a note: ./idea.md, ~/todo.txt, /tmp/scratch.sh, or a name with a file extension other than .md.

n0 ./new-idea.md

It’s created the first time you type.

Daily notes and quick capture

Today’s note

n0 --today        # open today's daily note
n0 -e --today     # …and start typing at the end

The palette has it too: Today’s daily note.

n0te follows the settings of Obsidian’s Daily notes core plugin: its folder, its date format and its template. If today’s note doesn’t exist yet, n0te creates it from the template, filling in {{title}}, {{date}} and {{time}}. Without those settings, the note is YYYY-MM-DD.md in the vault’s root.

Date formats use Moment.js tokens, as Obsidian’s do. n0te understands the common ones: YYYY, YY, MMMM, MMM, MM, DD, dddd, ddd, HH, hh, mm, ss, A, and ww, which n0te takes as the ISO week number. Bracketed literals like [Week] ww aren’t supported; the brackets end up in the name.

Quick capture

-t adds a task without opening a window:

n0 -t "buy milk"
# Added to Notes/Daily/2026-10-08.md

The task goes at the end of today’s daily note, as - [ ] buy milk. If the note ends with a list, the task joins it; otherwise it starts a new one after a blank line.

Name a note to add to it instead:

n0 -t "call the bank" errands
n0 -t "renew passport" "Projects/Admin"

The note is found the way n0 finds one. If it doesn’t exist, it’s created where Obsidian would put a new note.

From a key or a script

-t takes its text as an argument and prints one line, so anything that can ask for a line of text can feed it: a launcher in dmenu mode, or a script. Bind that to a key in your own Hyprland config, not in ~/.config/hypr/n0te.lua, which setup and the settings page rewrite. Empty text adds nothing: -t refuses it and exits with status 2.

Code and other files

n0te opens any text file, not just notes. Markdown files (.md) render as pages; everything else opens as code, in your monospace font with line numbers and syntax highlighting.

n0 src/main.rs:42          # at line 42
n0 src/main.rs:42:7        # file:line:col, as grep and compilers print it
n0 +42 src/main.rs         # the same, vim style
n0 -e +42 src/main.rs      # editing at line 42
n0 ~/.config/hypr/hyprland.lua

Paste a location from rg -n, a compiler error or a stack trace, and n0te opens it at that line.

Highlighting

Highlighting uses tree-sitter for bash, C, CSS, Go, HTML, JavaScript, JSON, Lua, Python, Rust, TOML, TypeScript, TSX and YAML. Smaller built-in highlighters cover Hyprland’s classic config, .conf, .ini, .desktop and .service files, diffs and markdown source. Shell dotfiles like .bashrc, .zshrc and PKGBUILD count as bash.

The colors come from your theme, mapped the way Omarchy maps them for Helix, so code in n0te looks like code in your editor.

Code blocks inside notes are highlighted the same way, by their fence’s language (```rust, ```sh).

Reading code

j kscroll a few lines
space b, gg Gpage down / up, top / bottom
wwrap long lines, or stop wrapping
/, n Nfind, next, previous
ycopy the whole file

Long lines don’t wrap until you ask with w.

Editing code

e, Enter or a double-click edits the whole file, with the caret on the first line in view (or, for a double-click, where you clicked). Highlighting catches up 200 ms after you stop typing. esc finishes. Saving works as for notes: automatic, atomic, and keeping the file’s mode, line endings and final newline.

When someone else changes the file while you’re editing (a git checkout, a formatter), n0te merges line by line. When the same lines changed in both places, it writes git-style conflict markers and stops autosaving until you’ve resolved them and pressed ctrl s. See Holds.

For bigger work, Edit in editor in the palette saves and opens the file in your editor (Omarchy’s omarchy-launch-editor).

Pipes and stdin

git log -1 | n0 -                # stdin
n0 <(git show HEAD:README.md)    # a pipe, read the same way
curl -s https://example.com/notes.md | n0 -

n0te reads the input into a private temporary file and renders it as markdown. Since that copy is deleted once read, an stdin window can’t be reloaded or opened in your editor.

Limits

  • Files up to 64 MB open. Larger ones show a short page saying so.
  • n0te opens text. A file that isn’t valid UTF-8, or that has NUL bytes in it (most binary files), shows a short page explaining why, and nothing is ever written to it.

Living next to Obsidian

n0te is built to share a vault with Obsidian, not replace it. Both read and write the same plain markdown files, and n0te takes care not to fight with Obsidian, sync, or anything else that touches them.

What n0te reads from Obsidian

Fromn0te uses it for
Obsidian’s obsidian.json (native, Flatpak or Snap)which vault is open, and the list of vaults in Settings
.obsidian/app.jsonwhere new notes go (Default location for new notes)
.obsidian/daily-notes.jsonthe daily note’s folder, date format and template
.obsidian/templates.jsonthe templates folder

n0te never writes to .obsidian/, and keeps no index, cache or database of its own. Folders starting with a dot (.obsidian, .trash, .git) are left out of the switcher.

Clean round trips

An edit in n0te changes only what you changed. Blocks you didn’t touch are written back byte for byte, with the file’s line endings (LF or CRLF), its final newline, its mode, and symlinks kept. A tick changes one character. So a note edited in n0te shows a one-line diff in git, and Obsidian (or Obsidian Sync) sees one small change.

When both have a note open

n0te watches the file it shows. When Obsidian (or Sync, Syncthing, git pull) changes it:

  • with nothing unsaved in n0te, the page reloads in place;
  • while you’re editing, n0te merges their change with yours, block by block;
  • if Obsidian renames the note, n0te follows it to the new name.

See Saving and changes on disk for the details.

Wikilinks resolve as Obsidian resolves them, [[ completion writes the shortest unique name as Obsidian does, and following a link to a missing note creates it where Obsidian would. Notes created in n0te start from your Obsidian templates.

The updated: date

If your notes carry a date in their frontmatter, like updated: 2026-10-01, switch on Set updated: to today when saving in Settings. Saving a change then sets it to today, the way plugins like Update time on edit do in Obsidian. It’s only touched when something else in the note changed, and never while you’re editing the frontmatter yourself.

Jumping across

Open in Obsidian, in the palette, opens the current note in Obsidian (through an obsidian:// link). Links to obsidian:// in your notes open on a single click.

What n0te doesn’t do

Some Obsidian features have no counterpart in n0te, by design or not yet:

  • No graph, backlinks or vault-wide search. n0te finds notes by name.
  • Embeds (![[note]]) show as links, and images as placeholders, for now.
  • Plugin syntax isn’t run: a Dataview query shows as a code block, and custom task statuses show as text.
  • No canvas, PDF or other attachment views; n0te opens text.

Themes

n0te has no theme of its own. It takes its colors from the running Omarchy theme, and when you switch themes, every open window repaints in place, without reopening anything.

Where the colors come from

  1. The theme’s n0te.css, in ~/.local/state/omarchy/current/theme/. Omarchy renders it on every theme switch from the template that n0te --setup installs (~/.config/omarchy/themed/n0te.css.tpl). A theme can also ship its own n0te.css, to color n0te its own way.
  2. The theme’s colors.toml, for any color n0te.css leaves out, or when there’s no n0te.css yet (before your first theme switch after setup, say).
  3. n0te’s own dark palette, when there’s no Omarchy theme at all: black, warm paper-white text and a crimson accent.

Light themes work too: n0te reads mode = "light" from colors.toml.

The colors

n0te.css sets colors with @define-color lines, in #rrggbb hex:

@define-color bg #1a1b26;
@define-color accent #7aa2f7;
NameUsed for
bgthe page
textbody text
headingheadings, table headers
text_dimquotes, links to missing notes
text_faintmarkdown syntax while editing, done tasks, #tags, frontmatter, rules
surfacecode and callout boxes
surface_2inline code
ruletable lines, card borders, the scrollbar
raisethe palette and other cards
accentlinks, done boxes, the caret, the current find match
selectionselected text, find matches
commentcomments and punctuation in code
red orange yellow green cyan blue magentacode highlighting and callouts, mapped as Omarchy maps them for Helix

A value that isn’t plain hex (a template placeholder that wasn’t filled in, say) is ignored, and n0te’s own value for that color stays.

Styling beyond colors

Everything in n0te.css other than the @define-color lines is ordinary GTK CSS, loaded above n0te’s own, for n0te’s windows: window.n0te, the status line (.status), the palette (.palette), the settings page (.settings) and the link list (.complete). For example:

window.n0te .status { font-size: 10px; }

For theme authors

To give your theme its own n0te colors, ship an n0te.css in the theme’s folder next to colors.toml, with any of the colors above. It comes along when your theme is picked, and n0te uses it instead of the one rendered from the template. Start from n0te’s template, which n0te --setup puts in ~/.config/omarchy/themed/n0te.css.tpl. It shows how most of the colors are derived from colors.toml by default, in Omarchy’s template syntax:

@define-color bg {{ background }};
@define-color text {{ foreground }};
@define-color heading {{ bright_foreground }};
@define-color text_dim {{ mix foreground background 28% }};
@define-color text_faint {{ mix foreground background 46% }};
@define-color surface {{ mix background foreground 4.5% }};
@define-color surface_2 {{ mix background foreground 9% }};
@define-color rule {{ mix background foreground 15% }};
@define-color accent {{ accent }};
@define-color selection {{ selection_background }};
@define-color red {{ red }};
@define-color yellow {{ yellow }};
@define-color green {{ green }};
@define-color cyan {{ cyan }};
@define-color blue {{ blue }};
@define-color magenta {{ magenta }};

raise, comment and orange aren’t in it; n0te derives those itself.

Window transparency

n0te opens as a regular window and keeps Omarchy’s default window opacity, like your terminal. To make it opaque, add a window rule for its class, dev.n0te.N0te, in your Hyprland config.

Keys

Every key in n0te, on one page. The palette (ctrl k) lists the commands too, each with its key.

Anywhere

KeyWhat it does
ctrl kpalette: notes, headings, commands (also : while reading)
ctrl pswitcher: notes only
ctrl o ctrl iback / forward (also alt ← alt → and the mouse’s back and forward buttons)
ctrl + ctrl - ctrl 0text size up / down / back to 100%, in every window
ctrl ,settings
ctrl ssave now, even when autosave is paused for a hold

Reading

KeyWhat it does
j k, ↓ ↑next / previous block (in code: scroll)
space b, Page Down Page Uppage down / up
gg G, Home Endtop / bottom
Enterthe block’s main action: edit it; follow it if it’s only a link; open or close a foldable callout
gdfollow the first link in the block
clickfollow a link, tick a box, fold a callout, or go to the block
taboutline: jump to a heading
/find in the note
n Nnext / previous match
ffocus mode
hhide or show done tasks
ycopy the block as markdown (in code: the whole file)
wwrap long lines (code)
rreload from disk
escleave focus mode, or clear find matches
qclose the window (everything is already saved)

Changing the note

KeyWhat it does
e, double-clickedit the block (in code: the file)
ctrl eedit the whole note as source
onew block below; in a list, a new item at the same depth
x, click the boxtick or untick the task
alt ↑ alt ↓, alt k alt jmove the list item (with its sub-items) up / down
uundo the last edit
ctrl rredo

Selecting blocks

V or v starts a selection (or drag the mouse across blocks).

KeyWhat it does
j k gg Gextend the selection
d, Delete, Backspacedelete the selected blocks
ycopy them as markdown
e, Enteredit them together
xtick or untick every task in them
esccancel

Editing

KeyWhat it does
escfinish editing
Enternew line; continues a list or quote, and ends it on an empty item
↑ ↓past the first or last line: move the edit to the block above or below
tab shift tabon a list item: nest it / move it back out
alt ↑ alt ↓on a list item: move it among its siblings
[[link to a note ([[# for a heading in this note)
ctrl zundo typing
ctrl efrom a block: edit the whole note as source; from the source: finish
ctrl o ctrl i, ctrl k, ctrl p, ctrl ,finish the edit, then do what they do
click another blockmove the edit there

All of GTK’s usual text keys work too: ctrl a, ctrl c, ctrl x, ctrl v, word movement with ctrl ← ctrl →, and so on.

The [[ list

KeyWhat it does
↓ ↑, ctrl n ctrl pchoose
Enter, tabinsert the link
escdismiss the list

The palette

KeyWhat it does
typefilter (fuzzy)
↓ ↑, tab shift tab, ctrl n ctrl pmove
Enteropen or run
escclose

Find

KeyWhat it does
typefind as you type
Enterclose the bar, keep the matches
escclose the bar and clear the matches

Settings page

KeyWhat it does
j k, ↓ ↑, tab shift tabmove
space, Enterswitch or change the setting
← →, h lpick the vault or the text size
esc, q, ctrl ,close

Command line

n0te [options] [file…]          n0 is the same command

n0 hands its arguments to n0te’s service and returns as soon as the window is up, so your shell stays free. If no service is running, it starts one first.

Opening things

n0                              # the switcher over your vault
n0 "Launch plan"                # a note by name, anywhere in the vault
n0 "Projects/Acme/Launch plan"  # vault-relative, with or without .md
n0 Projects/Acme                # a folder: the switcher, narrowed to it
n0 blue                         # no such note: the switcher, with "blue" typed in
n0 src/main.rs:42               # any file at a line; also file:line:col
n0 +42 src/main.rs              # the same
n0 '~/notes/todo.txt'           # a quoted ~ is expanded
n0 ./new-idea.md                # a file that doesn't exist yet opens empty
n0 a.md b.md                    # several files, a window each
git log -1 | n0 -               # stdin
n0 <(git show HEAD:README.md)   # a pipe, read the same way

How a name is found

  1. A real path, relative to where you are, wins.
  2. Then a path in the vault, with or without .md.
  3. Then any note with that name, the way a wikilink resolves.
  4. A folder in the vault opens the switcher narrowed to that folder.
  5. Anything else that looks like a path (./x, ../x, ~/x, /x, notes/x.md when notes/ is a folder where you are, or a short file extension other than .md, like x.sh) opens as a new, empty file, written when you type. So does everything, when there’s no vault.
  6. Anything else opens the switcher with what you typed, ready to find it or create it. A name with a dot that isn’t an extension, like v0.1 plan, counts as a name.

Options

OptionWhat it does
+Nstart at line N
-estart editing: at line N if given, else at the end of a note (at the top of code)
-t TEXT [NOTE]add - [ ] TEXT to today’s daily note, or to NOTE, without opening a window
--todaytoday’s daily note, created from your template if needed
-read from stdin
--settingsopen the settings page
--setupwire n0te into Omarchy; --setup default also opens .md files in n0te; --setup remove undoes it all
--servicerun the service (n0te starts it by itself when needed)
--quitsave every open note and stop the service
--help, -hprint the usage
--version, -Vprint the version
--benchreport the time to the first frame, then close
--checkrender every note in the vault and report problems (for development)

Examples

n0 -e --today                          # write in today's note
n0 -t "buy milk"                       # capture a task
n0 -t "call the bank" errands          # …into a note
rg -n TODO | head -1 | cut -d: -f1,2 | xargs n0    # the first TODO, at its line
n0 -e +1 "Inbox"                       # edit the top of a note

Exit status

StatusMeaning
0done; for an open, the window is up
1something failed, and n0te said what (-t couldn’t write, --quit couldn’t save a note)
2a usage mistake: an unknown --option, -t without text
127n0te-app couldn’t be started

Tab completion

In bash, n0 <Tab> completes note names from your vault, also by file name (n0 laun<Tab> → Projects/Acme/Launch plan), then files and options. n0te --setup installs it; it takes effect in new shells.

Environment

n0 sends only its arguments and the current folder to the service, so these are read where the service runs: your session’s environment if it starts at login, or the shell of the first n0. After changing one, n0 --quit so the next n0 starts a service that sees it. (Tab completion runs in your shell, so it sees your shell’s N0TE_VAULT straight away.)

VariableWhat it does
N0TE_VAULTuse this vault, over Settings and Obsidian’s
XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_STATE_HOMEwhere config, app entries and state go (defaults under ~)
XDG_RUNTIME_DIRwhere the service’s socket and log live
GSK_RENDERERGTK’s renderer; n0te defaults to gl, which measured fastest

Settings

Open the settings page with ctrl , in any window, Settings in the palette, or n0te --settings. j k move, space changes the row you’re on, ← → pick the vault or the text size, and esc closes. Changes take effect at once.

Notes

SettingWhat it doesDefault
VaultFollow the vault Obsidian has open, or always use one of Obsidian’s vaultsfollow Obsidian
Text sizeThe reading size, 70% to 240%; also ctrl + ctrl - ctrl 0 anywhere100%
Hide done tasks in new windowsNew windows start with done tasks folded away; h shows themoff
Set updated: to today when savingWhen a note’s frontmatter has updated: with a plain date (2026-10-01), saving a change sets it to today. Left alone while you’re editing the frontmatter yourselfoff

Omarchy

SettingWhat it does
super n opens n0teSwitches the binding line in ~/.config/hypr/n0te.lua on or off; Hyprland reloads its config
Start at loginThe same, for the line that starts the service at login
Open markdown files in n0teMakes n0te the text/markdown default. Switching it off gives .md files back to the app that had them

These three aren’t stored by n0te. The page reads them from Hyprland’s config and xdg-mime each time it opens, so it always shows what’s really set, even after a hand edit or n0te --setup. Without Omarchy’s Lua config (~/.config/hypr/hyprland.lua), the Hyprland rows are greyed out.

config.toml

The settings in the first group live in ~/.config/n0te/config.toml. Open config.toml, at the bottom of the page, opens it in n0te. It’s fine to edit by hand: n0te reads it fresh each time it needs a setting, except text_size, which a hand edit changes after n0 --quit.

# A vault to use instead of the one Obsidian has open.
vault = "/home/you/Notes"

# Start new windows with done tasks hidden (h shows them).
hide_done = false

# When saving a note, set its frontmatter `updated:` to today.
bump_updated = false

# Reading text size, percent (ctrl + and ctrl - change it).
text_size = 100
  • Lines are key = value. A line that starts with # is a comment; a comment after a value isn’t supported (hide_done = true # x reads as false). Unknown keys are ignored.
  • vault is a quoted path. Leave it out (or comment it out) to follow Obsidian.
  • text_size accepts 50 to 300 by hand; the keys step through 70, 80, 90, 100, 110, 120, 135, 150, 170, 200 and 240.
  • The vault in config.toml can be any folder, not only one Obsidian knows. N0TE_VAULT overrides it.

What renders

n0te reads CommonMark with the extensions Obsidian uses. Here is what each construct looks like in n0te, and what isn’t supported yet.

Text

MarkdownIn n0te
**bold**, *italic*, ~~struck~~as you’d expect
==highlight==highlighted, in the theme’s colors
`inline code`monospace, on a faint background
#tag, #nested/tagquiet, in faint text
%%comment%%, <!-- comment -->hidden, as in Obsidian’s reading view
hard-wrapped linesreflowed to the window’s width; deliberate line breaks stay

Blocks

MarkdownIn n0te
# Heading to ###### Headingthree sizes (#, ##, and ### and below), in the theme’s heading color
- item, 1. item, nested listshanging indents; every item is its own block
- [ ] task, - [x] taskdrawn boxes; see Checklists
> quoteindented, set in italics
> [!type] Title calloutsboxed, in the type’s color; + and - fold them
``` fenced codeboxed and highlighted by language; long lines wrap inside the box
tablesset in the reading face; see below
---a rule
[^1] footnotesa quiet [1] in the text, and the footnote, marked [1], where it’s written
YAML frontmatterone quiet line at the top
MarkdownIn n0te
[[Note]], [[Note|text]]a link, resolved like Obsidian’s
[[Note#Heading]], [[#Heading]]opens at the heading
[[Missing note]]dimmed; following it creates the note
[text](Other%20note.md)works like a wikilink
[text](https://…), <https://…>opens in your browser
![[Note]] embedsa link to the note, marked ↳; the note isn’t shown inline yet
![](image.png), ![[image.png]]a quiet ▣ placeholder with the file’s name; images aren’t drawn yet

Tables

Tables are set in the reading face, not as a monospace grid. Columns are measured from their text and line up on tab stops, including right and center alignment. Cells keep bold, italics, code, links and <br> line breaks.

A table that fits the text column lines up with the text. A wider one grows toward the window’s edges (up to 1200 px), and past that its cells wrap, with short columns keeping their width. A hairline sits under the header and fainter ones between rows. Resizing the window lays tables out again.

Not yet

  • Images and embeds show as placeholders and links. Drawing them is planned.
  • Math ($…$, $$…$$) shows as written.
  • Mermaid and other diagram blocks show as code.
  • Plugin syntax (Dataview, Tasks queries) shows as written, usually as a code block.
  • Custom task statuses ([-], [>]) show as text.
  • HTML beyond comments and <br> in tables shows as quiet source text.

Files and environment

Everything n0te reads and writes outside your notes. Paths are shown with their defaults; XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME move them when set.

What n0te writes

PathWhatWritten by
~/.config/n0te/config.tomlyour settingsthe settings page, ctrl + / ctrl -
~/.config/hypr/n0te.luathe super n binding and the service at loginsetup, the settings page
~/.config/hypr/hyprland.luaone require("hypr.n0te") line, with a commentsetup (removed by --setup remove)
~/.config/omarchy/themed/n0te.css.tplthe theme templatesetup
~/.local/share/applications/n0te.desktopthe app entry, unless a package installed onesetup
~/.local/share/bash-completion/completions/n0te, n0tab completion, unless a package installed itsetup
~/.local/state/n0te/markdown-defaultthe app that opened .md files before n0te, to give them backsetup, the settings page

If you’ve changed the app entry, the theme template or the completion file, setup leaves yours in place, and --setup remove leaves it too. n0te.lua is n0te’s own: setup and the settings page rewrite it, and --setup remove deletes it, so put your own Hyprland lines elsewhere. Files are replaced atomically, so Hyprland never reads half a file, and a symlink (dotfiles managed with stow, say) stays a symlink.

While it runs

These live in $XDG_RUNTIME_DIR (usually /run/user/1000):

PathWhat
n0te.sockthe service’s socket, readable only by you
n0te-service.logwhat the service prints, appended to
n0te-stdin-*.mda copy of stdin or a pipe, deleted once the window has read it

Without XDG_RUNTIME_DIR, they go in a private n0te-UID folder in $TMPDIR (or /tmp), which n0te creates readable only by you. It refuses a folder of that name that isn’t yours or that others can read.

While saving a note, n0te writes .NAME.n0te-PID~ next to it for an instant, then renames it over the note.

What n0te reads

PathFor
~/.config/obsidian/obsidian.json; Flatpak and Snap equivalentsObsidian’s vaults, and which one is open
VAULT/.obsidian/app.json, daily-notes.json, templates.jsonnew notes, daily notes, templates
~/.local/state/omarchy/current/theme/n0te.css, colors.tomlcolors
~/.local/state/omarchy/current/theme.namewatched, to repaint on a theme switch
~/.config/hypr/hyprland.lua, n0te.luawhat the settings page shows for super n and login

Environment

These are read by the service when it starts, not by each n0; see Command line.

VariableWhat it does
N0TE_VAULTthe vault to use, over Settings and Obsidian
XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_STATE_HOMEbase folders, defaulting to ~/.config, ~/.local/share and ~/.local/state
XDG_RUNTIME_DIRwhere the socket, log and stdin copies go
XDG_DATA_DIRSwhere to look for other apps’ entries (to name the markdown app you had)
HYPRLAND_INSTANCE_SIGNATUREwhen set, n0te asks Hyprland to reload after changing n0te.lua
GSK_RENDERERGTK’s renderer; n0te sets gl unless you choose another

For development, N0TE_APP_ID runs a separate instance with its own app id and N0TE_FLOAT makes windows fixed-size; see Contributing.

Saving and changes on disk

n0te’s promise is that it never loses your words and never clobbers anyone else’s. This page is how it keeps it.

When n0te saves

There’s no save button to forget. n0te saves:

  • a second after you stop typing;
  • when you press esc to finish an edit;
  • when its window loses focus;
  • when you close the window, or leave the note for another;
  • for every open note, on n0 --quit. If one can’t be saved, the service doesn’t quit, and says which.

A note you only read is never written. (The exception is today’s daily note: --today, the palette’s Today’s daily note and -t create it from your template as soon as they need it.)

How it writes

  1. It checks the disk first. Just before writing, n0te reads the file again. If it changed since n0te last saw it, that change is merged first (see below), so nothing is ever overwritten blind.
  2. It changes only what you changed. The new text is your edited block spliced into the file as it is on disk. Every other block goes back byte for byte.
  3. It writes atomically. The text goes to a hidden temporary file next to the note (.NAME.n0te-PID~), which is flushed to disk, then renamed over the note. Readers see the old file or the new one, never half of one, and Obsidian sees one clean change.

It keeps the file’s permissions, follows symlinks (the file they point to is replaced, and the link stays), and keeps the line endings (LF or CRLF) and the final newline, or the lack of one.

In a folder you can’t write to, the temporary file can’t be made; a note there that you can write is saved in place instead.

When a save fails

No permission, a full disk, a missing folder: the status line turns red and says why (NOT SAVED · …). Your text stays in the window. Closing the window or leaving the note then takes a second try (do that again to discard your changes), and only that drops the change.

When the file changes on disk

n0te watches the file it shows. When something else writes to it (Obsidian, Obsidian Sync, Syncthing, git pull, a script) n0te waits until two reads in a row agree, so a file still being written isn’t taken half-done. Its own saves are recognised and ignored.

  • Nothing unsaved: the page reloads in place, without jumping, and the status line says Reloaded: changed on disk.
  • You’re editing: n0te merges. It compares three versions: the file as it was when you started, yours, and the one on disk now. Changes on either side are kept. Notes merge block by block, and then line by line inside a block; code merges line by line. Your edit stays open where it was. The status line says Merged changes from disk.
  • The same lines changed on both sides: in a note, n0te keeps both versions, yours first, saves, and says kept both versions, yours first. Tidy up whichever you don’t want.

Holds

A few situations are too risky to settle on its own. Then n0te holds: autosave pauses, the status line says why in red, and you decide.

Status lineWhat happenedWhat to do
CONFLICT MARKERS FROM A MERGEA merge in a code file hit lines changed on both sides, and n0te wrote git-style <<<<<<< markers into your copyFix them, then ctrl s
CHANGED ON DISK TOOBoth sides changed, and the file was too big to mergectrl s keeps yours. To take the disk’s, esc, then r twice
MOVED OR DELETED ON DISKThe file is gone from where it wasctrl s writes your unsaved changes back at the old path. With nothing unsaved, there’s nothing to keep: close the window
BINARY ON DISK NOWThe file on disk turned binary (NUL bytes)ctrl s keeps yours. To take the disk’s (read-only), r twice

ctrl s works while reading and while editing. r reloads only while reading, and during a hold the first press warns that your version will be dropped (do that again to discard your changes); a second press within five seconds does it.

A moved file isn’t silently recreated, because that would undo a deliberate move or delete. But two common cases need no hold:

  • A rename in Obsidian is followed: the window switches to the new name.
  • Vim and Emacs rename a file to a backup while saving it. n0te ignores that rename and stays on the file.

Undo across changes

u undoes your edits as merges, too: it takes out your change and keeps anything that changed on disk since. History is kept per file for as long as the service runs, so it survives closing and reopening a note, but not n0 --quit or a logout.

The updated: date

With Set updated: to today when saving on, a save that changes something also sets a plain updated: YYYY-MM-DD in the frontmatter to today. The new date is exactly as long as the old one, so nothing else moves. It’s skipped while you’re editing the frontmatter yourself, or the whole note as source.

The service

Opening a note in n0te feels instant because almost nothing happens when you type n0. The work was done earlier, by a service that’s already running.

Two programs

ProgramJob
n0te (and n0)The command you type. It doesn’t load GTK, so it starts in under a millisecond. It sends its arguments to the service over a Unix socket and exits when the service answers.
n0te-appThe GTK app that renders notes and owns the windows. As n0te-app --service, it stays resident with the GPU, fonts and syntax grammars warmed up.

A GTK app takes 200 ms or more just to start. The service pays that once per session; every open after that costs only the work of rendering the note.

Starting and stopping

  • At login, if Start at login is on (n0te --setup turns it on), Hyprland runs n0te --service, so even the first open is instant.
  • Otherwise, the first n0 of a session starts the service in the background. That open takes about 350 ms; the next ones about 30.
  • The service runs in its own session, so closing the terminal you started it from doesn’t stop it.
  • n0 --quit saves every open note and stops it. Do that after an update or a rebuild, so the next open runs the new version.

If the service can’t be reached or started, n0 runs n0te-app directly for that open. It’s slower, but it works.

Timings

Measured from spawning n0 to the compositor showing the first frame, on the reference machine (RTX 3070, Hyprland 0.56, 240 Hz), opening a 31 KB note:

Openp50p95
Service running28 ms29 ms
Cold, no service203 ms207 ms

The first n0 of a session, which also starts the service, takes about 350 ms. Rendering every note in a 49-note vault takes 29 ms in total.

n0 --bench FILE reports the time to first frame on your machine, and bench/m0.sh in the repository runs the full measurement.

The socket and the log

The socket is n0te.sock in $XDG_RUNTIME_DIR (usually /run/user/1000), readable only by you. The service’s output goes to n0te-service.log next to it: start-up and socket errors, GTK warnings, and a crash’s last words. Problems with a file show in the window’s status line instead.

Without XDG_RUNTIME_DIR, both live in a private n0te-UID folder in /tmp that n0te creates for you, and refuses to use if anyone else owns it or can read it.

Memory

The service holds one GTK process with its windows. Your undo history lives there too, which is why u still works after you close and reopen a note, and why it’s gone after n0 --quit.

Blocks

Most of n0te’s keys act on blocks, and editing happens a block at a time. This page explains what a block is and how editing one stays exact.

What’s a block

When n0te renders a note, it cuts it into blocks:

  • each paragraph, heading, code block, table, rule and footnote;
  • each list item, nested ones too, so j stops on every task in a checklist;
  • each quote or callout, as one block with everything inside it.

A list item and its sub-items form a subtree. o adds a new item after the whole subtree, alt ↑ and alt ↓ move the item with its subtree, and tab nests it along with its subtree.

Every block knows its source

For every block, n0te records exactly which bytes of the file it came from, the way the file is on disk, %%comments%% included. That’s what makes block editing safe:

  • e swaps the block’s rendered text for those bytes, in an edit box. Only the box is editable; the rest of the page is locked.
  • While you type, only that block is parsed and restyled, so a long note stays fast.
  • On save, the edited text is spliced into the file in place of those bytes. Every other byte of the file stays as it was. Blank lines at the edges of your edit are tidied, so a block can’t swallow its neighbours’ spacing.
  • A tick changes the one status character between [ and ], and the page updates where it is, without re-rendering.

n0te --check tests this on your whole vault: it renders every note, checks that each block maps back to its source, and that putting an unchanged block back reproduces the file byte for byte. It also lists any markdown left on the page as literal text. On the maintainer’s vault, both counts are 0.

Edits of more than one block

  • V then e edits a run of blocks as one piece of source.
  • ctrl e edits the whole note as source, for changes that cut across blocks. n0te restyles it 200 ms after you stop typing.
  • Code files are one block: e edits the whole file.

The page doesn’t jump

When a block changes, n0te re-renders the note, but it doesn’t redraw the page. It keeps the parts of the page that are the same before and after, replaces only the middle, and holds the block you were on in place while GTK lays out the new text. So esc after an edit, deep in a long note, leaves you where you were.

Safety and privacy

n0te handles your notes, so here is everything it does that reaches beyond the window.

Privacy

  • No network. n0te makes no network connections: no telemetry, no update checks, no accounts. The only time anything leaves your machine is when you open a web link, which goes to your browser.
  • No index or cache. n0te reads your notes when it needs them and keeps nothing about them on disk. The only things it writes outside your notes are listed in Files and environment.
  • Private runtime files. The socket, log and stdin copies live in $XDG_RUNTIME_DIR, or a n0te-UID folder that only you can read. n0te never puts them in a shared /tmp, where another user could read or replace them.

Your files

  • Atomic saves, so a crash or power cut mid-save leaves the old file or the new one, never half of one. (The one exception is a note in a folder you can’t write to, which is saved in place; see Saving.)
  • Merges, not overwrites, when a file changes on disk while you edit.
  • Holds for the cases n0te can’t settle safely: conflict markers in code, a file moved away, a file turned binary.
  • Binary files are read-only. n0te opens text. A file with NUL bytes or invalid UTF-8 shows a short page explaining why, and n0te never writes to it.
  • No surprises from links. Web, mail and obsidian:// links open on a click. Any other scheme (file:, other apps’ handlers) first shows its target, and opens only on a second click, since link text can hide where a link goes.

Your system

  • Setup is explicit. Nothing changes in Hyprland, the app menu or xdg-mime until you run n0te --setup or flip a switch on the settings page, and n0te --setup remove undoes it.
  • Your edits are kept. Setup leaves the app entry, the theme template and the completion file alone if you’ve changed them, and it keeps symlinked dotfiles as symlinks.
  • The settings page reads the real state. It never trusts a cached copy of Hyprland’s or xdg-mime’s settings.

Reporting a problem

If n0te ever loses or mangles text, that’s the most serious kind of bug. Note the steps, what the status line said, and anything in n0te-service.log (see The service). n0te’s issue tracker, and private reporting for security problems, open with the public repository.

Troubleshooting

First, the status line and the log

Problems with a file (a failed save, a merge, a hold) show in the status line at the bottom of the window, in red, with what to do. Note what it says.

Problems with n0te itself (a crash, a GTK warning, the service failing to start or listen) go to the service’s log:

less "$XDG_RUNTIME_DIR/n0te-service.log"

To start fresh, n0 --quit, then open any note: that starts a new service.

super n does nothing

  • Open Settings (n0 --settings) and check super n opens n0te. If the row is greyed out, there’s no ~/.config/hypr/hyprland.lua: you’re not on Omarchy’s Lua config, and you’ll need to bind the key yourself (see Install).
  • Check that ~/.config/hypr/hyprland.lua still has the require("hypr.n0te") line. Running n0te --setup again puts it back.
  • Hyprland runs n0te from its own PATH. If you installed from source into ~/.local/bin, make sure that’s on the PATH your session starts with.

Every open is slow

The service isn’t staying up. Check whether it’s running:

pgrep -a n0te-app

If nothing shows --service, look at the log: a crash leaves its last words there. A first open of about 350 ms per session is normal without Start at login; every open taking 200 ms or more is not.

n0te still behaves like the old version

After an update or a rebuild, the old service is still running. n0 --quit, then open a note.

The wrong vault opens

n0te follows the vault Obsidian has open (or opened last). To pin one, pick it under Vault in Settings, or set vault = "…" in ~/.config/n0te/config.toml. N0TE_VAULT overrides both, but only in the environment the service started in (your session’s, or the shell of the first n0); check it isn’t set somewhere you forgot, and n0 --quit after changing it.

The colors don’t match my theme

  • Right after n0te --setup, the theme template hasn’t been rendered yet. Switch theme once (or switch away and back).
  • If the theme ships its own n0te.css, that one wins; check ~/.local/state/omarchy/current/theme/n0te.css.
  • Off Omarchy, n0te uses its own dark palette. See Themes.

The status line is red

It’s either a failed save (NOT SAVED · …) or a hold. Both say what happened and what to do. Your text is still in the window either way.

For NOT SAVED, check that you can write the file and its folder (ls -l), and that the disk isn’t full.

A file opens as a short page saying it’s binary

The file has NUL bytes or isn’t valid UTF-8. n0te opens text and won’t edit such a file, so it can’t damage it. Open it with a tool made for its format.

“isn’t a private folder of yours”

Without XDG_RUNTIME_DIR, n0te keeps its socket in n0te-UID in your temp folder, and refuses to use that folder if someone else owns it or others can read it. Someone (or something) created it with the wrong owner or permissions. Remove it, or make sure XDG_RUNTIME_DIR is set, as it is in any normal login session.

Tab completion doesn’t work

It needs bash with bash-completion 2.12 or newer, and takes effect in new shells. n0te --setup installs it, unless a package already did.

Markdown files open in another app

Switch on Open markdown files in n0te in Settings, or run n0te --setup default. If another app takes them back later (some do when they update), switch it on again.

Still stuck

n0te doesn’t have a public issue tracker yet; it comes with the public repository. When you report a problem, include what you did, what happened, what the status line said, n0te --version, your GTK version (pacman -Q gtk4) and anything in the log.

Contributing

n0te is MIT-licensed. Its repository isn’t public yet; this page is for working on n0te once it is, and for whoever is working on it already. Bug reports, fixes and docs improvements will be welcome. For a bigger change, open an issue first, so we can talk about whether it fits: n0te stays small on purpose.

Build and test

cargo build --release      # 0 warnings
cargo clippy --release     # 0 warnings too
cargo test --release       # unit tests

The unit tests cover splicing, merging, undo, tasks, lists and the updated: date in the app, and config, vault detection, the runtime folder and setup in the library. GTK tests run when there’s a display and are skipped otherwise.

N0TE_APP_ID=dev.n0te.Check target/release/n0te-app --check

--check renders every note in your vault and must report 0 with leftover syntax and 0 that don't round-trip. Run it after any change to the renderer. tests/fixtures/kitchen-sink.md has every construct the renderer handles.

Run a test instance next to your own

Your own n0te service keeps running while you work. To try a build without touching it:

  • N0TE_APP_ID=dev.n0te.Something runs a separate instance under its own app id.
  • N0TE_VAULT=target/test-vault, pointing at a copy of a vault, keeps tests from writing to your real notes.
  • To try setup or the settings page, point HOME and XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME at a sandbox (your shell may set the XDG ones explicitly), and unset HYPRLAND_INSTANCE_SIGNATURE so n0te doesn’t reload your Hyprland.

Driving a window from a script

N0TE_VAULT=<copy> bench/script.sh OUT FILE STEPS

bench/script.sh runs n0te on GTK’s Broadway backend with a headless Chromium as its viewer, so nothing appears on your screen and nothing takes focus. A steps file (keys, typing, clicks on text, scrolls, waits, screenshots) runs against a copy of FILE, and the log shows every scroll change with the step that caused it, plus where blocks and the caret are. The steps are documented at the top of src/bin/n0te-app/script.rs.

bench/drive.sh does the same with a real, floating window, wtype and grim. It takes focus, so don’t run it while you’re typing elsewhere.

Timing

bench/m0.sh [FILE] [RUNS]

Measures cold and warm opens to the first frame. The targets are under 50 ms warm and under 400 ms cold (p95). It stops any running n0te-app, including your own service, so close what you’re working on first.

How the code is laid out

PathJob
src/main.rsn0te, the client: std only, never links GTK; sends arguments to the service, starts it if needed, runs --setup and --complete
src/lib.rsshared, std only: finding the vault (obsidian.rs), settings (config.rs), the runtime folder (runtime.rs), Omarchy wiring (setup.rs)
src/bin/n0te-app/main.rsthe GTK app: the service, arguments, prewarming, --check, --bench
src/bin/n0te-app/window.rsone window: history, palette, find, keys, status line, editing, saving, file watching, undo
src/bin/n0te-app/render.rsmarkdown to a text buffer, with blocks and their source ranges, links, headings and folds
src/bin/n0te-app/edit.rsno GTK: splicing, atomic writes, the three-way merge, undo history, list helpers
src/bin/n0te-app/view.rsthe text view: boxes, the edit box, the block tick, the focus veil, line numbers
src/bin/n0te-app/table.rstables, measured and wrapped to the window
src/bin/n0te-app/highlight.rstree-sitter and the small built-in highlighters
src/bin/n0te-app/vault.rsthe note index, wikilinks, daily notes, templates
src/bin/n0te-app/theme.rsOmarchy colors and live reload
packaging/the Hyprland file, desktop entry and theme template; the release tarball (release.sh, install.sh, licenses.py) and the AUR package draft
site/this website and these docs

CLAUDE.md at the repository’s root has the working notes: design decisions, how editing works inside, and the gotchas already paid for. Read it before a big change.

These docs

The docs are an mdBook in site/docs/, and the front page is plain HTML in site/www/. To preview both:

site/build.sh --serve      # builds into site/dist and serves it on localhost

A change to how n0te behaves should update its page in the same commit. Releases and the site deploy from a maintainer’s machine with site/deploy.sh; see site/README.md.