github ZenNotes/zennotes v2.66.0
ZenNotes v2.66.0

latest releases: viewer-2.66.0-viewer.hd387d57577a93ead, web-2.66.0-web.hcd0a82b505c8cc0f, core-2.66.0-core.hf27b6fbd42368c71...
3 hours ago

ZenNotes 2.66.0 lets a published note carry up to 50 attachments and 100 MB, sent straight to storage instead of in one request.

Released from 461fe76d through #917 on 2026-10-09. All installers are verified.

Features

  • Publish notes with up to 50 attachments. A note published to the web could hold at most 20 attachments and 25 MB, because ZenNotes sent the note and every attachment in one request, and the Cloud service accepts at most 20 files per request. Publishing now sends each attachment straight to storage first, then publishes the note: up to 50 attachments and 100 MB per note, each file still up to 10 MB. Every limit is checked before anything uploads, so a note over a limit says so at once and names what to change, for example "This note has 51 attached files, but a published note can have at most 50. Remove some attachments and publish again." Attachments stream from disk instead of being loaded into memory, and a slow connection no longer has to carry one large request. Publishing needs the Publish add-on of ZenNotes Cloud. The iPhone and Android apps get the same in their next updates.

Fixes

  • The attachment limit is the same everywhere. The app checked for 50 attachments while the service took 20, so a note with 21 to 50 attachments uploaded everything before it was refused. The app and the service now both allow 50.

For contributors

  • Shared publish routine (packages/shared-domain/src/cloud-publish-uploads.ts): publishWithStagedUploads(api, input, platform, options) describes each attachment (size and SHA-256), starts an upload (POST /api/v1/shares/uploads), PUTs each file to its presigned URL four at a time, then publishes with upload_id and asset_refs and no files. A 404 from the upload endpoint falls back to the one-request (multipart) publish; any failure after the upload starts cancels it (DELETE /api/v1/shares/uploads/{id}). Desktop and both phone shells call it; only the file access differs (CloudPublishAssetPlatform).
  • Bridge contract: CloudPublishAssetInput names an attachment by its vault-relative path (no base64 crosses the bridge); CloudPublishEncodedAsset is the multipart form; new upload request, target and response types.
  • Desktop main: cloud-publish-assets.ts resolves each path through the vault's traversal guard (or a remote vault's server), hashes it while streaming it off the disk, and streams it again through uploadPublishAsset, which keeps the sync upload's safeguards: HTTPS (loopback excepted), no redirects, no bearer token, and an explicit Content-Length (object storage refuses a chunked PUT, 411).
  • app-core: collectCloudPublishAssets returns paths and checks the 50 limit; prepareCloudPublishLogo is gone (nothing called it; the logo is set for the whole publication in ZenNotes Cloud).
  • Service: ZenNotes/website e94851c (upload sessions, presigned PUTs into the share's directory, verification of each file's length, hash and real type at publish, single use, 30-minute expiry, cancel, a cap of ten open uploads per account, a cleanup sweep), behind CLOUD_PUBLISH_UPLOADS; 6d5c638 made 20 the official limit of the one-request publish, which older apps keep using. Design: docs/ideas/publish-staged-uploads.md.

Verification

How to test locally: build and launch with isolated stores, npm run build --workspace @zennotes/desktop, then ZENNOTES_USER_DATA_PATH=$(mktemp -d) ZENNOTES_CONFIG_DIR=$(mktemp -d) npx electron apps/desktop/out/main/index.js, open a vault, and connect ZenNotes Cloud (Settings → Cloud) with an account that has Publish.

  1. A note with 30 images. Paste or embed 30 images in a note, then run Publish Note from the command palette. Before: "This note has 30 attached files, but a published note can have at most 20." After: the note publishes, the link is copied, and the public page shows all 30 images.
  2. Republish. Remove a few images and publish again: the same link now shows the new set.
  3. Over the limit. A note with 51 attachments: before anything uploads, "This note has 51 attached files, but a published note can have at most 50. Remove some attachments and publish again."

