Skip to content

Feat(tasks): zn task watch prints each task status change as it happens - #10

Merged
adibhanna merged 2 commits into
mainfrom
feat/task-watch
Oct 14, 2026
Merged

adibhanna merged 2 commits into
mainfrom
feat/task-watch

Conversation

@adibhanna

@adibhanna adibhanna commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

For ZenNotes/zennotes#941: a script should be able to react when a task changes status (for example, start an agent when its card moves from Backlog to Ready) without ZenNotes running commands itself.

What it adds

  • zn task watch prints one line per task whose status changes: note path, title, task text, previous and new status. With --json each line is one JSON object:

    {"at":"2026-10-14T15:47:24Z","id":"inbox/Parser.md#task","path":"inbox/Parser.md","link":"zennotes://open?path=inbox/Parser.md","title":"Parser","kind":"file","content":"Parser","previousStatus":"backlog","status":"ready","lineNumber":0}
  • zn task list --json now carries each task's status (a task note's status:, a line's @status:, or the note's status a line inherits). Content and ids are unchanged. zn mcp's list_tasks uses the same grammar and gains the key too; its description now names the field, and its status argument says it filters by state (open, done, waiting), not by that column. The desktop's own MCP server gets the same field and words in Feat(mcp): list_tasks says which status column each task is in, as zn mcp does zennotes#943.

How a status is read

As the Kanban board shows it. A task note's status is its frontmatter status: (open when it has none). A checkbox line is done, cancelled or forwarded once its box says so, since a board grouped by status drops those cards; until then it is its @status: or its note's status, and otherwise waiting, in-progress or open.

Tasks are matched between scans by note and words, not position, so adding a task above another, editing a task's words or saving without a status change prints nothing. Tasks that appear or disappear are not reported. The watch remembers each task's last status, so a task one scan misses (a note read while an editor rewrites it in place, or a file a sync tool replaces) that comes back with another status is still reported, once.

Where changes come from

  • Local vault: the terminal app's file watcher, moved from internal/tui to internal/fswatch so both use it (names exported, behavior unchanged).
  • Server: changes pushed over /api/watch. While that stream is unavailable (an older server, a proxy without WebSockets) the server is asked every --interval seconds (default 5), and the stream is retried every 30 seconds.
  • A failed scan is reported (as JSON on stderr with --json) and the watch continues. Ctrl-C exits 0, as zn server run does.

Verification

  • go vet ./... and go test ./... pass; the watch tests also pass under -race -count=5.
  • On Linux (a cross-compiled test binary on an Ubuntu box), the watch tests pass 300 times in a row. The first CI run here failed: the JSON-lines test assumed an order, and a scan that read a note mid-rewrite lost a change. 58b0e56 fixes both; before it, the test failed 54 times in 100 there.
  • Built binary on a scratch vault: an atomic save (temp file plus rename), an in-place edit and zn task toggle each printed one line, a body-only save printed none, and Ctrl-C exited 0.
  • Desktop app driven over CDP with an isolated profile: on the Kanban board grouped by custom status, dragging a task note from Backlog to Ready and a checkbox task from Backlog to Review printed exactly those two lines.

How to test locally

go build -o ./zn ./cmd/zn
mkdir -p /tmp/zn-watch-vault/inbox
printf -- '---\ntags: [task]\nstatus: backlog\n---\nInstructions.\n' > /tmp/zn-watch-vault/inbox/Parser.md
./zn task watch --vault /tmp/zn-watch-vault

In another terminal:

sed -i '' 's/status: backlog/status: ready/' /tmp/zn-watch-vault/inbox/Parser.md

The watch prints backlog → ready Parser inbox/Parser.md. Editing only the body prints nothing, and --json gives the line above. Opening the same folder in the desktop app (Tasks, Kanban, Group by Custom status) and dragging the card works the same way.

Follow-ups (not in this PR)

ZenNotes/zennotes#941 asks for a script to run when a task changes
status, for example starting a coding agent when its card moves from
Backlog to Ready. ZenNotes will not run commands itself: workflows live
in the vault and sync to every device, so a step that executes would run
on any desktop that opens it. What a script needs is the signal, and
nothing gave it: `zn task list --json` left the custom status out, so a
script had to parse the files and remember every status it had seen.

