github ZenNotes/zennotes v2.57.0
ZenNotes v2.57.0

4 hours ago

ZenNotes 2.57.0: list items and tasks fold like headings, Split mode keeps its place in math-heavy notes, and an AI agent follows the app to another vault. A bullet, numbered item or task with indented lines under it now has a fold arrow in Edit and Preview, and the fold keys, Fold All and Vim's zc, zo, zM and zR fold list items too, so a long to-do note can show one line per task (discussion #848, from @Sullti). Callouts fold too, the Obsidian way: > [!example]- Screenshots starts collapsed, > [!example]+ expanded, and a click on the title row opens or closes it (discussion #853, also from @Sullti). In a note full of KaTeX formulas and callouts, the two panes of Split mode now stay on the same content whichever one you scroll: no more jumping back whole sections while you read the preview, and a click in the editor no longer throws the preview somewhere else (#859, reported by @ShowhyT). An MCP-connected agent now follows the app when you switch vaults, stopping once to confirm the switch so nothing planned in one vault lands in the other (reported on Discord by Kevin). And with Vim mode off, F3 brings the in-note find bar back every time instead of jumping to the last search's next match out of sight (#860, from @Sullti). In Atlas, notes you add after first opening the map land beside the notes they link to instead of piling onto them, and maps that already have piles tidy themselves the first time 2.57.0 opens them (#861, from @vinivinoo). Opening a note from the home screen, the Connections panel, the Assets view or the Atlas now puts the cursor in it, and a note that another program replaces on disk stays open with your unsaved text instead of dropping you on the home screen (#863, from @phoscoder).

Released on September 28, 2026 as v2.57.0, at 08096c0c. PR #864 fast-forwarded the release branch into main at 20:06 UTC after all checks passed. The branch started at 7d2ce23a (the 2.56.0 Homebrew mirror), carries nineteen reviewed cycle commits plus the version bump, and was restored after GitHub auto-deleted it. All installers and channels are verified. The three packaging commits follow the tag on main and v2.57.0, now at 140cd6a1.

✨ Features

  • Fold list items and tasks like headings (discussion #848, from @Sullti). A bullet, numbered item or task with indented lines under it (sub-items, another paragraph, a code block) now gets the disclosure arrow headings have. Hover the item, or put the cursor on its line, and an arrow beside its bullet, number or checkbox folds the lines under it while the item line stays in view, in Edit and in Preview; a folded item keeps its arrow showing so it can be opened again. The fold keys work on list items too: Ctrl+Alt+F and Ctrl+Alt+U (Cmd+Option+F and Cmd+Option+U on macOS), Vim's zc and zo, :fold and :unfold, and the palette's Fold and Unfold Heading or List Item at Cursor; on one of the lines under an item, they fold the item it belongs to and move the cursor to it. Fold All (zM, :foldall) now folds every heading and every list item, nested, so opening a heading shows its tasks one line each, and Unfold All (zR) opens everything. Folding only hides lines: the note on disk and the Tasks view are unchanged, and folds last while the note is open, like heading folds. An item with nothing under it gets no arrow, and neither does one that only wraps onto a second line. (43268472)
  • Foldable callouts, as in Obsidian (discussion #853, from @Sullti). Put - or + right after a callout's type: > [!example]- Screenshots starts collapsed and > [!example]+ Screenshots starts expanded; a callout with no marker stays as it was. Until now the marker broke the callout: Preview showed the whole title line as raw text in a plain quote, and Edit mode kept a stray dash in the title. In Preview the whole title row opens and closes it (it is a native disclosure, so Tab and Enter work, the browser's find opens a closed one on a match, and Vim's Space h hint mode labels the title); in Edit mode a chevron follows the title, and a closed callout folds to its title line. The fold keys work on callouts too: zc and zo or Cmd+Option+F and Cmd+Option+U (Ctrl+Alt+F / U elsewhere) fold a callout from its title line, a foldable one from any line inside it, and Fold All (zM) includes foldable callouts. A collapsed callout starts closed each time the note opens, prints open in a PDF export (a PDF cannot be clicked open) and never changes the note. Also fixed on the way: in Edit mode, a code block inside a callout or a quote showed its closing fence. (0ef69fc4)

πŸ› Fixes

  • Split mode stays on the same content in math-heavy notes, whichever pane you scroll (#859, from @ShowhyT). In a long note full of KaTeX formulas, matrices and callouts, Split mode lost its place. Scrolling the preview made it jump back whole sections, the editor trailed sections behind, and after reading ahead in the preview, a click on a line in the editor threw the preview back to wherever the editor was. The preview followed the editor by source line, but the editor followed the preview by scroll ratio, and rendered math makes such a note about twice as tall as its source, so the two disagreed by whole sections; worse, each time the editor re-measured the rendered math the sync had just scrolled into view, it pushed its lagging position back into the preview. Both panes now map through the same anchors, where each block of the note begins in each pane plus the top and the end, so the same content sits at the top of both and a formula that renders ten times taller than its source only stretches its own stretch of the other pane. The pane you scroll leads and the other follows, and clicking or typing in the editor no longer moves what you were reading in the preview. At the top of a note both panes now start at the top: the preview used to begin 16 px down, which pinned the note's H1 against the pane's top edge. (4d56f22d)
  • An AI agent follows the app to another vault, and never carries work across (reported on Discord by Kevin). The MCP server picked its vault at the first tool call and kept it until the MCP client restarted it, so after switching the app from a ZenNotes server back to a local vault, an agent went on reading and writing the server. The server now asks which vault the app has open before every tool call. A switch never redirects work silently, though: an agent that read notes in one vault and then writes after you switched would change a note at the same path in the other one, so after a switch the agent's next tool call is refused with a note naming the old and the new vault, and once it calls vault_info to confirm, it carries on in the new one. A headless Claude Code agent did exactly that in testing. Parallel calls stop together, and switching back before the agent confirms is no switch at all. The MCP server bundled with the app has this in 2.57.0; desktop-managed zn mcp gets it from zn 0.4.1, the terminal tool release this version pins, together with the second half of the report: with the app connected to a server that needs a token, run zn connect <server-url> --no-default once and every desktop-managed zn command and zn mcp authenticates with that token, with no token in any MCP config. The app keeps its own copy in the OS secret store, which zn cannot read; --no-default saves the token without making the server zn's own default. (5f8095b2; zn 0.4.1: 35a77be, 86e2149; pinned in 08ec14a9)
  • F3 brings the find bar back, and Mod+F follows its binding (#860, from @Sullti, who named the cause). With Vim mode off, the in-note find bar opened once. F3 and Mod+G run CodeMirror's find next, which opens the bar only while no search is set: after one search and Escape, they moved the cursor to the next match of that old search with the bar closed, the selection toolbar popping up over it, and nothing on the keyboard brought the bar back, in any note, until a restart. In the main window F3 was the only key into the bar with Vim off, since Mod+F opens note search there. Now F3 and Mod+G (Shift for the previous match) open a closed bar with your last search filled in and selected, and step between matches only while it is open, in every window and with Vim mode on or off. The report also found Mod+F hard-coded to note search inside the editor: unbinding Search notes in non-Vim mode under Settings β†’ Keymap did not free it. The editor now follows that binding, so moving or unbinding it hands Mod+F to the find bar in the main window too, which is what #376 asks for. The manual said Mod+F opens the find bar with Vim off, which was never true in the main window; it now says where Mod+F goes and how to change that, with a row for F3 and Mod+G. (db8eca79)
  • Atlas places notes you add later beside what they link to, not on top of it (#861, from @vinivinoo). Atlas lays your vault out the first time you open it and saves that map, so it stays a place you know. Notes that arrive afterwards (new, renamed or moved) are added one at a time, and each went to the middle of the notes it links to, with no look at what was already there. For a note that links to a single note, that middle is the note itself: the new dot landed on top of it, every later note linking there piled onto the same spot, and the saved map kept the pile. Only notes written after the first visit were affected, which is why it looked random. Now a new note takes the nearest empty spot beside what it links to, clear of every other dot, and a chain of new notes grows off the note it hangs from. Maps that already have piles tidy themselves the first time 2.57.0 opens Atlas: each note sitting on a note it links to moves to the nearest empty spot beside it, the more linked of the two staying put, and nothing else on the map moves. The seeded initial layout is unchanged; in large maps the repair pass can also separate a few linked notes that already touched. Dragging single notes by hand, which the report also asks about, is not part of this: in Atlas a drag moves the view. (67a65b93)
  • Opening a note by mouse puts the cursor in it, from every surface (#863, from @phoscoder). Opening a note from the home screen's Recent or Favorites list showed the note but left the keyboard nowhere: with Vim mode off the note had no cursor, so the first keys were lost, keys like the arrows did nothing, and what did get through landed at the very top of the note, in front of its title; with Vim mode on the keys were read as sidebar and leader commands, moving the sidebar cursor and opening pickers instead of writing. The same happened after opening a note from a link or backlink in the Connections panel, from the note that uses an asset in the Assets view, from the Atlas (Enter, or a second click on a note), and from Open in Cloud settings. All of them now hand the keyboard to the note, as the sidebar and the palettes already did, and New note on the home screen puts the cursor in the new note's title, like every other New note button. (21ac81d1, 6f18169d)
  • A note replaced from outside ZenNotes stays open, unsaved text and all (#863). When another program deleted an open note and wrote it back, the way git, some sync clients and some editors replace files, ZenNotes took the delete at face value: it closed the note, showed the home screen, and threw away whatever had not been saved yet, while the note reappeared in Recent as edited just now. An open note whose file disappears now waits a moment for it to come back and closes only if it stays gone, and it never closes over unsaved edits: its save writes the note back. Deleting, trashing or moving a note inside ZenNotes still closes it right away. (5a7a40df)

🧰 For contributors

  • Release review follow-ups: 2071ce15 compares parsed remote URLs so case-sensitive proxy paths and query values remain distinct, with an MCP-to-HTTP test proving a write cannot cross from /Work to /work before confirmation. a23bcd5c includes existing syntax folds in Fold All and deduplicates them against nested outline ranges. 2e10d065 reconfigures the pinned editor's keymap when overrides or Vim mode change, preserving text, selection and undo. 1a062dea reserves exact scroll limits for the paired end anchor, so a block at only one pane's end cannot truncate the mapping.
  • Foldable callouts (#853): Preview's remarkCallouts reads [!type] plus an optional - or + and emits <details class="callout" data-callout-fold="collapsed|expanded" open?> with the title as <summary class="callout-title"> (a callout with nothing under its title stays a <div>); DOMPurify's defaults keep details, summary and open, and data-callout-fold joined the allowed data attributes. Edit mode: lib/cm-callout-fold.ts holds CALLOUT_HEAD_RE (shared with cm-wysiwyg-blocks.ts), the fold range (the blockquote below its title line, so the chevron, the fold keys and Fold All agree), CalloutFoldChevron, and calloutFolding(): a state field that records when this document's collapsed callouts were folded, reset by a whole-document replace under noteEditingSync (a note swap) and kept across the live-preview compartment's reconfigures, which a theme or math-renderer change triggers, so what the reader opened stays open. cm-wysiwyg-blocks.ts hides the marker, draws the chevron off the caret line, closes a folded card at its title, redraws on fold effects, and now skips a quote it already decorated (a fold splits the viewport into ranges and the tree walk met the quote twice, doubling the chevron). foldAtCursor takes a callout's title line and the innermost enclosing foldable callout; foldAllOutline adds foldable callouts. HintOverlay targets summary; the export window opens every details.callout before it waits on images. Tests: 10 in cm-callout-fold.test.ts, 5 in markdown.test.ts, 1 in cm-wysiwyg-blocks.test.ts (the quoted closing fence). The live harness is docs/releases/v2.57.0/discussion-853/verify.mjs (local).
  • List folding (#848) lives in lib/cm-list-fold.ts. The range is the one @codemirror/lang-markdown's foldNodeProp already gives a ListItem (the end of its first line to the end of the item), so the arrow, the fold keys and CodeMirror's own foldCode fold the same span and each reopens what another closed; only an item with a child block starting below its first line counts (listItemFoldRange). listItemFoldArrows() draws one ListFoldArrow per line at the item's marker: a zero-width anchor boxed and aligned like .cm-task-checkbox, with text-indent: 0, because the list line's hanging indent is inherited and pushed the glyph out by the marker's width (six columns, off screen, on a task). lib/cm-heading-fold.ts gained foldAtCursor (the heading's section, else the list item on the line, else CodeMirror's syntax fold, else the item the cursor sits under, moving the caret onto it), unfoldAtCursor (unfoldCode) and foldAllOutline (every heading and list item over the fully parsed tree, nested; the caret moves to the line opening the outermost fold around it); the Vim actions, the ex commands, editor.foldHeading and editor.unfoldHeading and the palette use them, with every id unchanged, and @shared/keymaps-catalog carries the new titles. Preview: enhancePreviewListFolds in lib/preview-list-fold.ts, run in Preview.tsx after the task pass (wrapTaskItemOwnText would take an arrow into the task body). The arrow is the item's last child (as the first, it broke li.task-list-item > p:first-child, which seats a loose task's text), placed by --z-list-marker-chars (β€’ or N. ) against @property-registered --z-list-item-em and --z-list-item-ch, which resolve in the item's font rather than the arrow's 12 px one. Tests: 7 in cm-heading-fold.test.ts, 8 in preview-list-fold.test.ts. The live harness is docs/releases/v2.57.0/discussion-848/verify.mjs (local).
  • lib/split-scroll-sync.ts owns the split mapping. splitScrollAnchors(blocks, editorMax, previewMax) builds the list both directions share: {0, 0}, every block that begins further down in BOTH panes than the last one kept (blocks folded into one editor widget, or rendered with no height, share a position; the first stands for all), then {editorMax, previewMax}; a block beginning inside a pane's last screenful ends the list. mapSplitScrollTop(anchors, from, top) is piecewise linear over it, so preview to editor is the exact inverse of editor to preview. splitScrollEchoes(lead, pane, now) and SPLIT_SCROLL_LEAD_MS (250) are the lead window.
  • EditorPane builds the anchors in splitScrollAnchorsFor(view, previewEl): only [data-preview-content] > [data-source-line] (the article's own blocks, so an embed stamped with another note's lines cannot misplace one), the editor side from view.lineBlockAt(...).top plus the scroller's top padding (view.documentTop), the preview side from the element's rect. splitScrollLeadRef is the pane leading and until when; splitScrollDriverRef the pane last driven. Wheel, touch and keys claim a pane before its scroll events arrive; typing in the editor makes it the pane last driven without taking the lead, and a click does neither. schedulePreviewSyncFromEditorViewport settles the editor onto the preview while the preview leads and leaves the preview alone when it was last driven; handlePreviewRendered keeps the reader's place the same way. The one-shot ignoreEditorScrollRef is gone; ignorePreviewScrollRef stays for outline jumps only. scrollTopForScrollRatio went with the ratio sync.
  • Tests: 8 in split-scroll-sync.test.ts (no anchors falls back to the ratio, the top of one pane is the top of the other, a block lands on its counterpart and interpolates inside it, a round trip over the whole note, folded blocks, both ends, clamping and panes that cannot scroll, the echo window); the ratio test in preview-outline-jump.test.ts went with the function.
  • The live harness for #859 is docs/releases/v2.57.0/issue-859/ (local): note.mjs generates a 464-line lecture note shaped like the report, app.mjs launches the build in apps/desktop/out with both stores isolated, one instance at a time, and reads CodeMirror's view as .cm-content's cmTile.root.view (CodeMirror 6.43 no longer has cmView). scenarios.mjs <label> runs six scenarios and smooth.mjs <label> trackpad-like scrolling of each pane; try-app.mjs <dir> opens the build on the note in Split and leaves it running.
  • The MCP session is VaultSession in apps/desktop/src/mcp/server.ts (calls resolve one at a time through a promise queue, so a parallel batch sees one session; VaultSwitchedError names both vaults) and sameVault in apps/desktop/src/cli/vault-target.ts (servers by normalized URL, directories by device and inode). The Go zn mcp mirrors it in ZenNotes/tui internal/mcp/session.go (under the session's lock, since go-sdk runs tool calls concurrently). The bundled engine still reads a server token from the environment only; reading zn connect's credentials.toml there too is open.
  • The bundled terminal tool is pinned to ZenNotes CLI 0.4.1 (08ec14a9): apps/desktop/terminal-release.json names the release's source commit, 86e2149, and the four macOS and Linux archives with SHA-256 digests read from the release's asset digests and matched against its checksums.txt.
  • reopeningSearchKeymap in lib/cm-vim-default-keymap.ts wraps every searchKeymap binding whose run is findNext (F3 and Mod-G, with their shift) so it opens the bar when searchPanelOpen is false; vimAwareSearchKeymap hands it out everywhere (EditorPane, the floating window, the pinned reference pane, quick capture, the external file window). The main editor's and the pinned pane's note-search binding is keyBindingsFor(getKeymapBinding(overrides, 'global.searchNotesNonVim'), ...) instead of a literal Mod-f. Tests: 5 in cm-vim-default-keymap.test.ts run the bindings on a real EditorView (both keys open a closed bar with the last query and move nothing, step once it is open, Shift steps back, Shift on a closed bar opens it too); all five fail on the stock keymap. The live harness is docs/releases/v2.57.0/issue-860/check.mjs <label> (Vim off, two launches: the default keymap, then "global.searchNotesNonVim" = "" in config.toml); repro.mjs prints the raw panel, focus, query and caret states, and emulates Windows key handling in a floating window with Emulation.setUserAgentOverride (platform Win32) and a reload.
  • Atlas placement is settleAtlasSpace in lib/atlas.ts, run for the sky and then the map at the end of every layoutAtlas, full or incremental. Cached (or just relaxed) notes keep their positions unless one sits within LINK_GAP (12, edge to edge) of a note it links to; of the two, the lighter one (fewer links, then newer, then path) moves. Moved notes and newcomers take the first probe that fits on a sunflower (2D) or a filled ball (3D) around their anchor: PLACE_GAP (24) clear of every dot while within PLACE_NEAR (120) of the anchor, else the nearest spot MIN_GAP (4) clear of every dot and LINK_GAP clear of its links. Newcomers go breadth-first from the placed notes, anchored on their placed links' centroid, or on their region slot while none is placed. Every placement meets the rule the repair pass checks, so a settled layout settles to itself. The full layout's seeded jitter and relaxation are untouched, so a small vault lays out byte for byte as before. atlasNodeRadius(degree) is the dot radius the layout and AtlasView (drawing and hit testing) now share. Tests: 5 in atlas.test.ts (newcomers clear of every note and near their links with the old map untouched, 30 notes around one hub, a 2.56-style piled cache repaired with nothing else moved, settling to itself, determinism). The live harness is docs/releases/v2.57.0/issue-861/ (local): repro.mjs seed|reopen <dir> drives the build over CDP and saves each cached map, analyze.mjs <dir> reports overlaps and what moved, try-app.mjs <new dir> opens the build on the map 2.56 cached, and vault-def.mjs is the report-shaped vault.
  • The keyboard hand-off after a mouse open is focusEditorNormalMode() from lib/editor-focus.ts, called once the open promise resolves (void open(path).then(() => focusEditorNormalMode())). Added for Home's Recent and Favorites rows (HomeView's openNote), the Connections panel (openConnection, and both branches of the missing-link create), the Assets view (openUsingNote), the Atlas (openNote, used by Enter and the second click) and Cloud settings' openPath; Home's New note passes focusTitle. Tags, Quick Notes, Archive and Trash keep their one-shot setFocusedPanel('editor') plus requestAnimationFrame(() => editorViewRef?.focus()), which works. In preview mode the editor stays mounted with display: none, which cannot take focus, so the hand-off only sets focusedPanel there and can never put typing into a hidden editor. Tests: HomeView.test.ts (4), ConnectionsPanel.test.ts (4), AssetsView.test.ts (3, one a guard that opening the asset itself leaves focus alone), AtlasView.test.ts (1, the keyboard path, since jsdom has no canvas to pick a node on) and the Cloud settings Open copy test; all but the guard fail on the old components. The audit harness is docs/releases/v2.57.0/issue-863/openers.mjs (local), with its runs in before-openers.txt and after-openers.txt.
  • An unlink for an open note goes through settleUnlinkedNote in applyChange: it waits UNLINK_SETTLE_MS (1500), stands down if the vault changed, the tab closed or the buffer is dirty, then asks readNote and calls closeUnlinkedNote only when the file is still gone. refreshNotes keeps any tab with noteDirty, not only the selected one. Tests: 4 in store.test.ts under "an open note replaced from outside the app (#863)", on a small fake disk through onFakeClock; the #713 rename tests now make readNote reject for a really deleted file. Live harness: docs/releases/v2.57.0/issue-863/probe.mjs <scenario> (vim-normal, vim-insert, vim-off, external-gap, external-delete), plus keys.mjs, focus-trace.mjs and restart.mjs from the investigation.
  • #692 follow-ups for the phone shells, no desktop change: VaultInfo.folderName for a host whose root is a label rather than a path, with vaultFolderName moved into shared-domain's vault-display-name and preferring it (f8b24c09; the three core packages went to 2.56.1 for the phones' core-only release), and the shell snapshot carrying folderName through (02ef25cd).
  • Test and tooling hardening from the 2.56.0 gate: every note-integrity test settles its store's dirty buffers before the next one starts (14ad7597, typed without a circular annotation in 94c05d2f), and the Vim editor smoke puts focus back before each check block and says so in a note line (eada4bc8).

Demos

In docs/releases/v2.57.0/media/ (local), 1080p with captions burned in and a .vtt beside each. Every fix clip runs the same keys on the packaged 2.56.0 app, then on 2.57.0:

  • 853-foldable-callouts.mp4 (33 s): the - and + markers revealed on the caret line, the chevron and zo/zc in Edit mode, the title row in Preview (screenshots inside), and Space h hint labels toggling a callout.
  • 848-fold-list-items.mp4 (50 s): a to-do note, arrows on hover, folding by click, zM then zo on the heading for one line per task, βŒ₯⌘F from a detail line, Preview, and the Tasks view unchanged.
  • 859-split-scroll-sync.mp4 (47 s): 15 s of trackpad-like scrolling of the preview in Split on the lecture note, with a readout of the section at the top of each pane. 2.56.0 keeps jumping back and ends in section 1.2 with the editor stuck in 1.1; 2.57.0 reaches 2.3 with both panes together. The click-in-the-editor symptom is not in it: it reproduced once in the scenario harness and in none of four tries on 2.56.0 while recording.
  • 860-find-bar-f3.mp4 (28 s): F3, a search, Escape, then F3 twice.
  • 861-atlas-new-notes.mp4 (72 s): six notes linking to Zettelkasten arrive, then a zoom on it and Atlas's f jump hints, which show the 2.56.0 pile as labels stacked on one dot; then 2.57.0 on a fresh profile, and 2.57.0 reopening the map 2.56.0 saved (exactly the six piled notes move, 36 to 50 units).
  • 863-home-opens-take-keyboard.mp4 (28 s): a note opened from Home's Recent, then βŒ˜β†“, Return and typing. On 2.56.0 there is no cursor and the typing lands in front of the title.
  • 863-outside-replace-keeps-note.mp4 (31 s): unsaved words, then another program deletes the file and writes it back 0.2 s later, with a terminal overlay showing the commands and the file's last line after.

No clip for the MCP fix: it has no UI of its own (the evidence is mcp-follows-app/). Earlier screenshots for #859 (the top of a note in Split before and after, and Preview-only for comparison) are in docs/releases/v2.57.0/issue-859/; for the MCP fix, the app's own UI connecting and switching, in docs/releases/v2.57.0/mcp-follows-app/shots/; for #861, the piled hub and the Pasta and Kuchen pairs before, after an upgrade and on a fresh profile, in docs/releases/v2.57.0/issue-861/before/ and after/; for #863, the home screen an outside replace used to leave, in docs/releases/v2.57.0/issue-863/external-gap-home.png, and the openers audit before and after in before-openers.txt and after-openers.txt there.

Verification

  • Release review: the case-sensitive server switch, standalone code folds, live pinned-editor keymap changes, and exact-end scroll anchors were each demonstrated by failing tests before the fixes. All nine added cases pass. The complete uncached suite at 08096c0c passes: shared-domain 1700 tests, app-core 2853 (1 pre-existing performance skip), desktop 976 (4 pre-existing platform/performance skips), plus the bridge and viewer checks.
  • Release review, how to test locally: run npm run build --workspace @zennotes/desktop, then npm run dev. In a scratch note with two code fences and no heading, zM folds both where the candidate before review folded neither. With Vim mode off, pin a note, unbind Search notes in non-Vim mode in Settings β†’ Keymap, and press Cmd+F in the still-open reference: its find bar opens immediately; resetting the binding restores note search. In Split, scroll either pane to the end of a math-heavy note and the final content remains reachable. Run npm run test:run --workspace @zennotes/desktop -- src/cli/vault-target.test.ts src/mcp/server.test.ts to verify a proxy-path switch refuses writes until vault_info confirms it, without touching a real vault.
  • #853, unit: npm run typecheck clean; npm run test:run green: shared-domain 60 files and 1700 tests, app-core 248 files and 2846 tests (1 skipped), desktop 60 files and 973 tests (4 skipped). New: 10 tests in lib/cm-callout-fold.test.ts (collapsed callouts fold on open and the others stay open, marker hidden and chevrons only on foldable titles, the chevron toggles, no chevron on the caret line, a live-preview reconfigure leaves an opened callout open, a note swap folds the next note's and typing a marker does not, the fold keys from a title and from inside, a plain callout from inside is left alone, Fold All), 5 in markdown.test.ts and 1 in cm-wysiwyg-blocks.test.ts, which fails on the old fence regex.
  • #853, live (discussion-853/verify.mjs), Vim mode on, both stores isolated, one instance at a time: 16 of 16. In Edit mode the two collapsed callouts start folded and the expanded and plain ones open, markers hidden, chevrons on the three foldable titles only; the chevron, zc/zo and βŒ₯⌘F from inside a foldable callout (the caret moves to its title) work, βŒ₯⌘F inside the plain one leaves it open, zM folds the callouts nested under the heading, zR opens all, and reopening the note closes the collapsed ones again. In Preview the three foldable callouts are <details> in the right state and the plain one is unchanged, a click on the title row opens the screenshots callout with both images and closes it again, and Space h puts a hint label on every foldable title. The PDF export window prints all three open with both images. The first run failed three checks, all the harness's own (a click on a title scrolled out of view, f pressed for hint mode, which is Space h, and an image check that met the older quote limitation below).
  • #853, how to test locally: npm run build --workspace @zennotes/desktop, then node docs/releases/v2.57.0/discussion-853/verify.mjs. By hand: write > [!example]- Screenshots with a line or two under it, then > [!tip]+ Tip with a line under it. Before: Preview shows [!example]- Screenshots as plain text in a quote, and Edit mode titles it "- Screenshots". After: the first is closed in both views (click its title in Preview, or the chevron in Edit mode) and the second is open; zc and zo on a title fold and open it; ⌘6 for Preview.
  • #853, not exercised live: Windows and Linux, the web client, the phone shells and the share viewer (same core and CSS), and nested callouts (> > [!note]- folds in Preview, but Edit mode draws no callout card for a nested head, as before).
  • Found and left alone, older than #853: in Edit mode an image embed on a quoted line (> ![[shot.png]], the discussion's own example) shows as source text, since live preview draws images only on a line of their own; Preview renders them. A candidate follow-up, together with the image resize handle (#684), which rewrites that line.
  • #848, unit: npm run typecheck clean; npm run test:run green: shared-domain 60 files and 1700 tests, app-core 247 files and 2831 tests (1 skipped), desktop 60 files and 973 tests (4 skipped). New: 7 tests in lib/cm-heading-fold.test.ts (arrows only on items with children and none on a hard-wrapped item, the arrow toggles, folding at the cursor on task, bullet and numbered items, a detail line folds its item and moves the caret, a code fence still folds, Fold All nested with unfolding the heading leaving the items folded) and 8 in lib/preview-list-fold.test.ts (which items get an arrow, toggling, a loose item keeps its first paragraph, the arrow stays out of the preview's click handling and the task body, a loose task's paragraph stays first, marker widths). The full run caught the keymap catalog mirror still carrying the old titles.
  • #848, live (discussion-848/verify.mjs), Vim mode on, both stores isolated, one instance at a time: 32 of 32. Arrows on exactly the eight items with children; every glyph centered in its box, 8 px left of its marker and within 1 px of its line's middle, in Edit and Preview, for tasks, bullets, numbers (including 10.), a nested subtask, a bullet under a number and a loose task; hidden until hovered, and folded arrows stay visible; the Tasks view lists every task while five are folded; βŒ₯⌘F on a detail line folds the nested subtask and moves the caret to it, βŒ₯⌘U opens it; zM leaves only the top heading, zo on it shows each item still folded, zR opens everything; Vim's block cursor still sits on the list marker. The first live run found what the unit tests could not: the tasks' arrows painted off screen, Preview's task items had no arrow, the first-child placement broke loose task spacing, Preview's offsets shrank with the arrow's own font, and every arrow sat about 2 px high; all fixed, then re-run.
  • #848, how to test locally: npm run build --workspace @zennotes/desktop, then node docs/releases/v2.57.0/discussion-848/verify.mjs. By hand: write - [ ] Task with two indented - detail lines under it and hover the task: an arrow appears beside the checkbox; click it and the details fold. Put the cursor on a detail line and press Cmd+Option+F (Ctrl+Alt+F elsewhere): the task folds. With a heading above, zM then zo on the heading shows one line per task. ⌘6 for Preview: the same arrows. Open Tasks: every task is still there.
  • #848, not exercised live: Windows and Linux (CSS and CodeMirror only), the web client and the phone shells (same core; with no hover, the editor shows an item's arrow on the cursor's line), and themes that restyle list markers.
  • #859, unit: npm run typecheck clean, 7 of 7 tasks; npm run test:run green: shared-domain 60 files and 1700 tests, app-core 242 files and 2790 tests (1 skipped), desktop 60 files and 973 tests (4 skipped). New: 8 tests in lib/split-scroll-sync.test.ts; two of them failed on a first, lazily searched version of the mapping (a folded block broke the inverse), which is why the anchors are built once for both directions.
  • #859, live (docs/releases/v2.57.0/issue-859/), both stores isolated, one instance at a time, on the generated note (52 top-level blocks; 8,915 px of editor against 18,539 px of preview to scroll). The code before 4d56f22d, built from the same tree: trackpad-like scrolling of the preview yanked it backwards 21 times (worst 1,569 px), 45 of 80 samples had the panes on different sections, and after 400 wheel steps the reader had only reached 3,374 of 18,539 px; in the stepped scenario 6 of 16 preview steps landed on different sections and 2 jumped backwards; after reading ahead in the preview, a click on an editor line moved the preview 1,211 px back; at the top the preview sat at 16 px. With 4d56f22d: no backward jump in either direction, 1 sample of the preview run and 3 of the editor run on different sections (the old code's editor run had the same 3), both panes reach their ends together, the editor click moves nothing in three runs, and both panes start at 0.
  • #859, how to test locally: npm run build --workspace @zennotes/desktop, then node docs/releases/v2.57.0/issue-859/scenarios.mjs after and node docs/releases/v2.57.0/issue-859/smooth.mjs after, or node docs/releases/v2.57.0/issue-859/try-app.mjs <empty dir> to open the build on the lecture note in Split. By hand: open a note with a lot of math, press ⌘5 for Split, and scroll the preview. Before: it jumps back whole sections and the editor trails. After: it keeps moving forward and the editor stays on the same section. Then read ahead in the preview and click a line in the editor. Before: the preview jumps back. After: it stays.
  • #859, not exercised live: Windows, the reporter's platform (the fix is platform-independent), the web client and the phone shells (same core), real trackpad momentum (simulated with wheel events), and the note title drawn over the H1 that the report also mentions, which never reproduced and is not in its video; the issue asks for a screenshot if it persists.
  • #860, unit: npm run typecheck clean, 7 of 7 tasks; npm run test:run green: shared-domain 60 files and 1700 tests, app-core 242 files and 2795 tests (1 skipped), desktop 60 files and 973 tests (4 skipped). New: 5 tests in lib/cm-vim-default-keymap.test.ts, all failing on the stock keymap.
  • #860, live (docs/releases/v2.57.0/issue-860/check.mjs), Vim mode off, both stores isolated, one instance at a time: the build before db8eca79 passes 5 of 13. In the main window and a floating window, F3 after one search leaves the bar closed and moves the cursor to the next match (13 to 34, then 39 to 59), and with Search notes in non-Vim mode unbound, Mod+F never reaches the bar; Mod+F still opening note search and the floating window's Mod+F were fine. With db8eca79: 15 of 15, including F3 and Shift+F3 stepping between matches while the bar is open (13 to 34 and back).
  • #860, how to test locally: npm run build --workspace @zennotes/desktop, then node docs/releases/v2.57.0/issue-860/check.mjs after (and before on a build of the parent commit). By hand, with Vim mode off: open a note, press F3, search a word that appears a few times, press Escape, press F3 again. Before: the bar stays closed and the next match is selected. After: the bar opens with your search selected. Then unbind Search notes in non-Vim mode under Settings β†’ Keymap and press Mod+F in the editor: it opens the find bar.
  • #860, not exercised live: Windows, the reporter's platform (Windows key handling was emulated in the renderer for the floating window), the report's floating-window Ctrl+F, which reopened the bar here on every press and so could not be reproduced, Vim mode on (the same reopening keymap, with Mod-f filtered out where Mod is Ctrl; the filter's unit tests pass), the pinned reference pane (same binding change as the main editor), and the web client (same core).
  • #861, unit: npm run typecheck clean, 7 of 7 tasks; npm run test:run green: shared-domain 60 files and 1700 tests, app-core 242 files and 2800 tests (1 skipped), desktop 60 files and 973 tests (4 skipped). New: 5 tests in lib/atlas.test.ts. With the old atlas.ts swapped in (plus only atlasNodeRadius), the three placement tests fail and the settling and determinism tests pass, as guards for the new pass should.
  • #861, live (docs/releases/v2.57.0/issue-861/), both stores isolated, one instance at a time, on a vault shaped like the report (29 notes in 7 top-level folders, then 8 later notes: six linking to one hub, one each to two leaves). The build before 67a65b93: the first map has no overlapping dots; once the 8 notes arrive, 20 pairs overlap in the map and 11 in the sky, each newcomer 0.2 to 14 units from its link. With 67a65b93: reopening that cached map repairs it, 0 overlapping pairs in either, exactly the 8 stacked notes moved (24 to 54 units) and none of the other 29; on a fresh profile the first map is byte-identical to the old build's, the 8 notes land 37 to 59 units from their links with at least 24 between dots, and reopening moves nothing. try-app.mjs was run once end to end (the map 2.56 cached, injected before Atlas opened, came out with 0 overlaps).
  • #861, offline against the old layout, on synthetic vaults: full layouts of 45 and 300 notes are identical, and 1,000, 3,000 and 5,000 notes move 1, 3 and 3 notes (linked pairs the relaxation left touching) at the same speed (5,000 notes: 2.86 s before, 2.82 s after). Adding 150 notes to a cached 1,000-note map leaves none of them on another note (the old placement: 639 overlapping pairs involving a newcomer); adding 600 at once to a cached 3,000-note map takes about 2.4 s, once, where the old placement took 2 ms and piled them up.
  • #861, how to test locally: npm run build --workspace @zennotes/desktop, then node docs/releases/v2.57.0/issue-861/try-app.mjs <new dir>: the build opens on a scratch vault with the map 2.56 cached, piles included, and Atlas open; the piles are gone. Zoom in on Zettelkasten, Pasta and Kuchen (+, or the wheel after panning) and compare with issue-861/before/before-1-hub.png and before-1-pasta.png. By hand on any vault: open Atlas once (Space g), write a few notes that each link to one existing note, open Atlas again. Before: the new dots sit on the note they link to. After: each has its own spot beside it.
  • #861, not exercised live: the sky on screen (its positions were measured, not screenshotted), vaults of thousands of notes in the app (offline only), the web client and the phone shells (same core), and Windows and Linux (the fix is layout only).
  • #863, unit: npm run typecheck clean, 7 of 7 tasks; npm run test:run green: shared-domain 60 files and 1700 tests, app-core 246 files and 2816 tests (1 skipped), desktop 60 files and 973 tests (4 skipped). New: 4 store tests and 12 component tests, all but one guard failing on the old code, plus the Cloud settings Open copy test extended.
  • #863, live (docs/releases/v2.57.0/issue-863/probe.mjs), a vault shaped like the report's (notes at the vault root, a Plans folder), the note opened from Home's Recent list as the report did, both stores isolated, one instance at a time. Before 21ac81d1, focus fell to the page body: with Vim mode off the first keystrokes were lost and later ones went in at the top of the note through the browser's leftover selection (the demo shows βŒ˜β†“ doing nothing, then Return and "oat milk" landing in front of the title), and with Vim mode on "h" moved the sidebar cursor up to the folder and a space then "i" opened the Insert template picker. After, the keys reach the editor in both modes (text with Vim off or after i, Vim commands in normal mode). The outside replace, a delete and a rewrite 300 ms later: before 5a7a40df, the home screen, and the words typed just before were lost; after, the note stays open with them. A saved note deleted for good is still open 0.6 s later and on the home screen by 3 s.
  • #863 audit, live (docs/releases/v2.57.0/issue-863/openers.mjs), Vim mode off, a note opened by mouse from each surface: before, Home's New note, the Connections panel, the Assets view and the Atlas left focus on the page body (Home's New note lost the typing; elsewhere text slipped into the note only through the browser's leftover selection, with no caret and no editor keys); after, every surface lands in the editor, and Home's New note in the title field. Tags, Quick Notes, Archive and Trash already did.
  • #863, how to test locally: npm run build --workspace @zennotes/desktop, then node docs/releases/v2.57.0/issue-863/probe.mjs vim-off and node docs/releases/v2.57.0/issue-863/openers.mjs after. By hand: on the home screen, click a note under Recent and type. Before: with Vim mode off there is no cursor, arrow keys do nothing and whatever lands goes in at the top of the note; with Vim mode on, the sidebar moves. After: the text goes into the note. For the outside replace, keep a note open, type a few words, and within a second run cp "$f" /tmp/n && rm "$f" && sleep 0.3 && cp /tmp/n "$f" on its file. Before: the note closes and the words are gone. After: it stays open with them.
  • #863, not exercised live: Linux (the reporter's Omarchy and Hyprland; both fixes are platform-independent), the exact keystroke that sent the reporter to the home screen (both bugs fit the report, and the issue asks for a recording if it persists), Cloud settings' Open (it needs a sync conflict; unit test only), and real sync clients (the replace was a scripted delete and rewrite).
  • MCP, unit: in this repo, desktop src/cli and src/mcp green (the full desktop count above), with 5 new tests (the switch refused until vault_info confirms it, over a real in-memory client, where a parallel batch is refused whole and a write planned for the old vault never lands; a vault left before the first call is no switch; the refusal names a saved server by its quoted name and never the token; and two for sameVault, a directory however it is spelled, symlinks included, and a server by URL whatever its token) and the test that pinned the first vault rewritten. In ZenNotes/tui: go vet, go test ./... on macOS and the Ubuntu, macOS and Windows CI of 86e2149 (run 36333797032), with mutation checks (pinning, and following without confirmation) failing the new session tests.
  • MCP, live (docs/releases/v2.57.0/mcp-follows-app/), both stores isolated: against a real token-protected znserver over stdio JSON-RPC, zn 0.4.0 answers 401 in app mode with the token zn connect saved and keeps listing the server's notes after the config switches to a local vault; the fixed build lists the notes and follows the switch once vault_info confirms it. Then the dev app itself, driven through Settings β†’ Vault β†’ Quick Connect… and the sidebar's Switch vault with real clicks and keystrokes, with the Go and the bundled engine attached and a headless Claude Code agent (Haiku) holding one conversation across the switch: its next call was refused, it called vault_info on its own and listed the local vault's notes.
  • MCP, the released zn: the v0.4.1 darwin-arm64 archive from the GitHub release matches its checksums.txt, answers --desktop-integration with protocol 1 and version 0.4.1, and passes the same repro (the zn connect token in app mode, the switch refused and then followed); npm run terminal:stage downloads, checks and stages all four pinned archives, and test:terminal passes 12 of 12.
  • MCP, how to test locally: npm run build --workspace @zennotes/desktop, build the terminal tool (go build -o /tmp/zn-fix ./cmd/zn in ZenNotes/tui) and a server (go build -o /tmp/znserver ./cmd/zennotes-server in ZenNotes/znserver), then node docs/releases/v2.57.0/mcp-follows-app/repro-token-and-switch.mjs /tmp/zn-fix /tmp/znserver and node docs/releases/v2.57.0/mcp-follows-app/ui-agent-e2e.mjs /tmp/zn-fix /tmp/znserver <shots dir> (the second runs a headless Claude Code agent through your own claude CLI, a few Haiku calls).
  • MCP, not exercised live: the app storing a real server token in its secret store (the dev Electron would read "ZenNotes Safe Storage" from the Keychain and can prompt; the token path was proven with a real server and the zn binary against an app-shaped config), the packaged app, the desktop-managed launcher itself (the same ZENNOTES_WORKSPACE_SOURCE=app it exports was set by hand), and Windows.

Distribution channels

  • AUR: zennotes-bin 2.57.0-1 published from the separate clone at c4fb7c4; identical files mirrored to main and v2.57.0 in 5ba952d4. The downloaded x64 tarball matched GitHub's SHA-256 digest, 2770cf098875c8de9a85eaeb85cc70b9f04d3521c7464001c929065abcab9c75. AUR package checks 36478886617 and 36478885956 passed.
  • Nix: hashes generated and the desktop package built by 36478695469, then copied exactly from bot PR #866 into 6eef2da3 on main and the release branch. Main-branch build 36479600754 passed, including package validation and headless smoke launch. The bot PR's own event 36478889985 failed before creating any jobs; this was not a Nix build failure. desktopHash is the same digest AUR pins.
  • nixpkgs: PR #561418 updated to 2.57.0 at fork commit 45edcdc1, based on nixpkgs master 20ed14a0. Its diff changes only version, npm dependency hash and source hash, using the verified values above. The exact nixpkgs source derivation was not built locally; its upstream review and CI remain separate from the verified in-repository binary package.
  • Homebrew: tap commit 328be83, mirrored in 140cd6a1 on main and the release branch. DMG SHA-256: arm64 1aee08e0449b143e22a42bf8b5033b2f9fc0dced47578206167f5995bb9b9aee, x64 45ff0aa27040db2719ac4f1af4bdbca8c3cf10dc045465a9f025b792f61f5d18.
  • Website: PR #46 merged at 1b78ed66 at 20:52 UTC, after the complete desktop release was verified and latest. Main tests and deployment gate 36482268240 passed. zennotes.org/releases led with 2.57.0 at 21:00 UTC, showed all seven clips, and the first clip played at 1920 Γ— 1080 with its English caption track. The live docs include list folding, foldable callouts, the find-bar keys, immediate pinned bindings and MCP switch confirmation.
  • Final channel check: npm run verify:channels -- 2.57.0 passed after the website deployed: AUR, Homebrew, Nix, and all seven website download routes serve 2.57.0. GitHub releases/latest points to v2.57.0. The separate server and mobile boundary artifacts have not been published by this desktop release.

Release validation

  • Tagged source: 08096c0c145c8174f7c20edf8ab71d14c018955a, the version bump on nineteen reviewed cycle commits, through PR #864.
  • Fresh gates passed: npm run typecheck -- --force (7 tasks), npm run test:run -- --force (5 tasks), both without cached results; npm run build:prod from apps/desktop; and npm run pack.
  • The local arm64 package was signed with Developer ID Application: Lumary Labs LLC (WYY7PK57DM), and codesign --verify --deep --strict passed. Apple's normal timestamp endpoint worked; the 2.56.0 IPv4 workaround was not needed. Local notarization was explicitly skipped because Apple credentials are not configured on this host; the downloaded CI DMG subsequently passed the notarization checks below.
  • Packaged launch: the isolated packaged-launch-check.mjs found a real CDP page target in 1.4 seconds and read version 2.57.0. Both stores were isolated and the harness terminated its process.
  • Built-app smoke gates passed on the first run, sequentially with both stores isolated: test:vim-editor 12 checks, test:sidebar-vim 12 checks over 1000 notes, and test:editor-improvements 19 checks. No renderer errors. Each harness reused the existing build through its skip-build option.
  • ffprobe verified all seven demo MP4s as 1920 Γ— 1080 at 30 fps, with matching VTT files. Website validation passed: php artisan view:cache, Pint, and the full php artisan test suite (915 tests, 8316 assertions).
  • PR checks all passed on the tagged head: CI 36475592183 (macOS, Windows, Linux x64, Linux arm64 and production dependency audit), CodeQL 36475592207, web boundary 36475592227, and viewer candidate 36475592223. No CI retry was needed.
  • Release run 36476899900 passed all four platforms on the first attempt. Linux arm64 finished at 20:19:58 UTC, Linux x64 at 20:21:19, macOS at 20:37:42, and Windows at 20:49:57. The release has 32 uploaded assets: 25 installers, update manifests and support files, plus seven demo clips.
  • verify-installers.py 2.57.0 --download <scratch> passed: all four manifests (latest-mac.yml, latest.yml, latest-linux.yml, latest-linux-arm64.yml) exist, and all 13 files they name were downloaded and matched both their declared sizes and SHA-512 hashes. The x64 Linux tarball's SHA-256 still matches the value pinned by AUR and Nix.
  • The existing nixpkgs worktree's fetch stalled over both HTTPS and SSH, including a slow-transfer RPC error. A fresh shallow, sparse checkout of upstream master succeeded, and the version/hash-only update was pushed with a lease guarding the previous PR head. The old worktree's source was not modified. No signing workaround, code fix, tag move, asset replacement or boundary-artifact publication was needed during this release.
  • Distributed macOS arm64 app: the downloaded DMG matched GitHub's SHA-256, was mounted read-only, and the app inside passed spctl -a -vv -t exec as Notarized Developer ID from Lumary Labs LLC (WYY7PK57DM), xcrun stapler validate, and codesign --verify --deep --strict. The volume was ejected after verification.
  • Live website verification found no browser errors on the release page or docs. The sampled list-folding video played with duration 50.466667 seconds and dimensions 1920 Γ— 1080; the page exposes seven release clips. Screenshot evidence is kept locally in media/release-page-verified.png.

Local-first and keyboard-first, as always.

Don't miss a new zennotes release

NewReleases is sending notifications on new releases.