Skip to content

Fix(tasks): zn task and zn mcp see tasks in numbered lists and quotes, as the app does - #9

Merged
adibhanna merged 1 commit into
mainfrom
fix/cli-task-grammar
Oct 13, 2026
Merged

adibhanna merged 1 commit into
mainfrom
fix/cli-task-grammar

Conversation

@adibhanna

Copy link
Copy Markdown
Contributor

Fix(tasks): zn task and zn mcp see tasks in numbered lists and quotes, as the app does

zn task list, zn task toggle and the list_tasks and toggle_task tools of zn mcp read task lines with the CLI dialect, a bullets-only copy of the grammar kept that way so their task ids matched the desktop's own MCP server. ZenNotes 2.69.1 aligned that server with the app's task lines: numbered lists (1. and 1)) and quoted tasks (> - [ ]). The CLI dialect now reads the same lines, so the two MCP servers list the same tasks under the same ids again. Its other differences stay: @key:value fields stay in the content, and fences are read only up to three spaces in.

Task ids shift once in a note where a numbered or quoted task sits above another task: list again before toggling by an id saved from 0.7.1. TestCLIDialect now expects the four tasks the app sees, and TestCLIDialectReadsEveryTaskShape uses the same note as the desktop's MCP test; both fail with the old grammar. With the built binary on a scratch vault, 0.7.1 listed one task of five and this lists all five, and toggling the quoted one checks it off in place.

…, as the app does

`zn task list`, `zn task toggle` and the list_tasks and toggle_task tools of `zn mcp` read task lines with the CLI dialect, a bullets-only copy of the grammar kept that way so their task ids matched the desktop's own MCP server. ZenNotes 2.69.1 aligned that server with the app's task lines: numbered lists (`1.` and `1)`) and quoted tasks (`> - [ ]`). The CLI dialect now reads the same lines, so the two MCP servers list the same tasks under the same ids again. Its other differences stay: `@key:value` fields stay in the content, and fences are read only up to three spaces in.