`zn task watch` prints one line per task whose status changes, with the
note path, title, task text and the previous and new status, and with
`--json` each line is a JSON object a script can act on as it arrives.
A status is what the Kanban board shows. A task note's is its
frontmatter `status:` (open when it has none). A checkbox line is done,
cancelled or forwarded once its box says so, since a board grouped by
status drops those cards; until then it is its `@status:` or the status
its note gives it, and otherwise waiting, in progress or open. Tasks are
matched between scans by note and words, not by position, so adding a
task above another, editing a task's words or saving a note without a
status change prints nothing. The scans use the app's grammar, whose
task text leaves the fields out, so a status change never reads as an
edited task.

A local vault is watched through the terminal app's file watcher, moved
to internal/fswatch so both share it. A server pushes its changes over
/api/watch; while that stream is unavailable (an older server, or a
proxy without WebSockets) the server is asked every --interval seconds
and the stream is tried again every 30 seconds. A failed scan is
reported and the watch goes on. Ctrl-C ends it with exit 0, as
`zn server run` does.

`zn task list --json` now carries each task's `status`, read the way the
app reads it, without changing the content or ids the desktop's CLI
gives. `zn mcp`'s list_tasks shares that grammar and gains the same key;
the desktop's own MCP server does not have it yet.

What it deliberately does not do: run anything, report tasks that appear
or disappear, or report a status change made together with an edit to
the task's words, which reads as a different task.

Tests cover the status diff (every way a status moves, edits that keep
it, repeated tasks), one JSON line per change through the watch loop,
the readable line, a local vault's file events, a server's pushed
changes settling into one scan, polling without a stream, a bad
--interval, and the status in `task list --json`. With the built binary
on a scratch vault, an atomic save, an in-place edit and `zn task
toggle` each printed one line and a body-only save printed none. Against
the desktop app, dragging a task note from Backlog to Ready and a
checkbox task from Backlog to Review on its Kanban board printed exactly
those two changes.
The first CI run of the task watch failed on Linux. Most of the failures
were the test's own: when two notes changed before the watch scanned
again, both changes came out of one scan in vault order, and the test
expected them in the order it wrote them. One failure was real. A scan
that read a note while it was being rewritten in place saw an empty file,
so the task was missing from that scan, and the next scan compared against
it: the task looked new, and its status change was never printed. An
editor that saves in place, or a sync tool that deletes a file before it
writes the new one, could hide a change the same way.

The watch now keeps the last status of every task it has seen
(vault.TaskStatusTracker), so a task that drops out of one scan and comes
back with another status is still reported, once. A task seen for the
first time is still only remembered. DiffTaskStatuses is the same
comparison of two scans, now built on the tracker.

The JSON-lines test waits for each change before it makes the next one,
and a new test drops a task note from one scan and brings it back with a
new status. On a Linux box the old test failed 54 times in 100; the watch
tests now pass 300 times in a row there, and vet, the full suite and
-race pass on macOS.

zn mcp's list_tasks description now names the status field each task
carries, and its status argument says it filters by state, not by that
column. The desktop's own MCP server gets the same field and the same
words in ZenNotes/zennotes.
@adibhanna
adibhanna merged commit 58b0e56 into main Oct 14, 2026
5 checks passed
uNyanda pushed a commit to uNyanda/zennotes that referenced this pull request Oct 14, 2026
… mcp does

An assistant reading tasks through the MCP server saw a task's due date,
priority and tags, but not the column it sits in on a board grouped by
status: a task note's `status:`, a checkbox line's `@status:`, or the
status a line takes from its note's frontmatter. zn mcp reports it with
ZenNotes/tui#10 (ZenNotes#941), so the two servers would have
answered the same question differently.

list_tasks now carries `status` on every task note (`open` when it has
none) and on every line that has one, read the way the app reads it: the
first `@status:` on a line wins and the value is lower-cased. The token
stays in `content`, as every field does in this grammar, so ids and
content do not change. The app's own field reader, inlineTaskField, is
now exported from shared-domain and imported here rather than copied
again. The legacy TypeScript CLI shares this scan, so its `task list
--json` gains the field too.

The tool's description names the field, and its `status` argument says
it filters by state (open, done, waiting), not by that column, in the
same words as zn mcp.

