Repository navigation
Conversation
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.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 cicheckout 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.