Repository navigation
Conversation
…re the next one starts The 2.56.0 gate caught one test in this file leaking into another: typing arms the store's 350 ms debounced save, the test ended with the buffer dirty, and the timer fired while a later test was collecting writes into the file's shared writeCalls log. `2c3dd065` settled that one test by hand. Twenty-odd other tests here type and stop without settling; they pass because their timing has never lined up, which is not a reason. Each test's store module is now kept, and afterEach settles it: real timers back first (a fake-timer test's pending saves fall away with the fake clock), then persistNote on every dirty path, raced against 50 ms because a test may have left the bridge's writeNote gated forever. persistNote clears the pending timer before its first await, so the race can only cut short a write that goes to the finished test's own bridge anyway. Proven with a throwaway probe test that waited past the debounce after the dirty #852 test: it saw `unsaved typing` land with the guard neutered and nothing with it in place. The store is untouched.
…block On the 2.56.0 gate run the suite passed its first three checks and then failed four in a row, every one reporting focused:false over a cursor that had not moved: something took DOM focus from the editor after the `$` check, and from then on each key went nowhere. The second run passed 12 of 12, so the app was fine and the run was not. Each check block now starts with ensureEditorFocus: if the editor's .cm-content is not the active element and the ex prompt does not own focus on purpose, the script says so in a `note` line and calls the existing focusEditor before typing. gotoLine does the same, since five blocks begin there. One stolen focus therefore costs at most the block it happened in, and the log names it instead of four unrelated FAIL lines. Verified by injecting a focused button at the exact spot of the gate failure: the script logged the note and passed 12 of 12; without the injection it passes silently as before.
`14ad7597` typed the kept module through loadStore's return type while
loadStore's return type came from that same variable, which tsc rejects
as a circular annotation (TS2502, TS7023). vitest ran the file fine, so
the suite alone did not catch it; the workspace typecheck did. Both now
name `typeof import('./store')` outright.
The display name shipped in 2.56.0 falls back to the folder's name, and
the app took that from the root's last path segment: right for desktop,
the web client and a remote workspace, whose roots are paths. The phone
shells describe a vault by a label instead ("On this device › ZenNotes
› docs"), which has no path segment, so on a phone the whole label stood
in for the folder name. The first vault-settings save on a vault with no
display name (a favorite toggled is enough) then renamed the vault to
that label in the drawer, since the store re-derives the name after
every save, and clearing a display name landed on the label too.
Reproduced against the store with a phone-style root before this fix.
VaultInfo gains an optional `folderName`: the folder's own name on
disk, for a host whose root is not a path. `vaultFolderName` moves from
the renderer into shared-domain's vault-display-name module, prefers
that field, and otherwise keeps taking the root's last segment, so no
existing host changes behaviour; the store's fallback, the rename
prompt and the Settings field's placeholder all go through it. The
shells set `folderName` to the folder they opened. Desktop main is left
alone: its roots are paths.
The three core packages move to 2.56.1 for the phones' core-only
release; the desktop app stays 2.56.0.
The shell snapshot's vault is typed as a VaultInfo, but getShellSnapshot rebuilt it from three named fields: root, name and temporary. The folderName that f8b24c0 added for hosts whose root is a label never got through, so a phone shell reading it from the snapshot got undefined with no error, and its vault switcher could not tell which folder was open once a display name made the shown name a different string. Found while driving the iPhone shell's switcher on the simulator; the shells now take the folder from their own bridge, which holds the open vault by folder, so nothing waits on this, but the type's promise should hold. The rebuild names folderName too, and the change check compares it, so a host that hands over a different folder gets a fresh frozen snapshot. A test pins both.
… carries work across runMcpServer resolved the vault once and kept it for the session, so an agent on the bundled MCP (every Windows install, and any client set up without the managed zn) kept working in the vault the app had left until the client restarted the server. The Go zn mcp did the same (Kevin on Discord, 2026-09-25: the app went from his server back to a local vault and the agent kept hitting the server). The pin was deliberate, since an agent that read notes in one vault and writes after the user switched would change a note at the same path in the other one. VaultSession mirrors the Go fix (ZenNotes/tui 35a77be, session.go): the target is resolved before every tool call; after a switch every tool but vault_info fails with VaultSwitchedError, which names both vaults and runs nothing, and calls resolve one at a time so a parallel batch stops as one; vault_info moves the session and its notes say which vault it left. The startup check still reports an unresolvable --vault on stderr (#831), but the session starts at the first call it serves, so a vault the app left before any call is no switch. sameVault compares servers by normalized URL and directories by device and inode. A saved server is named in quotes with its URL after "at", because the app often names a profile after its host already. The test that pinned the first vault now expects the refusal, and two new ones drive a real in-memory client through a switch, a refused write that never lands, and vault_info. The MCP and CLI cards in the manual say how the server follows a switch and how a server token reaches zn: `zn connect <url> --no-default` once, or ZENNOTES_REMOTE_TOKEN. The token half describes zn's fallback (ZenNotes/tui 86e2149), which desktop-managed installs get with the next terminal-release.json pin; this engine still reads its token from the environment only. Verified with the dev app driven through its own UI and both MCP engines attached, plus a headless Claude Code agent that recovered from the refusal on its own.
…hever one you scroll (#859) Split mode kept its panes in step with two different mappings. The preview followed the editor by source line, but the editor followed the preview by raw scroll ratio, and in a note full of KaTeX and callouts the rendered preview is about twice as tall as its source, so the two disagreed by whole subsections. Worse, the editor's lag came back: every time CodeMirror re-measured the live-preview widgets the sync had just scrolled into view, its scroll and geometry updates snapped the preview to the editor's lagging line, and a one-shot "ignore the next event" flag caught only the first of them. Driving the app over CDP with a lecture-style note: smooth scrolling the preview yanked it backwards 21 times (up to 1569px) and the reader never got past a fifth of the note; after reading ahead in the preview, one click on an editor line threw the preview back 1211px (@ShowhyT's "click/selection jump"). At the top, the preview started 16px down, which pinned the H1 against the pane's top edge. Both directions now map through one list of anchors (lib/split-scroll-sync): the document start, every top-level block where it begins in each pane (blocks folded into one editor widget share a position; the first stands for all), and both panes' ends. Between two anchors a position maps by the fraction of the way it is from one to the next, so a formula that renders ten times taller than its source stretches only its own stretch, and preview-to-editor is the exact inverse of editor-to-preview. The top of one pane is the top of the other and the end is the end. The pane the reader drives now leads: wheel, touch and keys claim it before its scroll events arrive, and while it leads the other pane's scroll events are echoes, however many CodeMirror fires. An editor re-measure settles the editor onto the preview while the preview leads, and leaves the preview alone when the preview was the pane last driven, so clicking or typing in the editor no longer moves what the reader was looking at. Only the article's own blocks are anchors, so an embed stamped with another note's lines cannot misplace one. scrollTopForScrollRatio goes with the ratio sync. Not done: anchors stay at top-level blocks, so inside one tall callout the mapping is linear. The report also mentions the note title drawn over the H1, which never reproduced here and is not in the video; the top-edge pin above is the part that was. Same harness after the fix: no backward jumps in either direction, the click moves nothing, both panes start at 0 and end together. Tests cover the mapping (round trip, folded blocks, both ends, no anchors) and the echo window.
The desktop's managed zn still pinned ZenNotes CLI 0.4.0, so a 2.57.0 build would have shipped a zn mcp that keeps the first vault it finds and answers 401 on the server the app is connected to, whatever token zn connect saved for it, while this version's manual already describes both fixes (5f8095b). The pin now names 0.4.1, released today from ZenNotes/tui 86e2149 with the two fixes for that report (35a77be, 86e2149): the source commit, the four macOS and Linux archive URLs and their SHA-256 digests, each read from the release's asset digests and matched against its checksums.txt. Verified through `npm run terminal:stage`, the same download, checksum and binary inspection that `apps/desktop/build/after-pack.js` runs at packaging time, for all four platform pairs; the darwin-arm64 binary answers protocol 1, version 0.4.1. `test:terminal` 12 of 12. The released darwin-arm64 binary also ran the MCP repro against a token-protected server: with only a zn connect token it lists the notes, and it follows a vault switch once vault_info confirms it.
…ng (#860) With Vim mode off, the in-note find bar opened once. F3 and Mod-G run CodeMirror's findNext and findPrevious, which open the bar only while no query is set: after one search and Escape they moved the caret to the next match of the old query 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 (reported by @Sullti, who named findNext as the cause). The same report found the editor's Mod-f hard-coded to note search: unbinding "Search notes in non-Vim mode" under Settings → Keymap did not free it, in the main editor or the pinned reference pane. F3, Mod-G and their Shift variants now open a closed bar, the last query prefilled and selected as Mod+F does, and step through the matches only while it is open. The change sits in vimAwareSearchKeymap, so every editor surface has it. Both editors' Mod-f now come from the configured `global.searchNotesNonVim` binding, so moving or unbinding it hands Mod+F to the find bar (what #376 asks for, by way of Settings). The manual's Mod+F row said the bar opens with Vim off, which was never true in the main window; it now says where Mod+F goes and how to change that, and a new row covers F3 and Mod+G. Not reproduced: the report's floating-window Ctrl+F, which reopened the bar on every press here, with Cmd+F on macOS and with Ctrl+F under Windows key handling (navigator.platform emulated as Win32). The fix covers any path that runs findNext. Verified over CDP with Vim mode off, both stores isolated: the build before this change passes 5 of 13 checks (F3 jumps from match to match with the bar closed in the main and a floating window, and Mod+F never reaches the bar with note search unbound); this one passes 15 of 15, with F3 and Shift+F3 still stepping between matches while the bar is open and Mod+F still opening note search by default. Five new tests run the bindings on a real EditorView; all five fail on the stock keymap.
…ks, not on them (#861) Atlas lays the vault out once, caches the positions per vault, and from then on only places newcomers, so the map never shuffles. A newcomer (a new note, or one renamed or moved) went to the centroid of the notes it links to, give or take 11 units a side, with no look at what was already there. For the most common newcomer, a note that links to one note, that centroid is the note itself: the dot landed on top of it, every later note linking there piled onto the same spot, and the cache kept the pile for good. The first map of a vault never had this, only notes written after Atlas was first opened, hence the "sometimes" in @vinivinoo's report, whose screenshot shows a pile on one hub and pairs on two smaller notes. A newcomer now takes the nearest spot around the same anchor (its links' centroid, or its region slot while none of them is on the map) that keeps 24 units between dots, edge to edge. On a map too crowded for that near its links, it takes the nearest spot where it touches no dot and keeps 12 from what it links to. New notes that link only to each other are placed breadth-first from the map, so a chain grows off the note it hangs from. Maps that earlier versions already piled up repair themselves on the first open: a cached note within 12 units of a note it links to moves to the nearest free spot, and of the two the note with more links (then the older one) stays. Nothing else moves, and the result settles to itself, so reopening the map moves nothing. The dot radius now lives in lib/atlas.ts (atlasNodeRadius), shared by the layout and the view. The design doc's placement rule says the same, and its banner, which still said nothing had shipped, now says what has. What it deliberately does not do: separate dots that the first layout of a very large vault already leaves overlapping (about 390 pairs in the map of a synthetic 5000-note vault). They predate this and come from the relaxation, not from placement, and repairing them would move about 7% of such a map at once. Dragging single notes by hand, the report's other half, is left out as well: in Atlas a drag moves the camera by design. Verified over CDP on a vault shaped like the report's (29 notes in 7 folders, then 8 notes linking to one hub and two leaves), both stores isolated. The build before this change piles them up: 20 overlapping pairs in the map and 11 in the sky, each newcomer 0.2 to 14 units from its link. This one places them 37 to 59 units from their links with at least 24 between dots, and opening the map 2.56 cached repairs it: 0 overlaps, exactly the 8 stacked notes moved, none of the other 29. The first map is byte-identical to the one 2.56 draws, and reopening moves nothing. Offline against the old layout, full layouts of 45 and 300 notes are unchanged, 1000 to 5000 notes move one to three notes, and the time is the same. Five new tests; the three placement tests fail on the old layout.
Home's Recent and Favorites rows opened a note with no hand-off to the editor. The click swaps the Home view out, button and all, so DOM focus fell to <body>: the note showed, but with Vim mode off typing went nowhere, and with it on VimNav, which routes keys to the sidebar whenever no editor has focus, read the letters as sidebar and leader commands. That is what @phoscoder met on Omarchy (#863): typing moved the sidebar cursor up to the note's folder, the Plans row highlighted in the report's screenshot, and turning Vim mode off only made typing do nothing. The sidebar tree, the note list and the palettes have handed the keyboard over since #599; Home never did. Both rows now open the note and then call focusEditorNormalMode, the same hand-off. Home's New note buttons create the note with focusTitle, like every other New note button, so the new note's title field takes the keyboard instead of nothing. Verified over CDP on a vault shaped like the report's (notes at the vault root, a Plans folder), both stores isolated. Before, a note opened from Recent left focus on <body>: with Vim mode off the first keystrokes were lost, and with it on "h" moved the sidebar cursor up to Plans and a space and "i" opened the Insert template picker. After, the keys reach the editor in both modes, and New note leaves the cursor in the title field. Four tests render HomeView; all four fail on the old view.
… all (#863) An open note whose file another program deleted and wrote back, the way git, some sync clients and some editors replace files, closed under the reader. chokidar merges an unlink and an add into a change only inside 100 ms, so a slower replace reached the store as an unlink, and closeUnlinkedNote dropped the tab and the buffer with it, text not yet saved included; with one note open, the reader landed on Home while the note showed up again in Recent as edited "just now". A note-list refresh taken while the file was gone could likewise prune a background tab with unsaved edits. An unlink for an open note now waits 1.5 s and asks the disk again. The tab stays when the file is back (its add re-reads the text as before) and closes only when it is still gone, and it never closes over unsaved edits: the pending save writes the note back, the rule a stale listing already follows for the active tab. The refresh keeps any dirty tab, in the background as in front. The app's own deletes, trashes and moves close their tabs themselves and are unaffected. Verified over CDP: a note whose file was deleted and rewritten 300 ms later went to Home and lost the words typed just before; now it stays open with them. A saved note deleted for good is still open 0.6 s later and on Home by 3 s. Four new store tests fail on the old store; the three #713 rename tests now model the deleted file on disk and pass on both.
…d settings takes the keyboard The #863 audit of every place that opens a note by mouse. Four still opened it with no hand-off to the editor, so focus stayed on the clicked row or fell to <body>: typing went nowhere with Vim mode off, and to the sidebar and the leader with it on. - Connections panel: clicking a link or a backlink row, and creating the note a missing link names. Enter on a row already handed over through VimNav. - Assets view: the Used button's note, and the menu of notes when several use the asset. Opening the asset itself is unchanged. - Atlas: Enter on the focused note, and a second click on a node. - Cloud settings: Open and Open copy on a file that needs attention, which close Settings first. Each now calls focusEditorNormalMode once the note has opened. Left alone on purpose: Tags, Quick Notes, Archive and Trash already hand the keyboard over with a one-shot focus that works (checked in the app); hover previews, Excalidraw menus, the Obsidian drawing converter and Preview's asset and diagram links open no text editor; and a note link followed in Preview shares its code with the editor's own link following, where a reader is not asking to type. Verified over CDP with Vim mode off, opening a note by mouse from each surface: before, the Connections panel, the Assets view and the Atlas left focus on <body>; after, every surface lands in the editor. Cloud settings is covered by its test only (it needs a sync conflict). Eight new component tests, all but the asset-row guard failing on the old components, and the Cloud settings Open copy test now expects the hand-off.
…eview (#848) A long to-do note with a few indented detail lines under every task had no way to show just the task lines: headings folded, list items did not, not visibly. @Sullti asked in discussion #848 for the heading treatment: an arrow on hover for any item with indented children, in Edit and Preview, the fold keys, Fold All and Vim's fold commands on list items too, and the Tasks view left alone. A bullet, numbered item or task with a child block below its first line (a nested list, another paragraph, a code block) now gets an arrow beside its marker, shown on hover or on the caret's line and kept showing while folded. The range is the one @codemirror/lang-markdown already gives a ListItem, from the end of the item's 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. An item that only wraps onto a second line has nothing under it and gets no arrow. In Preview the same items get an arrow left of their bullet, number or checkbox, and the fold state lives in the DOM, as Preview's heading folds do. The fold keys were heading-only: Ctrl+Alt+F (Cmd+Option+F) did nothing on an item, while Vim's zc already folded one through the syntax fold. Both now run foldAtCursor: the heading's section, else the item on the line, else the syntax fold, else the item the cursor sits under, with the caret moved to it. Fold All (zM, :foldall) folded only the outermost ranges, so a heading swallowed its tasks; it now folds every heading and every list item, nested, and zo on the heading shows one line per task. Command ids stay; their titles, the keymap catalog, the manual and the demo tour's cheat sheet name list items. Folding only hides lines: nothing is written to the note, and the Tasks view lists the same tasks. Driving the built app turned up two layout traps, and the CSS says why. A list line's negative text-indent is inherited, and it pushed the arrow's glyph out by the marker's width (off screen on a task). In Preview an em or ch inside the arrow's own 12 px font shrank every offset, so the offsets use lengths registered with @Property and resolved on the item. The Preview arrow is the item's last child, since as the first it broke the rule that seats a loose task's paragraph beside its checkbox, and it is added after the task pass, which would otherwise take it into the task body. Tests: 7 in cm-heading-fold.test.ts, 8 in preview-list-fold.test.ts.
…idian (#853) Obsidian marks a foldable callout with - or + right after its type, and vaults moving over use it for long blocks that should stay closed: screenshots, logs, config excerpts. ZenNotes did not know the marker. Preview's callout pattern wanted whitespace right after [!type], so "> [!example]- Screenshots" fell back to a plain quote showing the whole line as text, and Edit mode titled it "- Screenshots". @Sullti asked in discussion #853 for - collapsed, + expanded, no marker as today, and a click on the title row to toggle it, in Edit and Preview. Preview renders a callout with a marker as a <details> with its title as the <summary>: the whole title row toggles it, Tab and Enter work, the browser's find opens a closed one on a match, and Vim's hint mode (Space h) now labels summaries. A callout with nothing under its title has nothing to fold and stays a plain box. DOMPurify's defaults already keep details, summary and open. A PDF cannot be clicked open, so the export window opens every callout before it waits on images and prints. In Edit mode the marker hides with the type, a chevron follows the title (off the caret's line, where the title is source; a click on the title text still edits it), and a folded callout closes its card on the title line. The fold range is the quote below the title line, so the chevron, the fold keys and Fold All close the same span. The fold keys fold a callout from its title line, and a foldable one from any line inside it; Fold All folds foldable callouts along with headings and list items. A collapsed callout folds each time a note opens. That state is a field rather than a view plugin's constructor: EditorPane reconfigures the live preview compartment on a theme or math renderer change, and a constructor would close again what the reader opened. The field keeps its value across a reconfigure and resets only when a note sync replaces the whole document, which is how the editor swaps notes. Two things turned up on the way. The WYSIWYG block walk visits a quote once per visible range, and a fold splits the viewport into ranges, so the chevron came twice; a quote already decorated is now skipped. And a code block's closing fence inside a quote or callout stayed visible in Edit mode, because the fence test expected the fence at the start of the line. Left as it was: an image embed on a quoted line (> ![[shot.png]]) shows as source in Edit mode, as in any quote; Preview draws it. The manual, the MCP instructions and the demo tour's callout note describe the marker. Tests: 10 in cm-callout-fold.test.ts, 5 in markdown.test.ts, 1 in cm-wysiwyg-blocks.test.ts (the quoted fence, failing on the old pattern).
The session compared remote targets by lowercasing the entire URL. A reverse proxy could serve different vaults at /Work and /work, yet a write planned against the first would silently reach the second. Compare parsed URLs so the origin is normalized while paths and query values retain their case. A real MCP session against a local HTTP server now proves that no write reaches the new path before vault_info confirms it, and that writes resume after confirmation. Verified the target and MCP suites (47 tests) and the desktop main-process typecheck.
Collecting only headings, list items and marked callouts made zM and :foldall do nothing in notes containing standalone code blocks. Those ranges still folded individually with zc. Ask the fold services and syntax tree at each non-heading line, then combine those ranges with the nested outline folds. Deduplicate shared ranges so reopening a heading leaves each child folded once. Existing code, table and quote folds participate again. Verified the heading and callout folding suites (25 tests) and the app-core typecheck. The regression cases cover standalone fences, repeated Fold All, and code remaining folded after its heading opens.
The reference editor read note search from its initial preference snapshot. Unbinding or moving the shortcut in Settings updated the main editor but left the reference capturing the old chord until it was unpinned. Keep the reference keymap in its own compartment and reconfigure it when overrides or Vim mode change. The editor instance, selection, document and undo history stay in place while the old shortcut is released and the new one takes effect. Regression tests drive a mounted CodeMirror editor through unbinding, rebinding and resetting, and verify undo survives. The 25 reference and search-keymap tests and the app-core typecheck pass.
A content block exactly at one pane's scroll limit was accepted as an anchor, then stopped the paired terminal anchor from being added. Scrolling that pane to the bottom could leave hundreds of pixels unread in the other. Reserve either scroll limit for the shared terminal anchor. Regression cases cover a block at the editor limit, the preview limit and both limits, checking reachability, near-end movement and round trips. The full uncached test suite, workspace typecheck and desktop build pass. Isolated CDP checks confirm code folding, live reference keymap updates and final content reachable from either split pane.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ZenNotes 2.57.0, cut from branch
v2.57.0.mainfast-forwards to this branch once the checks pass; the tagv2.57.0goes on the release commit.Features
> [!type]-starts collapsed and> [!type]+expanded. The title row toggles it in Preview and a chevron in Edit mode, the fold keys and zM work on it, and a PDF export prints it open (Support foldable callouts (> [!note]- and > [!note]+) #853, asked for by @Sullti).Fixes
zn connect <url> --no-default, the desktop-managedznandzn mcpauthenticate against the app's server. The bundled terminal tool is pinned to zn 0.4.1 (reported on Discord by Kevin).Maintenance
VaultInfo.folderNamefor hosts whose root is a label, carried through the shell snapshot (no desktop change).Docs
help.ts) carries list and callout folding, the find bar keys and the MCP and CLI cards; the MCP instructions and the demo tour describe foldable callouts. The website docs for the MCP change, Ctrl+F opens the find panel only once, then jumps to the old match (non-Vim, Windows) #860, Fold nested list items and tasks, like headings #848 and Support foldable callouts (> [!note]- and > [!note]+) #853 and the 2.57.0 release entry follow in one website PR once this release is complete.Release notes, demo clips and the verification log live in
docs/releases/v2.57.0/(local).