Merged
Conversation
After a visual-block I, A or c, Escape applied the text to every row but left Vim in insert mode (#803). CodeMirror runs every keymap from a single DOM handler placed at the first keymap provider, and the editor mounts one (markdown snippets) ahead of vim(), so every keymap binding sees a key before the Vim plugin does. Two Escape bindings were spending it. defaultKeymap binds Escape to simplifySelection, which reports the key handled whenever there is more than one range or a non-empty one. Since block edits became real multi-cursors (#792) that is exactly the state Vim is in when Escape should end the edit: CodeMirror collapsed the cursors and Vim never saw the key. The same binding made visual block need two presses, and left a v or V selection through the selection listener with the cursor on CodeMirror's head instead of the character Vim keeps it on. Escape now defers to Vim in insert, replace and visual mode. Normal mode keeps the native command, because there Vim itself hands Escape back to the editor so stray extra cursors can still collapse. The completion keymap's closeCompletion reports the key handled while any source is merely pending, and every typed character puts all sources into that state for the activateOnTyping debounce (about 100ms) even when none will match. An Escape pressed right after the last character was lost to a completion nobody could see, with a single cursor too: easy to hit with Escape on Caps Lock, and what kept a block insert stuck for fast typists even once the first binding was out of the way. A pending query is still cancelled, but only a visible popup keeps the key. A visible popup still takes the first Escape and stays in insert mode, as before. The #792 suite asserted a return to normal mode and passed falsely: it mounted vim() ahead of any keymap, the reverse of the app, so Vim got Escape first. The mount now mirrors the app's handler order, and with the fix removed nine tests fail. Fixes #803.
…scripts Four scripts that drive the built app over CDP could no longer start: vim-editor-smoke, sidebar-vim-smoke, editor-improvements-smoke and perf-editor-scroll all died with "Cannot find module 'electron'" before launching anything, so `npm run test:vim-editor` and its siblings were dead. Each one did createRequire(import.meta.url) and required electron from the repo root. electron is declared by the desktop workspace and is no longer hoisted to the root node_modules, and hoisting was never a contract. They now resolve it from apps/desktop/package.json, the pattern perf-desktop-runtime.mjs already used. ws and the other root-level requires are untouched, and pdf-export-smoke.py already looked electron up from the desktop workspace. With this, test:vim-editor passes 12 of 12, test:editor-improvements 19 of 19, and perf:editor-scroll runs to completion. test:sidebar-vim launches too; the failures it then reported were a real sidebar bug, fixed next.
In a folder large enough to be windowed (240 notes and up), walking the sidebar with j/k, opening a note with Enter and coming back with Ctrl+W h lost the cursor: nothing was highlighted, the next j showed nothing, the one after landed on Assets, and k on Trash. Every sidebar row carries a data-sidebar-idx handed out by a mutable counter, and the Vim cursor is stored as one of those numbers. Only Sidebar's own render restarts the counter, but FolderTreeRoot, FolderTreeContents and SubTree read it during theirs and hold state of their own, so they also render alone. A lone render read whatever the counter had been left at, and two things leave it wrong: - A full Sidebar render ends on the sidebar's total row count. FolderTreeContents re-renders alone on every sidebar focus change (its progressive entry limit resets when `progressive` flips with sidebarFocused), so the notes jumped from 10..1009 to 1010..2009 and the stored cursor matched no row. It stayed hidden in the simple keyboard flow by luck: the focus effect usually writes the cursor in the same batch and forces a full render. It bites when the cursor is already on the target row, which is exactly "return to the note I just opened". - React also runs Sidebar's body and then bails out without rendering its children. That restarts the counter and leaves it just past Sidebar's inline rows, so the next lone render numbered notes from 8 and collided with Quick Notes (8) and Inbox (9). The second case is why a "Sidebar rendered" flag on the counter, the first shape this fix took, cannot tell the two kinds of render apart. Only a prop can, because props change exactly when the parent really rendered the child. useStableSidebarIdxBase remembers the counter value a tree component starts from and restarts from it whenever the component renders again with the same idxPass. Each parent creates a new pass per render, and the prop is required in TreeRenderProps so the compiler flags a call site that forgets it. DateNotesNav holds no state and needs no guard. `npm run test:sidebar-vim` was reporting this as 4 failures of 12 and had not drifted: with the fix it passes 12 of 12 with no selector changes. The fourth failure was a knock-on, since with no cursor the script fell back to idx 0 and its gg wait became trivially true. The script did have one real gap: it never set ZENNOTES_CONFIG_DIR, so it read the developer's real config.toml and only passed where that file happened to enable Vim. It now seeds its own, like every other CDP script.
Running "Search Text in Vault…" from the command palette with the editor focused opened the search with the caret still in the note, so the query was typed into the note instead (ZenNotes/zennotesandroid#65). "Search Notes…" and "Open Note Outline…" did the same. Reported on Android in Edit mode, but the cause is in the shared core and desktop had it too. After a command runs, the palette hands DOM focus back to the editor unless it knows the command left something open on top. That list named Settings and three palettes and was missing the note search, vault text search and outline palettes. focusEditorNormalMode focuses on the next animation frame, which is after the opened palette's mount effect focused its input, so the editor took focus back a moment later. The overlays are lazy-loaded, which hid this on the first open of a session (the chunk mounted after the refocus) and broke every later one. In Read mode the editor is hidden and cannot take focus, which is why only Edit mode showed it. The list now lives in command-palette-mode.ts as COMMAND_OVERLAY_FLAGS behind shouldRefocusEditorAfterCommand, and a test requires every `…Open` boolean in the store to be classified, so a new overlay cannot silently regress to typing into the note behind it. Store flags cannot see everything: "Publish Note" opens its dialog through publish-note-requests without awaiting it, and the editor was taking focus behind that dialog as well. The caller now reports it next to the Cloud conflict review, as `dialogOutsideStoreOpen`. Prompts, confirms and date pickers need no entry, because their commands await the answer and they are closed by the time this runs. Verified on a desktop build with real key events, three runs per command since the first one after launch was the lucky one: the five palettes keep focus in their input and the note stays untouched, the Publish Note dialog keeps focus, and a command that lands on the editor is still handed focus.
…he note With Settings open, typing still edited the note behind it. It happened whether Settings was opened with Mod+, or from the command palette, because nothing pulled focus back: Settings simply never took it. It is the one dialog that draws its own backdrop and panel instead of sitting in the shared Modal shell, so it never got what the shell gives every other dialog, where opening moves focus into the panel, Tab cycles inside it, and closing hands focus back to the opener. Wrapping Settings in <Modal> would have changed how it looks (backdrop blur, top offset, corner radius, border against ring), so the shell's focus logic moves out of ModalRoot into two exported helpers, useDialogFocus and trapDialogTab. ModalRoot uses them itself, so the 21 dialogs on the shell behave exactly as before, and Settings calls them on its own panel rather than keeping a copy. The panel also gains dialog semantics (role, aria-modal, a label) and tabIndex -1 so it can be a focus target. Opening lands on the settings search, the first thing a keyboard user reaches for. On a touch device a focused input would raise the on-screen keyboard over the list the user is about to tap, so the panel takes focus there instead, the same rule PromptModal already follows. Escape, the backdrop and Done still close through closeSettings, which returns focus to the editor as before. Verified on a desktop build with real key events, Vim off and on, and with a touch device emulated: focus lands inside Settings from both ways of opening it, typing stays there and never reaches the note, 70 Tab presses never leave the window, Escape closes it and the caret is back in the note.
Settings opens on its search field, but the search itself still needed the mouse. Enter in the field did nothing, the arrows did not move through the results, and once focus had moved to a control there was no key to get back to the search: `/` and Cmd+F, the keys that search everywhere else in the app, were ignored inside Settings. That is what #108 asked for back in 2.2.0. In the field, Enter now opens the picked result, and the arrows (or Ctrl+J / Ctrl+K and Ctrl+N / Ctrl+P, the same keys the palettes use) walk the results, opening each one as a click would: right category, right sub-tab, the setting scrolled into view and flashed. The walk stops at both ends instead of wrapping, and the picked row is kept on screen when the list is longer than its column. From anywhere in Settings, Mod+F returns to the search with its text selected, and so does `/` in Vim mode. The single key follows the house rule: it is off when Vim mode is off, and a `/` typed into any field is just a character. The find key is handled on the Settings panel, not at the window. The three shortcut recorders in Settings capture keys at the window and have to win while they are recording, so recording `/` as an ignored key still records it. Fixes #108
…g a note With Trash, Tasks, Help or an asset in the active tab, Cmd+2, Cmd+3 and Shift+Cmd+C (and the calendar toggle) still flipped that pane's Connections, Outline, Comments and Calendar. There is no panel to see on those tabs, so nothing appeared to happen, and the change turned up on the next note instead: a panel you never asked for was open, or the one you had open was gone. The four toggle listeners now only act when the pane's active tab is a note. The test is the kind of tab, not whether content has loaded, so a shortcut pressed while a note is still opening is kept. Closing the right panel with its own shortcut is unchanged, since closing something you cannot see does no harm.
…toml `keepViewModeAcrossNotes` has been in PORTABLE_PREF_KEYS since it shipped (#543), which is the list of preferences that are supposed to travel between machines in `~/.config/zennotes/config.toml`. It never did. The desktop writer only emits keys that have a field mapping, the key had none, so it was portable in name only: set it on one machine and the file, and every other machine reading that file, never heard about it. It is now `keep_view_mode_across_notes` under `[editor]`. A key missing from an existing file keeps the local value when the file is read, so nobody's current choice is reset; the option simply appears in the file the next time the app rewrites it. To keep this from happening to the next preference, a test now requires every key in PORTABLE_PREF_KEYS to come back out of a freshly written config. Without the mapping it fails and names the key.
The Connections, Outline, Comments and Calendar panels have always belonged to the pane: open the Outline and it stays open on every note you move to. That suits browsing, but #794 asks for the other habit, where a project note lives with its Outline and Connections open and a scratch note lives with nothing, and each comes back the way it was left. Panels following the pane stays the default, so nothing changes for anyone who does not ask. Turn off Settings > Editor > "Keep panels when switching notes" (or run "Remember Panels per Note" from the command palette) and each pane keeps one set of panels per note instead. A note you have not opened yet starts with none. It is the same shape as the per-note Edit / Split / Preview memory and its "Keep view mode" switch, on purpose: one idea, two settings that read alike. What it chooses: - The memory lasts for the session, like the per-note view mode. It is never written into the note, which is a plain file. If it should outlive a restart, the workspace snapshot that already restores tabs and layout is where it belongs; that is left for a follow-up. - A note's panels follow it through a rename or a move, are dropped when the note is deleted, and are copied to a new split. - Flipping the setting never rearranges the screen. The panels on view are carried into whichever store takes over, and only later note switches behave differently. - With the automatic calendar on, a daily or weekly note whose calendar you closed stays closed when you come back to it. That needs a "dismissed" bit, recorded only in per-note mode, so the default mode keeps auto-opening the calendar on every arrival exactly as before and the #502 state machine is untouched. - The preference is portable: `keep_panels_across_notes` under `[view]` in config.toml. EditorPane swaps its four useState calls for one hook, usePanePanels, with the same names and setter shapes, so every toggle and auto-open below it is unchanged. The setters keep a stable identity and read the live preference and path through refs, because the editor hands them to CodeMirror handlers that are built once per mount. The store action runs its updater outside zustand's set(): the panel toggles write to the store from inside their updater, and a write nested in a set() callback is clobbered when the callback returns. Both "Keep ..." settings also gain the Settings search entries and the sub-tab registration the older one never had, so searching for either opens the Writing tab on the right row. Closes #794
With "Keep panels when switching notes" off, each note remembered its
own Connections, Outline, Comments and Calendar, but only until you
quit. Hand testing showed that as the one place the feature felt
unfinished: set up a project note with its Outline, a reading note with
Comments, relaunch, and every note was bare again while the tabs and
splits around them had come back.
The memory now rides in the workspace snapshot, the same record that
already restores tabs, layout and the sidebar, locally and as
`.zennotes/workspace.json` for a synced vault. Nothing is written into
the notes themselves, which stay plain files. A calendar you closed on
a daily note stays closed after a restart too, because the "dismissed"
bit travels with the rest.
What it is careful about, since that file syncs between machines and
versions and can be edited by hand:
- It stays small. Only panes that still exist are written, only what is
open is spelled out (a note with Outline open is `{"outline": true}`),
a note with nothing open is not stored at all, and each pane keeps the
200 most recently set notes. Setting a note's panels again moves it to
the back of that queue.
- Nothing in it is trusted on the way back in: unknown panes are
dropped and every field is coerced to its type. A snapshot written
before this change simply has no panels.
- Once the note listing is in, memory for notes that no longer exist is
retired, with the same empty-listing guard the tab check uses, so dead
paths do not pile up after deletes on another machine.
- Toggling a panel changes nothing else about the workspace, so that
action asks for the save itself instead of waiting for a tab change.
The default mode is untouched: with the setting on, panels belong to the
pane for the session and nothing is restored at launch, exactly as in
2.51.1. Per-note view modes (Edit / Split / Preview) are still
session-only; this change does not extend to them.
…#793) Make a few edits in a note, switch to another tab, come back, press u: nothing. Every undo step on the note was gone, and the same happened after a visit to Trash or Tasks, or after closing the note and opening it again. It was deliberate. A pane has ONE editor, and showing another note swaps that editor's document. Since #247 the swap also empties the undo history, because a history that crosses the swap lets Cmd+Z paste the previous note over the current one and save it. The price was that leaving a note for a second threw away everything you could undo in it. The history is no longer discarded. It is set aside under the note it belongs to (vault root and path, so it follows the note into another pane and survives closing the tab) and handed back when that note returns to an editor: CodeMirror's history field can be read out of one state and seeded into another. The editor is also torn down whenever a pane shows something that is not a note, so that is treated as leaving the note too. #247 stays fixed, by construction: a history only ever goes back onto the note it was taken from, and only while that note still reads exactly as it did then. Undo steps are edits at character positions, so on any other text they would corrupt it. If the note changed somewhere else in the meantime (another pane, sync, an external editor), it starts a clean history, which is what every switch did before. The fifty most recently left notes are kept. There is no setting: this is how an editor is expected to behave. It covers the in-session half of #793. The other half, undo that survives quitting the app (Vim's undofile proper), is not part of this change.
…o history Rename the note you are editing and the caret jumped to the top of it, the scroll reset, and its undo history was gone, although you never left the note. Moving it to another folder, or renaming the folder it lives in, did the same. A pane has one editor, and it took any new path for a different note. That is the moment it deliberately drops all three, because for another note they are meaningless and the undo history is dangerous (#247). A rename changes the path too, and nothing told the editor that the note under the new path was the one it was already showing. The rename also reaches it as two updates: the path first, then the title heading rewritten to match, and each of them emptied the history on its own. The store now keeps a short log of "this path is now that path", appended in the same update that rewrites the paths, at the one remap every note rename, note move, folder rename and delete goes through. A path change that log explains is the same note: the editor keeps the caret, the scroll and the history, and carries the remembered tab position along. Each editor remembers the newest entry it has accounted for, so an OLD rename can never make a real note switch look like one; that would be #247 again, and a genuine switch still goes through the full reset. The heading rewrite is why the second half was needed. When a note's text changes underneath the editor it used to replace the whole document, which tells CodeMirror that every position is gone: the caret can only be clamped, and every stored undo step collapses to nothing (a control test shows exactly that). It now applies only what changed, the span between the common prefix and suffix, and lets CodeMirror map the caret and the history through it. That reaches past renames: another pane typing in the same note, a rename elsewhere rewriting a link in it, or the file changing on disk used to silently empty the note's undo history, and now leave it working. Text with carriage returns keeps the whole replace, because CodeMirror folds \r\n into one line break and string offsets would not be document offsets. Two smaller pieces: the per-tab restore re-applies remembered offsets a frame later, which put the caret back on a stale offset once the heading had shifted the text, so a rename skips it. And undo histories set aside by the previous commit follow their note, so one that is moved, or whose folder is renamed, while it is off screen keeps its undo as well.
…fit (#805) Split the window, open the Outline and Connections in one pane, and the note between them was a strip a few characters wide. Add Comments and it had no room at all. The four right-hand panels each have a width the user chose (200 to 640 px), they never shrink, all of them can be open at once, and nothing reserved any room for the note. A full-width window hides that. A half-width pane does not: in a 632 px pane two panels left the note 84 px. The note comes first now. It keeps 320 px (520 in Split mode, which shows source and preview side by side), and the panels give way in two steps. First they shrink, in proportion, down to the smallest width a panel can be dragged to. If that is still too much, the panels opened longest ago are tucked into a slim rail at the pane's right edge until the rest fits. Tucked is not closed. Nothing the user opened is lost, and the per-note panel memory from #794 keeps recording what was opened rather than what happened to fit. A click in the rail brings a panel forward, and so does its usual shortcut: Cmd+3 on a tucked Outline shows it, and a second press closes it, which is what the key has to mean when the panel is open but not on screen. The toolbar tooltips say "Show" for a tucked panel for the same reason. Code that asks for a panel that is already open (jumping to a comment opens Comments) brings it forward as well, so it can never land on something the user cannot see. Tucked panels are not rendered. Vim pane navigation finds the panels through the DOM, so it skips them with no change of its own, and a panel that is tucked while it holds the keyboard hands it back to the editor, as does a click in the rail. A pane with room for everything behaves exactly as before, and so does the first paint, since an unmeasured pane changes nothing. In a pane too narrow for a note and a panel both, the panel that was just asked for still shows, at its smallest useful width. Fixes #805
27c09c9 made each note keep its undo history while the app is open. This is the other half of #793: reopen a note tomorrow and u / Mod+Z and redo still step through yesterday's edits. It is a setting, "Keep undo history after quitting" under Settings > Editor, and it is off by default. Undo history is made of the text you deleted, and writing that to disk is a choice the user should make. In Vim mode it is the option Vim users already type: `:set undofile`, `:set noundofile` and `:set undofile?` work, through Vim.defineOption, and there is a command palette entry as well. It travels in config.toml as `persist_undo_history` under `[editor]`. Where it lives is the part that needed deciding. Not in the vault: inside `.zennotes/` it would sync to other machines and land in the history of a git-backed vault. It sits under the app's own user-data folder, one file per note, the split Vim makes between a file and its entry in `undodir`: <userData>/undo-history/<sha256(vault)[:16]>/<sha256(note path)[:32]>.json That makes it a desktop feature. The bridge gains three OPTIONAL methods and a `supportsUndoFile` capability, so the web client and the mobile shells need no change and do not show the setting; they keep undo history while they are open, as before. The main process follows the usual trust model. The vault comes from main-process state, never from the renderer (with the remote profile id mixed in for a remote workspace), and the note path only ever feeds a hash, so nothing the renderer sends can steer a read or a write outside that folder. Payloads over 2 MB are dropped, writes go through a temp file and a rename so a quit mid-write cannot leave half a file, and a sweep once per launch, off the boot path, expires histories nobody came back to for 90 days and keeps the 400 most recent per vault. The renderer saves where it already sets a history aside (leaving a note, the editor being torn down) and on beforeunload for the note that is on screen, which the host finishes after the window is gone, the way it finishes the note saves fired from the same event. It reads a saved history only into an editor that still shows that note with nothing to undo yet, so a history kept in memory, or an edit made while the file was being read, wins. The rule from 27c09c9 decides whether a saved history may be used at all: only on the text it was taken from. A note can change between two launches in ways the app never sees (sync, git, another editor), so the file carries a fingerprint of the text, and a mismatch means a clean history. Over 1.5 M characters the OLDEST undo steps are dropped first. A save never erases. The same note can be open in a second pane that has just saved its history, and an empty one would have wiped it (found while testing). Only renaming the open note and turning the setting off erase anything, and turning it off erases everything, in every vault: the files do not outlive the setting that asked for them. That lives in an App effect rather than the setter, because config.toml can turn the preference off too.
Type `[[`, move to a note in the picker, press `|`, and you got `[[|]]`: the highlighted note was gone and the picker had closed. The picker's own tip reads "Type | to change display text", but `|` was never one of its keys. It was typed as plain text, and the wikilink source stops matching as soon as the text holds a `|`, so the list closed and the name it was offering went with it. `|` is now an accept key for note, asset and database suggestions, next to Enter, Ctrl+Y and Tab: it takes the highlighted suggestion and leaves the caret behind a `|` inside the brackets. `[[`, Linux, `|` gives `[[Linux|]]` ready for the display text, and a half-typed name is completed first, the way Enter completes it. The `@` note picker shares the suggestion kind, so it gains the key too. The key is matched as a character, not as a chord. It is Shift+\ on a US keyboard, Option+7 on a German Mac and AltGr+< on a German PC, which reports Ctrl and Alt together, so the modifiers cannot be pinned down; only a bare Ctrl or a Cmd chord is certainly not typing. Picking a new note for a link that already has display text keeps that text, so a second `|` there would break the link. The existing display text is selected, ready to be typed over. A `#section` the link already carries stays in front of the new `|`. Everywhere else `|` is still just a character: in plain text, in a table row, in pickers that are not wikilinks, and after a name that has no suggestions, so `[[Brand new idea|alias]]` types as it always did. With suggestions showing, `|` now takes the highlighted one even if a new name was meant, which is what Enter already did; Esc first keeps the typed name, and the manual says so. An exact title match ranks first, so typing a full existing name and then `|` gives the same link as before. `closedLinkTail` and the new edit live in a store-free module, `cm-wikilink-tail`, because `cm-wikilinks` reads the store and the key handler, and its jsdom tests, should not. Fixes #804
The mobile shells can rename and delete a folder or database from Browse, and move a note, but could not move a folder or database: desktop does it by dragging in the sidebar, which has no equivalent on a phone, and the public Browse API had no action for it. A Play Store review of the Android app asked for exactly that. requestMoveBrowseDirectory(host, directory) prompts for a new parent with the same inbox[/path] values as the move-note prompt, then calls the store action the sidebar drag already uses, so open tabs (database tabs included), folder icons and colors, favorites and manual order follow the move. The leaf name is kept, and with it a database's .base suffix. The prompt offers existing notes-area folders only: never the directory itself, anything under it, a database, or the archive. A destination that already holds the name is refused in the prompt, the current parent is a no-op, and the host still refuses to overwrite. The field starts empty on purpose: the touch prompt filters its list by the field's value, so a prefilled path would hide every other folder. Pins stay host-owned; the README says a host that pins folders re-keys them after a move. Verified by tests only, not in a running app: ten cases in browse-actions.test.ts, each rule checked by removing it and watching a test fail. The shells add their "Move to…" rows when they adopt this core.
…aid out `test:editor-improvements` failed one run in five at the release gate with "cursor position survives Preview and Edit mode switches", and the detail gave it away: the caret's left was 0. A caret at the start of that line sits around x=400, never 0. Preview mode tears the editor down and Edit mode builds a new one, and for a frame the new caret element exists without a layout box, so its rect is all zeros. The poll accepted the first sample that had the right active line, and sometimes that sample fell into the gap. Both samples now wait for a caret with a real box. Nothing in the app changed: the position was restored correctly in every run, including the failing one.
CodeQL flagged the new undo store on the release PR (js/file-system-race, high): readUndoHistory checked the file's size by path and then read it by path again, so the file could be swapped between the check and the read. It is the class 5cd4eb5 fixed for the CLI launcher. The file is now opened once, and both the size check and the read go through that handle. A missing file is still "no history", and anything over the cap is still ignored.
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.
ZenNotes 2.52.0
Started as the 2.51.2 patch branch and grew into a minor. Two requests from @uNyanda, a mobile Browse action, and ten fixes, most of them for keys that went somewhere other than where the user was looking. Every issue is closed with a commit-linked comment.
Features
:set undofile/ "Keep undo history after quitting" it survives quitting too (opt-in, desktop only, stored with the app, never in the vault) (Add Support forundofileSetting #793)Fixes
/searching (and standard search) inside the Settings modal #108)|in the link picker starts the display text (Wikilink Picker Does Not Apply Display Text Shortcut #804)Verified before this PR
npm run typecheck7 of 7;npm run test:run: shared-domain 1,666, app-core 2,498, desktop 860.npm run pack, then the packaged app launched with both stores isolated: CDP page target in under a second, version 2.52.0, the new main-process undo store loads inside the asar.test:vim-editor12 of 12,test:sidebar-vim12 of 12,test:editor-improvements19 of 19.Docs: in-app help updated in the commits; the website mirror is the branch
website/docs-2.52, merged with the release.