ZenNotes 2.78.0 gives notes a face and diagrams their math. A note can now carry a cover, a wide image across its top the way Notion draws one, picked from the keyboard and stored in the same frontmatter Obsidian's banner plugins read. Mermaid diagrams typeset $$...$$ math in their labels and keep labels readable on any fill, in every theme. The phones get a Calendar widget for the Home Screen and PDFs that open in the phone's own viewer.
Released from 52ab86fd through #1000 on 2026-10-27. All installers are verified.
Features
- Note covers. A note can carry a cover: a wide image across its top that scrolls away with the text.
Space l cin Vim mode, Add Cover in the command palette, or:coveropens the cover picker: one search field over eight gradients, six public-domain photographs (Hokusai, Van Gogh, Bierstadt and Cole from The Met's open collection, Earthrise and the Blue Marble from NASA), the images already in your vault, and any https image link you paste. Upload image takes a file from disk, and an image pasted or dropped on the picker becomes the cover. Hover the cover for Change cover, Reposition and Remove; Reposition follows a drag or the arrow keys (j/kin Vim mode), Enter keeps the new position and Escape puts the old one back. Every edit is a normal edit, souand Cmd+Z take it back. - Covers are plain frontmatter. A cover is saved as
banner: "![[assets/lisbon.jpg]]"with an optionalbanner_yfocal point, the keys Obsidian's banner plugins read, so a vault shared with Obsidian shows the same banner; a note that usescover:shows its image too. Everything a pick brings lands in your vault'sassetsfolder as an ordinary file, so covers work in a local vault, on a self-hosted server, in the web app and over Cloud sync, end-to-end encrypted vaults included, and the Assets view counts a cover image as used. Settings, Typography, Note covers chooses Scroll with note (the default), Pinned above the editor and Read view, or Hidden (note_cover_placementunder[appearance]in config.toml). - Covers on published notes and in Quick Look. A note you publish shows its cover on its public page, and the macOS Quick Look preview (Space in Finder) draws it too, at the note's focal point.
- Math in Mermaid labels. A label like
A["$$\frac{a}{b}$$"]is now typeset with KaTeX, as on mermaid.live, GitHub and Obsidian, instead of showing its TeX. It works in Preview, the editor's live preview, the expanded diagram view, Quick Look, published notes and the phones, in flowcharts, sequence, state, class and ER diagrams and mindmaps. Each formula is measured before the label is laid out, so it sits inside its box. - Mermaid labels readable on any fill. Diagrams now draw with Pele 1.12.0. A label on a fill you chose (
classDef,style, a sequencerectorbox) picks near-black or white text, whichever reads on that fill, where a dark node used to be unreadable in a light theme and a light one in a dark theme. Labels format<u>,<sub>,<sup>,<code>and<kbd>; text that only looks like a tag stays (List<T>used to come out asList); Chinese and Japanese labels wrap instead of stretching the diagram; and C4, sequence self-messages, timelines, sankey, treemap and block diagrams lay out better. - A Calendar widget for the phones (asked for by Dave). The Calendar sheet on your Home Screen: the month with its writing dots, open-task marks, week numbers and today ring, and the selected day's tasks below with a box to tick on each. On Android it resizes up to the whole screen and the task list scrolls; on iPhone the large size lists the day and iOS 27's extra-large size lists the week, paging with arrows. A tick shows at once; on Android it is written into the note even with the app closed, and on iPhone it lands the next time the app opens. "Add a task for ..." opens the Calendar on that day with the keyboard up. Ships in the next phone releases, built on this core.
- The bundled zn is 0.12.2. With
[terminal] math = "latex",zndraws display math and TikZ in the terminal preview through the TeX installed on your computer, says plainly when TeX is missing (zn doctorprints the install commands), and fits preview pictures to the pane as it resizes.
Fixes
- PDFs on the phones (thanks to Dave). On Android, "Open as reference" and Preview on an embedded PDF opened an empty sheet: Android's WebView has no PDF viewer. A PDF now opens in the phone's own viewer, the PDF card no longer offers a Preview that cannot draw, and the card keeps its file name instead of squeezing it to nothing and spilling its badge under the Preview button at phone width. Done and Back close the reference sheet again and leave you on your note. iPhone keeps drawing PDFs in the app, as before.
- Hide Reference Pane for a PDF pinned to one note. With a PDF pinned to the note it sits in, the command palette offered neither Hide Reference Pane nor Focus Reference Pane. Both now work for it, on the desktop as on the phones.
- Ticking tasks in notes saved on Windows. In a note with Windows line endings, ticking, moving or editing a task from the task list did nothing, and in a note mixing line endings it acted on the wrong task. It now acts on the right task and keeps the note's line endings.
- The Themes picker keeps your theme while you type. Typing a filter in the command palette's Themes picker could switch the app to a theme you never highlighted (from Gruvbox Hard dark, typing
ljumped to Nord Light) and keep it. A theme now previews only when you move to it, and Escape still puts yours back. The picker also finds a family named with its mode or flavor, such as "apple light" or "gruvbox dark hard", which used to list nothing. - The phones' drawer lists periodic folders once (thanks to uNyanda). The Daily, Weekly and Monthly Notes folders showed twice in the phone drawer, as shortcut rows and again under Notes; the Notes list now leaves out the folders those rows already open, as the desktop sidebar always has. Ships in the next phone releases.
For contributors
- Covers (
ea74766b,fec49657):shared-domain/note-cover.tsreads and writesbanner/banner_y(andcover:) throughupdateFrontmatterFields, so every other frontmatter line stays byte-identical; the value is an embed (![[...]]) so every host's asset index (desktopextractAssetEmbeds, the Go server's port, the phones' vault-core) counts it with no scanner changed. Picks import throughimportPastedImage. The editor draws the cover as a CodeMirror block widget (cm-note-cover.ts) that the pane portals the sameNoteCoverinto as the Read view;ReadOnlyNoteCoverserves the share viewer and Quick Look without the picker. Gradient specs live inshared-domain/note-cover-gradients.ts, so the share viewer redraws a built-in gradient instead of uploading its SVG; Quick Look resolves other cover forms over a newzenql://coverroute.COMMAND_OVERLAY_FIELDSnow names the picker and the reposition mode. New portable prefnoteCoverPlacement. - Diagrams (
7ae3afcf,fa668530): Pele 1.6.0 to 1.12.0 with no API change;lib/mermaid-label-math.tsis themathresolver passed throughrenderOptionsinmermaid-render: KaTeX to MathML, measured in an off-screen probe and cached by size and source, reached only throughpele-runtime, so nothing new loads before a diagram does. - Phones (
16722caf,4bacd562,b1fd339d,3fc40107):canShowPdfInlinedecides between the PDF frame andPdfOpenElsewhere;openVaultAssetExternallytakes{ confirm: false };activeReferencePathis the one reading of the pin the pane shows.lib/calendar-data.tsholds the Calendar panel's grid and marks;getCalendarWidgetDataandsetTaskChecked(@zennotes/app-core/calendar,/tasks) feed the widget and take its ticks back, each task carrying its line before and after a toggle, so no shell parses a task line. - Tasks (
57513b7e): the index walks inshared-domain/tasklists.tsmatch a line without its\rand put the ending back on what they write. The desktop MCP and the Go copies already count CRLF notes correctly (the MCP normalizes first; Go's.matches\r). - Themes picker (
52ab86fd): the preview effect remembers the mode and list it last saw and skips the commit in which either changed, so it never reads anactiveindex meant for the previous list;rankThemes(lib/themes.ts) adds the row read as one phrase. Read-only covers (e6bf83f2):readOnlyCoverSourcekeeps http(s),zenql://anddata:image/sources at the element, after CodeQL flagged the share page's coverimg. - zn 0.12.2 (
4bda75e8):apps/desktop/terminal-release.jsonis the release's signed manifest, copied byte for byte after verifying againstzn-release-1.
Demos
Clips and screenshots of this release on the release build, attached to this release:
Note covers: Space l c picks the Dusk gradient, then NASA's Earthrise; j and k reposition it, u and Ctrl+R take the change back and forth. Watch the clip (40 seconds, captioned).
Math in Mermaid labels: $$ labels typeset inside their boxes, and text on a dark and a yellow fill stays readable from Gruvbox dark to light. Watch the clip (22 seconds, captioned).
The Calendar widget on an Android Home Screen: a tick written into the note with the app closed. Watch the Android clip (24 seconds) or the iPhone clip, with iOS 27's extra-large week size (22 seconds). Both are in the phone releases built on this core.
PDFs on Android open in the phone's own viewer, and Back returns to the note: watch the clip (20 seconds). The drawer lists Daily, Weekly and Monthly Notes once: watch the clip (25 seconds).
Verification
How to test locally. Build once with npm run build --workspace @zennotes/desktop and launch with isolated stores: ZENNOTES_USER_DATA_PATH=$(mktemp -d) ZENNOTES_CONFIG_DIR=$(mktemp -d) npx electron apps/desktop/out/main/index.js. Put these notes in the vault:
Trip.md # Lisbon trip
Three days in October.
Diagram.md ```mermaid
flowchart LR
A["$$\frac{a}{b}$$"] --> B["$$x^2$$"]
B --> C["List<T> with <sub>sub</sub>"]
classDef dark fill:#1f2937
class C dark
```
Windows tasks.md - [ ] first
(saved with CRLF) - [ ] second
- Covers. Open Trip and press
Space l c(Vim mode) or pick Add Cover from the command palette. Typeduskand press Enter: a gradient spans the top of the note and the frontmatter readsbanner: "![[assets/cover-gradient-dusk.svg]]". Pressu: the cover and the frontmatter are gone. Run:cover earthrise, hover the cover, pick Reposition, presska few times and Enter:banner_ychanges. Run:cover!to remove it. In Settings, Typography, Note covers, pick Hidden: the cover stops drawing and the frontmatter stays. - Label math. Open Diagram in Preview: both labels are typeset fractions and powers, inside their boxes. Before: they showed
$$\frac{a}{b}$$as written. - Labels on fills. In the same diagram, node C sits on a dark fill: in a light theme its text is white and reads;
List<T>shows with its<T>, andsubis subscripted. Before: dark text on the dark node, andListalone. - Windows line endings. Open the Tasks view and tick "second": the note now reads
- [x] secondand still has CRLF endings (file "Windows tasks.md"says so). Before: the tick did nothing. - Reference pane. Put
![[assets/some.pdf]]in a note, use Open as reference on its card, then open the palette: Hide Reference Pane is offered and hides it. Before: neither Hide nor Focus was offered. - zn. In a terminal, the
znthat ships with this build reportszn v0.12.2forzn --version.
On the phones (in the next phone releases): on Android, tap an embedded PDF's card and it opens in the phone's PDF viewer; Back returns to the note. Add the ZenNotes Calendar widget to the Home Screen, tick a task, and open the note: it is ticked.
Distribution channels
- GitHub release: installers for macOS (Apple silicon and Intel DMG and zip), Windows (x64 and ARM64 installer) and Linux (x64 and arm64 AppImage, deb, rpm, pacman, tar.gz), with five update manifests; the clips and screenshots in Demos, desktop and phone, are attached too.
- AUR:
zennotes-bin2.78.0-1 (AUR clone5bd7333, repo8b178691), pinned toZenNotes-2.78.0-linux-x64.tar.gzSHA-256efad197f…0afc, after the tarball check confirmed the bundled CLI folder is world-readable. - Nix:
packaging/nix/release-data.jsonlifted from the update workflow (run 40582526633, bot PR #1002 closed) as2161e6f1, and nix-build on main passed. - nixpkgs: NixOS/nixpkgs#579420 now proposes 2.74.0 -> 2.78.0, changing only the version and two hashes; the source hash comes from the tag tarball, and the same command reproduces master's 2.74.0 hash.
- Homebrew:
zennotes2.78.0 in the tap (08a0f4a), mirrored ine8833509; both DMGs' SHA-256 match the cask and GitHub's digests;brew styleclean. - zn CLI: the bundled zn is 0.12.2, the latest ZenNotes/tui release (
4bda75e8). - Website: ZenNotes/website#94 merged with the 2.78.0 entry, two captioned clips (note covers, label math) and the Calendar widget screenshot, the docs for covers, label math, Pele 1.12 and zn 0.12.2, and the 2.78.0 share viewer. Laravel Cloud deployed it at
b0ce3eaf, and the live check passed: /releases leads with 2.78.0, every clip matches the local file byte for byte, seeking works, /docs has the new sections, and the live viewer files match their manifest. - Server and Docker: ZenNotes/znserver v2.78.0 pins the browser app
web-2.78.0-web.h769e1b278b8e65b3(f4ed35e1, ZenNotes/znserver#47); all five binaries matchSHA256SUMS, and the released darwin-arm64 binary served the 2.78.0 app on a scratch vault and answered 401 without its token.adibhanna/zennotes:2.78.0,2.78andlatestare published from the tag (one digest, amd64 and arm64). - Boundary packages:
core-2.78.0-core.h0bc32cbd73a78fac,web-2.78.0-web.h769e1b278b8e65b3andviewer-2.78.0-viewer.haeee4ee85b34a271, built by CI from52ab86fdand published as pre-releases (core once both phones were submitted on it). The viewer is new this time, since published notes now draw covers; the website keeps the 2.72.0 viewer for pages already open. "Latest" still names v2.78.0. - Phones: iPhone 1.34.0 (build 51, ZenNotes/zennotesios#79, on core 2.78.0) is Waiting for Review in App Store Connect, set to release automatically. Android 1.1.48 (versionCode 51, ZenNotes/zennotesandroid#136) is in Google Play review at 100% rollout with no devices lost. Both carry the Calendar widget, the PDF fixes and the drawer fix, plus everything in core 2.78.0.
Release validation
Every CI failure since 2.77.0 was read before this release: ci-sweep.py v2.77.0 found none (three bot-branch runs aside). hazard-scan.py --since v2.77.0 found no hazards and one warning: the Windows line-endings task fix changed the task walks in tasklists.ts. The other task-line copies were checked and need no change: the desktop MCP normalizes line endings before counting, the server does too and has no toggle, and zn's Go walks match a CRLF line because Go's . matches \r (checked with a probe on its main).
Release-time gates in the v2.78.0 worktree with its own node_modules: the production dependency audit (no high advisories), typecheck 8/8 and test:run with no turbo cache (shared-domain 2,164, app-core 3,593, desktop 1,198, quicklook 18), a fresh build, a Developer ID signed pack, and the packaged launch check (page target and drawn app in 1.6 seconds, version 2.78.0). The vim-editor, sidebar-vim and editor-improvements smoke suites passed on the first run.
Four problems were caught and fixed before this release shipped. While recording the diagram clip, typing a filter in the Themes picker applied a theme nobody had highlighted (52ab86fd, a bug older than this release; six runs of a CDP check pass, and one earlier run read the theme before it repainted on a machine at a load average of about 38). CodeQL flagged the read-only cover image as cross-site scripting through the DOM: an image source runs no script and the sources were already limited, so the alert was dismissed as a false positive after e6bf83f2 put an explicit allowlist at the element, with tests. On the phones, the iPhone CI build failed because Xcode 26 cannot name iOS 27's extra-large widget size (gated by compiler version in build 51, ZenNotes/zennotesios 5d14fe4), and Android lint failed on raw byte-order marks in the widget's tick writer (written as escapes in ZenNotes/zennotesandroid 9404133; the bundle rebuilt from that commit matched the uploaded one entry for entry, signature aside).
Release PR #1000: all 10 checks passed on 52ab86fd, CodeQL after the dismissal and a re-run. CI on main for 52ab86fd (run 40577585174): all six jobs green before the tag, and CodeQL too. Release run 40580480061: all six jobs green; verify-installers.py ALL GOOD by SHA-512 on the five manifests; the app in both DMGs is Notarized Developer ID with a valid stapled ticket. npm run verify:channels -- 2.78.0: every channel serves v2.78.0 (Homebrew, AUR, Nix and the website's eight download buttons).
The first core build failed its browser test on one unexpected 404: a background read of a note the test had just moved to the Trash, landing after the move on a slow runner (the same test passes locally on this commit and passed on 2.77.0's core build); the rebuild from the same commit passed. The first Nix update run failed on an npm registry 502 and passed on a re-run.
On the phones: Android typecheck, 350 tests, build and sync, and CI's build and emulator launch passed on core 2.78.0; iPhone boundaries, typecheck, 318 tests and sync, and CI's build passed. The Calendar widget was walked on an Android emulator and an iOS 27 simulator when it was built, and the PDF fix on an Android emulator.