Task ids shift once in a note where a numbered or quoted task sits above another task: list again before toggling by an id saved from 0.7.1. TestCLIDialect now expects the four tasks the app sees, and TestCLIDialectReadsEveryTaskShape uses the same note as the desktop's MCP test; both fail with the old grammar. With the built binary on a scratch vault, 0.7.1 listed one task of five and this lists all five, and toggling the quoted one checks it off in place.
@adibhanna
adibhanna merged commit 0708ac7 into main Oct 13, 2026
5 checks passed
renovate Bot added a commit to scottames/copr that referenced this pull request Oct 13, 2026
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ZenNotes/zennotes](https://redirect.github.com/ZenNotes/zennotes) |
minor | `2.68.0` → `2.69.1` |

---

### Release Notes

<details>
<summary>ZenNotes/zennotes</summary>

###
[`v2.69.1`](https://redirect.github.com/ZenNotes/zennotes/releases/tag/v2.69.1):
ZenNotes v2.69.1

[Compare
Source](https://redirect.github.com/ZenNotes/zennotes/compare/v2.69.0...v2.69.1)

ZenNotes 2.69.1 fixes renaming on Windows: renaming a note or an
attachment to a name another one already has could replace that other
file. It always stops and says the name is taken now. Assistants
connected through MCP see the tasks in numbered lists and quotes that
the app lists, and converting a table to a database in a long note
offers the right name.

Released from
[`86e98b49`](https://redirect.github.com/ZenNotes/zennotes/commit/86e98b49d6a0be5e9c52e4e9b1c414977cbfbd29)
through
[#&#8203;936](https://redirect.github.com/ZenNotes/zennotes/pull/936) on
2026-10-13. All installers are verified.

#### Fixes

- **A rename onto a name that is taken never replaces the other note
(Windows).** Before: on Windows, renaming a note or an attachment to the
name of another one in the same folder could go through and replace that
other file, instead of saying the name is taken. An assistant renaming a
note through MCP could do the same. ZenNotes lets a rename through onto
an existing name only when both names reach the same file, which is how
a rename that only changes capitals looks on a disk that ignores case.
It asked that by comparing file ids as JavaScript numbers, and a Windows
file id is 64 bits: two files saved one after the other could round to
the same number and read as one file. It compares the whole id now, as
Cloud sync already did. On macOS and Linux file ids stay small enough
not to round. The check came with 2.55.0
([#&#8203;839](https://redirect.github.com/ZenNotes/zennotes/issues/839)),
so every version since then could do this on Windows.

- **Converting a table offers the heading right above it, in a long note
too.** Before: in a long note, Convert Table to Database could offer the
name of an earlier heading when the editor had not yet read as far as
the table. The name could be edited before saving, so nothing was saved
under it unseen. It now reads the note up to the table before choosing.

- **Assistants see tasks in numbered lists and quotes.** Before: an AI
assistant connected through MCP saw only bullet tasks. `list_tasks`
skipped tasks in numbered lists (`1. [ ]`, `1) [ ]`) and in quotes (`> -
[ ]`), and `toggle_task` could not reach them, although the app's Tasks
view lists them; a task below one of them also got a different number
than the app gives it. The MCP server now reads task lines the way the
app does.

#### For contributors

- `apps/desktop/src/main/same-file.ts` (`6d750154`): `fileIdentity()` is
a bigint stat and `isSameFile()` compares its device and inode. Every
"is the destination the file itself?" question goes through it: note,
asset and folder renames in `vault.ts`, the shared sidecar mover in
`note-sidecars.ts`, a workflow rename's cleanup in `workflows.ts` (which
kept the old file instead of removing it) and the MCP server's
`renameNote`. `windows-file-ids.testing.ts` gives tests the ids Node
reports on Windows: the full id from a bigint stat, a rounded number
otherwise. Six tests use it, for desktop and MCP note and folder
renames, an asset rename and a workflow rename; each failed on the old
guards with the same symptom CI showed.
- `headingAbove()` in `table-to-database.ts` asks
`ensureSyntaxTree(state, pos, 50)` whether the parse reached the table
(`ba74c54f`): `noteMarkdown`'s outer tree spans the whole note even
while its body parse is short, so `syntaxTree(state).length` always said
yes. Its test cuts the first parse short with a clock that moves 30 ms
per read.
- Five CI failures since September are fixed at their causes
(`65d30daf`): tests that read `syntaxTree(state)` after a 20 ms parse
budget (task rollups, incremental parsing) or before the first parse
reached a code block (search highlighting), two timeouts for a cold
import and a slow runner, and a port race in `test-app-core-browser.mjs`
that gave the API server and Chrome the same port, the Ubuntu "Exercise
the installed editor in Chrome" failure. That step then failed once on
this release's PR for a second reason, read before re-running: a cold
Chrome on a busy runner took 26 seconds to open DevTools against a 30
second wait; it gets 90 now (`86e98b49`).
- The MCP server's `TASK_LINE_RE` matches the lines `@shared/tasklists`'
does (`a2b4bfe7`); a test pins that parity on a note with every shape.
zn 0.7.2 (`zn task` and `zn mcp`) and server 2.69.1 (the web app's Tasks
view), released the same evening, read the same lines; the release
runbook now checks every copy of this regex across the three repos
before a cut.
- The Release workflow starts on tags matching `v[0-9]*` instead of `v*`
(`c35ae438`). Publishing a boundary package's draft creates a tag such
as `viewer-2.69.0-viewer.h5e7ef0f99f145d0a`, and from 2.63.0 to 2.69.0
every viewer publish built a whole desktop release and uploaded it onto
the viewer pre-release. The desktop releases themselves were never
touched.

#### Verification

How to test locally:

1. The guards, on any OS: `cd apps/desktop && npx vitest run
src/main/note-sidecars.test.ts src/main/vault.test.ts
src/main/workflows.test.ts -t "Windows file id|round to one
number|rounds to the same number"`. Six tests pass. In
`apps/desktop/src/main/same-file.ts`, drop `{ bigint: true }` from
`fileIdentity` (the old number compare) and run them again: all six
fail, the rename resolving instead of refusing.
2. On Windows, in a vault with two notes `One` and `Two` in one folder,
rename `One` to `Two`. After: "A note named “Two” already exists in this
folder", and both notes keep their text. Before, on a disk whose file
ids round together, `Two` could end up holding `One`'s text; on a fresh
Windows install the ids are small and it would not happen.
3. Convert Table to Database in a long note: put `# Early` at the top of
a note, about fifty paragraphs, then `# Roadmap` and a table under it.
Open the note and right away choose Convert Table to Database from the
table's menu. After: the name offered is "Roadmap". The test `npx vitest
run src/lib/table-to-database.test.ts` in `packages/app-core` covers it
with a slow clock.
4. Tasks for an assistant: in a note with `1. [ ] numbered` and `> - [ ]
quoted`, ask an assistant connected through ZenNotes' MCP server to list
your open tasks. Before: neither appeared. After: both do, and it can
check them off; `cd apps/desktop && npx vitest run
src/mcp/vault-ops.test.ts -t "ordered"` covers it.
5. A rename that only changes capitals still works: rename `One` to
`one` on macOS or Windows. The note is renamed in place, and its
comments come along.

#### Distribution channels

- **GitHub release:** installers for macOS (Apple silicon and Intel DMG
and zip), Windows (x64 and ARM64 installer and zip) and Linux (x64 and
arm64 AppImage, deb, rpm, pacman, tar.gz), with five update manifests.
- **AUR:** `zennotes-bin` 2.69.1-1 (AUR clone `4dc9727`, repo
`9b754de5`), pinned to `ZenNotes-2.69.1-linux-x64.tar.gz` SHA-256
`e0077164…b4e3` (GitHub's own digest for the asset), after the bump
script checked the bundled CLI folder is world-readable;
[aur-check](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38456876873)
passed.
- **Nix:** `packaging/nix/release-data.json` lifted from the update
workflow ([run
38456877828](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38456877828),
bot PR
[#&#8203;937](https://redirect.github.com/ZenNotes/zennotes/issues/937)
closed) as `dde7064d`; its `desktopHash` is the same digest as the AUR
pin, and [nix-build on
main](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38457157441)
passed.
- **nixpkgs:**
[NixOS/nixpkgs#572180](https://redirect.github.com/NixOS/nixpkgs/pull/572180),
still waiting for review, now proposes 2.64.0 -> 2.69.1 (`17d94f17`);
the source hash comes from the tag tarball, by the same prefetch that
reproduces the 2.64.0 hash in nixpkgs today.
- **Homebrew:** `zennotes` 2.69.1 in the tap (`4cf0b75`), mirrored in
`5e01d4f4`; both DMGs were downloaded and their SHA-256 matches the cask
and GitHub's digest; `brew style` clean.
- **Website:**
[ZenNotes/website#78](https://redirect.github.com/ZenNotes/website/pull/78)
merged at `d8718ebe` with the 2.69.1 entry (three fixes); Laravel Cloud
deployed it and the live check passed: /releases leads with 2.69.1 and
all three fixes, and page 2 starts at 2.66.0. `npm run verify:channels
-- 2.69.1`: every applicable channel serves v2.69.1.
- **zn CLI:** 0.7.2
([ZenNotes/tui#9](https://redirect.github.com/ZenNotes/tui/pull/9), tag
`v0.7.2`, Homebrew formula `8744800`, tap `ebf346c`): `zn task` and `zn
mcp` list tasks in numbered lists and quotes, as the desktop's MCP
server does in this release. A zn installed from Settings → CLI updates
itself; the copy bundled in the app stays 0.7.1 until the next desktop
release.
- **Server:** [ZenNotes/znserver
v2.69.1](https://redirect.github.com/ZenNotes/znserver/releases/tag/v2.69.1)
lists the same task lines in the web app's Tasks view and pins
`web-2.69.1-web.he3742063f1986fdb`, built from this release's commit
([#&#8203;28](https://redirect.github.com/ZenNotes/znserver/pull/28));
its five binaries match `SHA256SUMS`, and the released darwin-arm64
binary reports v2.69.1, serves the app, answers 401 without the token
and lists all five tasks of the test note with it. Docker
`adibhanna/zennotes:2.69.1`, `2.69` and `latest` (linux/amd64 and arm64,
one digest `db6ced3f…`).
- **Phones:** unchanged; iPhone 1.25.0 and Android 1.1.39 run
`core-2.69.0`.

#### Release validation

Every CI failure on `main` since September was read before this release.
None was a product bug: seven were fixed already or were runner
infrastructure, five were test or harness bugs, fixed in `65d30daf`, and
reading one of them found the table naming bug. The Windows rename bug
was found like this: after 2.69.0 was tagged, the `windows-11-arm` CI
job on `main` failed once in `vault.test.ts` ("says when another note
already has the name": the rename resolved instead of refusing), on code
the release PR had passed on both Windows runners. The release process
now reads every CI failure since the previous release before the cut and
scans the tree for code patterns that already shipped a bug; on this
branch both are clean: the two failures since 2.69.0 are this bug and 16
tests in unrelated suites timing out at 5 seconds on one slow
`windows-latest` runner, and the scan, which flags the six old guards on
2.69.0's code, finds nothing.

Release-time gates on `a2b4bfe7` (the release commit `86e98b49` adds
only the browser check's longer Chrome wait): typecheck 8/8 and test:run
6/6 with no turbo cache (shared-domain 1,994, app-core 3,221, quicklook
15, desktop 1,147), fresh build:prod green, `npm audit --omit=dev
--audit-level=high` clean. Pack: Developer ID signed, deep strict
codesign OK, bundled `zn v0.7.1` in a drwxr-xr-x `Resources/zn-cli`, the
Quick Look extension; packaged launch check: page target and drawn app
in 1.5 s, version 2.69.1. Smoke suites vim-editor (12), sidebar-vim (12)
and editor-improvements (19) pass on the first run. `ci-sweep.py
v2.69.0`: two failures, both explained; `hazard-scan.py --since
v2.69.0`: clean. Release PR
[#&#8203;936](https://redirect.github.com/ZenNotes/zennotes/issues/936):
all 10 checks green on `86e98b49`, including the `windows-11-arm`
package and launch check. The run before it, on `a2b4bfe7`, failed once
in Ubuntu's Chrome step and was read before anything else: the ports
were distinct, and a cold Chrome took 26 seconds against a 30 second
wait, fixed in `86e98b49`. CI on `main` for `86e98b49` (run
[`3845207`](https://redirect.github.com/ZenNotes/zennotes/commit/38452074217))
passed all six jobs before the tag, and again for the packaging commits
(`dde7064d`). Release run
[`3845506`](https://redirect.github.com/ZenNotes/zennotes/commit/38455066243):
all six jobs green in about 45 minutes; `verify-installers.py` ALL GOOD
on the five manifests; both DMGs hash to GitHub's digests and the cask,
and the app in each is `Notarized Developer ID` with a valid stapled
ticket. `npm run verify:channels -- 2.69.1`: every channel serves
v2.69.1. What's New on the update from 2.69.0, in the built app over
CDP: "What's new in ZenNotes 2.69.1" with the Renames on Windows
highlight.

###
[`v2.69.0`](https://redirect.github.com/ZenNotes/zennotes/releases/tag/v2.69.0):
ZenNotes v2.69.0

[Compare
Source](https://redirect.github.com/ZenNotes/zennotes/compare/v2.68.0...v2.69.0)

ZenNotes 2.69.0 lets AI assistants reach a ZenNotes Server that needs a
token, with one click in Settings instead of a terminal, and makes `zn
connect` work for every MCP install. Setup no longer asks a server vault
for a layout, the Claude integrations say which tab of the Claude app
they reach, the New note form in search names the keys of the field you
are in, and a Cloud vault that another device encrypted reads as
encrypted, not deleted. Shortcuts no longer act behind What's New,
Settings or a palette, and the phones can now start a note in the folder
they are browsing.

Released from
[`d1344076`](https://redirect.github.com/ZenNotes/zennotes/commit/d13440764dfa66263714390fe044e2b2747f645d)
through
[#&#8203;934](https://redirect.github.com/ZenNotes/zennotes/pull/934) on
2026-10-13. All installers are verified.

#### New

- **Assistants reach your ZenNotes Server without a terminal**
([#&#8203;830](https://redirect.github.com/ZenNotes/zennotes/issues/830)).
When the app is connected to a ZenNotes Server that needs a token,
Settings → MCP → **Server token** now offers **Let assistants use it**.
It saves the token the app connects with where `zn connect` saves it, so
Claude Code, Codex and any other assistant you installed from Settings →
MCP reach the server on their next call, with no terminal and no typing
the token a second time. It is your choice, and the section says why:
the app keeps its own copy in your system's secure storage, which no
assistant can read, and sharing puts a copy in a private file that
programs running as you can read. **Stop sharing** takes it out again.
The shared copy follows the app: saving a new token for the server
replaces it, and forgetting the server in Settings → Vault removes it. A
token you saved yourself with `zn connect` is never touched. Thanks to
Kevin ([@&#8203;diazkev314](https://redirect.github.com/diazkev314)) for
the report and for asking for exactly this.
- **Start a note in the folder you are browsing, on the phones**
([ZenNotes/zennotesandroid#108](https://redirect.github.com/ZenNotes/zennotesandroid/issues/108),
[#&#8203;923](https://redirect.github.com/ZenNotes/zennotes/issues/923)).
Browse on Android and iPhone gains a **New note** row above New folder,
in every folder and at the top level. It opens a new note inside the
folder you were looking at, with its title selected so you can type the
name. Until now a note could only start at the top and then be moved.
The part inside ZenNotes ships here; the row arrives with the next
Android and iPhone updates. Thanks to
[@&#8203;davi-jorge-art](https://redirect.github.com/davi-jorge-art) for
the request.

#### Fixes

- **After `zn connect`, every MCP install reaches the server**
([#&#8203;830](https://redirect.github.com/ZenNotes/zennotes/issues/830)).
With the app connected to a token-protected server, an assistant could
keep answering "The MCP server has no valid token for this ZenNotes
server" even after `zn connect`, as the manual says to run. Install
registers one of two MCP servers: `zn mcp` when `zn` came from Settings
→ CLI, and otherwise the server bundled with the app (a `zn` from
Homebrew and every Windows install included). Only `zn mcp` read the
token `zn connect` saves. Both do now, for the server the app is
connected to, and the call after `zn connect` works without restarting
the assistant. The error now names the exact `zn connect <url>
--no-default` to run, and the new Server token button first. Thanks to
Kevin ([@&#8203;diazkev314](https://redirect.github.com/diazkev314)).
- **Setup skips the layout step for a server vault.** Connecting to a
ZenNotes Server in the setup wizard went on to ask how the vault should
be organized, a choice the server's vault has already made for every app
connected to it. Clicking through it could change the layout for the
phones and the web app too. Setup now goes from the vault straight to
Done for a server vault; a folder on this machine still gets the layout
step. Thanks to Kevin.
- **The Claude integrations say which tab of the Claude app they
reach.** Settings → MCP read "Claude Code" and "Claude Desktop", which
looks like one entry per app. But the Claude desktop app's Code tab runs
Claude Code and uses the Claude Code entry, while the Claude Desktop
entry reaches only its Chat tab. They now read **Claude Code (CLI +
Desktop Code tab)** and **Claude Desktop (Chat)**. Kevin suggested the
names.
- **The New note form in search names the keys of the field you are in**
([#&#8203;933](https://redirect.github.com/ZenNotes/zennotes/pull/933),
by [@&#8203;uNyanda](https://redirect.github.com/uNyanda)). The form's
footer always offered `↑↓ pick` and `↵ create`, which was wrong in most
fields: arrows only move through the folder and tag lists, and Enter
picks the highlighted row when there is one. Now in Name it reads `↵
create note`; in Folder `↑↓ pick`, and `↵ choose folder` once a row is
highlighted; in Tags `↵ add tag`; and text that is not a valid tag drops
the Enter hint, while the line above says why. Thanks to
[@&#8203;uNyanda](https://redirect.github.com/uNyanda), who also wrote
the component's first tests.
- **Cancelled tasks no longer count as open for assistants**
([#&#8203;929](https://redirect.github.com/ZenNotes/zennotes/pull/929),
by [@&#8203;vishalhabib99](https://redirect.github.com/vishalhabib99)).
The MCP `list_tasks` tool's open filter and `zn task list` listed `[-]`
cancelled tasks next to the ones still to do, although the calendar and
Kanban already leave them out. They are left out now; `status: all` and
`--all` still show them. The Go `zn` does the same from 0.7.1. Thanks to
[@&#8203;vishalhabib99](https://redirect.github.com/vishalhabib99).
- **A Cloud vault that another device encrypted says so.** When one
device moves a Cloud vault to end-to-end encryption, the other devices
linked to the old vault are told it was replaced. The desktop app
stopped syncing it but said "Cloud vault deleted", and the phones kept
the link and repeated "Update ZenNotes on this device" on every sync,
even when up to date. Now every app stops syncing the old vault, the
status bar says **Cloud vault now encrypted**, and Settings says the
vault was replaced by an end-to-end encrypted one and how to open it:
unlock encryption on this device if needed, then choose the encrypted
vault. The phones get this with their next update.
- **The manual shows bold names and paragraphs.** Thirteen entries of
the in-app manual printed `**` around the names of buttons and settings,
and longer guides ran their paragraphs together. They now read as
intended, and a search phrase that runs through a bold name finds its
entry. The end-to-end encryption entry also names the right button
(**Save archive** under Backups, in the desktop app; the phone apps send
you there) and says what other devices do when a vault is encrypted.
- **Shortcuts no longer act behind What's New, Settings or a palette.**
What's New opens by itself on the first launch after an update, so the
keys you press out of habit land on it, and none of them knew it was
there: Cmd+P opened a note search underneath it that you could not see
and took your typing, Cmd+N and Shift+Cmd+N made a note behind it,
Shift+Cmd+P opened the command palette and Cmd+, opened Settings under
it. Now no shortcut acts behind What's New; its own buttons, Tab and
Escape still work, and Cmd+, (or Settings… in the macOS menu) replaces
it with Settings. Behind Settings or any palette, Ctrl+Tab (previous
note), Alt+Left and Alt+Right (note history) and Shift+Cmd+T (reopen
closed tab) switched the note you would come back to, and Shift+Cmd+N
made a Quick Note and moved your typing into its hidden title. They wait
until the overlay is closed, as tab switching already did. Found while
recording this release's demos.
- **The Tasks calendar's keys no longer change tasks behind a dialog.**
With the calendar open under Settings, What's New or a palette, Space on
one of the dialog's buttons marked the selected day's task done (x in
Vim mode), Tab moved through the calendar's tasks instead of the
dialog's buttons, and in Vim mode dd deleted a task. The calendar now
leaves the keyboard to the dialog, as the task list and the board
already did.
- **An encrypted vault shows its name as soon as you unlock.** Settings
→ Cloud showed an encrypted vault as "Encrypted vault" until this device
unlocked encryption, and kept showing that after unlocking until
Settings was opened again. It reads the list again when you unlock or
lock. On the phones, an encrypted vault's backups no longer offer Save
archive, which could only fail there; they say to use Save archive in
the desktop app.
- **Settings on the phones and iPad offers only what the device has.**
Settings search listed the settings of sections the phones hide, so
"review" offered Review agent edits and a tap opened an empty page; and
Settings → Keymap on an iPad (and in the web app) listed the keys for
reviewing an assistant's edits, which only the desktop app can do, and
the grammar keys where grammar checking is not available. With the next
phone updates.

#### For contributors

- MCP token lookup
([#&#8203;830](https://redirect.github.com/ZenNotes/zennotes/issues/830),
`ef122a4b`): `apps/desktop/src/main/remote/zn-credentials.ts` reads and
writes zn's `credentials.toml` (`[tokens]`, keyed by the URL trimmed,
without trailing slashes, lowercased; `0600`; atomic replace; a file not
in zn's format is never overwritten, and errors never quote it), in the
portable config dir (`ZENNOTES_CONFIG_DIR`, then
`XDG_CONFIG_HOME/zennotes`, then the platform default). The bundled MCP
server (`apps/desktop/src/mcp/vault-ops.ts`) and the TypeScript `zn`
(`apps/desktop/src/cli/vault-target.ts`) resolve a server's token as
`--token`, `ZENNOTES_REMOTE_TOKEN`, a legacy profile token, then the
saved one, the Go server's order, for the URL the app already chose, so
a saved token never picks a server.
- Server token (`dd0f929b`): `apps/desktop/src/main/mcp-server-token.ts`
(states `none` with a reason, `missing`, `shared`, `different`,
`unreadable`; share, remove only the app's own token, and
`followAppServerToken` on profile save and delete), receipts in
`userData/mcp/shared-server-tokens.json`. The main process picks the
server from the persisted workspace, the way both MCP servers do, and
reads the token from the secret store; IPC `MCP_SERVER_TOKEN_STATE`,
`_SHARE` and `_REMOVE` take no arguments from the renderer, which only
ever sees the state. The bridge methods `mcpGetServerTokenState`,
`mcpShareServerToken` and `mcpRemoveServerToken` are optional and
desktop only (`faa8f873`): the phone bridges implement `ZenBridge` too,
and a required method broke their typecheck against core 2.69. UI:
`McpServerTokenSetting` in `SettingsModal.tsx`, Settings search id
`mcp-server-token`.
- Onboarding (`ed6fb808`): `OnboardingWizard.tsx` takes its steps from
the workspace mode (`visibleSteps`, `stepAfter`); connecting sets the
vault and the mode in one store update, so the auto-advance already
knows the next step, and `goToStep` now records the step it lands on
(the rail could not move forward to it after Back). The web app reports
no remote workspace and keeps the layout step, which is how a new server
vault gets made.
- Create form hints
([#&#8203;933](https://redirect.github.com/ZenNotes/zennotes/issues/933),
`bcc52592`, follow-up `fb9c6fcc`): `SearchCreateForm.tsx` derives `↑↓`
and the Enter label from the focused field and reads the highlighted row
exactly the way the Enter handlers do, so a list that shrinks under the
highlight (a sync dropping a tag while the form is open) cannot make the
hint lie. `SearchCreateForm.test.ts` is the component's first test file,
`.ts` because `vitest.config.ts` only includes `src/**/*.test.ts`.
- Cancelled tasks
([#&#8203;929](https://redirect.github.com/ZenNotes/zennotes/issues/929),
`c0d25dd2`): `list_tasks` in `apps/desktop/src/mcp/server.ts` and `zn
task list` in `apps/desktop/src/cli/commands/tasks.ts`; the Go mirror is
ZenNotes/tui `91bb33b` (zn 0.7.1).
- Superseded vaults (`00c592bb`):
`packages/shared-domain/src/cloud-vault-availability.ts` owns the check
(`isCloudVaultSuperseded`: a `CloudServiceRequestError` with 410 and
`VAULT_SUPERSEDED`) and `CLOUD_VAULT_SUPERSEDED_MESSAGE`.
`CloudSyncHostService`, the phones' sync host, retires the link on it
during a sync and the background remote-changes check, as it does for a
404; the desktop main process uses the same two pieces. app-core's
removal notice gains a third reason, `encrypted`.
- Manual rendering (`a72e97c9`):
`packages/app-core/src/lib/help-rich-text.tsx` (`renderHelpRichText`,
`helpSearchText`): `**bold**` only outside code spans, a blank line
starts a paragraph (block spans, since a section description sits in a
`<p>`). Deliberately not the task cards' inline-markdown renderer: the
manual mentions `[[` 43 times and shows link syntax as examples to type.
`help-rich-text.test.ts` renders every manual string and fails on any
`**` left outside code.
- Browse (`f9e219b2`): `requestCreateBrowseNote(host, directory)` in the
browse boundary beside `requestCreateBrowseFolder`, with the same
guards, creating through `createAndOpen` with the title focused. The
phone shells add the row: ZenNotes/zennotesandroid `4523a8b` (in
[#&#8203;111](https://redirect.github.com/ZenNotes/zennotesandroid/pull/111),
version 1.1.39) and ZenNotes/zennotesios `e5b5928` (in
[#&#8203;59](https://redirect.github.com/ZenNotes/zennotesios/pull/59),
version 1.25.0).
- MCP integration labels (`504c1551`):
`packages/shared-domain/src/mcp-clients.ts`; Settings search keyword
`code tab`.
- Shortcuts behind overlays (`12908407`): in `App.tsx`, the
capture-phase handler (Mod+P, Mod+N, pane focus and modes) adds
`whatsNewOpen` to the overlays it bails for; the bubble-phase handler
returns at once while What's New is open, except `global.openSettings`,
which closes it and opens Settings (the `onOpenSettings` listener does
the same for the macOS menu, which takes Mod+, before the page). The
overlay check that guarded tab selection and the template pickers
(`tabSelectBlocked`, now `overlayOpen`) moves to the top of the handler
and also guards `global.newQuickNote`, `global.historyBack`,
`global.historyForward`, `global.toggleRecentNote` and
`global.reopenClosedTab`. View toggles and the command palette's own
toggle are unchanged. Harnesses:
`docs/releases/v2.69.0/whats-new-overlay/` (local).
- Tasks calendar (`6da71c43`): `TasksCalendar.tsx`'s capture-phase
window handler bails on `isOverlayOrDialogOpen()`, like `TasksView` and
`TasksKanban`.
- Settings per host (`803ae295`): each Settings search result carries
`data-settings-result-category` (the section id), which the phone shells
use to hide the results of the sections they hide;
`keymapAvailableOnHost` in `lib/keymaps.ts` leaves the `editor.review*`
/ `vim.review*` keys out without `supportsAgentEditReview` and the
`vim.harper*` keys without `supportsHarper`.
- Encrypted vaults in Settings → Cloud (`d1344076`): `CloudSettings`
reads the vault list again when the encryption status's `unlocked`
changes (not on the first status); the new optional capability
`supportsEncryptedBackupArchive` (set by the desktop preload) decides
whether an encrypted vault's backups offer Save archive.
- Phone shells (on their release branches): Settings search hides the
results of hidden sections (Android `b6fea3f`, iPhone `6fa07b5`); an
assistant's edit record (`.zennotes/agent-edits/<note>.json`) is a third
sidecar in `vault-fs.ts`, moving with renames, moves, Trash and folder
renames, going with deletes and Empty Trash, and never inherited by a
new note (Android `dc6cb34`, iPhone `329ee69`); the shells' own strings
drop the long dash (Android `d72ec37`, iPhone `c17556a`).
- Bundled CLI: zn 0.7.1 (`69342fa3`), its `terminal-release.json` copied
byte for byte after it verified against `zn-release-1` (source commit
`91bb33b`, protocol 1, all four archives matching `checksums.txt`); the
staged darwin-arm64 binary reports `zn v0.7.1`. It carries
[#&#8203;929](https://redirect.github.com/ZenNotes/zennotes/issues/929)'s
task filter and a token message that names the Server token button.
- What's New: the 2.69.0 entry in
`packages/app-core/src/lib/whats-new.ts` (`87c7e216`).
- Docs: the in-app manual's MCP card (which server Install registers
when, the Code tab, the Server token paragraph) and CLI card; on the
website, the MCP section, the Server token docs, the encryption notice,
and the CLI version mentions (`website/docs-2.69`).

#### Demos

In `docs/releases/v2.69.0/media/` (local), 1080p with captions burned in
and a `.vtt` beside each, recorded on the 2.69.0 build with scratch
vaults; the server clips run a real ZenNotes Server with a token and a
real MCP client named `claude-code` driving the app's own MCP server:

-
[setup-server-vault.mp4](https://redirect.github.com/ZenNotes/zennotes/releases/download/v2.69.0/setup-server-vault.mp4)
(12 s): the setup wizard connects to a ZenNotes Server with its token
and goes from the vault straight to Done, with no layout step.
-
[server-token.mp4](https://redirect.github.com/ZenNotes/zennotes/releases/download/v2.69.0/server-token.mp4)
(25 s): Settings → MCP shows the renamed Claude entries; Server token's
**Let assistants use it** saves the token where `zn connect` would; then
Claude Code creates a note on the server through MCP and it shows up in
the app.
-
[new-note-hints.mp4](https://redirect.github.com/ZenNotes/zennotes/releases/download/v2.69.0/new-note-hints.mp4)
(28 s): search for a note that does not exist, open the New note form,
and watch the footer follow the field: Name, Folder (pick and choose
`Inbox › Projects`), Tags (add `#launch`), text that is not a tag, then
Create.

#### 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`. For the server steps, run
a ZenNotes Server with a token, for example ZenNotes/znserver's `go run
./cmd/zennotes-server` with `ZENNOTES_VAULT_PATH=<folder>` and
`ZENNOTES_AUTH_TOKEN=<token>` (it listens on `127.0.0.1:7878`).

1. **Setup for a server vault.** With a fresh profile, the wizard opens.
On the Vault step choose **Connect to ZenNotes Server**, enter
`http://127.0.0.1:7878` and the token. Before: the next step was "Vault
layout". After: "You're ready to write", and the step rail reads
Welcome, Vim mode, Theme, Vault, Done. Back returns to Vault; **Choose
vault folder** with a local folder still leads to the layout step.
2. **Server token.** Connected to that server, open Settings (Cmd+,) →
MCP. The Claude entries read "Claude Code (CLI + Desktop Code tab)" and
"Claude Desktop (Chat)". Under **Server token**, the section says
assistants can't reach the server yet. Choose **Let assistants use it**:
`credentials.toml` in your config dir now holds the server's URL and
token (mode `0600`), and `zn --server http://127.0.0.1:7878 vault info`
answers without `zn connect`. **Stop sharing** removes the line and zn
is refused again. A local vault shows a one-line note instead of the
button.
3. **zn connect reaches the bundled MCP server**
([#&#8203;830](https://redirect.github.com/ZenNotes/zennotes/issues/830)).
Without sharing, run `zn connect http://127.0.0.1:7878 --no-default` and
enter the token, then call any tool from an MCP client running
`apps/desktop/out/main/mcp.js` with `ELECTRON_RUN_AS_NODE=1` and the
same `ZENNOTES_CONFIG_DIR`. Before: "no valid token for this ZenNotes
server" in the same session and a fresh one. After: the next call reads
the server's vault.
4. **New note form hints**
([#&#8203;933](https://redirect.github.com/ZenNotes/zennotes/issues/933)).
Press Cmd+P, type a name no note has, press Shift+Enter. The footer
reads `↵ create note` in Name; Tab to Folder: `↑↓ pick`, and after ↓ `↵
choose folder`; in Tags, ↓ gives `↵ add tag`; type `!!!` and the Enter
hint disappears while the line above explains. Before: `↑↓ pick` and `↵
create` everywhere.
5. **Cancelled tasks**
([#&#8203;929](https://redirect.github.com/ZenNotes/zennotes/issues/929)).
In a vault with `- [ ] open` and `- [-] dropped`: `zn task list` (and
the MCP `list_tasks` with its default open filter) lists only "open";
`--all` lists both.
6. **The manual.** Open Help (or `:help`) and search "Encrypt a Cloud
vault": two paragraphs and bold button names, no `**`. Search a phrase
that runs through a bold name, like "choose encrypt this vault in
settings": the entry is found.
7. **Shortcuts behind What's New.** Launch with a fresh
`ZENNOTES_USER_DATA_PATH` and an existing `config.toml` in
`ZENNOTES_CONFIG_DIR` (that marks a returning user, so What's New
opens). Press Cmd+P and type: before, a hidden search took the text and
Escape revealed it; after, nothing happens behind the window. Cmd+N:
before, `Untitled.md` appeared in the vault; after, nothing. Cmd+,:
What's New closes and Settings opens. With What's New closed, open two
notes, then Settings, and press Ctrl+Tab: before, the note under
Settings changed; after, it stays.
8. **Tasks calendar.** Put a task due today in a note, open Tasks →
Calendar, open Settings (Cmd+,) and click Appearance, then press Space.
Before: the task was marked done on disk. After: unchanged, and Space
toggles it again once Settings is closed.
9. **Phones** (once the updates are out): Browse, open a folder, **New
note**: the note opens in that folder, title selected. Settings → search
"review": no Review agent edits.

#### Distribution channels

- **GitHub release:** installers for macOS (Apple silicon and Intel DMG
and zip), Windows (x64 and ARM64 installer and zip) and Linux (x64 and
arm64 AppImage, deb, rpm, pacman, tar.gz), with five update manifests.
- **AUR:** `zennotes-bin` 2.69.0-1 (AUR clone `281358c`, repo
`97a16340`), pinned to `ZenNotes-2.69.0-linux-x64.tar.gz` SHA-256
`4e790b5c…208e` (GitHub's own digest for the asset), after the bump
script checked the bundled CLI folder is world-readable;
[aur-check](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38430995718)
passed.
- **Nix:** `packaging/nix/release-data.json` lifted from the update
workflow ([run
38431006332](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38431006332),
bot PR
[#&#8203;935](https://redirect.github.com/ZenNotes/zennotes/issues/935)
closed) as `e0fd70cf`; its `desktopHash` is the same digest as the AUR
pin, and [nix-build on
main](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38431352262)
passed.
- **nixpkgs:**
[NixOS/nixpkgs#572180](https://redirect.github.com/NixOS/nixpkgs/pull/572180),
still waiting for review, now proposes 2.64.0 -> 2.69.0 (`b5870af`); the
source hash comes from the tag tarball, by the same prefetch that
reproduces the 2.64.0 hash in nixpkgs today.
- **Homebrew:** `zennotes` 2.69.0 in the tap (`e317675`), mirrored in
`634f67a8`; both DMGs were downloaded and their SHA-256 matches the cask
and GitHub's digest; `brew style` clean.
- **Website:**
[ZenNotes/website#77](https://redirect.github.com/ZenNotes/website/pull/77)
merged at `18ceb3eb` with the releases entry and its three clips, the
docs for this release (Settings → MCP → Server token, the Claude tabs'
names and the zn token the bundled server reads, both MCP servers
following a vault switch, the notice for a vault another device
encrypted, and CLI 0.7.1 leaving cancelled tasks out), the dashboard's
note that an encrypted vault's archive is saved in the desktop app, and
the 2.69.0 share viewer (2.68.0 stays installed for pages cached against
it). Laravel Cloud deployed it, and the live check passed: the releases
page and docs carry 2.69.0, one clip and its captions match the local
files byte for byte, a Range request on another answers 206, and the
live 2.69.0 and retained 2.68.0 viewer files match their manifests. `npm
run verify:channels -- 2.69.0`: every applicable channel serves v2.69.0.
- **zn CLI:** 0.7.1
([ZenNotes/tui#8](https://redirect.github.com/ZenNotes/tui/pull/8), tag
`v0.7.1`, Homebrew formula `bac26a2`), bundled here:
[#&#8203;929](https://redirect.github.com/ZenNotes/zennotes/issues/929)'s
task filter and a token message that names the Server token button.
- **Server:** [ZenNotes/znserver
v2.69.0](https://redirect.github.com/ZenNotes/znserver/releases/tag/v2.69.0)
pins the published `web-2.69.0-web.h90d11c06b6e3cf34` browser artifact
([#&#8203;26](https://redirect.github.com/ZenNotes/znserver/pull/26));
its five binaries match `SHA256SUMS`, and the released darwin-arm64
binary reports v2.69.0, serves the app and turns away a request without
the token. Docker `adibhanna/zennotes:2.69.0`, `2.69` and `latest`
(linux/amd64 and arm64, one digest `6328d4c9…`); README
[#&#8203;27](https://redirect.github.com/ZenNotes/znserver/pull/27).
- **Boundary packages:** built by CI from `d1344076` and published as
pre-releases, never "latest": `web-2.69.0-web.h90d11c06b6e3cf34`,
`viewer-2.69.0-viewer.h5e7ef0f99f145d0a` and
`core-2.69.0-core.h27a2c5df8414d1dd`, published once both phone apps had
shipped on it; publishing the viewer's draft also started a stray
desktop Release run onto the viewer pre-release (the workflow's `v*`
trigger matched `viewer-...`), which was cancelled; v2.69.0's own files
were never touched, and 2.69.1 narrows the trigger to `v[0-9]*`.
- **Phones:** iPhone 1.25.0 (41) and Android 1.1.39 (42), both on
`core-2.69.0-core.h27a2c5df8414d1dd`: Browse's New note row
([#&#8203;923](https://redirect.github.com/ZenNotes/zennotes/issues/923),
[zennotesandroid#108](https://redirect.github.com/zennotesandroid/zennotes/issues/108)),
the notice and the name for a Cloud vault another device encrypted,
Settings search that offers only what the device has, and an assistant's
pending edits that move with a renamed note. iPhone:
[zennotesios#59](https://redirect.github.com/ZenNotes/zennotesios/pull/59)
merged at `dfa8128`; its UI tests passed on iPhone and iPad simulators,
the account-backed Cloud UI tests were not run for this build, and it
was submitted to App Review on 2026-10-13. Android:
[zennotesandroid#111](https://redirect.github.com/ZenNotes/zennotesandroid/pull/111)
merged at `1664759` (tag `v1.1.39`, the same tree as the uploaded
bundle) and sent to Google Play review on 2026-10-13 for a full rollout;
the [GitHub release
v1.1.39](https://redirect.github.com/ZenNotes/zennotesandroid/releases/tag/v1.1.39)
carries Play's signed universal APK (31,883,725 bytes, SHA-256
`3e2d5f90…7f5d`, signer `9f1af3a4…`), byte-identical when downloaded
again.

#### Release validation

Cycle checks:

-
[#&#8203;830](https://redirect.github.com/ZenNotes/zennotes/issues/830)
end to end with real binaries (`docs/releases/v2.69.0/issue-830/`): a
token-protected ZenNotes Server, the Go `zn` built at 0.7.0 running `zn
connect`, and `mcp.js` run exactly as Install registers it. Before:
`vault_info` still failed after `zn connect`, in the same session and a
fresh one. After: the same running session read the server's vault on
its next call. Server token over CDP in the dev app, with its
`safeStorage` swapped for an in-memory fake so no Keychain was touched:
connected from the wizard, **Let assistants use it** wrote
`credentials.toml` (`0600`, the app's token under zn's key), and the
real Go `zn` and `mcp.js` both reached the server; **Stop sharing**
turned both away; sharing again and saving a new token for the server
updated the file; forgetting the server emptied it. Unit tests:
`zn-credentials.test.ts`, `mcp-server-token.test.ts` and the MCP and
resolver tests, five of which fail with the fallback removed.
- Setup over CDP with throwaway profiles and a loopback server: before,
connecting landed on "Vault layout"; after, on Done, with the server's
layout untouched; a local folder still lands on the layout step.
-
[#&#8203;933](https://redirect.github.com/ZenNotes/zennotes/issues/933)
in the built app over CDP: the footer in Name, Folder (with and without
a highlight, and filtered to nothing) and Tags (with a highlight and
with invalid text) matches the PR; the form, search-create and
palette-nav suites (42 tests; the follow-up's new test fails on the
merged code). The PR's first CI failure (a test seeded `vaultSettings:
null`, which vitest does not typecheck) is fixed in `fb9c6fcc`.
- Superseded vault: the desktop live test against a local Cloud with
`CLOUD_E2EE=beta`, the second device getting the real 410; the new host
tests fail against the old service; the status bar and both Settings
states over CDP.
- Browse New note on an Android emulator with a core built from this
tree: Browse, New folder "Projects", into it, New note opened "Untitled"
with its title selected and wrote `inbox/Projects/Untitled.md`; the
iPhone shell's row in its browser preview.
- Manual: `help-rich-text.test.ts` (8), and Open Help over CDP shows two
paragraphs with four bold names in the encryption guide.
- Found during the release and fixed before it: shortcuts behind What's
New and other overlays, reproduced in the built app over CDP with
throwaway profiles before the fix (Cmd+P opened a hidden search that
took the typed text, Cmd+N wrote `inbox/Untitled.md`, Shift+Cmd+N a
Quick Note, Shift+Cmd+P and Cmd+, opened their dialogs under What's New;
behind Settings, Ctrl+Tab, Alt+Left and Shift+Cmd+T changed the active
note and Shift+Cmd+N wrote a Quick Note) and after it (none acts, focus
stays, Cmd+, replaces What's New, and with nothing open all four note
shortcuts still work). The Tasks calendar behind Settings: Space (Vim
off) and x (Vim on) wrote `- [x]` before and nothing after; with nothing
open both still toggle. `CloudSettings.test.ts` gains three tests
(unlocking reads the list again; Save archive per host), each failing
with its fix removed; `keymaps.test.ts` three. The phone shells: three
new vault tests each, failing on the old `vault-fs.ts`; Android 278 and
iPhone 253 tests pass. With a rehearsal core from `d1344076` in the
iPhone shell's browser preview: at phone width, Settings search hides
every MCP, CLI and Keymap result and nothing else; at iPad width, Keymap
lists no review key; Browse's New note row creates the note in the
browsed folder with its title focused.

Release-time gates on `d1344076`: typecheck 8/8 and test:run 6/6 with no
turbo cache (shared-domain 1,994, app-core 3,220, quicklook 15, desktop
1,139), `npm audit --omit=dev --audit-level=high` clean (the same 15 low
or moderate as 2.68.0). Pack: Developer ID signed, deep strict codesign
OK, bundled `zn v0.7.1` in a drwxr-xr-x `Resources/zn-cli`, the Quick
Look extension; packaged launch check: page target and drawn app in 1.5
s, version 2.69.0. Smoke suites: sidebar-vim (12) and
editor-improvements (19) pass; vim-editor failed one check on its first
run (repeated L read the same scroll position twice, a timing flake) and
passed all 12 on the re-run. The commit messages carry no long dash
except the merged contributor commit `bcc52592`, which stays as its
author wrote it. Release PR
[#&#8203;934](https://redirect.github.com/ZenNotes/zennotes/issues/934):
all 11 checks green, including the `windows-11-arm` package and launch
check (CI run
[`3842635`](https://redirect.github.com/ZenNotes/zennotes/commit/38426359796)).
Release run
[`3842891`](https://redirect.github.com/ZenNotes/zennotes/commit/38428914590):
all six jobs green in about 45 minutes; `verify-installers.py` ALL GOOD
on the five manifests; both DMGs hash to GitHub's digests and the cask,
and the app in each is `Notarized Developer ID` with a valid stapled
ticket. `npm run verify:channels -- 2.69.0` passed locally and from a
clean runner (run
[`3843749`](https://redirect.github.com/ZenNotes/zennotes/commit/38437499904)).
After the cut, CI on `main` failed twice on Windows: 16 tests timing out
at 5 seconds on one slow `windows-latest` runner, and, on
`windows-11-arm`, one rename test that found a real bug: on Windows,
renaming a note onto another note's name could replace that note. It
dates from 2.55.0 and is fixed in
[2.69.1](https://redirect.github.com/ZenNotes/zennotes/releases/tag/v2.69.1).

</details>

---

### Configuration

📅 **Schedule**: (in timezone America/Los_Angeles)

- Branch creation
  - Every minute (`* * * * *`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/scottames/copr).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNTUuMiIsInVwZGF0ZWRJblZlciI6IjQ0LjE1NS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
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