ZenNotes 2.28.0: your notes, everywhere
ZenNotes Cloud brings sync, backups, and publishing into the desktop app. Connect your account, link a local vault, and ZenNotes keeps changes moving between your devices while the status bar tells you when everything is up to date. Create recovery points automatically or by hand, restore one note without rolling back the rest of the vault, and publish notes at public links that survive every update. Local vaults continue to work without a Cloud plan. Installers for macOS, Windows, and Linux (x64 + arm64) are attached below.
✨ New
-
ZenNotes Cloud sync. Settings → Cloud now connects the desktop app to your ZenNotes account and links the current local vault to a new or existing cloud vault. Sync runs after local changes, when the app returns online, and when it comes back to the foreground. The status bar shows the current state and last successful sync, with Sync now always close at hand. Account credentials live in the operating system's secure credential store, and disconnecting removes this device's access without touching the notes on disk.
How to test locally: open a saved local vault, choose Settings → Cloud, connect your account, create or select a cloud vault, then edit a note. The status bar moves through syncing and returns to “Synced just now.” Open the same cloud vault on another connected device and run sync there.
-
Back up a vault, then recover exactly what you need. Create a labeled backup before a major edit or turn on automatic daily backups. Recovery can start from a date, fall back to the closest earlier recovery point, and search the notes inside that snapshot. Restore the entire vault or restore one note as a new synced revision, leaving every other note alone. Ready backups can also be downloaded as archives.
How to test locally: in Settings → Cloud, enable Automatic daily backups or create one manually. Edit a note, open the backup, search for that note, and choose Restore note. Its backed-up contents return and sync normally; the rest of the vault stays unchanged.
-
Publish a note, keep its link. Publish from a note's menu or the link button beside its breadcrumb, and the note keeps the same public link as you update it. Theme and logo are managed once for your whole publication in ZenNotes Cloud, so every public note shares one look and publishing never stops to ask design questions. Published notes are marked in the editor and sidebar, where the same control opens their public page or unpublishes them, and the Cloud panel lists every published note with its link.
How to test locally: open a note, choose Publish, and open the public link in a browser. Edit the note and choose Update note: the same link now shows the new content. Back in ZenNotes the published indicator appears in the editor, and the same control unpublishes.
🐛 Fixes
-
Databases open from the keyboard. The grid only opened by clicking the sidebar row: in Vim mode
Enter/ltook the plain-folder path, and the row'smmenu had no way in (reported by yeminn on Discord).Enterorlon a focused database row now opens the grid exactly like a click, and themmenu leads with an Open database entry. The chevron still expands the folder to browse record pages, and the open grid closes like any tab.How to test locally: focus the sidebar (
Alt+H), move onto a database row withj/k, pressEnter: the grid opens. Pressmon the row: "Open database" heads the menu. -
Cloud errors explain what needs attention. Validation failures now surface the server's field-level message instead of the generic “request data is invalid,” including when publishing an empty note.
-
Cloud-vault moves protect local notes. If a vault was linked to another account or service origin, ZenNotes explains the mismatch and replaces the old link only after the new cloud vault is ready.
-
Vim
jandknever type into the note again. On a line soft-wrapped into five or more display rows, at certain zoom levels, a normal-modejorkcould insert the literal character instead of moving the cursor (#574, reported by @corpserot with videos that made the geometry findable). The display-line motion asks the editor for pixel coordinates, that query can crash on some display geometries (fractional display scaling on Linux), and the vim layer re-throws motion exceptions in a way that lets the pressed key fall through to the editor as plain text.Two fixes ride together: every pixel-based vim motion (
j/k,$,g0,H/L, and the display-rowA/I) now catches a measurement failure and degrades that one press to plain logical-line movement, so a coordinate crash can never again edit the note; and the editor's view library moves to 6.43.8, which fixed several crashes in exactly that coordinate code upstream.How to test locally: write a paragraph long enough to wrap into six display rows, zoom out once (Ctrl/Cmd and minus), put the cursor above it, and hold
jthenkthrough the wrap. The cursor only ever moves; no strayjorkcharacters appear in the text. -
Vim
jandkstep one visual row everywhere on a wrapped line. On displays with fractional scaling, a right-edge goal column could park the cursor exactly on a soft-wrap point, the offset shared by the end of one row and the start of the next; from therejskipped a row andkbounced in place (#580, @corpserot again, with the videos that made all three wrapped-line bugs findable). Wrap-point landings are now disambiguated by the goal column: right-edge landings rest on the row's last character, like$, and left-edge landings keep the next row's start.How to test locally: in a paragraph wrapping into several rows, press
$then holdjandk. The cursor hugs the row ends and moves exactly one row per press, in both directions. -
Vim
Aand$reach past a line-ending wikilink. With live preview hiding the closing brackets,Aopened insert inside the link (typing after[[wikilink]]produced[[wikilinjk]]) because the line end was resolved from visible pixels, and the rendered chip's height could also fool the row measurement (#582, reported by @uNyanda, confirmed on the branch by @corpserot). Row membership is now decided by vertical overlap, and when coordinates are unusable the motions fall back to the logical line end, plain Vim's answer.How to test locally: put
This is a [[wikilink]]on a line, cursor at the line start in normal mode, pressA: insert opens after the closing]]and typing appends to the line.$rests on the closing]. -
Vim visual mode highlights the text, not the column. Visual selections drew browser-style: every row but the last filled to the content column's right edge, so selecting across a short line under a wide wrapped paragraph swept a stretch of empty space (#581, the fourth of @corpserot's wrapped-line reports). In visual mode the selection is now painted on the text itself and hugs the glyphs of every wrapped row, like Vim. Yank and every other operation were always correct; only the paint changed.
How to test locally: under a wide wrapped paragraph, put the cursor on a short line, press
vthenj. The highlight covers exactly the selected text on each row instead of filling to the column's right edge;Vlinewise hugs each line's own text. -
Vim
$andAreach the real end of a wrapped display row. On soft-wrapped lines,$could land several characters short of the visible row end, or on a neighboring row, andAopened insert at the same wrong spot (#575, also from @corpserot). The row boundary came from hit-testing an x coordinate at the viewport edge, which mislands under fractional display scaling. It is now computed from vertical glyph coordinates, which are immune to that imprecision, and on the last row$reaches the actual end of the line by construction.g0andIshare the new path.How to test locally: put the cursor in a middle row of a wrapped paragraph and press
$; the cursor lands on the last character of that visual row, andAopens insert right after it. On the last row,$reaches the end of the line. -
gtandgTswitch tabs from the Kanban board. The board'sggroup-by shortcut consumed the key on the spot, so the Vim tab switches could only ever re-group the board (#573, reported by @dwarjie). In Vim mode the board now waits out the same half-second window the tab keymap uses:gt/gTswitch tabs with the grouping untouched, and a baregstill cycles the grouping when the window expires. The board follows your keymap settings, so rebinding Next/Previous tab off thegprefix restores the instant cycle, and with Vim mode off nothing changes.How to test locally: with a note tab open, go to Tasks, press
3for the board, thengt. You land on the note tab with the board's grouping unchanged;gTbrings you back, andgalone still cycles the grouping. -
NixOS launches open your vault, not a "desktop" folder. The nixpkgs package starts ZenNotes as bare Electron with the app's own install directory (
…/apps/desktop) as an argument, and on Wayland an extra Chromium switch lands in front of it; the startup argument scanner then mistook the app's own directory for a folder to open and started a temporary session on a vault literally named "desktop" every launch (#579, reported by @greedoftheendless). Launch arguments are now filtered against the app's own path by value, so the configured vault opens regardless of how a launcher arranges its flags.How to test locally: launch the built app as
electron --some-switch apps/desktopwith a vault configured. The configured vault opens; before the fix a "desktop" vault opened instead. -
Rebound pane-mode shortcuts work while the editor has focus. Mapping Editor/Split/Preview mode onto editor chords like
Ctrl+PorCtrl+Eleft them dead exactly when the editor held focus, which split mode guarantees, so you could switch into split and not out (#579, same report). The mode shortcuts now resolve in the same capture phase as the pane-focus shortcuts (#124's fix, extended), beating the editor's own key handling; the defaultMod+4/5/6bindings are unaffected.How to test locally: in Settings, Keymaps, bind Preview mode to
Ctrl+Pand Editor mode toCtrl+E. Enter split mode, click into the editor, and pressCtrl+PthenCtrl+E: the pane switches to preview and back to edit instead of ignoring the keys. -
Rosé Pine survives a relaunch. Since the family arrived in 2.10.0, picking any Rosé Pine theme held only until the next launch: the prefs loader validates saved theme families against an allowlist, built so themes removed in earlier versions fall back cleanly, and Rosé Pine was never added to it. On every start the saved selection reloaded with its family reset to Gruvbox, so Settings highlighted the wrong family, switching light and dark jumped into Gruvbox variants, and Automatic mode resolved into Gruvbox outright. The allowlist now knows the family, and the selection reloads exactly as saved.
How to test locally: in Settings, Appearance, choose Rosé Pine (Main, Moon, or Dawn), then quit and relaunch. Settings still highlights Rosé Pine, and the light/dark toggle stays within the family.
-
Links respond to the pointer, not to their line. With a Markdown link at the end of a line, the entire blank stretch to the right of that line acted like the link: the status bar showed its address and a click opened it in the browser, even with the mouse nowhere near any text (#587, reported by @SandeepKumarKuanar). The editor resolves the pointer to the nearest text position, and live preview hides a link's closing syntax, so blank-space coordinates clamped to a position inside the link. Hovering and clicking now first confirm the pointer actually rests on the link's rendered glyphs; clicking a rendered link, and Ctrl/Cmd-click, follow exactly as before.
How to test locally: put
3. UiPath's academy → [website](https://example.com)in a note, move the cursor to another line, and sweep the mouse through the empty space to the right of that line. The status bar stays empty and a click just places the cursor at the line end; hovering the rendered word "website" still shows the address, and clicking it still opens the link.
🧰 For contributors
- Cloud sign-in uses PKCE with a short-lived loopback callback. The app accepts only the exact local callback it opened; credentials are stored outside the renderer.
- The portable sync engine lives in
@zennotes/shared-domain. Desktop filesystem and API adapters sit around it, so conflict, tombstone, cursor, and idempotency behavior stay shared with the mobile clients. - Publishing state is event-driven across the Cloud panel, sidebar, and editor control. New publish surfaces should use the shared request and published-note event modules instead of maintaining another cache.
- @codemirror/view is now 6.43.8 (tile crash fixes; @codemirror/state 6.7.1 rides along). Any custom vim motion that consults pixel coordinates must run inside
pixelMotionFallbackincm-vim-display-line.ts: an exception that escapes a motion skips the keydown's preventDefault and the pressed key lands in the note as text.
Local-first and keyboard-first, as always.