A small, sharp release: two reader-reported bugs where ZenNotes was throwing your text away. A table cell holding just
-or+rendered blank in Edit mode, because the editor parsed the character as a bullet marker instead of showing it. And if you use tag-driven Typst preambles, your tag list had been filling up withletand a scatter of variable names, because a preamble's#letdefinitions were being read as hashtags. Both came down to the same mistake in different places: parsing text as something it is not. The preamble folder is configurable now as part of the second fix, so a folder you already calltypstis not a trap. Installers for macOS, Windows, and Linux (x64 + arm64) are attached below.
🐛 Fixes
-
Typst preambles stop dirtying your tag list. With tag-driven Typst preambles on, the vault's tag list filled up with
letand a scattering of variable names (#562, reported by @Batres3, who traced it to #486 themselves). A preamble is Typst source, not prose:#let vec(x) = bold(x)defines a variable and formulas call it back as#vec, and every tag scanner read a#wordafter whitespace as a hashtag. Notes in the preamble folder are now left out of the tag index on every runtime, the app, theznCLI, MCP, and the self-hosted server. Tags are the only thing skipped: a preamble keeps its excerpt, its wikilinks and its searchability, which is what #486 made preambles ordinary notes for, and tags never took part in resolving one.The preamble folder is configurable now, because skipping it is not free. A vault that already keeps ordinary tagged notes in a folder it happens to have called
typstwould have lost those tags with no way out. Settings → Editor → Typst preamble folder names it, the setting lives in vault.json so it travels with the vault and every runtime agrees, and moving it moves the preamble resolution and the tag exclusion together.Upgrading fixes existing vaults on first launch: the cached note metadata in
.zennotes/now records which preamble folder it was built under and is thrown away when that disagrees, so a polluted tag list is corrected on the next scan instead of lingering until each file happens to change.How to test locally: with the Typst renderer and Typst definitions from tags on, make
inbox/typst/physics.mdcontaining#let vec(x) = bold(x)and a$ #vec(v) $formula, plus an ordinary note with a couple of tags. Before: the sidebar TAGS section shows#letand#vecbeside your real tags. After: only your real tags. Then rename the folder in Settings and watch the exclusion move: the old folder's notes get their tags back, and the newly named folder's notes drop theirs. -
+and-stay visible in table cells. A cell holding just a plus or minus rendered blank in Edit mode while Preview showed it perfectly, so a table of signs or a placeholder column of dashes looked like an empty grid (#559, reported by @ShowhyT with a split-view screenshot that showed both panes disagreeing at once). The live-preview table widget rendered each cell by running its text through the block markdown parser, which read a lone-or+as a bullet-list marker and produced an empty list item: the character was parsed away, not mis-styled. The same root cause reached further than the report:#and1.rendered blank too,---turned into a horizontal rule inside the cell, and- xshowed onlyx.Cells are now parsed as what they actually are. GFM gives a table cell inline content and never blocks, so the text round-trips through the real GFM parser as a one-cell table. The editor widget and the reading view are now on the exact same parser rather than two implementations that can drift, and inline markup in cells (bold, code, links,
[[wikilinks]],#tags, math) is untouched.How to test locally: make a note with
| - | + |,| --- | --- |,| - | - |and open it in Edit or Split with Live Preview on. Before: an empty grid, while the Preview pane beside it shows the characters. After: every-and+visible in both panes. Click into a cell and the raw source still appears as usual.
🧰 For contributors
- The Typst preamble folder rules live once in
packages/shared-domain/src/typst-preamble-folder.ts, mirrored inapps/server/internal/vault/typst_preamble.goand tested on the same inputs on both sides. MCP imports the shared helpers rather than keeping a copy, and app-core'stypst-preamble.tsnow re-exports the shared predicate, so preamble resolution and tag exclusion cannot disagree about the folder.isTypstPreamblePathtakes the folder as a required argument on purpose: a caller that has not read the vault's setting would silently classify againsttypst. - Anything derived from a vault setting that also gets cached needs BOTH caches taught about it. The in-memory note metadata was the obvious one; the persisted
.zennotes/snapshot was not, and whole-root invalidation re-arms hydration, so clearing memory alone let the stale tags load straight back off disk. The snapshot now carries the folder it was built under. This was invisible to typecheck and unit tests and only showed up driving the real UI. - Known, pre-existing, NOT introduced here: settings search cannot switch tabs. Searching a setting that lives in a tabbed section (the shipped
math-rendererrow included) navigates to the section but strands you on its default tab. Worth fixing on its own; the new preamble-folder row behaves exactly like the existing ones. renderInlineCell(packages/app-core/src/lib/cm-table.ts) is the one place that turns cell source into HTML, and it now renders through a synthetic one-cell table instead of the bare block pipeline. The point is that the widget has no markdown semantics of its own: if a cell ever needs different rendering, change the pipeline, do not add an inline-only parser here. This repo already carries five synced copies ofstripCodeContentand four of the task-line parser; a sixth family was not worth the convenience.escapeCellis now exported frommarkdown-table.tsso the widget escapes pipes exactly the way the serializer writes them. What a cell displays and what its row writes to disk go through the same rule.- The renderer's error fallback used to reach the DOM as raw HTML through this path.
renderInlineCellnow falls back to escaped source text, so a cell can never silently render blank again.