Skip to content

Release 2.73.0: move the window by its tab strip, and a title bar switch in the browser - #968

Merged
adibhanna merged 10 commits into
mainfrom
v2.73.0
Oct 20, 2026
Merged

adibhanna merged 10 commits into
mainfrom
v2.73.0

Conversation

@adibhanna

@adibhanna adibhanna commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

ZenNotes 2.73.0: desktop, released with zn 0.9.1 (already out), server 2.73.0 with Docker, the boundary packages, the website release page, and both phones on core 2.73.0.

Fixes

  • With the title bar off, the tab strip moves the window, and the browser app can hide its title bar (4df2a6bc, reported on Discord): chromeCarriesWindowDrag and isPaneOnTopEdge in lib/window-chrome.ts; the top panes' tab strips, the sidebar header and the note list header become drag regions with tabs and buttons no-drag; onboarding and the no-vault screen keep a strip; in-app drags lift the regions; the web runtime honors showWindowTitleBar and shows the switch as Title bar.
  • Settings rows wrap under their label in a narrow pane (56937694, c634af68, 49fb604a).
  • Vim o and O keep the quote marker on a quoted line (Enhancement: Automatically add the '>' callout syntax when adding a LaTEX block inside a callout #961, 890ef3c8).
  • Callouts and code fences redraw when a cut parse finishes (10597fe9), found by this PR's Windows run: the block plugin rebuilt on edits, selection, viewport and folds but not on a tree-only update.
  • Perf harness: the markdown stack is the editor's; the guard is now "not fetched before App mounts" (dc3cef58).

Also

  • Bundled zn 0.9.1, verified against zn-release-1 (f743c04f); What's New 2.73.0 (3180c55e); version bump (60ddd549).

Gates on 60ddd549 (pack and launch check repeated on 10597fe9), in a clean checkout with its own npm ci: typecheck 8/8 uncached; shared-domain 2,048, app-core 3,328, desktop 1,183 and quicklook 15 uncached; fresh build:prod green; npm run pack and the packaged launch check pass (page target, rendered, 1.6 s, 2.73.0, Developer ID team WYY7PK57DM); vim-editor, sidebar-vim and editor-improvements pass on the first run. ci-sweep.py v2.72.0: no failed jobs. hazard-scan.py --since v2.72.0: no hazards. dash-lint clean on the notes and every commit. The title bar fix was verified in the built app over CDP and with a real mouse drag (the window moved by exactly the drag); the web switch in a browser against a local server.

…t instead

ZEN_PERF_ENFORCE=1 failed every run of perf:desktop-runtime on the
deferred-chunk check, because vendor-markdown and vendor-highlight load
in the normal flow. That was already true of 2.71.0; ENFORCE being off
was the only reason its run passed.

The list is from #37, when markdown rendering belonged to Preview. Since
then the live preview took the stack into the editor: table cells render
through renderMarkdown (cm-table's renderInlineCell, 2.4.0, kept on the
reading view's parser on purpose by #559), so do comment bodies in
CommentsPanel, and formulas render through KaTeX (cm-math-render,
cm-latex-completions), all synchronously in a widget's toDOM or a render.
KaTeX lives in vendor-markdown, which statically imports vendor-highlight
through lowlight, so each of the four puts both chunks in the Editor
chunk's static graph. App also warms the Editor module during boot
(workspace.ready waits for it), so the stack arrives before any note is
open, by design.

Deferring it would paint raw source for a frame on the first table,
formula or comment of a session. Stubbing the four imports out in a
throwaway build bought 5 to 9 ms on the first note open and 1.3 MB of
heap, and nothing on boot or note switches. The check was wrong, not the
app.

The two chunks leave the normal-flow list and get the check that still
means something: they must not be fetched before App's first render.
Anything that early is in the entry's static graph, ahead of first paint,
which is the failure e7a9879 fixed (a manualChunks rule hoisted mermaid
into the entry and vendor-markdown came with it). The renderer loads over
file://, which Resource Timing never records, so the window is timed from
each CDP request's wallTime against the page's timeOrigin and the
renderer.app.mounted sample. The report prints the chunks fetched before
mount.

When that timing cannot be taken (no renderer.app.mounted sample, as in
an older ZEN_PERF_DESKTOP_APP_PATH build, or the entry chunk outside the
window, which means the two clocks disagree), the report says
"unavailable", and under ZEN_PERF_ENFORCE the run fails and says which
of the two it was. A guard that quietly cannot run would pass exactly the
regression it is there to catch.

No app change. On the 764c10d build, interleaved: the old harness exits
1 in 4 of 4 runs, the new one exits 0 in 4 of 4 with the same numbers
(ready-to-show 205 ms, workspace ready 531 ms, first note open 110 ms,
switch p50 33.6 ms), and the before-mount list is exactly the entry's
static closure (index, vendor-react, app-store, vendor-editor,
app-wikilinks). A build importing renderMarkdown from main.tsx fails the
new check with both chunks named, and a copy of the harness forced into
the unavailable case exits 1 under ENFORCE and 0 without it. The final
harness passes ENFORCE on a fresh build. typecheck 8/8, app-core 3,308
tests, desktop 1,183 tests.
On an iPad or an Android tablet, Settings > Vault > Location showed the
Vault name label and its description one word per line, with the last
word of a sentence running under the field. A narrow desktop window did
the same at a milder scale. Every text field in Settings had the problem,
and so did the daily, weekly and monthly note template pickers.

TextInputRow put its field on one line beside the label and the line
never wrapped. The field was 23rem and could not shrink, and its only cap
was half the viewport. Settings is a modal much narrower than the
viewport, so that cap never engaged before the label ran out of room:
the label column was the one thing that gave way. TemplateSelectRow used
the same field classes and broke the same way. Phones never showed it,
because the phone shells already stack every Settings row.

Both rows now wrap the way the Vault Location row does since #871. The
label keeps a 14rem minimum, the field moves under it when the row is
too narrow, and the field is capped at the row's width instead of the
viewport's. At full width nothing moves.

Not done here: SegmentedRow, FontRow, SliderRow and NumberRow have the
same unwrapping shape with narrower controls. The worst of them, the
Default drawings, databases and tasks location rows, still collapse at
tablet widths; that is a separate change.

Measured over CDP on a built app at 1280, 1100, 1000, 900, 800, 768, 700
and 600 px, across the 27 rows that use the two components (periodic
notes on): before, every row's label was 0 to 18 px wide at 800 px and
below, and the Vault name label was 124 px with 7 description lines at
900 px, the desktop's minimum window. After, no row overflows or
overlaps at any width, the Vault name label is 384 px with 3 lines at
768 px, and layouts at 1280 and 1100 px are unchanged. The template
dropdown opens under its wrapped trigger. app-core typecheck clean.
Settings is a modal (min(1120px, 96vw) with a 252 px nav), so at tablet
widths and at the 900 px desktop minimum window its content pane is much
narrower than the viewport. SegmentedRow, FontRow and SliderRow put a
fixed-width control on one unwrapping line beside a min-w-0 label
column, so only the label gave way. At 768 px the Default drawings,
databases and tasks location rows had a 0 px label column with 29 to 31
description lines, and their three options sat on top of the description
and ran past the card. Completed task style got 88 px, the font pickers
118 px, the sliders 148 px. At 900 px the location rows were 117 px.

The three components now use the Vault Location pattern from #871: the
row wraps, the label keeps at least 14rem before the control moves under
it, and the control may not exceed the row. A segmented group also wraps
its options within itself when even a row of its own is too narrow (600
to 700 px), and a slider's track gives way before its value does.

The periodic-notes token table chose two columns on a viewport
breakpoint, which kept two columns in a 374 px card at 768 px: meanings
got 25 px, broke a word per line and collided with the second column's
tokens. It now keeps two columns only while the card has room for them
(the same widths as before at 1000 px and up, one column below), and a
meaning drops under its token when less than 6rem is left. The Saved
Remote Workspaces intro line, squeezed to 77 px and 8 lines beside New
Remote at 600 px, wraps the button under it.

Not touched: NumberRow, which has no call sites, and the Keymap rows and
a few one-off rows (Reset to defaults, the Open today's note buttons,
template and workflow rows) that squeeze the same way below 768 px.
The rows keep the classes the phone shells' mobile.css matches, so
phones keep stacking them.

Measured over CDP on a desktop build at 1280, 1100, 1000, 900, 800, 768,
700 and 600 px across every Settings category and tab (249 label and
control rows): at 1280 and 1100 no row changed height, label position,
line count or control placement, and the token table and intro line are
identical. At 768 px no segmented, font or slider label is under 160 px
(was 18 of them), the token table has no cell past the card's edge (was
6) and the location rows sit under full-width descriptions of 1 to 3
lines. The font picker's dropdown opens directly under its wrapped
trigger. typecheck clean.
The Keymap rows put a binding chip and three buttons (about 275 px) on
one unwrapping line beside a min-w-0 label, so in the Settings modal's
narrow content pane only the label gave way. At 768 px all 130 rows had
a label under 160 px, the narrowest 16 px, and Hop past next marker ran
38 description lines in an 836 px row; at 700 and 600 px every row broke,
with chips past the card's edge and the list scrolling sideways. At the
900 px desktop minimum the labels got 211 px and 14 rows ran 7 to 13
lines. The header's 288 px filter field ran past the card below 768 px.

The same squeeze hit a few one-off rows below 768 px: Reset to defaults
in Periodic notes, the Open today's, this week's and this month's note
rows, Create a custom template, the Built-in templates row and every
template row, and the Guided tutorial and Built-in recipes rows in
Workflows (7 px wide at 600 px).

They all take the Vault Location pattern from #871: the row wraps, the
label keeps at least 14rem before the controls move under it, and a
group of controls wraps within itself rather than run past the row. The
filter field shrinks to a narrow card and Reset all wraps under it. The
rows keep the classes the phone shells' mobile.css matches, so phones
keep stacking them.

Measured over CDP on a desktop build at 1280, 1100, 1000, 900, 800, 768,
700 and 600 px across every Settings category and tab (249 rows): at
1280, 1100 and 1000 no row changed height, label position, line count or
control placement. At 900 most keymap rows now put the binding under the
title; the list is 7% taller and its tallest row drops from 316 to 218
px. At 768 no keymap label is under 160 px, nothing runs past the card,
and the list is 43% shorter. typecheck clean.
On a line inside a blockquote or callout, Vim's o and O opened a bare
line. The open-line action only knew list items (#320); everything else
fell back to codemirror-vim's own open-line, which copies leading
whitespace and nothing else, so the `>` was dropped and the new line left
the quote. On a code line in a quoted fence that broke the block, as
Enter did before aac55e5. Insert-mode Enter already kept the marker.

The prefix for a quoted line is now read off Enter's own commands: the
quoted-fence command, then lang-markdown's markup continuation, run at
the end of the line on a scratch state. The new line starts with what
Enter would start it with, so o and Enter cannot disagree: `> - item`
opens `> - `, `> 1. one` opens `> 2. ` (the renumber pass moves the rest),
`>   more` keeps its item indent, and a code line in a quoted fence opens
`> ` plus its indentation. Where Enter ends the markup instead (an empty
list item, a second blank `>` line), o still continues it, as it does on
an empty item outside a quote: the line is read with placeholder content.
insertNewlineContinueFencedCodeIndent became a StateCommand so it can run
on a scratch state; what Enter does is unchanged.

The action and its o/O mappings moved from Editor.tsx into
cm-vim-open-line.ts (registerOpenLineContinuation, like the heading
motion), so tests can press the real keys. Lines outside a quote take the
same path as before: list continuation, else Vim's open-line. With Vim
mode off nothing changes.

12 tests through a real codemirror-vim mounted like the note editor; the
7 quote ones fail without the change. Driven over CDP on a built app with
Vim on: 17 quote scenarios (o and O on quotes, callouts, nested quotes,
quoted lists, tasks and ordered lists, quoted fences, a count and `.`)
opened a bare line before and keep the quote after; the 4 controls
outside quotes are identical before and after. With Vim off, Enter in a
quote and in a quoted fence still keeps the marker. typecheck 8/8;
app-core 3,320 pass.
Turning off Settings > Appearance > Chrome > Window title bar left one
place to drag the window by: blank space in the sidebar header. With the
sidebar closed nothing at the top of the window was a drag region, so on
macOS the window could not be moved at all (reported on Discord, with a
video of a note open, the sidebar closed and the tab strip on top). In the
built app the top 40 pixels were 0 of 80 draggable points with the sidebar
closed and 1 to 5 of 80 with it open.

With the title bar off, the blank space of a pane's tab strip is now the
title bar, as the top tab headers are in Obsidian: tabs, back and forward
and every button keep the mouse, the rest moves the window. Only panes on
the window's top edge do this (isPaneOnTopEdge in lib/window-chrome.ts: a
row split keeps every child on the edge, a column split only its first),
so a tab strip in the middle of a stacked split is not a title bar. A pane
with tabs turned off uses its header the same way, and an empty pane with
neither gets a 32-pixel strip. The sidebar header's drag now reaches into
the padding above it, the note list header joins it, and onboarding and the
no-vault screen, which have no other chrome, keep a strip where the title
bar was. While a tab or note is dragged inside the app the strips stand
down (a zn-app-dragging class on the root), since a drag region belongs to
the window and may not take a drop.

The browser app could not hide its title bar at all: the switch was
desktop-only and TitleBar ignored the preference on the web. The switch
now shows there as Title bar, worded for a page, and hides the bar; the
page never gets drag regions (chromeCarriesWindowDrag is desktop-only).
Two tests that pinned the old browser behavior now pin the new one. The
in-app manual's entry says where to drag and covers the browser switch.

Verified in the built app over CDP: with the title bar off and the sidebar
closed, 67 to 72 of 80 points along the top are draggable (49 to 72 with
the sidebar open), the tab and the back button are not, and with the
title bar on nothing changes (80 of 80, the title bar). A real mouse drag
on the blank tab strip moved the window by exactly the drag, +150 and +80
points, and during an in-app drag the strips stood down and came back
after it. Onboarding with the title bar off has a 32-pixel strip, 80 of 80
draggable. In the browser app against a local server, Settings search finds
Title bar, turning it off removes the bar, and it stays off after a reload.
A drop on the blank tab strip could not be proven either way: the scripted
mouse never completed a drop, even with the title bar on. typecheck 8/8,
app-core 3,328 tests.
What's New opens on the first launch after the update with three highlights. First, moving the window by the blank tab strip with the title bar off, and the browser app's new Title bar switch. Second, Settings rows that move under their label in a narrow window or on a tablet instead of squeezing it. Third, Vim's o and O keeping the quote and list markers on a quoted line (#961).
The bundled CLI is the floor a desktop build guarantees, and 0.9.1 is the
newest release. It carries ZenNotes/tui#11 (by @Nebula-Arcanum): Glamour
styles a paragraph word by word, so a wikilink of more than one word
reached the preview's link style with escape codes inside it, the style
split them into visible characters, and the row grew past the pane until
the terminal wrapped it and the whole preview scrolled. 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 c6e98860, integration protocol 1, all six archives matching
checksums.txt). Staged through `npm run terminal:stage`; the native
darwin-arm64 binary reports zn v0.9.1. The in-app manual's CLI card says
0.9.1 and what it changes.
The live preview's block plugin (wysiwygBlocksPlugin: callout cards,
hidden code fences, bullets, quote bars, rules) rebuilt its decorations
on an edit, a selection change, a viewport change or a fold, but not when
the syntax tree changed on its own. CodeMirror parses under a time budget
and finishes a cut parse in a later update that changes nothing else, so
on a slow machine or in a long note a callout could stay drawn as raw
`>` lines, with its code fences showing, until the next keystroke or
cursor move. CI's Windows runner hit it on the release PR for 2.73.0: the
test "hides a code block's closing fence inside a callout too" read the
decorations right after its last change and found the whole callout raw
(expected '# T> Log> ```> fetch: done> ```End.' not to contain '```').
The test has passed on every other run since #853 added it in 2.57.

The plugin now also rebuilds when syntaxTree(update.startState) differs
from syntaxTree(update.state), as the main live-preview plugin already
does. The test harness finishes the parse after its last change before
reading, so it checks the whole tree's decorations. A new test changes
only the tree (the markdown language swapped in through a compartment, no
edit, selection or viewport change) and expects the callout card and the
hidden fences: it fails without this change and passes with it.

Not in this change: nine other plugins that read the tree without a
tree-change rebuild (callout and heading folds, code block font and
flair, hashtags, template tokens, highlights, list indent, wikilinks).
Each needs reading first; they are a follow-up.

app-core 3,329 tests, typecheck clean.
@adibhanna
adibhanna merged commit 10597fe into main Oct 20, 2026
10 checks passed
@adibhanna
adibhanna deleted the v2.73.0 branch October 20, 2026 14:52
@adibhanna
adibhanna restored the v2.73.0 branch October 20, 2026 14:52
@adibhanna
adibhanna deployed to boundary-artifacts October 20, 2026 15:25 — with GitHub Actions Active
@adibhanna
adibhanna deployed to boundary-artifacts October 20, 2026 15:27 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
boundary-artifacts — 10597fe9 Deployed Oct 20, 2026 by adibhanna via draft #65
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