ZenNotes 2.23.0: not every checkbox is a task
A reading list can be a checklist without flooding your Tasks view: opt a note out with one line of frontmatter, retire whole folders from the sidebar, and keep a project note on the board without its checklist spilling onto it. Plus display math inside table cells now renders in the reading view. Installers for macOS, Windows, and Linux (x64 + arm64) are attached below.
✨ New
-
Separate checklists from tasks. Until now one rule decided everything: every
- [ ]line in a live note was a task, and a task with no due date landed in Today, the most prominent bucket in the app. So a reading list, a movie backlog, or a project checklist did not just appear in Tasks, it sat on top of your real work, and the only escape was splitting vaults.Two mechanisms fix that, both stored in the vault itself so every surface agrees (desktop, web, the self-hosted server,
zn task list, MCP, and mobile). Addtasks: false(oroff) to a note's frontmatter and it becomes a pure checklist: checkboxes render and toggle exactly as before, the note just stops feeding the Tasks views, the boards, the calendars, the CLI, and MCP. It also wins over atags: [task]file task. On a #task note,tasks: notekeeps the note's own card on the board while silencing its internal checklist, so a project is one card instead of twenty. And whole folders can opt out: right-click one in the sidebar and choose "Exclude from Tasks", or manage the list under Settings → Tasks. Because a database is a folder on disk, this covers "exclude this database" too.Exclusions are total on purpose: no surface shows a different truth. When you do want everything, the escape hatches are explicit:
zn task list --include-excluded,includeExcludedon the MCPlist_taskstool, and?includeExcluded=1on the server's tasks API. Toggling a task by its id still works even if it is excluded, because an explicit id is an explicit ask.Deliberately not built: the Obsidian-Tasks-style per-line
#taskopt-in. As a default it would empty every existing Tasks view on upgrade; the vault settings leave room for it later as a per-vault mode. View-side filtering (by tag, query, or folder) is a separate feature and stays on the list. (Asked for by @littlelinuxoid in #458, with the folder angle from @naingyeminn, the frontmatter angle from @yfernandes, and the project-note case from @i-zhivetiev. @vlcinsky's filtering idea is the deferred half.)How to test locally: add
tasks: falseto a checklist note's frontmatter and watch it leave the Tasks view while its checkboxes keep toggling in the editor. Puttags: [task]plustasks: noteon a project note with checkboxes and see exactly one card on the Kanban. Right-click a folder → "Exclude from Tasks", then comparezn task listwith and without--include-excluded.
🐛 Fixes
-
Display math inside a table cell renders in the reading view. A worked answer like
$$\frac{800}{10000} \times 100\% = 8\%$$living in a table cell rendered in the editor but came out as raw source in Preview and Split, so the same cell told two different stories. The culprit was our own currency guard: the rule that keeps "I paid $5 and got $10 back" from rendering as a formula re-checked every inline math span against the editor's strict single-dollar rule, and a double-dollar token can never pass a one-dollar test, so genuine math was demoted to literal text.The guard now recognizes
$$…$$inside a table cell as what it is, math with no currency reading, and renders it in display mode, under KaTeX and Typst alike. Mid-line$$in ordinary prose stays literal, because the editor treats it that way too, and currency in cells and prose stays plain text. (Reported by @BTC_1Ly on X, who then found the single-dollar workaround in the docs; the double-dollar form they wrote first is legal and now just works.)How to test locally: put
| 9 | B | $$\frac{800}{10000} \times 100\% = 8\%$$ |in a table and open Split. Before this release the right pane shows the raw source; now both panes show the rendered fraction, and$5anywhere still stays text. -
The self-hosted server's single-note task rescan respects the trash. Rescanning one note's tasks on the Go server parsed whatever path it was handed, including trashed notes the full scan skips. It now applies the same gates as the full scan, folder exclusions included, so an edit can never resurrect tasks the walker would drop.
🧰 For contributors
- The in-app manual got the same drift-audit pass the website docs received: find-and-replace,
Mod+O, editor mode shortcuts, tag rename/delete, rename-fixes-wikilinks, task reordering keys, database header/row menu keys, and more now match the running app on both surfaces. - The frontmatter
tasks:value set lives innoteTasksModein shared-domain and is imported by the MCP parser; the Go mirror inparse.gomust stay byte-identical. The excluded-folders rules aretasks-excluded-folders.tswith the Go mirrortasks_exclude.go, same discipline assystem-folder-paths. - Adding a field to Go
VaultSettingstakes three synced spots: the struct intypes.go,normalizeVaultSettings, andcloneSettings. The clone rebuilds field by field, and a forgotten field survives every unit test while the live server silently drops it;TestScanTasksHonorsExcludedFoldersnow walks the full path (constructor, settings cache, clone, scan) so the next forgotten field fails loudly. - No YAML library exists anywhere in the app: frontmatter values are raw strings, so
tasks: offis the string "off" in every runtime. Any new frontmatter toggle should match lower-cased strings, not booleans.
Keyboard-first and local-first, as always.