Skip to content

Release 2.81.0: recurring tasks and reminders, New Kanban, column colors and a folder picker - #1039

Merged
adibhanna merged 34 commits into
mainfrom
v2.81.0
Nov 2, 2026
Merged

adibhanna merged 34 commits into
mainfrom
v2.81.0

Conversation

@adibhanna

@adibhanna adibhanna commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

The release PR for 2.81.0. It fast-forwards main once every check is green.

Features:

Fixes:

  • Lists: Enter in a list never adds blank lines of its own (SLP, Discord).
  • New Drawing: in a Vault root vault, a drawings folder under assets/ falls back to the vault root, and Settings says why (charles, Discord).
  • config.toml: keeps the tables and keys only zn uses.
  • Drawings: opening a drawing no longer logs 230 CSP font errors.

Found by the phone walks of the candidate core, fixed before the cut:

  • Reminders settings show only where reminders fire, the desktop (d9e7c36a).
  • A database opened in the same pane starts at its own first column instead of the previous board's scroll (08e46c85).
  • A narrow pane keeps the database's name; the header's tools wrap to a second line (c79731f0).
  • Phone menus leave out keyboard hints; side text that is not a key moves to a new mark (507f117d).
  • Default databases location copy no longer points at the drawings row (2dc9388c).
  • createBrowseKanban in @zennotes/app-core/browse, for the phones' folder-sheet New Kanban (dc7f3eec).
  • Test: the list Enter test drops its caret marker by index (CodeQL Add Flatpak packaging #145, 8bc2209c).

Release:

Gates, run in a clean npm ci worktree on f5ecdef8:

  • typecheck;
  • every test suite (shared-domain 2341, app-core 4682, desktop 1376, Quick Look 18);
  • a fresh build:prod;
  • npm run pack plus the packaged launch check (page target, rendered, 2.81.0);
  • the vim-editor, sidebar-vim and editor-improvements smoke suites;
  • the hazard scan;
  • the CI sweep since v2.80.1 (no failed jobs);
  • dash-lint.

A blank line between the first two bullets of a list spaced out every
bullet typed after it. SLP showed it on Discord: Shift+Enter at the end
of "one" left an empty line above "two", and from then on each Enter
put a blank line before the new bullet, "three" and "four" included. A
fresh list under the next heading stayed tight, which made it look
random.

CodeMirror's markdown Enter calls a list loose when a blank line
separates its first two items, and continues a loose list with a blank
line of its own every time. Replayed over CDP, the released 2.80.1
saved `- one\n\n- two\n\n- three\n\n- four`.

Enter now runs upstream's command and takes that one blank line back
out of the finished transaction, so ordered-list renumbering, quoted
lists and task boxes stay upstream's. The blank line upstream inserts is
the only one its Enter ever adds: a line break, a line of nothing but
indentation and `>` marks, then the line break before the marker. With
`nonTightLists: false`, Enter on an empty second bullet ends the list,
as it already did on an empty third one, instead of pushing the bullet
down under a blank line. Vim's `o` already opened the next item right
below; its quoted-line prefix now reads this command, so `o` and Enter
still agree. A blank line the writer typed stays where it is. The same
replay now saves `- one\n\n- two\n- three\n- four`.
…o assets/drawings

charles reported on Discord that New Drawing does nothing on his Mac. In
a default vault it works from every entry point, but in a Vault root
vault whose drawings location is the folder `assets/drawings` (the
example the Settings field itself gave) the file was written and then
never opened: `vault:read-note` refused it with "Note not in a known
folder". Each try left one more invisible `Untitled N.excalidraw`
behind. In a root vault a Specific folder's first segment is a
top-level directory, and `assets/`, the other attachment folders and dot
folders hold files the vault never lists or opens as notes. Databases
and task files resolve their Specific folder the same way, so they had
the same trap.

resolveCreateLocation now sends such a location to the primary location
instead, and reservedCreateFolderTop names the folder that caused it, so
the Settings row can say why ("`assets/` holds attachments, so new
drawings go to the vault root."). The examples become `Drawings` and
`Databases`. An Inbox vault is untouched: there `assets/drawings` lives
in `inbox/` and always worked. A system folder named as the first
segment (`archive/x` in a root vault) keeps its old behavior; this only
stops creating files where they cannot be opened.

Over CDP in a root vault set to `assets/drawings`, the released 2.80.1
wrote the drawing and opened nothing; this build opens it at the vault
root from Home and from the command palette.
Opening any drawing logged one "Loading the font
'https://esm.sh/@excalidraw/...' violates the following Content Security
Policy directive" error for each of Excalidraw's 230 font faces, on the
drawing view and on every embed preview. It read like #324 had regressed
and drawings had fallen back to system fonts. They had not: in the
released 2.80.1 every family a scene uses loads from zen-excalidraw: and
renders in its own typeface.

Excalidraw 0.18.1 lists two sources for each FontFace it builds:
EXCALIDRAW_ASSET_PATH, then a CDN fallback it always appends
(ExcalidrawFontFace.ASSETS_FALLBACK_URL, on esm.sh). Chromium checks
every listed source against the CSP when the FontFace is constructed,
not when it loads, and font-src does not allow esm.sh. So each face
logged an error the moment Excalidraw registered it, even though it then
loaded from the first source. Constructing a face with both sources logs
the error at once and its load() still succeeds from zen-excalidraw:; a
face that lists only local sources logs nothing. The noise has been
there since #324 shipped, on the same Excalidraw version.

A renderer build plugin now rewrites that fallback to
zen-excalidraw://assets/, the base the renderer already sets, so every
source a face lists is one font-src allows, and the fonts stay local even
if the global were ever unset. It has to happen at build time: Excalidraw
does not export the class and fixes each face's URLs when it constructs
it. If an Excalidraw upgrade reshapes the fallback, the build fails
rather than quietly bringing the errors back. The base is one constant
shared by main.tsx and the config. The CSP is unchanged; allowing esm.sh
would have silenced the errors by permitting a remote font origin. The
web app's server CSP shows the same noise and is left for its own
change, since its asset path is relative and only known at runtime.

Checked on a packed build (npm run pack from a clean npm ci checkout)
over CDP, with both stores isolated and ZEN_PERF=1, one fresh launch per
surface: a drawing with text in every Excalidraw family plus Chinese, a
note embedding it in the editor, and the same note in Read mode.
Released 2.80.1: 230 esm.sh requests and 230 CSP violations on each
surface. This build: 0 and 0 on each, with the same 10 font loads from
zen-excalidraw: and Virgil, Cascadia, Excalifont, Nunito, Lilita One,
Comic Shanns and Xiaolai loaded in document.fonts. Screenshots show each
family rendered as before.
…1033)

Task folders took a typed path and nothing else: to add one you had to
know its path from the vault root and spell it right, so a folder you can
see in the sidebar had to be retyped by hand. The same was true of every
Settings field that names a folder, except the template folder, which
went the other way and could only pick, never type.

Now each of them is one folder picker. Focusing the field lists the
vault's folders under it, typing narrows the list and highlights the
first match, Enter or a click picks, and a path that matches nothing is
kept as typed, so a folder can still be named before it exists. The keys
are the palettes': the arrows, Ctrl+N / Ctrl+P (and Ctrl+J / Ctrl+K),
Enter, and Escape, which closes the list and leaves Settings open; the
next Escape is the field's own. Letters are only ever text, Vim on or off.

Each field lists folders in the form it stores, and keeps its own
validation and save rules: Task folders and the excluded folders take a
path from the vault root, the areas themselves included as the sidebar
menu offers them, and a pick adds the folder at once; the Specific folder
rows for drawings, databases, tasks and shares, and the Kanban folder
root, take a folder inside the primary notes area, leaving out one whose
files would land elsewhere (assets/ or a hidden folder at a root vault's
top), and still say where new files go; the template folder takes a path
inside an area, with an empty field for none; the Typst preamble folder
takes a folder name, matched at any depth. Database folders are never
offered: they hold rows.

The New note form in Search notes (#826) already had this picker for its
Folder field. Its ranking and its keyboard state moved out into
lib/folder-picker.ts and lib/use-folder-picker.ts, and that form now runs
on them unchanged; FolderPickerInput is the Settings face of the same
behavior. One thing the app showed that the form never handled: a window
that loses focus and gets it back refocuses the field, which used to look
like landing in it anew and threw the narrowed list away. The Settings
picker now resumes the list it had.

Deliberately left as typed fields: the daily, weekly and monthly
directory patterns, which read date tokens (a folder called Meetings
would turn its M into the month), and the system Folder Paths, which
remap a whole area rather than name a folder inside one.
…ade (#1037)

A board told its columns apart only by their titles (williamcheuk03,
#1037: gray for To Do, blue for In Progress, green for Done).

Any column on any board can now carry one of seven colors: Gray, Red,
Yellow, Green, Teal, Blue or Purple. Pick one from Color… in the column
header's menu, which opens a picker with a swatch per color and the current
one checked (No color takes it off), or press `C` on the board in Vim mode,
or type `:color blue` on the Tasks ex line; a bare `:color` clears it. All
three act on the column under the cursor, the way `W` and `:wip` do.

The palette is names, never hexes: each name maps to one of the hue tokens
every bundled theme defines (`--z-blue`, `--z-aqua` for Teal, `--z-grey-1`
for Gray), so Blue is Gruvbox's muted blue in Gruvbox and Apple's blue in
Apple, light and dark alike, and a test holds every theme block to defining
all seven. The accent stays quiet: a dot before the header's title and a
2px top edge (the border plus an inset line, so no column shifts by a
pixel, composed with the ring vars so the drop highlight still draws). On a
field board the color replaces the value's automatic accent dot, and the
cards' `@key:value` chips and a field lane's dot take the color stored for
that value's column, so a gray To Do no longer holds yellow `@status:todo`
chips.

Colors are kept per board and column under the column-title key
(`status:in-progress`, `field:status:done`), global and portable as the new
`[kanban_column_colors]` table in config.toml, next to the titles and the
WIP limits; like the limits it is not a per-vault override. A hand-edited
name is read in any case, `grey` and `aqua` included, and anything the
palette does not have, a hex among them, is dropped rather than guessed.
The card's left edge is deliberately left alone, after a look at both.

Database Board views (`.base`) do not get colors here: theirs belong on
the select option in `schema.json` (`SelectOption.color` is already typed
as a palette token name), and that board needs a keyboard path first.

Tests: the palette parser (case, aliases, hexes and unknown names refused),
every color drawn from a theme token that every theme block defines, the
store's per-column colors through a restart and its cleanup of bad names
and keys, the `[kanban_column_colors]` round trip, and `C` in the Keys
sheet. Over CDP on the built app, both stores isolated: right-click To Do,
Color…, Gray colors the column, its dot and its chips from the token; `C`
then "blu" Enter colors In Progress; `:color purple`, a refused
`:color orange`, a bare `:color`, `:colour Purple`; Shift+F10 on Done's
header, Color…, "gree" Enter; all four written to config.toml; a hand edit
there recolors To Do live; the Status board keeps its own colors, the
header row keeps them with lanes on; after a restart with Vim off the
colors come back, `C` does nothing, the menu names no letter, and the
arrows and Enter pick a color.
)

A folder's menu could make a note, a drawing or a database, but not a
board, and the one Kanban the app had was the Tasks board: every task in
the vault on one set of columns (williamcheuk03, #1038: New Kanban in the
folder menu, so each college subject or project gets its own board).

A Kanban is a database. New Kanban creates `Untitled Kanban.base` with a
Name and a Status select of To do, Doing and Done, and it opens on a Board
grouped by Status, with the Table beside it. The seed gains an optional
`board` that turns one of its columns into that select and opens on the
Board, and initialDatabaseContents handles it, so desktop main and
database-ops (the server, the web app and both phones) write the same
files. No bridge method, no Go route and no new file format; zn base reads
it like any database. A folder-scoped Tasks board would share one set of
columns across the vault, and an Obsidian Kanban-plugin `.md` is a format
we do not own, whose cards would also flood the Tasks view.

It sits under New database in the folder menu and the empty-area menu, in
the palette as New Kanban, on Space k and as `:kanban [folder]`. Like
Space G, the palette, Space k and a bare `:kanban` use the folder under
the sidebar cursor while the sidebar has focus, and otherwise go where new
databases go (Settings, Vault, New Drawings, Databases & Tasks). They never
file a board in Quick Notes, the Archive, the Trash, or inside another
database's `.base` folder.

The database Board was a mouse-only second view, which a Kanban's main
surface cannot be. It now runs its own keyboard, matching the Tasks board:
h / j / k / l move, H / L carry the card a column, o opens its page, a adds
a card and m opens its menu, all Vim mode's; with Vim off the arrows,
Enter and Shift+F10 do the work. Each column ends in Add card: Enter adds
the card and keeps the field open for the next, Escape drops what was
typed, and down from a column's last card reaches it. The card menu has
Open page, Move to each other column and Delete card, which asks. The No
status column shows only while a card sits in it, so a fresh board fits
its own three columns. The cursor ring follows the board's CSS :focus,
since React saw no focus event when the window was in the background.

Docs: a help.ts topic, a Database board shortcut section, Space k,
`:kanban`, the location setting, and a pointer from the Tasks Kanban topic,
mirrored on the website.
A task that comes back every week had to be retyped every week, and
nothing told you a task was due unless you went and looked. Now two
fields do both, written on the line like everything else a task carries:

  - [ ] Water the plants @repeat:weekly due:2026-11-01 @remind:9am

Both ride the existing @key:value field grammar (#354), so the four
synced task parsers (app, MCP, zn, Go server) already read them and none
changed. Obsidian sees plain text; ZenNotes does not read Obsidian Tasks'
emoji dates, so its 🔁 would have been half a format.

Checking off a recurring task writes its next occurrence right above it,
with the next date in due:, and leaves the finished line below as the
record. The record drops @repeat and @remind, the two tokens that only
mean something on open work, so unchecking it or dragging a Done card
back never spawns a second copy. The task's subtree repeats too, every
checkbox open again, so a weekly checklist starts each week unticked.
The next date counts from the due date (else the daily note's, else
today) and is the first one after both the due date and today: a daily
task finished five days late comes back tomorrow, not as five overdue
copies, where Obsidian Tasks steps one interval and can land in the past.
Rules: daily, weekdays, weekly, monthly, yearly, Nd/Nw/Nm/Ny, weekday
lists like mon/thu, and after-<interval> to count from the day you finish.
Months count from the due date each time and clamp to the month's end.

One edit, in shared-domain task-recurrence.ts, so every surface makes
the same one: x and the menu in the Tasks views, a drop into Done on the
board (applyTaskMutation now applies set-checked last, so the copy is
the line as the other mutations left it), Mod+L and both clickable
markers in the editor (one transaction, one undo, the cursor kept on the
open occurrence), Preview's checkboxes, MCP toggle_task and the desktop
CLI's remote toggle. A hand-typed x stays text, as in Obsidian Tasks.
task-recurrence.cases.json is the case table the zn Go mirror will run.
The Tasks views show "Next: Sun, Nov 8" with an Undo that restores the
note if nothing changed it since, and a recurring row skips the list's
linger, since by then the row is the next occurrence and a second x
would check that one off. Skip this time in the task menu records the
occurrence as cancelled and schedules the next.

Reminders: @remind:9am (also 930am, 1730, 17h30) on the due date,
@remind:1d the day before at the reminder time, @remind:2d-6pm. The
renderer computes the schedule from the task index it already keeps, so
local and remote vaults behave alike, and hands it to the main process,
the one part alive with the window hidden in the tray. task-reminders.ts
there times it with one timer capped at an hour, re-checks on wake and
unlock, and keeps a ledger of fired keys and the last moment it was
watching in userData, so nothing fires twice and a launch shows what
came due while the app was quit, at most a day back, folded into one
notification. An empty schedule at startup cannot mark the stale one as
watched. The payload is untrusted: only well-formed, vault-relative
items are kept, and a click only hands the path back to the renderer.

Keyboard first: r and R open the Repeat and Remind pickers on the list,
board and calendar (also in the task menu, with the keys shown in Vim
mode), :repeat weekly and :remind 9am act on the editor's task line,
Repeat Task… and Remind Task… are in the palette, and typing @repeat: or
@remind: on a task line offers the values. The editor draws the tokens as
chips in their own colors, dimmed when the value means nothing; the
Tasks views show a repeat icon and a bell with the time instead of the
raw chips, and the board never offers them as columns or lanes.

Settings → Tasks → Reminders: Task reminders (on) and Reminder time
(09:00), portable as task_reminders and task_reminder_time under [view].

Not in this change: whole-note task files (TaskNotes' recurrence and
reminders), the zn mirror, a daily summary, and the phones. The design
and those slices are in docs/ideas/recurring-tasks.md.
…can carry a color, drawn in the theme's own shade (#1037)
…f its own for a folder (#1038)

# Conflicts:
#	packages/app-core/src/lib/help.ts
…1036)

Checking off a recurring checklist copied its subtasks, unticked, into the
next occurrence, and left the finished line's subtree as it was. A subtask
nobody got to that week stayed open in the record, so the Tasks list showed
it twice, beside its fresh copy, and it never went away. Adib chose to
cancel them.

The record now keeps what was ticked as the history of that occurrence and
writes the subtasks still open, [ ] and [/] at any depth, as [-]. Ticked,
cancelled and forwarded ones stay as they were, and the next occurrence's
copies stay open. It is part of the shared edit (planRecurringCompletion
returns the record's subtree and how many lines it replaces), so every
path that checks a recurring task off does the same: the Tasks list, the
board, the editor's Mod+L and clickable markers, Preview, MCP toggle_task
and the desktop CLI's remote toggle. Skip this time cancels them with its
record.

In the editor the change now replaces the whole task block, still one
transaction and one undo. Subtasks selected along with the task (Vjj then
Mod+L) are what you are checking off, so the record writes those done
instead of cancelled, and Mod+L no longer adds a second, overlapping change
for a selected line inside a block it already rewrote.

The shared case table gains nested, in-progress, forwarded and CRLF
subtrees; help.ts and the design doc describe what the record keeps.
…eminders (#1036)

# Conflicts:
#	packages/app-core/src/components/TasksKanban.tsx
The desktop app rebuilt config.toml from the options it knows, at every
launch and every settings change, so anything else in the file was gone
after the first save: zn's whole [terminal] table (mouse, preview_style,
math, and since zn 0.14.0 view_mode_field and [terminal.view_mode_rules])
and keys zn keeps in the app's own tables, like [vim] motions for the
Helix work in ZenNotes/tui#13. Anyone using both apps lost those settings
every day, and zn's README had to warn about it.

The writer now carries through what it does not write. A table it does not
know goes back at the end of the file as the text it was, comments
included; a key it does not know goes back at the end of its own table,
with the comment lines right above it. The text comes from a small scan of
the file's layout (headers, key/value lines, multi-line strings and
arrays), so a bracket or a hash inside a value is never taken for a table
or a comment. A layout the scan cannot split, such as a dotted
`vim.motions = ...` before any table, is carried as data instead, without
its comments. Either way the result must read back with every carried
value or nothing is carried, so a rewrite never quietly drops a value or
leaves a file that does not parse.

Each write reads the file first, so a `zn config set` that landed after
the app last read the file survives the next settings change. A typo in a
table the app does not use no longer costs it its own settings: it reads
around that table and leaves it as written, for zn to report. Damage in
the app's own options still falls back as before, but zn's tables now
survive the save that follows. An entry the user sets in the app replaces
a carried one with the same key rather than duplicating it.

The file header and the in-app manual say that tables and keys the
desktop app does not use stay as they are. Comments next to the app's own
options are still rewritten, as the header has always said.
The bundled CLI is the floor a desktop build guarantees, and 0.14.0 is
the newest release (ZenNotes/tui#31). It brings month, week and day views
to the TUI's Tasks calendar (#30), a bar cursor while you type (#29), a
note's own view mode from its frontmatter (#28), new notes that always
open in the editor (#26), [b and ]b from every view (#27), and settings
grouped by the file they save to (#22).

The pin is the release's terminal-release.json copied byte for byte
after it verified against the zn-release-1 key this app trusts
(cli-update-core.ts), naming source commit 072a3866 (the v0.14.0 tag),
integration protocol 1, and four archives that match both the manifest
and checksums.txt. Staged through `npm run terminal:stage`; the native
darwin-arm64 binary reports zn v0.14.0. The in-app manual's CLI card
says 0.14.0.
…y folder and column colors

The window after the update names the release's six things people will look for: recurring tasks and reminders (#1036), New Kanban (#1038), column colors (#1037), the folder picker (#1033), lists that keep their bullets together, and config.toml keeping what only zn uses, with the bundled zn at 0.14.0.
…attern

CodeQL flagged the list-Enter test's mount helper (alert #145,
js/incomplete-sanitization): `marked.replace('|', '')` replaces only the
first `|`, which reads like an escaping bug. Here it was not one, since the
single `|` marks the caret and the helper had already found it with
indexOf. The doc now slices that one character out at the index it found,
which says what the helper means and gives the scanner nothing to flag.

No behavior change: the nine tests pass as before.
… they cannot honor

Settings, Tasks, Reminders showed on every host, but reminders fire only
from the desktop app's main process: the renderer hands its schedule to
window.zen.setTaskReminders, which only the desktop bridge has. On a phone
or in the web app the toggle and the reminder time did nothing.

Settings now shows the section, and lists its two rows in Settings search,
only where that method exists, the same check task-reminders-runtime makes
before it schedules anything. The help entries say the setting is desktop
only. Writing @remind: on a task still works everywhere: it is part of the
note, and the desktop app fires it.
…first column

Open a Kanban, scroll its Board right, then make or open another database
in that pane: the new board opened scrolled just as far, its To do column
and focused Add card off screen. The iPhone walk of the 2.81.0 candidate
core found it, and the desktop does the same (a 564 px pane: the second
board opened at scrollLeft 592).

EditorPane rendered DatabaseView without a key, so React kept one instance
across databases and with it the Board's scroller, the raw-CSV toggle and
any open menu. It is now keyed by its tab, as GalleryView already is, and
each database starts fresh. Renaming a database moves its tab, so the view
remounts then too; the title field has closed by that point, so the rename
loses nothing but the scroll position.
…p folder menu does

New Kanban (#1038) reaches the phones as core's palette command, which
puts the board where new databases go. The desktop also offers it from a
folder's menu, but the phones' drawer builds its folder sheet from core's
public Browse actions, and core exported no folder-scoped Kanban create, so
the phones could not offer it there.

createBrowseKanban(host, directory?) joins createBrowseDatabase in
@zennotes/app-core/browse and places the board exactly as that one places a
database: the configured location without a directory, that folder with
one, refused inside a database folder or for a folder that is not there.
Both now share one placement path. The store's createKanban takes the same
isCurrent guard createDatabase takes, so a vault switch mid-create leaves
nothing behind.
…e drawings row is

"Default databases location" was described as "Same options as drawings,
applied to new databases." The phones hide the drawings row, so on a phone
it pointed at nothing. It now reads "Where new databases and Kanban boards
are created." Its options are drawn beside it on every device.
The phone walks of 2.81.0 showed "Open page · Enter" on the new database
card menu, and the Tasks board's menus already showed "Rename column ·
Enter" and the like. A menu item's hint is a keyboard shortcut, and a phone
has no keyboard to press it on. The phone shells hide hardware-key hints in
the palette with CSS (#842), but menus draw theirs from ContextMenu, which
nothing reached.

ContextMenu now leaves `hint` out on a touch-first device (a coarse
pointer, the same test the Vim IME guard uses). The few items whose hint
was not a key move to a new `mark` field, shown on every device: the dot
beside a database field's type or option source, the dates on the task
menu's Due today / tomorrow / next week, a code language's tag, why a
callout item is unavailable, the arrow on the sort field in use, and the vault switcher's Current and Remote.
The desktop draws exactly what it drew before.
In a pane about 560 px wide (a split, or a small window) the database
header's tools did not fit on its one 48 px line. Every tool button is
fixed-width, so the title gave way first: it shrank to a sliver, and the
"..." menu ran off the right edge. It was so in 2.80.1 too; the 2.81.0
check of the database scroll fix showed it.

The header now wraps the way GalleryView's does. When room runs out, the
tools drop to a second line whole, and the title keeps its width. A wide
pane still draws one 48 px line (checked at 1200 and 1700 px windows), and
the phones' own header layout, which already wraps, is unchanged.
Open a note, go to Tasks, open a task: the note opens at its line. Then
Back on a phone, or Ctrl+O on the desktop, skipped Tasks and landed on the
note before it. The iPhone walk of the 2.81.0 candidate core found it; the
desktop and the shipped apps do the same.

openTaskAt opened the note through openNoteInPane, the raw tab primitive,
which keeps no history, and never recorded the jump the way
openNoteAtOffset (#484) and the Tasks and Tags views (#633) do. It now
records it first, so Tasks sits on the jumplist behind the note. This
covers every way a task opens: the Tasks list, the calendar's rows, board
cards, and the phones' widgets and deep links.

Checked on the desktop build: from Reading, :tasks, Enter on a task, then
Ctrl+O lands on Tasks (it landed on Reading before), and a second Ctrl+O
on Reading.
…ow says

Searching Settings for the databases location showed the old line, "Where
new databases are created: ...", while the row itself now names Kanban
boards too (2dc9388). The search item says the same now. Found by the
iPhone walk of the 2.81.0 candidate core.
…o move the window

While anything is dragged inside the app, the window-drag strips stand down
(the zn-app-dragging class) so a tab or note can drop on them, and come back
on dragend or drop. A dragstart that something cancels later in its
dispatch never sends either: the image resize handle cancels one, and so do
the phones' long-press guards. The class stayed, and the title bar no longer
moved the window until some later drag ended. The Android walk of the
2.81.0 candidate core found the leftover class.

Once a dragstart's dispatch is over, a cancelled one now ends the drag.
Checked on the desktop build: a cancelled dragstart leaves no class behind
(it stayed before).
A new Kanban opens with its Board focused, so the keyboard keys work at
once, and the focused card or Add card wears a ring as the keyboard's
cursor. On a phone there is no keyboard to move it, and every New Kanban
opened with Add card ringed (the Android walk of the 2.81.0 candidate
core). A touch-first device now draws no ring; the desktop keeps it.
Settings, Vault, Notes titled its location rows "New Drawings, Databases &
Tasks". The phones hide the drawings rows, and the section also holds the
shares location, so the title was wrong on a phone and short on a Mac. It
is now "Default Locations", which every row in it already starts with, and
the description says where new files go without listing them. The manual
and the New Kanban notes name the section the same way.
The Tasks list tints its cursor row and shows inline key hints on it ("⏎
open"), and the board rings its cursor card, header or empty cell. Both
mark where the keyboard is. A phone has no keyboard to move them, yet its
list opened with the first row tinted and hinted and its board with a ring
(the Android walk of the 2.81.0 candidate core; already so before 2.81).

A touch-first device now draws neither, as the database Board does since
0628c9e. Only the drawing changes: the cursor itself, its refs and the
scroll that follows it are as they were. Checked on the desktop build: the
list still tints its cursor row and the board still rings its header.
"Where new task files (from `+ New task`, the `a` key, `:newtask`, or the
command palette) are created. `New Task in Folder…` and `:newtask
<folder>` still override this per task." On a phone that named keys it has
no keyboard for, and it was the longest line in Default Locations. The row
now reads "Where new task files are created."; the manual's Default
Locations entry carries where task files come from and the per-task
overrides.
…y hints

507f117 imported isTouchPrimaryDevice for the key-hint rule while the file
already had coarsePointer(), the same (pointer: coarse) test its find field
uses. The menu now reads the one it has. No behavior change.
…Tasks calendar, Tags or the Timeline

The iPhone walk of the 2.81.0 candidate core found two more keyboard
cursors a phone drew with no keyboard to move them, both older than 2.81:
the database Table opened with its first cell ringed, and the Tasks
calendar's day list ringed its first task. The Tags view and the Timeline
tint their cursor row the same way.

useKeyboardCursor() (lib/keyboard-cursor.ts) answers whether to draw the
cursor: not on a touch-first device. The Table's active cell and header,
the calendar's day list, Tags and the Timeline now ask it, and so do the
database Board (0628c9e) and the Tasks list and board (1673446), which
had their own copies of the check. Only the drawing changes; a selected
calendar day is a selection, not a cursor, and keeps its ring. Checked on
the desktop build: the Tasks list's cursor row, the board's header ring
and the Table's active cell ring all still draw.
@adibhanna
adibhanna merged commit bcce61e into main Nov 2, 2026
10 checks passed
@adibhanna
adibhanna deleted the v2.81.0 branch November 2, 2026 00:38
@adibhanna
adibhanna restored the v2.81.0 branch November 2, 2026 00:38
@adibhanna
adibhanna deployed to boundary-artifacts November 2, 2026 01:05 — with GitHub Actions Active
@adibhanna
adibhanna deployed to boundary-artifacts November 2, 2026 01:06 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
boundary-artifacts — bcce61e1 Deployed Nov 2, 2026 by adibhanna via draft #109
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.

2 participants