Skip to content

Release 2.76.0: any due date, No due date, tasks by priority, Cloud conflicts settle - #994

Merged
adibhanna merged 10 commits into
mainfrom
v2.76.0
Oct 23, 2026
Merged

adibhanna merged 10 commits into
mainfrom
v2.76.0

Conversation

@adibhanna

Copy link
Copy Markdown
Contributor

ZenNotes 2.76.0: desktop, zn 0.11.0 bundled, server 2.76.0 with Docker, the boundary packages, the website release page, and both phones on core 2.76.0 (Android with its navigation-bar fix, iPhone with its home-indicator fix; the iPhone build replaces 1.31.0, still Waiting for Review).

Features

Fixes

  • Cloud conflicts settle cleanly, and none deletes a note (Dave on Discord, 5a34aea7): settleMovedArrivals after each pull and on resolve; the other device's version moves a file in its way to the next free name (cloudSyncFreePath, displace_existing); PATH_CONFLICT has its own message.
  • One folder label for a note in note search, the buffer switcher and the [[ picker (d825b149, noteFolderLabel).
  • Move asset accepts v1..2 and says an empty answer goes to assets/ (0b3c8ce9, lib/move-asset.ts).

Also

Gates on 61628218, in a clean worktree with its own npm ci: typecheck 8/8 uncached; shared-domain 2,065, app-core 3,435, desktop 1,192 and quicklook 15 uncached; fresh build; npm run pack (signed) and the packaged launch check pass (page target, rendered, 1.6 s, 2.76.0); vim-editor, sidebar-vim and editor-improvements pass on the first run; the built MCP server answers over stdio on zod 4.6.6. ci-sweep.py v2.75.0: no failed jobs outside bot branches. hazard-scan.py --since v2.75.0: no hazards. dash-lint clean on the notes and every commit. #991, #990 and #989 were driven in the built app over CDP, which is where the release screenshots come from; zn 0.11.0 was driven in tmux.

…a folder the same way

2.75.0 gave note search a folder column (#982), and it showed the whole
vault path: `inbox/Projects/Atlas/docs`. The [[ picker already showed the
same note as `Projects/Atlas/docs/`, and the buffer switcher showed the raw
parent directory, so one note read three ways depending on where you looked.
The picker had the opposite problem one area over: it stripped every area's
directory, so an archived `2025/Atlas/` looked like an inbox folder of the
same name.

All three now ask one helper, `noteFolderLabel` in vault-layout. In the
main notes area it is the folder inside the area, without the area's own
directory, following a root-mode vault and a remapped inbox the way the
sidebar does. Anywhere else it is the folder from the vault root, area
first (`archive/2025/Atlas`, or the remapped directory name), so an archived
note never passes for an inbox one. A note at the top of the main notes
area has no folder and gets '': each surface keeps its own area label there
(note search's INBOX column, the picker's `inbox/`, the switcher's `inbox`),
since that label is surface styling, not a path. A buffer whose note the
store does not list still falls back to its path's parent.

The bidi embedding around the search folder is now written as ‪ and
‬ escapes, like the buffer switcher's, instead of invisible literals.
… empty answer goes

The Move asset prompt (sidebar tree, a folder's note list, a rendered
image's menu) refused any path containing two dots in a row, so `v1..2` or
`Notes..old` could never receive an asset: the same substring check that
broke dotted names in the Android vault (zennotesandroid#130). Its copy also
promised that an empty answer moves the asset to the vault root, while every
host files it in `assets/` instead: the desktop vault and the server
(`cleanAssetTargetDir`) and the phone shells' local vaults alike.

The three copies of the prompt are now one, `lib/move-asset.ts`. It refuses
only a whole `..` segment, with either separator, keeps the refusal of the
internal `.zennotes` folder, and says "Leave empty to move it to the assets/
folder." The renderer turns an empty answer into `assets` before its no-op
check, so the move does what the copy says: an asset loose at the vault root
now goes to assets/ on an empty answer (it used to stay put), and an asset
already in assets/ makes no round trip. The hosts still resolve the target
against the vault and refuse an escape on their own; this check is for the
message.
…none deletes a note

Dave (Discord, 2026-10-23) came back to two Untitled.md conflicts on his
phone after its sync had been stuck for a while. Keeping the computer's
version said "trash/Daily Notes/Untitled.md already exists"; after he kept
the phone's version of the first, the second refused both choices, keeping
the phone's as "The file changed again on another device". Reproduced with
two devices against a local Cloud server, it was three problems.

A note this device never had was queued against one of its files. While the
phone was offline the computer made a new Untitled.md at the path of the
note the phone was editing and trashed it. Catching up, the phone met that
new note at its own edited file and queued a conflict there, then only
moved the queued Cloud side when the note went on to the trash. Both
conflicts now claimed the phone's one file. Keeping the phone's version of
the first gave that path to the first note in Cloud, so the second was
refused for good, and taking the computer's version of the second deleted
the phone's file, which was never that note's: the run after pushed the
delete and the kept note was gone from both devices. Such a conflict now
settles after each pull: once Cloud has moved the note off the file it met,
it lands where Cloud keeps it, as any new file would, and the device's file
is left alone; a deleted one just leaves the queue. A decision made on one
an older version saved settles it the same way and never touches the file.

The other device's version could not take its place. It goes where that
device put it, and any file of this device already there blocked the
choice, including an unrelated one: the phone had trashed an Untitled.md of
its own into the slot the computer's went to. That file now moves to the
next free name beside it, Untitled 2.md and on, the way a second note
moved into the folder is named (cloudSyncFreePath). Only the other
device's version makes room; a name the user typed for keeping both still
refuses a taken path, anything but a plain file still refuses, and a failed
write moves the file back.

A Cloud path taken by another file read as "changed again". The server
answers PATH_CONFLICT when a different file holds the destination, which
choosing again can never fix; that now says another file in Cloud uses the
name.

The phone shells' own staged resolve gets the same room-making in their
repos; the settling reaches the phones with the next core.
Davi (#991) asked for a date picker under the due presets. The task menu
stopped at next week, so a day further out meant looking it up on a
calendar first and then typing `due:2026-11-04` on the line, which is the
most work on a phone.

Choose a date… now follows Due next week in the menu every task surface
shares: the Tasks list, the board, both calendars, and on the phones a
long-press on a task. It opens the month calendar typing `@date` already
opens in a note, on the task's due date or today, and a tap on a day sets
it, so it is two taps from the menu. `@` opens it on the selected task in
the list, the board and the Tasks calendar, Vim-gated like x, i, c and >,
and named in their footer hints and the manual; with Vim off the menu entry
stays. The calendar's help line now ends "Enter picks the day", which reads
right for both uses.

Picking a daily-note task's own date writes it out, so it stays when the
task moves; picking the date a task already has, or dismissing, leaves the
line alone.
#990)

Davi (#990) made a task with + New task and found it under Today: it had
no due date, only the dateCreated New task writes, and the "No due date"
he went looking for did not exist. The grouping has filed every undated
open task under Today since the first Tasks view, so Today mixed what is
due with what has no date at all, and Home's Today did the same.

Undated tasks now have their own group in the shared grouping. The Tasks
list shows No due date after Upcoming; the Kanban status board gets a No
due date column between Upcoming and In progress, and dropping a card there
reopens it and clears its `due:`; Home's Today keeps only what is due today
or overdue. A saved column order from before slots the new column after
Upcoming.

The daily-note workflow keeps its tasks in Today. A task in a daily note is
due on the note's date wherever "Tasks are due on the note's date" is on,
which the Tasks views already applied; Home and getTodayTasks now apply it
too, so their Today and overdue count match the list. Such a task's date is
its note's, not a `due:` on the line, so the board refuses to drop it on No
due date rather than let it bounce back, as the menu offers no Clear due
date for it. The setting's description now says where it applies.

The zn terminal app mirrors the rule on its own branch.
…989)

Davi (#989) could see tasks by priority only on the Kanban board. The list
groups by date, and inside each group it always follows the notes' line
order, so Shift+J/K and dragging move the real lines; the priority shown on
every row never changed where it sat.

The list now has an Order menu above it, Note order or Priority, the
counterpart of the board's card order. Priority puts high before medium
before low before none inside Today, Upcoming, No due date and Waiting,
ties by due date and then line order; forwarded, done and cancelled keep
line order. `s` switches it in Vim mode, as on the board; `:order priority`
and `:order note` set it, and bare `:order` in the list switches it; it is
also in Settings, the palette, and `tasks_list_order` under `[view]` in
config.toml, so it travels like the board's order.

Reordering moves lines in the note, which only shows in Note order, so in
Priority order Shift+J/K say so instead of moving lines out of sight, and
rows cannot be dragged; the footer drops "J/K reorder" there.

Also: dragging a task inside the No due date group (#990) reorders it now;
the drag handler did not know the group existed.
What's New opens on the first launch after the update with four highlights. First, Choose a date… in every task menu opens the month calendar, and @ opens it on the selected task in Vim mode (#991). Second, a task without a due date waits under No due date instead of in Today, with a matching Status board column whose drop clears the date (#990). Third, the Tasks list's Order menu puts high priority first inside each date group (#989). Fourth, Cloud conflicts settle without removing a kept note, and a file in the way moves to the next free name.
The bundled CLI is the floor a desktop build guarantees, and 0.11.0 is the
newest release (ZenNotes/tui#16). It is the terminal side of two Tasks
changes in this release, so zn and the app put a task in the same place:
a task without a due date waits under No due date in the list and on the
Status board (#990), and the list orders each date group by priority with
`O` or `:order priority`, sharing `tasks_list_order` with the app's Order
menu (#989). The pin is the release's terminal-release.json copied byte
for byte after it verified against packaging/release-signing/zn-release-1.pub
in ZenNotes/tui (source commit 88105711, integration protocol 1, all four
archives it names matching checksums.txt). Staged through
`npm run terminal:stage`; the native darwin-arm64 binary reports zn v0.11.0.
The in-app manual's CLI card says 0.11.0 and what it changes.
CI's production dependency audit (`npm audit --omit=dev --audit-level=high`)
fails on zod 4.3.6 since GHSA-mrhx-c7xc-m3w5 (high): validating an
oversized constrained collection can exhaust memory. zod reaches the app
only through @modelcontextprotocol/sdk, which the bundled MCP server uses to
validate tool arguments, and the SDK accepts `^3.25 || ^4.0`.

4.6.6 is the first fixed release. Only the hoisted zod entry moves in the
lockfile (zod has no dependencies of its own); package.json files are
unchanged. The audit now reports no high advisories, and the desktop's MCP
and CLI suites pass on it. What the audit still lists is below that gate:
moderate fast-uri, hono, ip-address and smol-toml, and low katex.
@adibhanna
adibhanna merged commit 6162821 into main Oct 23, 2026
10 checks passed
@adibhanna
adibhanna deleted the v2.76.0 branch October 23, 2026 21:29
@adibhanna
adibhanna deployed to boundary-artifacts October 23, 2026 21:51 — with GitHub Actions Active
@adibhanna
adibhanna deployed to boundary-artifacts October 23, 2026 21:55 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
boundary-artifacts — 61628218 Deployed Oct 23, 2026 by adibhanna via draft #71
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