Distribution channels

  • GitHub release: installers for macOS (Apple silicon and Intel DMG and zip), Windows (x64 installer and zip) and Linux (x64 and arm64 AppImage, deb, rpm, pacman, tar.gz), with the four update manifests.
  • AUR: zennotes-bin 2.66.0-1 (AUR clone 596869e, repo e88440ce), pinned to ZenNotes-2.66.0-linux-x64.tar.gz SHA-256 5af85d6a…606f (GitHub's own digest for the asset), after the bump script checked the bundled CLI folder is world-readable. The aur-check runs first failed before reaching the package: GitHub's runners could not pull the Arch Linux image while Docker Hub had an incident ("Hub Registry Authenticated Actions Failing"). The same makepkg build passed in an Arch Linux container on our Linux test machine, and once Docker Hub recovered the check passed on GitHub (run 37994840827, third attempt).
  • Nix: packaging/nix/release-data.json lifted from the update workflow (run 37994850296, bot PR #918 closed) as 0949e6c7; its desktopHash is the same digest as the AUR pin, and nix-build on main passed.
  • nixpkgs: NixOS/nixpkgs#572180, which proposed 2.64.0 -> 2.65.0 and had not merged, now proposes 2.64.0 -> 2.66.0; the source hash comes from the tag tarball.
  • Homebrew: zennotes 2.66.0 in the tap (1a88d6c), mirrored in fe239e8c; both DMGs were downloaded and their SHA-256 matches the cask and GitHub's digest; brew style clean.
  • Website: ZenNotes/website#66 merged at cfc85344: the releases page entry and the publishing docs (up to 50 attachments and 100 MB per note, each file up to 10 MB, and which app versions still publish up to 20). Its main tests and deploy passed (run 37998170662), Laravel Cloud's deployment succeeded at 22:21 UTC, and zennotes.org serves the 2.66.0 entry and the new docs line. The share viewer stays on 2.65.0: the 2.66.0 viewer artifact is published, but only share-viewer.js differs and nothing a reader sees changed.
  • zn CLI: unchanged; this release bundles 0.6.3.
  • Server: ZenNotes/znserver v2.66.0 pins the published web-2.66.0-web.hcd0a82b505c8cc0f browser artifact (nothing changes for self-hosters: the browser app does not publish to Cloud); Docker adibhanna/zennotes:2.66.0, 2.66 and latest (linux/amd64 and arm64, one digest aec76d49…); README znserver#21 fast-forwarded. Its CI first failed on the same Docker Hub incident and passed on a rerun.
  • Phones: iPhone 1.22.0 (38) and Android 1.1.36 (39) on core-2.66.0-core.hf27b6fbd42368c71 (published), with the same publishing: up to 50 attachments and 100 MB per note, each attachment uploaded straight to storage. iPhone 1.21.0 was approved before this was ready, so the iPhone build ships as 1.22.0; it is waiting for App Review. Android 1.1.36 is in Play review (submitted at 22:43 UTC), and its GitHub release carries Play's signed universal APK.

Release validation

Cycle checks:

  • Service, before any app shipped: the website suite (1,263 tests, 21 new for staged uploads: limits and their messages, a missing, altered or disguised file, reuse, expiry, another account's upload or share, republish, cancel, the open-upload cap, the sweep, and the presigned PUT and parallel read-back on a fake S3 disk), then a live run in production as the QA account with the switch on: a 30-image note (5.6 MB) staged in 0.4 s, its PUTs to Cloudflare R2 all 200 in 1.7 s, published in 1.5 s with every file verified, the public page and its first and last images byte for byte; a republish with 12 images replaced the set and deleted the old files; a cancelled upload deleted its files; unpublishing left nothing behind.
  • Apps: the shared routine's tests (staging, republish, the 404 fallback, a refusal that must not fall back, cancelling on a failed file or publish, the logo and empty cases, bounded concurrency), the desktop client's PUT, and over real loopback HTTP the desktop's whole staged publish and republish (each PUT streamed with its exact Content-Length and no token) plus the fallback; the phone shells' platform tests (native file-backed PUT, a remote file hashed in JavaScript, an insecure URL refused).

Release-time gates on 461fe76d:

  • npm run typecheck 8/8 and npm run test:run 6/6 with no turbo cache: shared-domain 1,929, app-core 3,140, quicklook 15 and desktop 1,059 tests passed. npm audit --omit=dev --audit-level=high is clean (15 low or moderate advisories remain, in dompurify, fast-uri, ip-address, katex, smol-toml and sprintf-js).
  • npm run pack: build:prod passed; codesign then lost its timestamp on this Mac ("A timestamp was expected but was not found" on the first locale.pak), the timestamp probe passed a minute later, and the pack finished against Apple's timestamp server by its IPv4 address. codesign --verify --deep --strict passed, the log shows electron-builder's "skipped macOS notarization" (CI notarizes), and the app bundles zn v0.6.3 in a world-readable Resources/zn-cli. Packaged launch check: a page target in 1.6 s reporting 2.66.0.
  • Smoke suites on the built app: test:vim-editor, test:sidebar-vim and test:editor-improvements passed on their first run, with their windows now on the built-in display.
  • The packaged 2.66.0 app published a 30-image note (1.7 MB) from the Publish Note command against production, signed in to Adib's account: "Note published. Link copied." after 5.4 s; the service recorded it through a staged upload (30 files, sizes matching); the public page listed 30 images and all 30 came back byte for byte. Unpublished after (the page then answered 404). The same run showed that an account without Publish is refused before anything uploads ("Publishing requires a ZenNotes Cloud plan.").
  • PR #917: all nine checks passed on 461fe76d; main fast-forwarded to it and the annotated tag v2.66.0 started release run 37993402714.
  • The release run published every platform in one pass: Linux arm64 and x64 in 13 minutes, Windows in 37 and macOS in 43. Each Mac app was notarized once: the log shows electron-builder's "skipped macOS notarization" and then one [notarize] pass per architecture (arm64 2 minutes 28 seconds, x64 2 minutes 40 seconds).
  • All four update manifests and the 13 files they name passed size and downloaded SHA-512 verification. The apps in both DMGs passed notarization (Notarized Developer ID), staple and strict deep signature checks, report 2.66.0 and carry ZenNotesQuickLook.appex and a world-readable Resources/zn-cli (the arm64 zn reports v0.6.3). GitHub latest is v2.66.0, and verify:channels -- 2.66.0 passed for Homebrew, AUR, Nix and all seven website download routes.

Don't miss a new zennotes release

NewReleases is sending notifications on new releases.