A new test reads a task note with and without a status, a line that
inherits its note's status, and one with two `@status:` tokens, and
checks every line against shared-domain's parser. With the built server
over stdio, list_tasks on a scratch vault returned the same ids, content
and statuses as zn mcp, and tools/list showed the new descriptions.
renovate Bot added a commit to scottames/copr that referenced this pull request Oct 14, 2026
This PR contains the following updates:

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

---

### Release Notes

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

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

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

ZenNotes 2.70.0 lets a script react the moment a task changes status:
the zn CLI bundled with the app is now 0.8.0, whose `zn task watch`
prints one line for each status change, and assistants connected through
MCP see each task's status. Quick capture's shortcuts work with Vim in
normal mode, Ctrl+P moves up in Search Notes on Linux and Windows
instead of closing it, and the phones finish syncing when you leave the
app.

Released from
[`9e50b245`](https://redirect.github.com/ZenNotes/zennotes/commit/9e50b2450e3ac3eb38ca1a3465ec76777838fc35)
through
[#&#8203;945](https://redirect.github.com/ZenNotes/zennotes/pull/945) on
2026-10-14. All installers are verified.

#### New

- **React when a task changes status: `zn task watch`**
([#&#8203;941](https://redirect.github.com/ZenNotes/zennotes/issues/941)).
The zn CLI that ZenNotes bundles on macOS and Linux is now 0.8.0 (it was
0.7.1). `zn task watch` prints a line each time a task's status changes:
a task note's `status:`, a checkbox task's `@status:`, or the checkbox
itself. Dragging a card on the Kanban board, editing the note in
ZenNotes or any other editor, and `zn task toggle` all count; editing a
task's words, or saving it with the same status, does not. With
`--json`, each change is one line with the note's path, title and link,
the task's text, and its `previousStatus` and `status`, so a script can
start an agent the moment a card reaches Ready without parsing files or
remembering what it saw. zn only reports: what runs on a change is your
own script, so nothing that executes lives in the vault, where it would
sync to every device. It watches a local vault through its files, and a
ZenNotes Server through the changes it pushes. `zn task list --json`
gives each task's status too. A `zn` installed from Settings → CLI moves
to 0.8.0 with this update if it has not already; with Homebrew, run
`brew upgrade zn`. Thanks to
[@&#8203;mateusmatiazzi](https://redirect.github.com/mateusmatiazzi) for
the idea and the clear write-up.
- **Assistants see each task's status.** Through ZenNotes' MCP server
(Settings → MCP), `list_tasks` now gives each task's `status`, the
column it sits in when the Kanban board is grouped by Custom status: a
task note's `status:` (`open` when it has none), and for a checkbox task
its `@status:` or the status its note's frontmatter gives it, read the
way the app reads them. Asked "what is in Review?", an assistant can now
answer. The tool's `status` argument still filters by state (open, done,
waiting), and its description now says so. `zn mcp` gives the same field
from zn 0.8.0, so both servers answer alike.
- **The phones finish syncing when you leave the app.** On iPhone and
Android, ZenNotes Cloud synced only while the app was on screen, and
leaving it stopped a sync that was running, so a note written just
before switching apps reached your other devices only the next time you
opened ZenNotes on the phone. Leaving the app now finishes the sync
first: the last edit is saved and uploaded within seconds. **Settings →
Cloud → Sync in the background** turns it off. It does not sync while
the app stays closed: both systems freeze an app soon after you leave
it, so changes made on other devices still arrive when you open it
again. With iPhone 1.26.0 and Android 1.1.40. Thanks to Dave for asking
on Discord.

#### Fixes

- **Quick capture's shortcuts work with Vim in normal mode**
([#&#8203;942](https://redirect.github.com/ZenNotes/zennotes/issues/942),
[#&#8203;944](https://redirect.github.com/ZenNotes/zennotes/pull/944) by
[@&#8203;uNyanda](https://redirect.github.com/uNyanda)). With Vim on,
the Quick capture window's Ctrl+P (open a note), Ctrl+N (new note) and,
on Linux and Windows, Ctrl+W (hide) did nothing unless the editor was in
insert mode: Vim took them as its own keys first. On Linux and Windows
those are the window's main shortcuts; on macOS the Cmd versions worked
and the Ctrl ones did not. They now work in every mode, and inside the
note picker or the command palette Ctrl+N and Ctrl+P move the selection,
as the footer and the manual now say. The picker shows each note's name
in full, with its folder before it and the path inside the folder after
it, cut from the left when it is long; until now the path came first and
pushed the name out of view. Both lists keep the selected row in view as
you move past the bottom. The footer shows the keys that work at that
moment, and its close hint is your Close active tab key, following a
rebind, or gone when it is unbound. Thanks to
[@&#8203;uNyanda](https://redirect.github.com/uNyanda), who reported it,
fixed it and wrote 11 tests.
- **Ctrl+P moves up in Search Notes on Linux and Windows**
([#&#8203;938](https://redirect.github.com/ZenNotes/zennotes/issues/938),
[#&#8203;939](https://redirect.github.com/ZenNotes/zennotes/pull/939) by
[@&#8203;uNyanda](https://redirect.github.com/uNyanda)). Search Notes
opens with Ctrl+P on Linux and Windows (Cmd+P on macOS), and Ctrl+P is
also the key that moves up in every list in ZenNotes, as Ctrl+N moves
down. In Search Notes it closed the palette instead. It moves the
selection up now. Search Notes is no longer a toggle on any platform:
Escape, opening a note or clicking outside closes it, so on macOS Cmd+P
inside the open palette no longer closes it either. Thanks to
[@&#8203;uNyanda](https://redirect.github.com/uNyanda).

#### For contributors

- MCP task status
([#&#8203;943](https://redirect.github.com/ZenNotes/zennotes/pull/943),
`b74d25ba`): `VaultTask.status` in `apps/desktop/src/mcp/vault-ops.ts`,
set on every task note (`open` when it has none) and on every line that
has a status, from the first `@status:` on the line (lower-cased) or the
note's frontmatter default. The app's own field reader,
`inlineTaskField`, is now exported from `packages/shared-domain` and
imported by the MCP server rather than copied again. The token stays in
`content`, so task ids and content do not change. The legacy TypeScript
CLI shares this scan, so its `task list --json` gains the field.
`list_tasks`' description names the field, and its `status` argument
says it filters by state, in the same words as `zn mcp`. The Go mirror
is ZenNotes/tui `7657716` and `58b0e56` (zn 0.8.0).
- Quick capture
([#&#8203;944](https://redirect.github.com/ZenNotes/zennotes/pull/944),
`e91aa5cd` by [@&#8203;uNyanda](https://redirect.github.com/uNyanda);
review follow-up `f8e7fe17`): `QuickCaptureApp.tsx` claims Mod+N, Mod+P
and Mod+Shift+P in a capture-phase window listener that stands aside
while an overlay is open, the treatment `App.tsx` gives its own editor
chords
([#&#8203;510](https://redirect.github.com/ZenNotes/zennotes/issues/510),
[#&#8203;579](https://redirect.github.com/ZenNotes/zennotes/issues/579));
`useCloseWindowShortcut` takes a `capture` flag that only this window
sets. The picker row mirrors `BufferPalette`'s
[#&#8203;635](https://redirect.github.com/ZenNotes/zennotes/issues/635)
layout, with the subpath wrapped in U+202A/U+202C inside a `dir="rtl"`
span so a path starting with digits reads in order. Both overlays scroll
the selection into view, keyed on `data-qc-idx`.
`useCloseShortcutDisplay()` (`close-shortcut.ts`) reads the Close active
tab binding the way `useCloseWindowShortcut` does, kept current through
the same storage event. 13 new tests.
- Search Notes
([#&#8203;939](https://redirect.github.com/ZenNotes/zennotes/pull/939),
`7565112b` by [@&#8203;uNyanda](https://redirect.github.com/uNyanda);
comment follow-up `4ac1f637`): `focusPaneHandler` in `App.tsx` runs in
the capture phase and its `global.searchNotes` branch toggled the
palette closed, with `preventDefault()` and
`stopImmediatePropagation()`, before the palette's
`isPalettePreviousKey()` saw Ctrl+P. The toggle is gone; the open path
and
[#&#8203;510](https://redirect.github.com/ZenNotes/zennotes/issues/510)'s
capture-phase registration are unchanged.
- Note grammar
([#&#8203;940](https://redirect.github.com/ZenNotes/zennotes/pull/940),
`ea5528ea`): no change to the parser. The header of
`cm-markdown-language.ts` says why both full reads stay (after a parse
runs out of time, and when the body start moves): reusing blocks after a
cut was tried and was stale 29 times in 50,000 checks against a fresh
parse, against 3 for the grammar as it is. A new test pins the read
after a cut.
- Bundled CLI: zn 0.8.0 (`fb4177b7`), its `terminal-release.json` copied
byte for byte after it verified against `zn-release-1` (source commit
`58b0e56`, protocol 1, all four archives matching `checksums.txt`); the
staged darwin-arm64 binary reports `zn v0.8.0`.
- What's New: the 2.70.0 entry in
`packages/app-core/src/lib/whats-new.ts` (`9e50b245`).
- Docs: the in-app manual's CLI card (0.7.2 and 0.8.0), the Tasks and
folders card (`zn task watch`), and the Quick capture window's Ctrl+N /
Ctrl+P; on the website, the same three and the zn docs for `zn task
watch` (live since
[ZenNotes/website#79](https://redirect.github.com/ZenNotes/website/pull/79)).
- Phones: final sync on leaving the app,
[ZenNotes/zennotesios#61](https://redirect.github.com/ZenNotes/zennotesios/pull/61)
and
[ZenNotes/zennotesandroid#113](https://redirect.github.com/ZenNotes/zennotesandroid/pull/113):
`src/bridge/background-sync.ts` in each shell, a native begin and end
around the last sync (iOS `UIApplication.beginBackgroundTask`), setting
key `zn:background-sync` (absent means on). Android's 15-minute
WorkManager job was built and removed before merging: once Android
freezes the app, the WebView renderer that runs the sync is a separate
frozen process a job cannot wake.

#### Demos

In `docs/releases/v2.70.0/media/` (local), 1080p with captions burned in
and a `.vtt` beside each, recorded on the packaged 2.70.0 build with
scratch vaults:

-
[zn-task-watch.mp4](https://redirect.github.com/ZenNotes/zennotes/releases/download/v2.70.0/zn-task-watch.mp4)
(20 s): the zn 0.8.0 bundled in the app runs `zn task watch --vault
./Work` under the Kanban board, grouped by Custom status. Shift+L moves
Parser rewrite from backlog to ready and on to review, then a save from
outside the app moves Docs refresh to ready. Each change prints one
line: the time, the old and new status, the task and its note. The
terminal is zn's real output, recorded on the same clock as the app.
-
[quick-capture-vim.mp4](https://redirect.github.com/ZenNotes/zennotes/releases/download/v2.70.0/quick-capture-vim.mp4)
(18 s): the Quick capture window with Vim on. A thought is typed and
Escape returns to normal mode; Ctrl+P opens the note picker, where each
note's name comes before its subfolder; Ctrl+N and Ctrl+P move; Escape
closes it; and Ctrl+N saves the draft to Quick Notes and starts a new
one.

#### 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 Vim steps, put
`[vim]` and `enabled = true` in a `config.toml` in that config dir
first.

1. **`zn task watch`**
([#&#8203;941](https://redirect.github.com/ZenNotes/zennotes/issues/941)).
In a vault with a task note `inbox/Parser.md` whose frontmatter says
`status: backlog`, run `zn task watch --json --vault <vault folder>` in
a terminal (the bundled one is
`ZenNotes.app/Contents/Resources/zn-cli/zn` on macOS). In the app, open
Tasks → Kanban, set Group by to Custom status, and drag the card to
another column. One line appears:
`"previousStatus":"backlog","status":"ready"`. Change a word in the
task, or save the note unchanged: nothing. In a note with `- [ ] Ship
@status:review`, change the token to `@status:done`: one line,
`"previousStatus":"review","status":"done"`, without `kind`, which only
a task note carries (`"kind":"file"`).
2. **Status through MCP.** In the same vault, ask an assistant connected
through Settings → MCP to list your tasks: each carries `status`. Or `cd
apps/desktop && npx vitest run src/mcp/vault-ops.test.ts -t "status"`.
3. **Quick capture with Vim**
([#&#8203;942](https://redirect.github.com/ZenNotes/zennotes/issues/942)).
Open the Quick capture window (`Space q`, or the global hotkey), type a
line and press Escape for normal mode. Press Ctrl+P: the note picker
opens (before: the cursor moved up a line and nothing opened). Ctrl+N
and Ctrl+P move in it; a note `inbox/2026/Planning/Roadmap.md` reads
`INBOX Roadmap 2026/Planning`, its name before its path. Escape, then
Ctrl+N: the draft is saved to Quick Notes and a fresh capture opens. On
Linux and Windows, Ctrl+W hides the window. The footer reads `⌘W close`
on macOS; rebind Close active tab in Settings → Keymap and the hint
follows.
4. **Search Notes**
([#&#8203;938](https://redirect.github.com/ZenNotes/zennotes/issues/938)).
On Linux or Windows press Ctrl+P, type a few letters, press Ctrl+N
twice, then Ctrl+P. Before: the palette closed. After: the selection
moves up one row. Escape closes it. On macOS, Cmd+P inside the open
palette no longer closes it.
5. **Phones** (once the updates are out): with Cloud linked, type a line
in a note and switch to another app right away; on another device the
line arrives within seconds. With Settings → Cloud → Sync in the
background Off, it waits until ZenNotes is opened again on the phone.
6. **What's New.** Launch 2.70.0 with a profile last used by 2.69.1:
"What's new in ZenNotes 2.70.0" lists four highlights.

#### 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.70.0-1 (AUR clone `dcc7b9c`, repo
`e169b57a`), pinned to `ZenNotes-2.70.0-linux-x64.tar.gz` SHA-256
`f9eb5ce4…967d` (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/38626628642)
passed.
- **Nix:** `packaging/nix/release-data.json` lifted from the update
workflow ([run
38626637349](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38626637349),
bot PR
[#&#8203;946](https://redirect.github.com/ZenNotes/zennotes/issues/946)
closed) as `9408dab6`; its `desktopHash` is the same digest as the AUR
pin, and [nix-build on
main](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38626951598)
passed.
- **nixpkgs:**
[NixOS/nixpkgs#572180](https://redirect.github.com/NixOS/nixpkgs/pull/572180),
still waiting for review, now proposes 2.64.0 -> 2.70.0 (`df5b1f8e`);
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.70.0 in the tap (`234f96b`), mirrored in
`5ff943df`; both DMGs were downloaded and their SHA-256 matches the cask
and GitHub's digest; `brew style` clean.
- **Website:**
[ZenNotes/website#81](https://redirect.github.com/ZenNotes/website/pull/81)
merged at `40be0c90` with the 2.70.0 entry and its two clips, and the
docs for this release (desktop 2.70.0 bundles CLI 0.8.0, and Quick
capture's Ctrl+N / Ctrl+P); the zn docs for `zn task watch` went live
earlier today with
[ZenNotes/website#79](https://redirect.github.com/ZenNotes/website/pull/79).
Laravel Cloud deployed it, and the live check passed: /releases leads
with 2.70.0 and its new titles, one clip and the other's captions match
the local files byte for byte, a Range request on the other answers 206,
and /docs says desktop 2.70.0 bundles CLI 0.8.0.
- **zn CLI:** 0.8.0
([ZenNotes/tui#10](https://redirect.github.com/ZenNotes/tui/pull/10),
tag `v0.8.0`, Homebrew formula `0b664e8`, tap `205389f`), released
earlier today and bundled here: `zn task watch`, each task's status in
`zn task list --json` and in `zn mcp`'s `list_tasks`.
- **Server:** [ZenNotes/znserver
v2.70.0](https://redirect.github.com/ZenNotes/znserver/releases/tag/v2.70.0)
pins `web-2.70.0-web.h661b8da927ac7586`, built from this release's
commit
([#&#8203;30](https://redirect.github.com/ZenNotes/znserver/pull/30)),
so the browser app gets the Search Notes fix; its five binaries match
`SHA256SUMS`, and the released darwin-arm64 binary reports v2.70.0,
serves the app, answers 401 without the token and lists all five tasks
of the test note with it. Docker `adibhanna/zennotes:2.70.0`, `2.70` and
`latest` (linux/amd64 and arm64, one digest `39b1974b…`); README
[#&#8203;31](https://redirect.github.com/ZenNotes/znserver/pull/31).
- **Boundary packages:** built by CI from `9e50b245` and published as
pre-releases, never "latest": `web-2.70.0-web.h661b8da927ac7586` and
`core-2.70.0-core.hbed9639c9831ecf7`, the core published once both phone
apps had been submitted on it. No new share viewer: nothing that
published pages render changed since `viewer-2.69.0`, which the website
keeps.
- **Phones:** iPhone 1.26.0 (42) and Android 1.1.40 (43), both on
`core-2.70.0-core.hbed9639c9831ecf7`: the final sync on leaving the app,
Convert to database's name in a long note (core 2.69.1), and the Search
Notes key on a hardware keyboard. iPhone:
[zennotesios#62](https://redirect.github.com/ZenNotes/zennotesios/pull/62)
merged at `7991ba0`, submitted to App Review on 2026-10-14. Android:
[zennotesandroid#114](https://redirect.github.com/ZenNotes/zennotesandroid/pull/114)
merged at `196e6c3` (tag `v1.1.40`, the same tree as the uploaded
bundle) and sent to Google Play review on 2026-10-14 for a full rollout;
the [GitHub release
v1.1.40](https://redirect.github.com/ZenNotes/zennotesandroid/releases/tag/v1.1.40)
carries Play's signed universal APK (31,887,821 bytes, SHA-256
`69385f52…1f11`, signer `9f1af3a4…`), byte-identical when downloaded
again.

#### Release validation

Every CI failure since 2.69.1 was read before this release: `ci-sweep.py
v2.69.1` found none, and `hazard-scan.py --since v2.69.1`, which runs
every copy of the task-line pattern across the desktop, its MCP server,
zn and the server on the same lines, is clean.

Release-time gates on `9e50b245`, in a clean checkout of it with its own
`npm ci`: typecheck 8/8 and test:run with no turbo cache (shared-domain
1,994, app-core 3,235, quicklook 15, desktop 1,148), the terminal tests
(13), the web-dist lock, `test:shared-packages`, `test:app-core-package`
and `test:app-core-browser`, and `npm audit --omit=dev
--audit-level=high` clean. Pack: Developer ID signed, deep strict
codesign OK, bundled `zn v0.8.0` in a drwxr-xr-x `Resources/zn-cli`, the
Quick Look extension; packaged launch check: page target and drawn app
in 2.1 s, version 2.70.0. Smoke suites vim-editor (12), sidebar-vim (12)
and editor-improvements (19) pass on the first run. What's New on the
update from 2.69.1, on the packaged build: "What's new in ZenNotes
2.70.0" with the Cloud card and its four highlights.

A first pack, in the release worktree, failed the packaged launch check
(no page target in 90 s; the main process stood at an error dialog).
That worktree resolves packages from the main checkout, and
electron-builder left every dependency hoisted there out of the app
(chokidar, ws, electron-updater and five more), which a scan of the
extracted archive confirmed. It was not a product bug: the same commit,
packed in a clean checkout, launched in 2.1 s, and the release runbook
now packs in a checkout with its own `node_modules`.

Release PR
[#&#8203;945](https://redirect.github.com/ZenNotes/zennotes/issues/945):
all 10 checks green on `9e50b245`, including the `windows-11-arm`
package and launch check. CI on `main` for `9e50b245` ([run
38621899138](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38621899138))
passed all six jobs before the tag; the only red run at that commit was
Dependabot's own refresh of its development-dependency PR
([#&#8203;926](https://redirect.github.com/ZenNotes/zennotes/issues/926),
an npm peer conflict in its dry-run install), not product CI. CI on
`main` for the packaging commits passed too: `9408dab6` (Nix, [run
38626951568](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38626951568),
whose run superseded the AUR commit's) and `5ff943df` (Homebrew, [run
38629467604](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38629467604)).
Release [run
38624159807](https://redirect.github.com/ZenNotes/zennotes/actions/runs/38624159807):
all six jobs green; `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.70.0`: every channel serves v2.70.0 (Homebrew,
AUR, Nix and the website's eight download buttons).

</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:eyJjcmVhdGVkSW5WZXIiOiI0NC4xNTYuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjE1Ni4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=-->

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