Skip to content

ZenNotes 2.28.0

Latest

Choose a tag to compare

@github-actions github-actions released this 13 Aug 18:29
· 2 commits to main since this release

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/l took the plain-folder path, and the row's m menu had no way in (reported by yeminn on Discord). Enter or l on a focused database row now opens the grid exactly like a click, and the m menu 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 with j/k, press Enter: the grid opens. Press m on 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 j and k never type into the note again. On a line soft-wrapped into five or more display rows, at certain zoom levels, a normal-mode j or k could 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-row A/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 j then k through the wrap. The cursor only ever moves; no stray j or k characters appear in the text.

  • Vim j and k step 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 there j skipped a row and k bounced 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 hold j and k. The cursor hugs the row ends and moves exactly one row per press, in both directions.

  • Vim A and $ reach past a line-ending wikilink. With live preview hiding the closing brackets, A opened 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, press A: 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 v then j. The highlight covers exactly the selected text on each row instead of filling to the column's right edge; V linewise hugs each line's own text.

  • Vim $ and A reach 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, and A opened 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. g0 and I share 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, and A opens insert right after it. On the last row, $ reaches the end of the line.

  • gt and gT switch tabs from the Kanban board. The board's g group-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/gT switch tabs with the grouping untouched, and a bare g still cycles the grouping when the window expires. The board follows your keymap settings, so rebinding Next/Previous tab off the g prefix restores the instant cycle, and with Vim mode off nothing changes.

    How to test locally: with a note tab open, go to Tasks, press 3 for the board, then gt. You land on the note tab with the board's grouping unchanged; gT brings you back, and g alone 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/desktop with 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+P or Ctrl+E left 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 default Mod+4/5/6 bindings are unaffected.

    How to test locally: in Settings, Keymaps, bind Preview mode to Ctrl+P and Editor mode to Ctrl+E. Enter split mode, click into the editor, and press Ctrl+P then Ctrl+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 pixelMotionFallback in cm-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.