ZenNotes 2.72.0 adds Task folders, so Tasks can collect from one Tasks folder and leave every other checkbox in the vault alone, and rewrites every Cloud error to say what happened and what to do, with the technical line kept under Details. A device away from Cloud for months now catches up by itself, the sidebar's icon and color pickers take the keyboard, a math or code block opened in a callout stays in the callout, a pasted screenshot is named for when you pasted it, Vim's normal mode keeps an input method out of every editor, and the bundled zn is 0.9.0.
Released from 3792c985 through #963 on 2026-10-19. All installers are verified.
New
- Task folders (#962). Tasks used to collect every checkbox in the vault, and the only way to keep a reading list or an imported archive out was to exclude its folder, then the next one, and remember every folder made later. Now you can say where tasks live instead: right-click a folder in the sidebar and choose Use as Task Folder (with Vim mode on,
mon the folder opens the same menu). Once any Task folder is set, only the notes inside Task folders feed the Tasks list, the boards and the calendars, and every other checkbox stays a plain checkbox. Leave the list empty and Tasks covers the whole vault, as before. Exclude from Tasks still works inside a Task folder, soTaskswithTasks/Somedayexcluded leaves the someday list out. Both lists live under Settings → Tasks → Checklists, where you can also type a path, and both are saved in the vault itself (.zennotes/vault.json), sozn task list, the MCP tools and the self-hosted server follow them too. Renaming or moving a folder now carries both lists along, which the excluded list never did. The phones follow the Task folders from iPhone 1.28.0 and Android 1.1.42, both submitted to the stores today. Thanks to @davi-jorge-art for the request. - Cloud errors you can act on. Asked what they would change about Cloud, uNyanda said the error messages: not everyone using Cloud is technical, so a message should say what went wrong, why, and what to do. Before, desktop users read whatever a failure happened to say: "fetch failed" when offline, "The operation was aborted due to timeout", "ZenNotes Cloud object upload failed (411).", the server's "Authentication is required." for a device removed on the website, and about thirty internal sync checks such as "Expected sync sequence 41, received 43"; the conflict review and the publish dialog printed Electron's "Error invoking remote method ..." wrapper whole. Every Cloud failure is now worded for people: what happened, whether it was on Cloud's side or yours, and what to do, naming the button or page to use (Connect ZenNotes Cloud, Disconnect, Sync now, Review, Save archive, Billing on zennotes.org). Settings → Cloud shows the sentence with a closed Details disclosure under it holding the original text, status and code, with Copy details and Report an Issue; the status bar, its hover text and toasts show the sentence alone, and an offline or timed-out run reads "Can't reach Cloud". Storage, file-count and vault limits, a plan without Sync, Backups or Publishing (including a free month that ended), the encryption passphrase and recovery-key errors, and the conflict review's stale choices are reworded to match, and so are the answers the Cloud service itself sends, which older apps show as they are. The in-app manual has a new entry, When Cloud reports a problem. Thanks to @uNyanda.
Fixes
- A device away from Cloud for months catches up by itself. ZenNotes Cloud keeps its change log for 90 days, and a device whose last sync was older than that got an answer no app handled: every sync failed, and unlinking and relinking did not help, so a laptop left in a drawer for a season could not sync again. Sync now rebuilds from Cloud's full file list and then follows the log again. It keeps what you changed on this device while it was away: an edit made here is pushed as an edit, a delete as a delete, a new file is uploaded, and a file edited on both sides is queued for your decision, never overwritten. A file deleted in Cloud and edited here is kept and queued too. While it runs, the status says sync is catching up by comparing every file with Cloud. The same fix reaches the phones in iPhone 1.28.0 and Android 1.1.42.
- A device Cloud no longer accepts signs out. A device removed from your account on the website, or whose sign-in has expired, now signs out on the desktop too, as the phones already did: the status row reads "Signed out of Cloud", and Settings says why above Connect ZenNotes Cloud until you connect again. Your notes stay on the device. Only Cloud's own answer does this: a refusal from the storage host during a large upload, or a public Wi-Fi page answering in Cloud's place, says so and leaves you signed in.
- A failed Cloud sign-in says why. On Windows, Linux and development builds, where sign-in returns through a local loopback page, a failed sign-in showed its reason nowhere: the browser said only that sign-in failed, and Settings kept "Finish signing in in your browser" for up to five minutes. Every platform now shows the reason in a dialog and goes back to Connect ZenNotes Cloud, and a sign-in that met a busy or unreachable Cloud says to choose Connect ZenNotes Cloud again, since sign-in does not retry by itself.
- The icon and color pickers take the keyboard (#960). From the sidebar,
mand Change icon… or Change color… opened a picker, but the keys kept driving the sidebar behind it:jandkmoved the row cursor under the backdrop,lopened that row and pulled focus out of the picker into the editor, so typing edited the note behind it, and Enter opened the row instead of picking. With Vim mode off, the arrows and Enter did the same. Any open dialog now keeps the sidebar and the other panels out of the keyboard. In both pickers the arrows move between the choices, andhjkldo with Vim mode on; Enter picks, Escape closes, and the picker opens on the icon or color already set, with a visible focus ring. Pane focus (Alt+h/j/k/l) and the shortcuts that switch, reopen or create a note (Cmd+N, Shift+Cmd+N and Ctrl+1 to 9 on macOS, Ctrl+N, Shift+Ctrl+N and Alt+1 to 9 elsewhere) also wait while a dialog is open: under the color picker, Alt+L used to move focus into the editor behind it and Cmd+N made a note there. Thanks to @Luistfer for the report. - A math or code block opened in a callout stays in the callout (#961). In a quote or callout, typing
$$and Enter closed the block with lines that had lost their>, so the block fell out of the callout: Preview drew an empty math box inside it and the formula as plain text below. The new line and the closing$$now keep the quote marker (>,> >, or a list item's indent inside a callout), and so does a fence (```). Enter on a code line inside a fenced block in a callout keeps the>too, so a code card no longer ends where you pressed Enter. Thanks to @athu16 for the request. - A pasted screenshot is named for when you pasted it (#775). Every image pasted from the clipboard landed in
assets/asimage.png, thenimage 2.png,image 3.pngand on, because the app's browser engine names every clipboard pictureimage.pngand that name was kept as if you had chosen it. A pasted screenshot is nowPasted Image 2026-10-19 142312.png, the date and time you pasted it, as Obsidian does. A file you copied from the Finder or Explorer keeps its own name. Thanks to @0xFFO for the request. - Vim's normal mode keeps an input method out of every editor (#921). With a Korean, Chinese or Japanese input method on, a Markdown file opened from outside the vault took the input method's text in normal mode:
hjklmade the candidate window flash, andiawas typed into the file instead of entering insert mode. The main editor already blocks this (Settings → Editor, Keep the input method out of normal mode), and so did the floating note and Quick capture; the window for a file opened from outside the vault, the pinned reference pane and the template editor did not. They do now. Thanks to @tonghejiao for the report. - The bundled zn is 0.9.0. On macOS and Linux, the
znthat ZenNotes installs is now 0.9.0: it follows the vault's Task folders inzn task list,zn task watchandzn mcp, and the TUI's Settings now saves the excluded folders where every app reads them (an edit there used to change nothing). It also carries 0.8.1: a finished task note stays in its Custom status column on the TUI's board, andzn mcpgives assistants this app's instructions again.
For contributors
- Task folders (#962,
cc4cdb51):VaultSettings.tasksgainsincludedFoldersbesideexcludedFolders(bridge-contractipc.ts). The rule isisPathExcludedFromTasks(relPath, excluded, included = [])inpackages/shared-domain/src/tasks-excluded-folders.ts; the third argument is optional, so a caller passing only the excluded list keeps the old rule.rewriteTasksFoldersForRenamerepoints both lists after a folder rename or move, in main'srenameFolder(vault.ts) and in the renderer's copy (store.ts), so a later toggle cannot write the old paths back. Desktop main (tasks.ts), the MCP server and the Node zn (mcp/vault-ops.tsreadTasksFolderLists) read both lists;includeExcluded/--include-excludedscan past both. The two toggles go throughtoggleTasksFolder(app-core/lib/tasks-folder-lists.ts), which keeps the other list: the old exclusion toggle rebuilttasksfrom its own list and would have dropped the Task folders. Settings has oneTasksFolderListRowfor both lists and atasks-task-folderssearch entry worded so "tasks folder" still finds Default tasks location. Synced copies: ZenNotes/znserver34da661(struct,normalizeTasksSettings,cloneSettings, both scans,RenameFolder, and the walker now checks the lists before reading a file) and ZenNotes/tuia0d335c(zn 0.9.0; also fixes the TUI writing a top-leveltasksExcludedFolderskey no runtime reads). The phone bridges apply the lists in their ownvault-fs.ts(the iPhone passed only the excluded list, Android neither); they move to the new rule with this core. An older build that rewrites vault.json rebuildstasksfrom the fields it knows and dropsincludedFolders. - Cloud error copy (
72ab890e,edea7da0): one translator,describeCloudErrorinpackages/shared-domain/src/cloud-error-copy.ts(24 tests of its own), words every Cloud failure from the HTTP status and code where they still exist (the desktop main process before an error crosses IPC, which everycloud*handler now goes through, and the phones, whose hosts run in-process), the runtime's network and timeout texts, the encryption error codes, and disk errors. Internal sync checks throwcloudProtocolErroror aCloudIssueError(download incomplete or damaged, Cloud changed during a first sync, two names that differ only in case) with their old messages unchanged, so tests and logs still match. Limits are told apart by the dimension Cloud sends (sync_active_vaults,publish_*), with the old and new service sentences as the fallback.CloudErrorMessage.tsxdraws the sentence and the Details disclosure. Only Cloud's API refusal signs the desktop out, as the phones did:UNAUTHENTICATED(401, or 403 for an outdated sign-in),ACTIVE_DEVICE_REQUIRED, or a code-less 401 whose body was Cloud's JSON (4e2637a4, from the same review: the first version treated any 401 as signed out, andlogout()then revoked the device, so a storage 401 on a direct upload could sign out a valid device).cloudSignInExchangeErrorwords a busy, broken or unreachable sign-in exchange as connect again; a loopback sign-in failure reaches the window as a dialog. The service's own wording is ZenNotes/websitebfad4b9andd865d4e. - Expired change cursor (
6db3043d,48a8039d): the shared coordinator (cloud-sync-coordinator.ts) catches 410CURSOR_EXPIREDin both pulls of a run and rebuilds from the manifest, replaying what the pruned log would have said, item by item, from the difference between the tracked state and the manifest, through the same path the change feed takes. Deletes run first, then renames and edits, then arrivals; an arrival already on disk is judged from a fresh scan. Renames land in passes (48a8039d): one waits while its destination is still the tracked path of a file that is itself leaving, so a chain such as B to C then A to B lands in order; only a true ring falls back to conflicts. A pre-release review found the first version could, in that chain, queue a false conflict whose Cloud side deleted the note; three tests cover the chain, a longer chain with an edit, and a ring. A failure midway saves what landed at the old cursor (#813). A Cloud delete of a file edited here becomes a delete conflict with a null Cloud revision. Because it lives in the coordinator, the phones get it with no host change. Tests cover no local edits, local edits, a both-sides edit, delete conflicts, a moved decision, a re-created same-bytes file, a failure midway, a metadata-first host, the reference-mode streaming path and the desktop repository on disk through the real HTTP client; a built app against a fake Cloud with an expired cursor passed 25 checks, where the build before the fix showed "Sync failed". - Dialog keyboard ownership (#960,
e0967986,705f0020): VimNav's top guard and the Vim-offhandlePanelKeyWithoutViminApp.tsxnow also stand down for any[aria-modal="true"]panel, which everyui/Modalsets; routing follows the store's focused panel, not DOM focus, so marker-only guards let keys through the pickers. App's global-shortcutoverlayOpenandfocusPaneHandleruseisOverlayOrDialogOpen()too, keeping the store flags, so the keymap recorder (#124) and Mod+P over a palette (#938) behave as before.lib/picker-grid.tsis the pickers' grid step (arrows always, hjkl only with Vim on; the column count is read from the rendered rows). - Quote-aware block Enter (#961,
5e60a4a4,aac55e5d):blockContinuationPrefixincm-markdown-snippets.tskeeps quote markers and turns list markers into spaces, and a quote marker may be followed by several spaces, so> $$in a quoted list item opens a block.insertNewlineContinueFencedCodeIndent(cm-code-fence-indent.ts) keeps the>on the opener line and now takes the content and closing lines of a quoted fence, reading where the quote markup ends from the parse so a>>>prompt is code.cm-code-fence-indent.test.ts'smount()now adds the snippets ahead of the markdown keymap, as EditorPane does; in the old order markdown's Enter took$$in a quote first. VimoandOstill open a bare line on a quoted line; that is separate. - Input-method guard (#921,
339f613d):vimImeGuardnow goes intoExternalFileApp,PinnedReferencePaneandTemplateEditorModal, reading the samevimBlockImeInNormalModepreference.lib/cm-vim-ime-guard-coverage.test.tsreads every app-core source that callsvim()and fails when one leaves the guard out. - Pasted image names (#775,
3792c985):pastedImageFilename(shared-domain/pasted-image.ts, used by desktop main, the web client and the iPhone bridge) treats the bare stemimageas no name. The Android bridge kept its own copy of this naming; it now calls the shared function with this core. - zn pin (
3e7e2356):apps/desktop/terminal-release.jsonis ZenNotes/tui v0.9.0's signed manifest copied byte for byte after it verified againstpackaging/release-signing/zn-release-1.pub(source commita0d335c, protocol 1). The in-app manual's CLI card says 0.9.0. - Docs: the in-app manual's "Not every checkbox is a task" covers Task folders, and "When Cloud reports a problem" is new; the website carries the same (ZenNotes/website
a7a927d, and the Cloud docs card09350b5). What's Newa6b96a65, version bump5a52b484.
Demos
Screenshots of this release on the dev build, in the default theme, attached to this release:
Task folders (#962): Settings → Tasks → Checklists, with inbox/Todo as the Task folder and inbox/Todo/Someday excluded inside it.
A Cloud error you can act on: what happened, whose side it was on, and what happens next, with the technical line under Details.
A device Cloud no longer accepts signs out and says why, above Connect ZenNotes Cloud.
The color picker takes the keyboard (#960): j j l, then ↓ and →, walked the grid to Violet while the sidebar stayed on Bravo.
Math in a callout (#961): $$ and Enter inside the callout kept every line quoted, so Preview draws the formula inside it.
Verification
How to test locally. Build once with npm run build --workspace @zennotes/desktop and launch with isolated stores: ZENNOTES_USER_DATA_PATH=$(mktemp -d) ZENNOTES_CONFIG_DIR=$(mktemp -d) npx electron apps/desktop/out/main/index.js.
- Task folders (#962). In a vault with a
Tasksfolder and checkboxes elsewhere, open Tasks: every checkbox shows. Right-clickTasksin the sidebar and choose Use as Task Folder (or, with Vim on, focus the sidebar, put the cursor on the folder, pressm, typeuse as task, Enter). Tasks now lists only that folder's checkboxes, and.zennotes/vault.jsonholds"tasks": {"includedFolders": ["inbox/Tasks"]}. Right-click a subfolder of it and choose Exclude from Tasks: its tasks leave and both lists stay in vault.json. RenameTaskstoTodo: Tasks keeps the same tasks, and Settings → Tasks → Checklists showsinbox/Todoandinbox/Todo/.... Choose Stop Using as Task Folder: the whole vault comes back. Before: every checkbox in the vault, with no way to say where tasks live. - Cloud errors. With a Cloud vault linked, turn off the network and press Sync now: the status row reads "Can't reach Cloud", and Settings → Cloud shows a sentence that says your notes are safe on this device, with Details holding the original text. Press Copy details and paste: the status and code are there. Before: "fetch failed". Remove this device on zennotes.org and sync: the app reads "Signed out of Cloud" and Settings says why above Connect ZenNotes Cloud. Before: "Authentication is required." and the device kept retrying.
- The pickers take the keyboard (#960). With Vim on, focus the sidebar (
Ctrl+W h), pressmon a note, choose Change color…, then pressjjland Enter. The focus ring walks Red, Green, Blue, Indigo, Enter applies Indigo, and the sidebar cursor never moves. Before: the sidebar moved behind the picker andlopened another note under it. With Vim off, right-click a note, choose Change color… and use the arrows and Enter: the same. Open the picker and pressAlt+LorCmd+N: nothing happens behind it. - Blocks in a callout (#961). In a note, type
> [!note] Heading, Enter, then$$and Enter. The caret sits on>with> $$below. Typex^2, then switch to Preview: the formula renders inside the callout. Before: two lines with no>, an empty math box inside the callout andx^2as plain text below it. Do the same with```js, type a line, press Enter: the next line starts with>and the code card stays whole. - Pasted screenshots (#775). Take a screenshot to the clipboard (Cmd+Ctrl+Shift+4 on macOS) and paste it into a note twice:
assets/holdsPasted Image <date> <time>.pngtwice, a second apart, and the embeds name them. Before:image.pngandimage 2.png. Copy an image file in the Finder and paste it: it keeps its file name. - Input methods in a file from outside the vault (#921). On macOS, turn on a Chinese or Japanese input method and Vim mode, and open a
.mdfile from outside the vault (drag it onto the Dock icon, oropen -a ZenNotes file.md). In normal mode, presshjkl: no candidate window. Pressi: insert mode starts, and typing composes as usual. Before: the candidate window flashed on every motion andiawas typed into the file. - zn 0.9.0. In a terminal,
zn --versionreports 0.9.0 after the managed install updates (or once this release installs it). In a vault with Task folders,zn task listlists only their tasks, andzn task list --include-excludedlists everything.
Distribution channels
- GitHub release: installers for macOS (Apple silicon and Intel DMG and zip), Windows (x64 and ARM64 installer and zip) and Linux (x64 and arm64 AppImage, deb, rpm, pacman, tar.gz), with five update manifests; the five screenshots in Demos are attached too.
- AUR:
zennotes-bin2.72.0-1 (AUR clonee61b377, repoadf080ee), pinned toZenNotes-2.72.0-linux-x64.tar.gzSHA-256630e0ae8…676e(GitHub's own digest for the asset), after the tarball check confirmed the bundled CLI folder is world-readable; aur-check passed. - Nix:
packaging/nix/release-data.jsonlifted from the update workflow (run 39319100451, bot PR #964 closed) as0232acaa, and nix-build on main passed. - nixpkgs: NixOS/nixpkgs#575797 now proposes 2.70.0 -> 2.72.0 (
ea0a325f), rebased on master; the source hash comes from the tag tarball, by the same prefetch that reproduces the 2.70.0 hash in nixpkgs today. - Homebrew:
zennotes2.72.0 in the tap (1b20b38), mirrored in764c10d2; both DMGs' SHA-256 match the cask and GitHub's digests;brew styleclean. - Website: ZenNotes/website#86 merged at
a0a07aa9with the 2.72.0 entry and its five screenshots, the docs for Task folders and pasted screenshot names, the Cloud service's reworded answers (bfad4b9,d865d4e), the zn 0.9.0 docs, and the 2.72.0 share viewer, which brings the Pele mermaid renderer and D2 to published notes (2.69.0 stays installed for pages cached against it). Laravel Cloud deployed it, and the live check passed: /releases leads with 2.72.0 and Task folders, all five screenshots match the local files byte for byte, /docs carries Task folders, /tui/docs names zn 0.9.0, and both the 2.72.0 and the retained 2.69.0 share viewers are served byte for byte. - Server and Docker: ZenNotes/znserver v2.72.0 pins the browser app
web-2.72.0-web.habddb79fb3c63358(bd2cd1f) and carries the Task folders rule (34da661); all five binaries matchSHA256SUMS, and the released darwin-arm64 binary served the app and honored a Task folder on a scratch vault.adibhanna/zennotes:2.72.0,2.72andlatestshare one digest,sha256:ac4e0a6b…(linux/amd64 and linux/arm64); the README's install commands follow (ZenNotes/znserver#37). - zn CLI: ZenNotes/tui v0.9.0 (
a0d335c), its Homebrew formula (tap2e07ae9), and the same release bundled in this one. - Boundary packages:
core-2.72.0-core.h2a3462a340c755ce,web-2.72.0-web.habddb79fb3c63358andviewer-2.72.0-viewer.he8cf1042e1814d5b, all built by CI from3792c985, published as pre-releases; "latest" still names v2.72.0. - Phones: iPhone 1.28.0 (build 44) and Android 1.1.42 (versionCode 45), both on core 2.72.0 with Task folders, the Cloud error copy and the catch-up fix, are in store review; the Android GitHub release v1.1.42 carries Play's signed universal APK.
Release validation
Every CI failure since 2.71.0 was read before this release: ci-sweep.py v2.71.0 found none. hazard-scan.py --since v2.71.0 found no hazards, before the cut and again at 3792c985. An Opus review of the five commits from side sessions (705f0020, aac55e5d, 72ab890e, edea7da0, 6db3043d) found two bugs, both fixed before the cut with tests: a chain of renames after an expired cursor could queue a false conflict whose Cloud side deleted a note (48a8039d), and any 401, including a storage host's during a direct upload, signed the device out and revoked it (4e2637a4).
Release-time gates, in a clean checkout with its own npm ci: typecheck 8/8 with no turbo cache, test:run with no cache (shared-domain 2,048, app-core 3,308, desktop 1,183, quicklook 15 and the rest), and a fresh build:prod. Pack at 3792c985: Developer ID signed (team WYY7PK57DM); packaged launch check: page target and drawn app in 1.5 s, version 2.72.0. Each fix was driven in the built app over CDP (the screenshots in Demos come from those runs), and three bugs were first reproduced on the build before their fix: the pickers leaking keys to the sidebar (#960), the input method in the external-file window (#921: contenteditable stayed true and ia was written), and pasted image names (#775: image.png, then image 2.png). perf:desktop-runtime on 5,000 notes against the packaged app: ready to show 251 ms, workspace ready 537 ms, note switch p50 33.8 ms, search 22 ms, no long tasks, each equal to or better than installed 2.71.0. With ZEN_PERF_ENFORCE=1 its deferred-chunk check fails on vendor-markdown and vendor-highlight loading in the normal flow; 2.71.0 loads the same two chunks in the same flow, so this predates 2.72.0 and is a follow-up, not a regression. Smoke suites vim-editor, sidebar-vim and editor-improvements pass on the first run.
Release PR #963: all 10 checks green on 3792c985, including the windows-11-arm package and launch check. CI on main for 3792c985 (run 39314475723) passed all six jobs before the tag. CI on main for the packaging commits: the AUR commit's run was cancelled when the Nix commit superseded it, and 0232acaa's (run 39319735781) passed; and so did 764c10d2's, the Homebrew commit (run 39323353659). Release run 39316861367: all six jobs green; verify-installers.py ALL GOOD on the five manifests by SHA-512 (14 files); the app in both DMGs is Notarized Developer ID with a valid stapled ticket. npm run verify:channels -- 2.72.0: every channel serves v2.72.0 (Homebrew, AUR, Nix and the website's eight download buttons).




