Skip to content

ZenNotes v2.58.0

Latest

Choose a tag to compare

@github-actions github-actions released this 29 Sep 17:11
· 4 commits to main since this release

ZenNotes 2.58.0: the bundled zn command works on Linux, the Linux packages install and start where they could not, and Quick Connect saves one server instead of two. On Linux, every package since 2.52.0 (the AUR package, the .deb and the .pacman) installed the bundled command-line tool so that only root could read it, so zn quietly stayed on the old version; upgrading to 2.58.0 switches it to the current one, with nothing to fix by hand (#869, from @diazkev314, who found the cause). The .pacman package installs on an up-to-date Arch again, and the .deb now starts on minimal Ubuntu and Debian systems. Connecting to your own ZenNotes server with Quick Connect saves that server once, without an extra "ZenNotes Server" entry, and a saved server with no token says so when you connect (#870). Settings → Vault stays readable in a narrower window while you are connected to a server (#871). And while an update downloads, the app no longer redraws the whole sidebar and Settings every second.

Released on September 29, 2026 as v2.58.0, at 230844ff. PR #873 fast-forwarded the release branch into main at 17:10 UTC after all checks passed. The branch started at 140cd6a1 (the 2.57.0 Homebrew mirror), carries seven 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 v2.58.0 (now at fc87e025) and on main, where the Homebrew mirror is e2b67d56 because main also carries 178fab2b, a phone-only layout fix for the cloud settings dialog that ships in core 2.58.1 for the Android and iPhone apps, not in this desktop release.

🐛 Fixes

  • The bundled zn now switches to the Go CLI on Linux package installs (#869, from @diazkev314, who found the cause). Every Linux package since 2.52.0 (the tarball the AUR package copies, the .deb and the .pacman) installed the bundled CLI's folder so that only root could open it. ZenNotes could not read it as a normal user and quietly kept zn on the old Node CLI, while Settings → CLI blamed "an invalid manifest". The folder now ships readable by everyone and under a new name, zn-cli, because Linux package managers keep an existing folder's permissions when they upgrade: the new name is created fresh and the old locked folder is removed. So upgrading to 2.58.0 (from the AUR, a .deb or a .pacman) and opening ZenNotes once switches zn to the Go CLI (0.4.1) on its own; the chmod workaround is no longer needed. If the bundled CLI ever cannot be read, Settings → CLI now says it is a permission problem and names the folder. (3dec2a68, 35de243e)

  • The .pacman package installs on current Arch again. It listed an old library, http-parser, that Arch no longer ships, so pacman -U refused to install it and only -dd got past. The package now lists what ZenNotes actually needs (gtk3, nss, alsa-lib, mesa, libnotify, libsecret, xdg-utils and desktop-file-utils) and installs with plain pacman -U. The AUR package (zennotes-bin) was never affected. (49890e3b)

  • The .deb package now starts on minimal Ubuntu and Debian installs. It did not ask for the audio library ZenNotes links (libasound2), and on Ubuntu 22.04 not for libgbm1 either, so on a system without them it installed fine and then refused to start ("error while loading shared libraries: libasound.so.2"). Desktop installs normally already have both. The package now asks for them, and works with both names the audio library goes by (libasound2t64 on Ubuntu 24.04+ and Debian 13, libasound2 on Ubuntu 22.04 and Debian 12). (134b5d20)

  • Quick Connect saves one remote workspace, not two (#870, from @diazkev314, who found the cause). Connecting to a server with Quick Connect saved the workspace you connected to plus an extra "ZenNotes Server" entry for the same address, with no vault and no token, and the vault switcher listed both. The extra entry is no longer created. One you already have can be removed under Settings → Vault → Saved Remote Workspaces and will not come back. Connecting to a saved workspace that has no token, when the server needs one, now says so and points at Edit, instead of asking you to check a token that was never sent. (14093022)

  • Settings → Vault → Location stays readable on a narrower window (#871, from @diazkev314). Connected to a remote vault, the Location row put its four buttons on one line that never wrapped: in a window around 1000 pixels wide or less they covered the "Remote workspace" label and Quick Connect… was cut off at the edge, and the vault path and server address were squeezed out of sight. The buttons now move under the label as a group, and wrap among themselves if the window is narrower still, so the label, path, address and every button stay visible. (178f0429)

  • Downloading an update no longer redraws the sidebar and Settings every second. While an update downloads, the updater reports its progress about once a second. The sidebar and the Settings dialog each redrew in full on every report to move one percentage, which is 77 components per tick with a note open and 83 with Settings open. Now only the pieces that show the progress update: the Settings badge in the sidebar, the update notice, and the Updates card in Settings > About (8 and 6 components). Everything looks and behaves as before. (9b2dd31f, a follow-up to #868, which is still open.)

🧰 For contributors

  • The updater state is subscribed in leaves only: SidebarSettingsAction in Sidebar.tsx (the footer's Settings row, badge and title) and AppUpdatesCard in SettingsModal.tsx (the About page's Updates card, with its check/download/install handlers and release notes), next to the existing AppUpdateNotice in App.tsx. useAppUpdateState has no other callers; keep it out of large components, since download-progress broadcasts about once a second for the whole download. The Settings row takes its sidebarIdx as a prop from Sidebar's pass, so its lone re-renders keep the index Vim navigation uses.

  • Measurement harness: docs/releases/v2.58.0/issue-868/measure.mjs (local) launches a build with both stores isolated, installs a minimal __REACT_DEVTOOLS_GLOBAL_HOOK__ that counts components with PerformedWork per commit, broadcasts app-updater:on-state from the main process over --inspect, and runs 12 behavior checks. Numbers in PERFORMANCE.md. The build directory must be named out: isTrustedRendererUrl only trusts .../out/renderer/index.html, so a baseline copied to out-before/ gets every IPC call blocked.

  • Terminal bundle folder (#869): packaged as resources/zn-cli (named in apps/desktop/build/after-pack.js, read in cli-install.ts; the dev path apps/desktop/build/terminal/<platform>-<arch> is unchanged). Renamed from terminal because dpkg and pacman keep an existing directory's mode on upgrade (pacman: "directory permissions differ ... filesystem: 700 package: 755"), so a mode fix alone never reached installs of 2.52.0 to 2.57.0; the release skill's check-linux-tarball.sh now rejects a tarball that still has resources/terminal/.

  • Terminal bundle permissions (#869): installStage in tooling/scripts/terminal-artifact.mjs stages with mkdtemp (always 0700) and renames the stage to resources/terminal; it now chmods the folder and zn to 0755 and LICENSE and manifest.json to 0644 (chmod is not narrowed by umask). prepare() in apps/desktop/src/main/terminal-runtime.ts maps EACCES/EPERM on the manifest read to a permission message naming the folder, and keeps "invalid" for everything else but ENOENT. Tests: "the staged folder is readable by every user, whatever the umask" in terminal-artifact.test.mjs (runs under umask 077; the old stager fails it with 0o700), and the permission case in terminal-runtime.test.ts (skipped as root, which reads through any mode). Evidence harness: docs/releases/v2.58.0/issue-869/cli-check.mjs (local) opens Settings → CLI over CDP.

  • .pacman dependencies: build.pacman.depends in apps/desktop/package.json replaces electron-builder's default pacman list (c-ares, ffmpeg, gtk3, http-parser, libevent, libvpx, libxslt, libxss, minizip, nss, re2, snappy, libnotify, libappindicator-gtk3). Derived on a bare Arch install with readelf -d + ldd + pacman -Qo: every library the binary links is owned by gtk3, nss, alsa-lib or what they pull in, plus mesa (libgbm, linked directly); libnotify and libsecret are loaded at runtime (notifications, safeStorage keyring); xdg-utils for external opens; desktop-file-utils for the x-scheme-handler/zennotes entry. build.deb.depends keeps electron-builder's default deb list and adds libasound2t64 | libasound2 (the t64 rename; on 24.04+ plain libasound2 is virtual and also provided by the OSS shim liboss4-salsa-asound2, so the real package is named first) and libgbm1 (linked directly; Ubuntu 22.04 did not pull it in). The rpm list is unchanged: a clean Fedora 44 installs the released .rpm with nothing missing.

  • Remote profiles (#870): normalizePersistedConfig in apps/desktop/src/main/vault.ts makes up a "ZenNotes Server" profile for remoteWorkspace only when the config has no remoteWorkspaceProfiles key at all (a pre-profile config). In a current config remoteWorkspace is the live connection, which setRemoteWorkspace persists a moment before saveRemoteWorkspaceProfile saves the real profile; the old unconditional synthesis ran in between and the profile save persisted it. connectRemoteWorkspaceProfile in index.ts maps a 401 on a profile with no saved token to a message naming the profile and Edit. Tests: three in vault-config.test.ts (pre-profile config still gets its profile; a current config gets none; the Quick Connect write order ends with one profile; the last two fail without the fix). The MCP's read-only copy in apps/desktop/src/mcp/vault-ops.ts is unchanged (stable id, listing only). Harness: docs/releases/v2.58.0/issue-870/quick-connect.mjs (local).

  • Location row (#871): the row in SettingsModal.tsx is flex flex-wrap with the label block min-w-0 flex-1 basis-56 and the buttons in their own flex flex-wrap items-center gap-2 group (the button-group pattern used elsewhere in Settings). Measured at 700 to 1280 px over CDP (docs/releases/v2.58.0/issue-871/location-row.mjs, local): before, the label box was 0 px wide at 1000 px and below with Change Remote Vault… on top of it and Quick Connect… clipped; after, no overlap or clipping and the label on one line at every width, local and remote.

Demos

In docs/releases/v2.58.0/media/ (local), 1080p with captions burned in and a .vtt beside each. Each clip runs the same steps on the installed 2.57.0 app, then on the packaged 2.58.0, both connected to a scratch ZenNotes server:

  • 870-quick-connect-one-workspace.mp4 (40 s): Settings → Vault → Quick Connect… with the server address and token, then Saved Remote Workspaces. 2.57.0 ends with two entries, one a "ZenNotes Server" with no vault and no token; 2.58.0 with one.
  • 871-location-row-wraps.mp4 (17 s): Settings → Vault while connected, laid out 1000 px wide. On 2.57.0 the buttons sit on the "Remote workspace" label and Quick Connect… is cut off; on 2.58.0 the label, path and server stay readable and the buttons wrap under them.

Recorder and caption kit: docs/releases/v2.58.0/tools/ (demo-remote.mjs 870|871 before|after, then burn-clip.py), the 2.57.0 kit with a custom viewport size and --use-mock-keychain.

No clips for the Linux package fixes (#869, .pacman, .deb): they have no screen of their own on a Mac. Their evidence is in issue-869/ (screenshots of Settings → CLI on Ubuntu and Arch before and after an upgrade) and in the Verification section below.

Verification

  • Unit and suites: npm run typecheck (7 tasks) and npm run test:run (5 tasks) fresh, nothing from cache: shared-domain 1700 tests, app-core 2853 (1 skipped), desktop 980 (4 skipped). New tests: the bundled CLI folder is staged world-readable under a 077 umask and the old stager fails it with 0700; an unreadable bundle reports a permission problem; a pre-profile config still gets its migrated server profile while a current config and the Quick Connect write order end with one profile (both fail without the fix).
  • #869 on real Linux (Adib's Beelink, Ubuntu 26.04, root installs in Docker, the app run as a normal user under Xvfb over CDP): the released 2.57.0 .deb and the AUR makepkg package show "The bundled terminal manifest is invalid." and the legacy CLI; upgrading to the fixed build with dpkg -i and with pacman -U (2.57.0-1 to 2.57.0-2) removes the locked resources/terminal, installs resources/zn-cli as drwxr-xr-x, and the same user gets zn v0.4.1. The first fix (mode only) was proven NOT to reach upgrades before the rename: dpkg and pacman keep an existing folder's mode. The packaged macOS app finds the renamed folder too.
  • .pacman on a fresh archlinux:latest, nothing skipped: 2.57.0 fails on http-parser; 2.58.0's installs, no library missing, zennotes:// registered, and it launches through /usr/bin/zennotes as a normal user.
  • .deb with apt on clean Ubuntu 22.04, 24.04, 26.04 and Debian 12 and 13: 2.57.0 leaves libasound.so.2 unresolved on all five (exit 127 at launch on 26.04); 2.58.0 resolves every library from the real ALSA package. The .rpm needed nothing on Fedora 44.
  • #870 and #871 in the built app against a scratch server: one Quick Connect saved two profiles before and one after; Connect on a tokenless profile shows the new message; the Location row measured at 1280 to 700 px went from a 0 px label with a button on top and up to three buttons clipped to clean at every width.
  • #868 follow-up: per progress tick, 77 components rendered before and 8 after with a note open, 83 and 6 with Settings open (PERFORMANCE.md).

How to test locally:

  1. cd apps/desktop && npx electron-vite build && cd ../..
  2. OUT=$PWD/apps/desktop/out LABEL=after node docs/releases/v2.58.0/issue-868/measure.mjs prints the components rendered per tick (8 with a note open, 6 with Settings > About) and 12 PASS lines.
  3. By eye, on a real older install with a newer release published: turn on React DevTools "Highlight updates" and click Download in the update notice. Before (2.57.0) the whole sidebar flashes once a second; after, only the Settings row and the notice. Settings > About still shows the progress bar, percentage, bytes and speed, then Install and Relaunch.

For #869 (bundled CLI folder permissions):

  1. cd apps/desktop && npx electron-builder --linux tar.gz --x64 --publish never -c.directories.output=/tmp/zn-869 && cd ../..
  2. ~/.agents/skills/zennotes-release/scripts/check-linux-tarball.sh /tmp/zn-869/ZenNotes-*-linux-x64.tar.gz prints "terminal folder OK" (resources/zn-cli/ is drwxr-xr-x; the 2.57.0 tarball fails with "OLD FOLDER NAME"). The same folder is drwxr-xr-x inside a .deb (ar x then tar -tvf data.tar.xz) and a .pacman (tar -tvf).
    2b. Upgrade on Linux (the real test): install 2.57.0 as root, run it as a normal user (Settings → CLI: "The bundled terminal manifest is invalid."), upgrade to 2.58.0 with the same package manager, run again: resources/terminal is gone, resources/zn-cli is drwxr-xr-x, Settings → CLI has no warning, and zn --version prints zn v0.4.1.
  3. Message check on macOS: npm run terminal:stage, chmod 000 apps/desktop/build/terminal/darwin-arm64, run node docs/releases/v2.58.0/issue-869/cli-check.mjs: Settings → CLI shows "cannot be read (permission denied)" with the folder path (before: "The bundled terminal manifest is invalid."). chmod 755 it back and rerun: no warning, and the page describes the Go CLI ("Run zn tui…").
  4. On a Linux package install of 2.58.0: Settings → CLI shows version 0.4.1 and zn --version prints the Go CLI's version, not zn v2.58.0.

For the .pacman dependencies:

  1. cd apps/desktop && npx electron-builder --linux pacman --x64 --publish never --prepackaged <linux-unpacked> -c.directories.output=/tmp/zn-pac, then tar -xOf /tmp/zn-pac/*.pacman .PKGINFO | grep depend lists the eight packages (before: fourteen, including http-parser).
  2. In a fresh archlinux:latest container: pacman -Syu, then pacman -U <the .pacman> without -dd. Before: cannot resolve "http-parser", nothing installed. After: installs; ldd /opt/ZenNotes/ZenNotes | grep "not found" is empty; zennotes launches as a normal user.

For the .deb dependencies:

  1. Build the .deb (electron-builder --linux deb --x64 --prepackaged <linux-unpacked>), then ar x it and tar -xOf control.tar.xz ./control | grep Depends ends with libasound2t64 | libasound2, libgbm1.
  2. In clean ubuntu:22.04, ubuntu:24.04, ubuntu:26.04, debian:12 and debian:13 containers: apt-get install -y ./ZenNotes-*.deb, then ldd /opt/ZenNotes/ZenNotes | grep "not found" is empty. Before: libasound.so.2 missing on all five (and libgbm.so.1 on 22.04), and launching as a user fails with exit 127.

For #870 (Quick Connect):

  1. Run a scratch server that cannot touch your home: in ~/Developer/opensource/znserver, HOME=<scratch>/home ZENNOTES_BIND=127.0.0.1:7979 ZENNOTES_CONFIG_PATH=<scratch>/server.json ZENNOTES_DEFAULT_VAULT_PATH=<scratch>/workspace ZENNOTES_AUTH_TOKEN=devtoken go run ./cmd/zennotes-server (without a vault setting it creates ~/ZenNotesVault).
  2. SERVER=http://127.0.0.1:7979 TOKEN=devtoken node docs/releases/v2.58.0/issue-870/quick-connect.mjs launches the built app with isolated stores and --use-mock-keychain, runs Settings → Vault → Quick Connect… and prints the saved profiles. Before: two (workspace (127.0.0.1:7979) and ZenNotes Server). After: one. With TRY_TOKENLESS=1 it also connects a profile that has no token: before, "rejected the connection. Check the auth token"; after, "No auth token is saved for ... Choose Edit ...".

For #871 (Location row):

  1. Connect to any server (Settings → Vault → Quick Connect…), stay on Settings → Vault → Location, and make the window narrow (about 1000 px wide or less).
  2. Before: the buttons cover "Remote workspace" and Quick Connect… is cut off. After: label, path and "Connected to" line on top, buttons underneath, all visible; keep narrowing and the buttons wrap onto more rows.
  3. Scripted: with a scratch server on 127.0.0.1:7979 (see #870 above), node docs/releases/v2.58.0/issue-871/location-row.mjs measures both rows at six widths and prints "clean at every width".

Distribution channels

  • AUR: zennotes-bin 2.58.0-1 published from the separate clone at 0c73c08; identical files mirrored to main and v2.58.0 in 296bad7c. Before any edit, aur-bump.sh verified the UPLOADED x64 tarball against GitHub's digest, e41bccd6ef586caa65e49f3be1febb66cec4d91232aa7713d5a2182e2823e24c, and its new check-linux-tarball.sh gate confirmed the #869 fix on the CI-built asset: resources/zn-cli/ is drwxr-xr-x with a world-readable zn, manifest.json and LICENSE, and the old resources/terminal/ is gone. AUR package checks 36604794447 (v2.58.0) and 36604799625 (main) passed.
  • Nix: hashes generated and the desktop package built by 36604814467, then copied byte for byte from bot PR #875 into 4efce0ea on main and the release branch; the bot PR was closed and its branch deleted. Main-branch build 36605204498 passed. desktopHash (sha256-5BvM1u9YbKpl5J874f67Zs7E2RIyqncT1aIYLigj4kw=) is the same digest AUR pins.
  • nixpkgs: the previous bump (PR #561418, 2.57.0) was merged upstream, so 2.58.0 went out as a new PR #568334 from adibhanna:zennotes-desktop-2.58.0 at fork commit 6f8d81510, based on nixpkgs master 308a67671. Its diff changes only version, npm dependency hash and source hash, using the verified values above. The nixpkgs derivation was not built locally; upstream review and CI remain separate from the verified in-repository package.
  • Homebrew: tap commit a12b41a, mirrored in fc87e025 on the release branch and e2b67d56 on main. DMG SHA-256: arm64 f53663c2f80400b5c25ae6a2d245b270c930359602be22f9b96e7d2e97fe8512, x64 b112bb4201ca8b6c50ae1523dd778ad32e1c65228338cb6d803a8a3a2cbd8e70.
  • Website: PR #47 merged at ed9b5006 at 18:12 UTC, after the complete desktop release was verified and latest. Main tests and deployment gate 36610351290 and linter 36610351311 passed. zennotes.org/releases leads with 2.58.0 and serves both clips as video/mp4 at the same byte sizes as the local masters; the #871 clip downloaded from the live site probes as 1920 × 1080 H.264, 17.4 seconds.
  • Final channel check: npm run verify:channels -- 2.58.0 passed: AUR, Homebrew, Nix, and all seven website download routes serve 2.58.0. GitHub releases/latest points to v2.58.0.
  • Boundary artifacts: core-2.58.0-core.heef7cf536beafc5d was built from the tag by 36603230389, and core-2.58.1-core.h87d94b8810f1f1d1 from 178fab2b by 36608508089 for the Android 1.1.28 and iPhone 1.15.0 releases. After both apps were submitted to their stores, both were published as pre-releases on September 29 at 19:14 UTC, together with the older core-2.57.0-core.h9cc6b81dd406dfe7, and not marked latest (releases/latest stayed on v2.58.0). Before publishing, every archive matched its recorded SHA-256 and SHA-512, packed version and clean source commit, and the phone apps' pins (iPhone 3c5015f and Android b23cf86 for 2.58.1, a9c3a11 and 0d5e877 for 2.57.0) name exactly those bytes; the published files download without signing in and match.

Release validation

  • Tagged source: 230844ffcba5503f8ef8c4d925c223598a046199, the version bump on seven reviewed cycle commits, through PR #873.
  • Fresh gates passed: turbo run typecheck --force (7 tasks) and turbo run test:run --force (5 tasks), both without cached results (shared-domain 1700 tests, app-core 2853 plus 1 skipped, desktop 980 plus 4 skipped); build:prod from apps/desktop; and the pack.
  • The local pack died in codesign on the first locale.pak with "A timestamp was expected but was not found", the same Mac-side timestamp problem as 2.56.0: a copy of /bin/ls failed to sign with the default timestamp server and signed with its IPv4 address, while curl reached timestamp.apple.com fine. Since build:prod had passed, the pack was finished with npx electron-builder --dir -c.mac.timestamp=http://17.32.213.161/ts01: Developer ID Application: Lumary Labs LLC (WYY7PK57DM), a real Apple timestamp, and codesign --verify --deep --strict passed. CI signs on GitHub runners and was not affected; a reboot of the Mac is the likely cure.
  • Packaged launch: the isolated packaged-launch-check.mjs found a real CDP page target in 1.5 seconds and read version 2.58.0; Contents/Resources/zn-cli is drwxr-xr-x. 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, and test:editor-improvements 19 checks.
  • The Linux fixes were proven on real Linux before the cut (Adib's Ubuntu 26.04 x86_64 mini PC, with root installs in throwaway Docker containers): fresh installs and upgrades from 2.57.0 for the .deb, the .pacman and the AUR makepkg flow (#869), the new .pacman on a bare up-to-date Arch, and the new .deb on clean Ubuntu 22.04, 24.04 and 26.04 and Debian 12 and 13. After release, the uploaded .deb and .pacman were checked to carry the new dependency lists.
  • ffprobe verified both 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, 8365 assertions).
  • PR checks all passed on the tagged head: CI 36600254157 (macOS, Windows, Linux x64, Linux arm64 and production dependency audit), CodeQL 36600254252, web boundary 36600254148, and viewer candidate 36600254153. CI needed one retry: the Windows build failed the first time with an unhandled rejection after all 248 app-core test files had passed (a store timer ran after store.test.ts tore down its test window: window is not defined), and a --failed rerun passed. No code was changed for it.
  • Release run 36603132513: Linux x64 finished at 17:23:32 UTC, Linux arm64 at 17:23:46 and macOS at 17:59:09. Windows failed in "Build release artifacts" at 17:13:55 on a test flake (a timeout in undo-history-store.test.ts, then an ENOTEMPTY cleaning its temp folder) before uploading anything. Following the upload-failure playbook, Windows alone was rebuilt by dispatch in 36603855858, which passed at 17:53:17; no Linux file was rebuilt, so the tarball AUR and Nix pin never changed. releases/latest pointed at v2.58.0 the whole time, so a Windows update check between 17:11 and 17:53 UTC found no latest.yml for it. Both Windows flakes need their own fix. The release has 27 assets: 25 installers, update manifests and support files, plus two demo clips.
  • verify-installers.py 2.58.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.
  • Distributed macOS arm64 app: the downloaded DMG 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; its zn-cli folder is drwxr-xr-x. The volume was ejected after verification.
  • No code fix, tag move, asset replacement or boundary-artifact publication was needed during this release.

Local-first and keyboard-first, as always.