Skip to content

Release: ZenNotes 2.60.0 - #882

Merged
adibhanna merged 8 commits into
mainfrom
v2.60.0
Oct 1, 2026
Merged

adibhanna merged 8 commits into
mainfrom
v2.60.0

Conversation

@adibhanna

Copy link
Copy Markdown
Contributor

Summary

  • The desktop-managed zn updates itself from signed ZenNotes/tui releases (Ed25519 manifest, checksum and integration probe before the swap, previous version kept). Settings > CLI > Updates and a palette command; only for a zn ZenNotes installed.
  • Bundle the verified CLI 0.6.2, the first CLI release signed by its own workflow; the pin is its terminal-release.json byte for byte.
  • Note switches in large vaults no longer recount every backlink (Performance: synchronous backlink counting blocks note switches for ~5 seconds in a 6.6k-note vault #880): 1,077 ms to 77 ms click to paint on a 6.5k-note vault.
  • The desktop runtime benchmark seeds wikilinks and times note switches to paint.
  • Open CLI Settings lands on the CLI page.

Verification

  • Fresh typechecks: 7 tasks, no cache. Fresh unit suites: 5,553 passed, 5 skipped.
  • Signed macOS package (npm run pack) launched isolated with a CDP page target in 1.4 s, reporting 2.60.0 and bundling CLI 0.6.2.
  • Vim editor, sidebar navigation and editor improvements smoke suites passed.
  • CLI updates end to end: a build shipping 0.6.0 updated itself from the live v0.6.1 and v0.6.2 releases with the production key, plus scripted scenarios (relaunch, automatic off, tampered manifest refused, no managed zn) on the dev and packaged builds.

No new demo media.

Desktop-managed zn installations still receive CLI 0.6.0. Pin the released
0.6.1 archives and their source commit (4c4038e) for all four supported
desktop targets, keeping integration protocol 1, and bring the in-app
manual's terminal entry up to date with what 0.6.1 changes for desktop
users: `zn vault list` now says which entries this app saved and which zn
saved, so `zn vault remove` stops being trial and error; every command
starts without waiting on a terminal that never answers a background-color
query; and the help output lines up.

The four digests were read from the release's asset digests and matched
against its checksums.txt. Each archive was staged through
`npm run terminal:stage`, the same download, checksum and native
architecture validation packaging runs, and the native darwin-arm64
integration probe answered protocol 1, version 0.6.1.
…880)

Switching notes in a large vault froze the window for seconds. The status
bar shows how many notes link to the open one, and it counted them by
resolving every wikilink in the vault, while each resolution filtered and
searched the whole notes list. That is links x notes on every switch, even
for a note nobody links to and for one already open in a tab. Yago
Fernandes measured 4.8 s per switch in a 6,577-note vault with 13,672 links
and traced 94% of the CPU time to that count.

Resolution now reads lookup tables built once per notes array: first note
per normalized title, first note and count per normalized path, and the
same per `/`-led path tail, which is every string `endsWith` could match.
backlinksForNote resolves the vault's links once into an incoming map, so a
note switch is a lookup. Store updates replace the notes array, so its
identity is the revision; length and both ends are rechecked so a caller
growing an array in place cannot read a stale table.

