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 withupload_idandasset_refsand 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:
CloudPublishAssetInputnames an attachment by its vault-relativepath(no base64 crosses the bridge);CloudPublishEncodedAssetis the multipart form; new upload request, target and response types. - Desktop main:
cloud-publish-assets.tsresolves 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 throughuploadPublishAsset, 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:
collectCloudPublishAssetsreturns paths and checks the 50 limit;prepareCloudPublishLogois 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), behindCLOUD_PUBLISH_UPLOADS;6d5c638made 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.
- 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.
- Republish. Remove a few images and publish again: the same link now shows the new set.
- 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-bin2.66.0-1 (AUR clone596869e, repoe88440ce), pinned toZenNotes-2.66.0-linux-x64.tar.gzSHA-2565af85d6a…606f(GitHub's own digest for the asset), after the bump script checked the bundled CLI folder is world-readable. Theaur-checkruns 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 samemakepkgbuild 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.jsonlifted from the update workflow (run 37994850296, bot PR #918 closed) as0949e6c7; itsdesktopHashis 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:
zennotes2.66.0 in the tap (1a88d6c), mirrored infe239e8c; both DMGs were downloaded and their SHA-256 matches the cask and GitHub's digest;brew styleclean. - 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 onlyshare-viewer.jsdiffers 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.hcd0a82b505c8cc0fbrowser artifact (nothing changes for self-hosters: the browser app does not publish to Cloud); Dockeradibhanna/zennotes:2.66.0,2.66andlatest(linux/amd64 and arm64, one digestaec76d49…); 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 typecheck8/8 andnpm run test:run6/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=highis 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 firstlocale.pak), the timestamp probe passed a minute later, and the pack finished against Apple's timestamp server by its IPv4 address.codesign --verify --deep --strictpassed, the log shows electron-builder's "skipped macOS notarization" (CI notarizes), and the app bundlesznv0.6.3 in a world-readableResources/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-vimandtest:editor-improvementspassed 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;mainfast-forwarded to it and the annotated tagv2.66.0started 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 carryZenNotesQuickLook.appexand a world-readableResources/zn-cli(the arm64znreports v0.6.3). GitHub latest is v2.66.0, andverify:channels -- 2.66.0passed for Homebrew, AUR, Nix and all seven website download routes.