This release includes breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
Summary
AI summaryUpdates 🐛 Fixes, ✨ Features, and url across a mixed release.
Full changelog
ZenNotes 2.19.0
Installers for macOS, Windows, and Linux (x64 + arm64) are attached below.
✨ Features
-
The
znCLI can talk to a self-hosted server. The desktop app and the web client could both work against a vault behind a ZenNotes server; the CLI was the odd one out, resolving every--vaultto a path on the local disk and reading every note straight off the filesystem. So scripting, automation and any headless box had to keep a local copy of the vault, even when the server was right there. Every command exceptzn opennow works either way. A server you have already connected the desktop app to is nameable straight away, because the CLI reads the same saved profiles:zn list --server home,zn capture "..." --server home,zn task toggle inbox/Daily.md#0 --server home.zn vault listshows local vaults and servers together, and--vault <name>resolves either kind, so the short name you already type keeps working;--serverwins when both are given, and if a local vault and a server share a name,--vaultrefuses to guess rather than pick the wrong one. For a server you have not saved, pass the URL —zn list --server 192.168.1.10:7878 --token "$TOKEN"— and a barehost:portgetshttp://filled in. For CI and headless machines,ZENNOTES_SERVERandZENNOTES_REMOTE_TOKENdo the same without writing anything to disk (precedence is--token, then the environment, then the saved profile). Edits mean the same thing on both sides:zn append,zn prependandzn task togglerun the identical transform on a body fetched over HTTP that they run on one read from disk, rather than a second implementation that drifts.zn openis the exception — it hands file paths to the desktop app, so it needs a local vault and says so. (#493, proposed by @usarral) -
Step across formatting markers without the arrow keys.
Mod+Bdrops the cursor between a fresh**|**, which is right for typing and wrong for everything after: finishing the word left you inside the markers, and getting out meant arrow keys, the mouse, or a Vim motion. Alt+] now hops the cursor to the far side of the next run of markers on the line —**bold|**becomes**bold**|in one press — and Alt+[ hops back over the previous one, so a second press lands before the opening**. Repeat either to walk a line pair by pair. It crosses everything the formatting shortcuts write (**,*,~~,==, backticks,$) plus the bracket pairs, which makes[text](url)three quick steps: out of the text, into the URL, out of the link. Works in Vim mode (normal and insert) and out of it, and both hops are rebindable under Settings → Keymaps or inconfig.toml. With no marker ahead the key does nothing rather than inventing a jump. (#490, requested by @uNyanda) -
Typst math definitions that follow your tags. Different subjects want the same notation to mean different things — a vector is an arrow in physics and bold in maths — which normally means redefining it at the top of every note. Turn on Settings → Editor → Typst definitions from tags (with the Typst math renderer) and a note's tags decide which definitions its formulas compile against. A preamble is an ordinary note in a folder named
typst, titled with the tag path in dots:typst/physics.mdserves#physics,typst/physics.mechanics.mdserves#physics/mechanics, layering general → specific so the narrower tag wins. Several tags on one note apply alphabetically, so the same tags always compile identically. Because preambles are notes rather than hidden files, they sync, they're searchable, they're editable in the app, and they work over a remote vault — and editing one immediately re-renders every note that uses it, in both the editor and the reading view. Off by default. (#486, proposed by @Batres3) -
Keyboard navigation reaches every panel, by both routes. ZenNotes had two ways to move focus between panels and they disagreed about which panels existed.
Ctrl+W h/j/k/l(Vim mode) walked the sidebar, note list, editor, connections, comments and calendar; the always-onAlt+H/J/K/Lstopped dead at Connections; and the Outline panel was unreachable by either, having no keyboard presence at all. Which route you had to use depended on which panel you wanted, which is precisely the thing a keyboard-first app should never ask. Both now resolve the same list, in the order the panels actually appear on screen — sidebar → note list → editor → connections → comments → outline → calendar — soAlt+LandCtrl+W lwalk the identical cycle andAlt+H/Ctrl+W hwalk it back. (#477, requested by @uNyanda) -
The Outline panel is now keyboard-navigable. Focus it (
Alt+LorCtrl+W lfrom the editor) andj/k— or the arrows — move a cursor through the headings,ggandGjump to the first and last,Enterjumps the editor to the heading under the cursor, andEschands focus back. It picks up where you left it: return to the panel later and the cursor is on the heading you were last on, clamped to a real row if the note's headings changed while you were away. Same shape as the Connections panel, which is the one panel that already worked this way.While tracing the focus cycle, one more dead end turned up: focusing the Calendar panel silently bounced focus back to the editor, because the panel lives inside the editor pane and the pane claimed any focus landing inside it. Walking onto the calendar then walking further would restart the cycle from the beginning rather than continue. Panels now keep the focus that pane navigation hands them.
-
An empty vault offers a first action instead of a hint. The home screen's RECENT section used to greet a brand-new vault with "No notes yet — create one to start writing." — a sentence telling you to do something, with nothing there to do it with. It now offers New note, New from template, and New database, each wired to the action it names. The template option matters most: templates are easy to miss entirely, and the moment you have nothing is the moment they are most useful.
-
Panels can be driven with Vim mode off. Reaching a panel is only half of navigating it, and until now the keys inside one came exclusively from the Vim layer — which isn't loaded at all when Vim mode is off. So a non-Vim user could land on Connections or the Outline and find no way to move a row. The keys that are universal everywhere else in ZenNotes now work there too, in either mode: ↑ / ↓ move the row cursor, Home / End jump to the ends, Enter opens the row under the cursor, and Esc (or ←) hands focus back to the editor. The single-key motions —
j/k,gg/G— remain Vim-only, exactly as they are in the sidebar and every other list. The Calendar panel was already the exception here, since it has always handled its own keys.
🐛 Fixes
-
Column alignment says what it is, and no longer undoes itself. The table menu's alignment entries were a hidden toggle: choosing the alignment a column already had cleared it back to none. Since a column with no alignment renders exactly like a left-aligned one, that made a fresh column baffling — choose Align left, nothing appears to change; choose it again, still nothing, because the second choice quietly removed what the first one set. The three entries are now a ticked group with a fourth, Align default, so the current state is visible (a new column shows
Align default ✓) and choosing an alignment only ever sets it. Clearing is deliberate rather than a side effect of picking twice. Separately, a table edit that landed while the markdown parser hadn't caught up with a just-created table was silently discarded — the commit looks the table up in the syntax tree, and finding nothing there it dropped the edit without a word. It now falls back to reading the table block out of the document, so an action can't vanish. (#485, reported by @uNyanda) -
Ctrl+Iitalicises a Vim selection instead of jumping to another note. On Linux and Windows, whereModis Ctrl, Vim's forward jump (Ctrl+I) and the italic shortcut (Mod+I) are the same chord — and the jumplist won it in every mode but insert. So selecting a phrase in visual mode and pressingCtrl+Inavigated away and discarded the selection, even though every other format chord (Mod+Band friends) applies to it, and the selection toolbar sitting right there offers Italic. Visual mode now gives that chord to italic. Normal mode is unchanged —Ctrl+Istill jumps forward, as it should — and so isCtrl+O, which stays the jump-back in every mode because nothing collides with it. macOS is untouched: thereCtrl+Iwas never the italic shortcut (Cmd+Iis), so the jumplist keeps it in all modes. (#488, reported by @ah-yah) -
Moving a Kanban card keeps it selected — and stops moving the wrong task.
Shift+H/Shift+Lsend the focused card to the next column, but the selection stayed behind, so walking a task across a board meant finding it and reselecting it after every step. The cursor is a pair of indices, and the move set them to "the column I aimed at, card 0" — and neither half survives the rebuild that a move triggers. Columns sort by priority and due date, so the card usually lands below what's already there and card 0 is a different task; and when the moved card was the last one in an auto-discovered column, that column disappears, shifting every column to its right down one index. Together those put the ring on an unrelated card in a neighbouring column, and the nextShift+Lthen moved that card — a task you never selected. The cursor now re-derives from the rebuilt board: the target column by id, the card by its identity (its note plus its position in that note, which a column move doesn't change). PressShift+Lthree times and one task walks three columns.</>(reorder columns) keeps the focused card too, since reordering columns doesn't move the cards inside them. (#492, reported by @uNyanda) -
Tags in frontmatter show up in the sidebar. A vault written in Obsidian keeps its tags in frontmatter —
tags: [project, area/work]— and ZenNotes only ever scanned the body for#tags, so those notes landed in the tag pane under nothing at all unless the same tags were repeated inline. Frontmattertagsis now a first-class source everywhere tags are read: the sidebar's live count as you type, the main process's index, the MCP server, and the self-hosted Go server. Inline arrays (tags: [a, b]), block lists (tags:then- a), quoted values and a leading#all parse, nested tags likeproject/compilerkeep their hierarchy, andtags: daily, workis two tags rather than one oddly-named one. The frontmatter block is now excluded from the inline scan as well, so a stray#in some other field —title: #draft— stops being counted as a tag, which it quietly was before. Existing vaults reparse once on upgrade so notes that already had frontmatter tags pick them up. (#444, by @junereycasuga) -
Pressing a formatting shortcut again gets you out of the formatting.
Cmd/Ctrl+B, type a word, pressCmd/Ctrl+Bagain to stop being bold — and instead of leaving the span, ZenNotes inserted a second pair of markers inside the first, so the rest of the sentence carried a stray****around it. Same for italic, code, strikethrough and highlight. The second press now steps past the closing marker, the way it works in every other editor, and pressing the shortcut twice on an empty span removes the markers rather than nesting them. Toggling a different format inside an empty one still nests properly —Cmd+BthenCmd+Igives you bold-italic, not a mangled pair. (#443, by @junereycasuga) -
Remote-vault prompts stop fighting the touch keyboard. Connecting to a self-hosted server from a phone or tablet failed with a 401 for a reason nothing on screen explained: the soft keyboard auto-capitalised the token as it was typed, turning
zn-…intoZn-…. The sign-in, server-URL and auth-token prompts now switch off auto-capitalise, auto-correct and spell-check — no effect at all with a hardware keyboard. The same prompts were trimmed for small screens (Connect to Remote Vault / Auth Token, one-line descriptions), and the token prompt's copy was simply wrong: it claimed an empty box reuses your stored token, when it actually connects without one. It now says so. Found while bringing remote vaults to the iPhone app. -
Resized table columns keep their widths. Drag a table column to a custom width and then use any action from the table menu — align a column, add a row, sort — and the widths reset to automatic. Column widths have no GFM syntax, so they persist in a trailing `` marker, and every structural edit runs through one
clonehelper that copied the headers, rows and alignments but not the widths. The editor replaces the table and that marker line in a single write, so a model without widths didn't merely forget them, it deleted the marker from the note. Widths now travel with the table, and they follow their own column: insert a column and it starts automatic while its neighbours keep their sizes, delete or move one and the rest stay with the columns they belong to. Found in-house while investigating #485. -
Go Back works after creating a note from a template. Create a note with New Note from Template and
Ctrl+Ohad nothing to go back to — with one note behind you it did nothing at all. Opening a note at an offset went straight to the low-level "add a tab" primitive, which keeps no history, so the note you left was never recorded. Templates take that path whenever they carry a{{cursor}}marker, which the built-in ones do. Every navigation that lands you somewhere specific now records where you came from, so this also restores Go Back after jumping to a vault-search hit or following a[[note#heading]]link — both used the same path and had the same gap. (#484, reported by @uNyanda) -
Forwarded tasks land under the Tasks heading, not below the rule. Forwarding a task into a note whose
## Taskslist is followed by a horizontal rule — the shape most daily-note templates use — dropped it after the rule, stranded between sections. The section scan ended only at the next heading, so with## Notessitting below the separator, the rule itself became the section's last line and the task was appended after it. A---,***or___line now closes the section the same way a heading does, so the task joins the end of the existing list and the separator stays put. Rules inside fenced code blocks are still ignored, and a- [ ] taskline can't be mistaken for one. Rollover into daily notes goes through the same insertion point, so it benefits too. (#483, reported by @uNyanda) -
Self-hosted servers accept the CORS settings you actually write. Two values operators reach for first did nothing, for the same reason: the origin parser threw away anything without both a scheme and a host.
ZENNOTES_ALLOWED_ORIGINS=*normalised to nothing and matched nothing, so the obvious wildcard was a silent, total lockout with no hint that it was unsupported.nullandfile://were discarded the same way, on the configured list and the incoming request, so listing them verbatim still left them rejected. Both work now, along with scheme-only and custom-scheme origins likecapacitor://localhost. The wildcard deliberately withholds the session cookie cross-origin — echoing any origin and allowing credentials would let any site you visit drive your session — so*covers token clients while explicitly listed origins keep credentials. The startup banner now prints acors:line with the effective policy, and a rejection log names the wildcard option, so a deployment can be checked without waiting for a client to fail. (#482, reported by @cipradu)Worth knowing, because it caused some of the confusion: the desktop app is never subject to CORS. Its server traffic goes through the Electron main process, which sends no
Originat all — connecting the packaged app to a LAN server logs no rejection. CORS rejections in a server log come from browser or WebView clients, and the log line says so now. -
macOS can connect to a self-hosted server on your own network again. On macOS 15 and later, reaching anything on the local network needs the system Local Network permission, and an app only gets asked for it if its bundle declares
NSLocalNetworkUsageDescription. ZenNotes never declared it, so macOS denied local-network access outright — without a prompt, and without listing the app under System Settings → Privacy & Security → Local Network, so there was nothing to switch on. Connections to a LAN server failed instantly with "fetch failed" and, as the reporter's packet capture showed, not a single SYN ever left the machine. It looked exactly like a server that was down. The declaration ships now, so macOS asks the first time you connect and the app appears in that privacy list. A server reached over the public internet was never affected.If you dismiss that prompt you land in the same silent failure, so the connection error now says where to switch it back on, and there's a new Help entry covering the whole flow. Upgrading is the fix — the permission is granted per app bundle, so 2.18.0 and earlier can't be talked into it. (#481, reported by @cipradu)
-
Task metadata reads the same in the editor and the preview.
!high/!med/!lowin a source note were coloured red / amber / blue as 2.16.0 described, but colour was doing all the work — and on the palettes where those hues sit near the editor's text colour, it did none. On the default Gruvbox dark the!medamber lands within a hair of the body text, so all three levels read as plain bold text and you couldn't tell them apart at a glance, which was the whole point. Across the bundled themes, a third had at least one priority colour close enough to the foreground to disappear.Priorities are chips now — the same colour, plus a tinted background that carries the distinction when the hue can't, with the bold weight kept. And the whole metadata set (
!priority,due:,@fields) now renders in the reading preview too, not just the editor: open a note in Split and both halves show the same chips, including the red overdue tint on a past due date. It rides through to PDF export as well, which shares the preview renderer. Inline code is untouched —`!high`in a sentence stays literal text. (#479, reported by @uNyanda) -
H5 headings keep the case you typed.
##### Fifth headingrendered as FIFTH HEADING — atext-transform: uppercasesat on the H5 rule in both renderers, so it hit the editor's live preview and the reading pane alike (and PDF export, which shares the same stylesheet). It made H5 look like a small-caps label rather than a heading, and it silently rewrote the author's text, which no heading level should do. The transform is gone from both rules; H5 still separates from H4 above and H6 below by size, and from body text by weight. There's now a test over the stylesheet asserting no heading level in either renderer transforms case, so this can't quietly come back. (#478, reported by @ShowhyT) -
Cancelled tasks no longer sit on the calendar as though they were still open. Mark a dated task as cancelled —
- [-], the state added in 2.17's cycle — and it kept appearing in the Calendar panel (Leader+C) and in the Tasks tab's Calendar view, drawn with an empty checkbox right next to genuinely actionable work. The Tasks list had it right all along, grouping those tasks under Cancelled, so only the calendar surfaces disagreed. The two helpers behind them filtered on one question — is this task checked? — which a cancelled task answers "no" to, so it sailed through as open:tasksDueOnfeeds the panel's per-day lists and counts, andbucketTasksByDueDatefeeds the Tasks calendar's day badges, the selected day's list, and the No date strip. The panel's month-grid dot had the same blind spot, counting a cancelled task towards its "N open tasks" tooltip. All three now go through one shared definition of open: not done and not cancelled. A@waitingtask with a due date still shows on its date as before, and undated cancelled tasks no longer pad the No date strip. (#476, reported by @uNyanda)Worth knowing: a forwarded
[>]task still shows on the calendar on its original date. That is deliberate for now — forwarding writes the copy from the task's stripped text, so the copy has nodue:date of its own, and hiding the origin would take the task off the calendar entirely instead of moving it. Carrying the date across to the copy is a change to forwarding itself, so it gets its own issue rather than riding along with this one.
Keyboard-first and local-first, as always.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
Beta — feedback welcome: [email protected]