Matching does not change: `first` is the note the old find returned,
`count` is the length the old filter saw, trash is still skipped and
anchors are still stripped. A differential run over 35,411 targets (every
link plus each note's title and path in 19 spellings) and 224 edge cases
returned identical notes, and identical backlink lists for all 6,577 notes.
Atlas, wikilink rendering, link clicks and the Connections panel's
wikilink matching share the tables.

In the built app, a vault shaped like the report (6,542 notes, 12,973
links) went from 1,077 ms to 77 ms click to paint per switch, and from
1,077 ms to 61 ms to reopen a note, with the same backlink counts. A test
counts title reads across 200 switches: the old code made 1.2 billion,
the new one at most 2,000.

This leaves the Connections panel's disk scan and Markdown-link matching as
they were, and adds no exclusions (#431).
The desktop runtime benchmark could not see #880. Its synthetic vault had
no wikilinks, so resolving every link against every note cost nothing, and
its note-open budget reads the store's note.open.* sample, which finishes
before React renders the note. On the 2.59.0 code with links seeded, that
sample stayed at 30.6 ms while the window froze for 1.5 s per switch.

ZEN_PERF_WIKILINKS_PER_NOTE seeds links into every synthetic note with a
fixed seed and the report's mix: about half resolve (titles, #Details and
^block anchors, inbox/ and / paths), the rest name notes that do not exist,
which were the expensive ones. It defaults to 0, and a 0 run seeds a vault
byte-identical to before (checked on 5,000 notes), so older numbers stay
comparable. ZEN_PERF_DESKTOP_NOTES=6577 with 2 per note is roughly the
reported vault.

Every run now also opens 5 notes and reopens 2, timed from click until the
editor's doc sync for that path, the matching active tab and two frames.
That finish line lands after the commit that renders the note and the
status bar, and reads no seeded content, so it works on an external vault.
The step runs after the heap, DOM and long-task numbers are taken, and
`note switch wall p50` gets a 150 ms budget (enforced only under
ZEN_PERF_ENFORCE, like the rest, and in perf:runtime-repeat). Same harness,
6,577 notes, 13,154 links: 1,513.6 ms on the 2.59.0 code, 33.7 ms now.

The search step sends Ctrl+P instead of Meta+P off macOS; the reporter had
to patch that to run the harness on Linux. The web harness is unchanged.
Open CLI Settings in the command palette only opened Settings, and
Settings reopens on whatever page it showed last (Appearance on a fresh
launch), so the command never reached the page its name promises. It now
asks for the CLI page through the same settings-navigation target the
status bar uses for Cloud and :unbind uses for Keymaps.

Found while driving the CLI update flow over CDP, where the command
landed on Appearance every time.
Every CLI fix needed a desktop release: the app copied its bundled zn
back over the managed one whenever the bytes differed, and zn update
refused desktop-owned installs. 2.59.0 existed only to ship CLI 0.6.0,
and the pin moved five times in two weeks.

For a zn that ZenNotes put on PATH, the app now checks the latest
ZenNotes/tui release 90 seconds after launch and then daily. Each
release carries terminal-release.json (the same schema as this repo's
pin) and an Ed25519 signature over its exact bytes; RELEASE_KEYS holds
the public key for zn-release-1. The manifest is verified before it is
parsed, its download URL must be the release asset its version names,
the archive must match the signed sha256, and the extracted zn must
answer the integration probe with that version before `current` moves.
A failed or tampered download never replaces a working CLI, and the
version it replaced stays on disk. The bundle becomes the floor: a newer
verified copy survives restarts, a tie goes to the bundle (whose copy
carries this build's signature), and a desktop update that ships
something newer takes over. A release on a protocol this build does not
speak waits for a ZenNotes update.

Settings > CLI > Updates shows the running and bundled versions, Check
for updates, and Update zn automatically; off still checks and offers
the install. State is pushed to open windows, so a scheduled check that
lands while Settings is open shows up. The palette gains Check for zn
Updates. The switch lives in the per-machine zennotes.config.json rather
than config.toml: it governs this machine's copy under userData, like
the install location. Homebrew, Go and manual installs are untouched,
and nothing is downloaded without a ZenNotes-installed zn, the same
condition MCP setups use to pick the managed binary.

Verified against the real v0.6.1 release: a build bundling 0.6.0
installed it on its own, kept it across a relaunch, offered it with
automatic updates off, refused a manifest edited after signing, and
skipped the download with no managed zn, with PATH isolated through a
stand-in login shell. The packaged, Developer ID signed, hardened app
ran the ad-hoc signed download fine. 14 new tests.
The bundled CLI is the floor a desktop build guarantees, and 0.6.2 is
the newest release. It is also the first one signed by the ZenNotes/tui
release workflow itself, so the pin is that release's
terminal-release.json copied byte for byte after it verified against
packaging/release-signing/zn-release-1.pub there and against
RELEASE_KEYS here (source commit 140a38f, integration protocol 1, all
four archives matching checksums.txt). Staged through
`npm run terminal:stage`; the native darwin-arm64 binary reports
zn v0.6.2. The in-app manual's CLI card says 0.6.2 and that it is the
first release this app can update its managed zn from.
Windows CI failed three parseReleaseManifest tests in the release PR.
They built their manifest for the machine running the test, and on
Windows that is win32, a platform ZenNotes deliberately manages no CLI
on, so parsing threw "does not manage a CLI on this platform" before the
assertion it was meant to reach. The install tests in the same file,
which execute a real zn, were already skipped on Windows.

The manifest and parsing tests now name a platform ZenNotes manages
(linux on Windows, this machine's own elsewhere). App code is
unchanged. Checked by forcing process.platform to win32 around the
file: the old version fails exactly the three CI tests, this one passes
9 with the 5 install tests skipped, and the native run passes all 14.
@adibhanna
adibhanna merged commit 1582939 into main Oct 1, 2026
9 checks passed
@adibhanna
adibhanna deleted the v2.60.0 branch October 1, 2026 15:59
@adibhanna
adibhanna restored the v2.60.0 branch October 1, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant