Skip to content

Release 2.82.0: import from Notion and Obsidian, open a vault's folder, New Kanban column colors - #1046

Merged
adibhanna merged 8 commits into
mainfrom
v2.82.0
Nov 3, 2026
Merged

adibhanna merged 8 commits into
mainfrom
v2.82.0

Conversation

@adibhanna

Copy link
Copy Markdown
Contributor

Release 2.82.0 (Adib: "let's release all!", then "make sure to release TUI as well. and any other zennotes repo").

Checked before this PR

In a clean npm ci checkout at 6ea4775: typecheck, every test suite uncached, build:prod, npm run pack, the packaged launch check (page target, rendered, 2.82.0), and the three smoke suites. No failed CI since v2.81.1, no hazards, no high audit findings.

A candidate core from 0fbdd6c was walked on an iPhone simulator, a 13-inch iPad simulator and an Android emulator. That found one gap in the iPhone shell, fixed there: a long press on a database Board's column did nothing.

Not fully proven

Notion import ran end to end on one real workspace. Teamspaces and link pinning have not run on real Notion data.

Leaving Notion meant losing your structure or rebuilding it by hand. Import
from Notion (the palette, or Space I) now brings a workspace over: sign in
once, untick what should stay behind, and it arrives as one Notion Import
folder of plain files. Nothing already in the vault changes, and deleting
that folder undoes it.

Sign in with Notion uses Notion's hosted service, because the alternatives
stop short of "all of it". A public integration makes the person tick pages
on Notion's own screen and needs a secret on our server. A token means making
an integration and connecting every top-level page by hand; that route stays
behind "Use a token". The hosted service takes OAuth with PKCE and a loopback
redirect, needs no secret, and reads what the person can read. It answers in
Notion-flavored Markdown, so nfm.ts parses that into the REST block objects
and mcp-source.ts presents it as the same client. Collecting, paths, databases
and the report have one input whichever route brought the data.

Everything is built in staging under userData and moved in with one rename,
so a vault never holds half an import and a sync never uploads one. A run
stores each page as it reads it, so a stopped run picks up where it was.
Links need every page's final path, which is why reading comes first; files
download as they're met, since Notion's signed links last minutes.

Pages become notes in Notion's own hierarchy (Title.md beside Title/), and
links become wikilinks. Databases become real .base folders: typed fields,
relations as note links, table and board views with their columns and sorts.
Images, PDFs, audio, video and other files land in the folder's assets/,
including ones hosted elsewhere; one that can't be fetched stays a link, and
a link to a private address is never fetched.

A run on a real workspace is what shaped most of this. Notion's spec omits the
inline <code> tag its pages use. A page in a column names the invisible column
as its parent page, so the page it was found in wins. A view's filter can hide
rows, which would have lost every Done row in one database without a word, so
rows never come through a filtered view: an unfiltered view first, then an
unfiltered query, then SQL, and the report says so if a plan's quota stops it.
The editor shows a backslash as a backslash, so text is escaped only where it
would mean something, and one $ or [1] stays as typed.

What it does not do: formula and rollup columns keep their values and stop
calculating, calendar and gallery views become tables, and comments, page
history and permissions stay behind. Import Report lists what was left out.
Imported to-dos stay out of Tasks unless asked, and page icons are dropped
rather than leave a frontmatter line on every note. Desktop and local vaults
only. Teamspaces are read through search, untested on a real workspace.

A Board card now calls a note link by its title rather than its whole path,
which long import paths made obvious.

How to test locally:
  npx turbo run build --filter=@zennotes/desktop
  electron apps/desktop/out/main/index.js, on a throwaway vault
  Space I, Enter to sign in, approve in the browser
  l or the arrows to open pages, Space to untick, then Import
  Open report. Compare the folder with the sidebar in Notion.
Getting to a vault's folder meant a sidebar footer button or an unlabelled
palette command, and both only knew the vault already open. Someone with
several vaults had to switch to one just to look at its files.

Every local row in the sidebar's vault menu now has a folder button, shown on
hover and on the cursor's row, and Shift+Enter runs it from the keyboard.
ContextMenu gains `rowAction` for this: it sits beside the row rather than in
it, since a button cannot hold a button, and it stays live on a disabled row,
because the current vault's row is disabled only as something to switch to.
The cursor still skips that row, so the menu also lists "Open in Finder" for
the current vault. In the Space v picker Shift+Enter does the same, and it
never falls through to switching: a remote row or Browse… has no folder to
show, and that key asked to look.

Settings → Vault → Location gets a button, named for the platform. Space F is
the Vim binding. "Reveal Vault Root" already did this for the open vault, so it
becomes "Open Vault in File Manager" in the Vault category rather than gaining
a twin; it keeps the `app.assets.reveal` id, so a key bound to it still works.

The renderer is untrusted and shell.openPath launches what it is given, so the
new handler takes a root only to pick from main's own list: the local windows,
the remembered vaults and the configured root. What it opens is main's copy of
the path. A subfolder, a note, a sibling that only shares the prefix and a
remote window's server path are all refused. A listed vault's folder can be
gone, so that case and an openPath failure both reach a toast.

The bridge method is optional because the phone shells implement the bridge
themselves. Remote workspaces and the web client do not offer any of this; a
remote window can still open a local vault from its menu.

The Linux config file button now reads "Reveal in File Manager", from the
shared label helper.
…d onboarding

The Notion import had its own dialog behind one palette command, so someone
coming from Obsidian had nowhere to go, and a new user only found out about
importing after setup. There is now one Import window (Import Notes in the
palette, or Space I) that opens on the two sources, and a Bring your notes step
in onboarding that opens the same window. Web and server vaults skip the step,
since a run needs a main process and a local vault.

Obsidian offers two things, because they answer different questions. Open as
its own vault points ZenNotes at the folder, so both apps share the same files.
Copy in brings the vault into the one that's open, as one Obsidian Import
folder with the same structure, built outside the vault and moved in with one
rename like the Notion import. The source is only read, and undoing it means
deleting that folder. The folder is chosen by main's own dialog and kept there:
the renderer can ask about the vault that was picked but can't name a folder to
read.

ZenNotes already reads callouts, wikilinks, embeds and block references, so a
note nothing has to change is copied rather than rewritten and stays byte for
byte what it was. A link changes only where the move changes what it means: a
title two folders share, one already in this vault, an image name used twice, or
a path written from the old vault's root. Those get pinned to the note Obsidian
would have opened, with their alias kept so they read the same. A link to
nothing stays, since in Obsidian that's a note waiting to be written. Unticking
a folder leaves it out, but an image a kept note uses still comes along.

What it does not do: plugins stay behind, so Dataview queries remain code blocks
and canvases and Obsidian Bases remain files ZenNotes doesn't open. It doesn't
touch settings either; Import Report says where daily notes and templates went,
and only names a folder that actually came over, since Obsidian remembers one
whether or not it exists. Symlinks aren't followed, and a folder that holds this
vault, or sits inside it, is refused.

Tested on a real vault of 19 notes, which had no wikilinks, callouts or tags, so
link pinning has only run on fixtures.

The Notion dialog's picker moved into ImportTree, which both imports now use, and
Space I's keymap id and the capability lost "Notion" from their names. Neither
has shipped.

How to test locally:
  npx turbo run build --filter=@zennotes/desktop
  electron apps/desktop/out/main/index.js, on a throwaway vault
  Space I, arrows to Obsidian, Enter, then choose a vault folder
  Untick a folder with Space, Copy in, Open report
  For onboarding, launch with an empty ZENNOTES_CONFIG_DIR and walk to Import
…s folder in the file manager

Both sides added a Vim leader row to the Keys sheet at the same spot, Space I for
importing and Space F for this, so that conflict keeps both lines.
Column colors shipped in 2.81.0 alongside New Kanban, and the release note sold
them together, but they only reached the Tasks board. MrClyfar made a New Kanban,
right-clicked a column and pressed C, and nothing happened: a database board had
no column menu at all. Reproduced with real right-clicks before changing anything.

A database column is a select option, so that's where its color goes, as #1037
said it should: SelectOption.color in schema.json, one of the same seven names.
It travels with the database, and a Tasks board's colors stay in config.toml. The
board draws the same dot and top edge from the same theme tokens. The Table's
chips and a select value on a card take the option's color too, which also gives
Notion imports their select colors, saved until now but never drawn.

The column header gains a menu (Add card, Color…), C opens the colors in Vim
mode, and the palette has Set Board Column Color…. With Vim off a letter still
does nothing, so Shift+F10 on Add card opens the column's menu, and a card's menu
has Column color…, which is the way there on a column full of cards. "No status"
offers no color, since it has no option to hold one. The Tasks board's list moved
to ColumnColorMenu so both offer the same names.

:color stays on the Tasks board, since it runs on that board's ex line.

Checked over CDP with both stores isolated, Vim on and off: right-click, Color…,
Blue colors the column and writes schema.json; C then typed letters and Enter;
No color leaves no empty key; Shift+F10; the card menu; the palette; the Table's
chip computes to the theme's blue. The Tasks board's picker still colors a column
and writes config.toml.

How to test locally:
  npx turbo run build --filter=@zennotes/desktop
  electron apps/desktop/out/main/index.js, on a throwaway vault
  Palette: New Kanban. Right-click a column header, Color…, Blue
  Press C on another column, type "pur", Enter
  Switch to a Table view: the status chips carry the same colors
… keyboard

The button that shows a hidden sidebar lives in the tab strip. With Note tabs
off there is no tab strip, so on a note or on Tasks the only way back was
Cmd+1. An iPad with no keyboard has no Cmd+1, and hiding the sidebar is saved,
so relaunching landed on the same screen: the reporter of zennotesios#88 had to
delete the app. A mouse user on desktop had the same dead end, with the
shortcut as the way out.

sidebarRestoreSlot now says where each pane offers the control: the tab strip
when there is one, the header of a note, a slim strip of its own on Tasks and
the other views that have no header, and Home's own button on an empty pane.

Zen mode stays out of this on purpose, since it hides all chrome. Its own way
out still needs a keyboard, and it looks reachable only from one. Both controls
carry data-sidebar-restore so the phone layouts, which have no sidebar, can
leave them out.
Raises the floor to zn 0.15.0, whose undo can no longer wipe a note. Manifest
copied byte for byte from the v0.15.0 release and verified against zn-release-1.
@adibhanna
adibhanna merged commit 6ea4775 into main Nov 3, 2026
15 of 17 checks passed
@adibhanna
adibhanna deleted the v2.82.0 branch November 3, 2026 19:18
@adibhanna
adibhanna restored the v2.82.0 branch November 3, 2026 19:18
@adibhanna
adibhanna deployed to boundary-artifacts November 3, 2026 19:44 — with GitHub Actions Active
@adibhanna
adibhanna deployed to boundary-artifacts November 3, 2026 19:49 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
boundary-artifacts — 6ea4775a Deployed Nov 3, 2026 by adibhanna via draft #116
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