Skip to content

ZenNotes 2.41.0: code stays code, tables learn yank and paste, and a web-exported note carries its title - #717

Merged
adibhanna merged 4 commits into
mainfrom
v2.41.0
Sep 1, 2026
Merged

ZenNotes 2.41.0: code stays code, tables learn yank and paste, and a web-exported note carries its title#717
adibhanna merged 4 commits into
mainfrom
v2.41.0

Conversation

@adibhanna

@adibhanna adibhanna commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Release branch for 2.41.0. Work accumulates here; main fast-forwards at release time.

Shipped so far

Fix(editor): Auto-close Markdown keeps its hands off code (9cb2008, closes #718)

Typing a == b inside a fenced code block wrapped the == into a ==highlight== snippet, and the only escape was disabling Auto-close Markdown entirely. The snippet engine now consults the syntax tree at trigger time: inside FencedCode or InlineCode no inline pair fires (==, **, ~~, backtick, [[, %%), and a $$ typed inside a code block no longer expands on Enter. Prose is untouched, and the fence-opener line stays exempt so ``` + Enter still expands (an opener parses as unclosed code the moment it is typed). A rule, not a setting: formatting means nothing inside code. Validated live over CDP, plus 6 new parser-backed unit tests.

Fix(tables): yank and paste work inside table cells, through the real registers (87b316f, closes #706)

In Vim mode a WYSIWYG table cell runs its own modal normal mode, and that mini-Vim knew how to delete but not how to move text: y and p were swallowed as stray keys, and visual-mode y wrote only to the system clipboard, where no p could reach it. The cell's y/d/p now go through codemirror-vim's register controller, the same unnamed register the main editor uses, so text moves freely in both directions: yy a cell and p it onto a line of prose, or yiw a word in the body and paste it into a cell. y works as an operator (yy/Y, yw, y$, yiw, ya"), p/P paste around the block cursor, visual y/p behave like Vim (including the register swap on visual paste), and deletes feed the register so dd then p moves text. With "Sync clipboard with Vim registers" on, table yanks ride the system clipboard exactly like editor yanks; a multi-line register flattens to one line on paste so a row's markdown can never break. Verified live over CDP, plus 8 new unit tests.

Fix(export): a note exported from the web build carries its title (1f1dbff)

Exporting a note to PDF from the self-hosted web app dropped its title whenever the body did not already open with an # heading. The shared withExportTitle rule (body H1 wins, then frontmatter title:, then filename) was wired into the desktop PDF window, Word export and copy-for-email in 4214c59; the web export window predates that by three months and was never connected. This connects it, and resolves document.title through it too, since the browser seeds the saved PDF's filename from there. Found while investigating #715, whose wikilink-image bug does not reproduce (the markdown pipeline converts ![[image.png]] before the export ever sees it), but which correctly sensed the web export window was missing something the other exports had.

Verification

  • npm run typecheck clean, npm run test:run green (4,007 tests)
  • All three fixes exercised against the built app over CDP with isolated stores; the table fix's full matrix and the auto-close matrix (code stays literal / prose still wraps / fence opener still expands) passed live with the saved markdown as proof
  • The web export change mirrors the verified desktop path at code level and has not been run through the full web stack

https://claude.ai/code/session_01GCwiToGRTKKsDENaY56Vm2

A note whose body does not open with its own `# heading` exported from
the self-hosted web build as a PDF that starts mid-thought, with the
title nowhere on the page. The same note exported from the desktop app
got its title. A note that named itself in frontmatter (`title:`) also
came out under its filename instead, both in the document and in the
filename the browser's print dialog suggested.

The rule already exists and is shared: `withExportTitle` resolves the
title (a body H1 wins, then frontmatter `title:`, then the filename) and
inserts it as a leading heading when the body does not state one, so it
flows through the ordinary rendering pipeline. 4214c59 wired it into the
desktop PDF window, the Word export and the copy-for-email path, and its
own header note says it covers "every export format that builds on this
module". The web export window predates that change by three months and
was simply never connected to it.

So this connects it. It is the same call the desktop window makes, on
the same shared helper: no new rule, no second definition of what an
export title is, and nothing changes for a note that already opens with
its own H1. `document.title` is resolved through it too, because the
browser seeds the saved PDF's filename from there and a note titled in
frontmatter should not save itself under its filename.

How to test locally: run `npm run dev:web` with the Go server on :7878,
open a note whose body has no `# heading` (a frontmatter-titled note is
the clearest case), and export it as a PDF. Before: the PDF opens on the
first paragraph and the title appears nowhere. After: the note's title
is the first line, and the print dialog offers it as the filename.

Claude-Session: https://claude.ai/code/session_01GCwiToGRTKKsDENaY56Vm2
… registers

In Vim mode a WYSIWYG table cell runs its own modal normal mode, and
that mini-Vim knew how to delete but not how to move text: `y` and `p`
were swallowed as stray printable keys, and the one yank that existed
(visual mode `y`) wrote straight to the system clipboard, where no `p`
could reach it. You could destroy text in a table. You could not move
it, and nothing yanked in the note body could be pasted into a cell.

The fix is not a private cell clipboard, on purpose. The cell's y/d/p
now go through codemirror-vim's register controller, the same module
state the main editor's motions use ('"' is the unnamed register object
itself in the vim dist), so text moves freely in both directions: yank
a word in your prose and paste it into a cell, or `yy` a cell and `p`
it onto a line. It also means cm-vim-clipboard's pushText patch applies
unchanged, so with "Sync clipboard with Vim registers" on, table yanks
and pastes ride the system clipboard exactly like editor yanks, with no
second implementation of that setting.

What the cell understands now: `y` as an operator (`yy`/`Y` for the
cell, `yw`, `y$`, `yiw`, `ya"`, the motions the existing `d`/`c`
operators already took), `p`/`P` around the block cursor, visual `y`,
and visual `p` replacing the selection with the Vim register swap.
Deletes and changes feed the register too, so `dd` then `p` moves text
the way it does everywhere else. A multi-line register flattens to one
line on paste (interior breaks become spaces) because a cell is one
line of a row's markdown and a pasted newline must never break the
table.

One deliberate behavior change rides along: visual-mode `y` in a cell
used to hit the system clipboard unconditionally; it now respects the
clipboard-sync setting like every other yank in the app.

Verified live over CDP against the built app, vim mode on: cell to
cell, cell to body, body to cell, dd+p round-trip, visual yank and
paste, and the serialized markdown on disk after blur carries every
edit.

How to test locally: npm run dev, vim mode on, a note with a table.
Click a cell, `yy`, click another cell, `p`: the text lands there. Esc
to the body, `yiw` on a word, back into a cell, `p`. Before: those keys
did nothing in a cell. After: they behave like Vim.

Closes #706

Claude-Session: https://claude.ai/code/session_01GCwiToGRTKKsDENaY56Vm2
@adibhanna adibhanna changed the title ZenNotes 2.41.0: a note exported from the web build carries its title ZenNotes 2.41.0: yank and paste reach table cells, and a web-exported note carries its title Sep 1, 2026
Typing a == comparison inside a fenced code block and following it with
a space wrapped the pair into a ==highlight== snippet, because the
Auto-close Markdown engine never asked where it was: every inline pair
(==, **, ~~, a backtick, [[, %%) fired anywhere in the note. The only
escape was turning the whole feature off, which trades one wrong
trigger in code for losing auto-closed bold, fences, and math blocks
everywhere else. That trade is what #718 was living with.

The snippet engine now consults the syntax tree at trigger time: inside
a FencedCode block or an InlineCode span, no inline pair fires, and a
$$ or nested fence marker typed inside someone else's code block no
longer expands into a block on Enter. This is a rule, not a setting, on
purpose: markdown formatting means nothing inside code, so there is no
situation where firing there is right, and no toggle is worth the
explanation it would need.

Two edges are deliberate. The check runs only at trigger time, on a
settled state, never inside the pending-block StateField update, where
the syntax tree can be one keystroke stale. And a fence's own opener
line stays exempt: the moment ``` is typed it parses as an unclosed
FencedCode, so a naive inside-code test would kill the Enter-to-close
snippet that the feature is named for.

Verified live over CDP against the built app: == then space inside a
fence stays literal, the same keystrokes in prose still wrap, and a
bare ``` still expands with Enter, with the saved markdown as proof.

How to test locally: npm run dev, a note with a fenced code block, type
`a == b ` inside it. Before: on the space, == wrapped into a highlight
pair around the cursor. After: it stays code, and == followed by space
in a prose paragraph still wraps like it always did.

Closes #718

Claude-Session: https://claude.ai/code/session_01GCwiToGRTKKsDENaY56Vm2
@adibhanna adibhanna changed the title ZenNotes 2.41.0: yank and paste reach table cells, and a web-exported note carries its title ZenNotes 2.41.0: code stays code, tables learn yank and paste, and a web-exported note carries its title Sep 1, 2026
@adibhanna
adibhanna merged commit 18d9d2b into main Sep 1, 2026
6 checks passed
@adibhanna
adibhanna deleted the v2.41.0 branch September 1, 2026 16:28
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.

[Feature] Auto-close Markdown conflict resolution Can't yank from and paste into table column

1 participant