Added
- Neovim RPC connection lifecycle — adds an opt-in desktop-only backend foundation that spawns a user-configured Neovim, attaches with an in-repo msgpack-RPC client, requires API level 12 (Neovim 0.12+), reports actionable startup/crash errors, and terminates the child on setting disable, Vim disable, plugin unload, or failed attach. Milestone 1 intentionally does not forward keys, synchronize text, or bridge decorations.
- Plugin:
src/rpc/msgpack-rpc.ts,src/rpc/neovim-connection.ts,src/main.ts,src/settings.ts
- Plugin:
- Neovim RPC active-editor text synchronisation — mirrors the active Markdown editor into the single Neovim buffer on connection and leaf activation, then applies content-carrying
nvim_buf_lines_eventnotifications to CM6 with byte-to-UTF-16 coordinate conversion. Neovim is authoritative for RPC-originated text; key delegation, multi-leaf buffers, decorations, IME, and frontmatter policy remain deferred.- Plugin:
src/rpc/document-sync.ts,src/rpc/msgpack-rpc.ts,src/rpc/neovim-connection.ts,src/main.ts
- Plugin:
- Dedicated Neovim configuration setting —
neovimConfigPathoptionally points the backend at a minimal Obsidian-specificinit.lua, avoiding terminal-only LSP, dashboard, and statusline startup. Empty preserves the production default of loading the user's normal configuration. A configured path starts under--clean, prepends its directory toruntimepath, and loads only that file with-u; measured startup dropped from 115 ms to 24 ms locally.- Plugin:
src/settings.ts,src/main.ts,src/rpc/neovim-connection.ts
- Plugin:
- Neovim RPC key delegation — while connected, a lifecycle-owned capture handler forwards every non-composition editor key with
nvim_input, suppresses the bundled fork throughsetKeyInterceptActive, prevents CM6 input, and synchronizes cursor and exposed mode state after a blocking RPC barrier. Every active-leaf activation seeds Neovim from that editor. IME composition remains deferred to M6.- Plugin:
src/rpc/key-delegation.ts,src/rpc/document-sync.ts,src/rpc/neovim-connection.ts,src/main.ts
- Plugin:
- Neovim RPC decorations — a bundled Lua companion registers one redraw-driven decoration provider, queries all namespaces over visible buffer ranges, and forwards highlight and virtual-text extmarks in buffer coordinates. The host attaches a fixed-size UI only as a redraw clock, discards grid events, maps byte columns through the text-sync coordinate path, resolves Neovim highlight groups, and clears CM6 decorations on disconnect. The companion remains a real
.luasource file embedded intomain.jsby esbuild's text loader; no runtime file or user configuration is modified.- Plugin:
src/rpc/companion.lua,src/rpc/decorations.ts,src/rpc/document-sync.ts,src/rpc/msgpack-rpc.ts,src/rpc/neovim-connection.ts,src/types/lua-modules.d.ts,src/main.ts,esbuild.config.mjs
- Plugin:
- Neovim RPC write and read routing — the named mirror is now buffer-locally
buftype=acwrite. A buffer-scopedBufWriteCmdroutes:wand:w!through Obsidian'seditor:save-filecommand and clears Neovim's dirty flag; a buffer-scopedBufReadCmdmakes:eand:e!re-seed from the current Obsidian document with line-event echo suppressed instead of loading stale disk content.- Plugin:
src/rpc/companion.lua,src/rpc/decorations.ts,src/rpc/document-sync.ts
- Plugin:
- Neovim RPC Obsidian feature bridge (M4a) — generates selected mappings and user commands from
VimRegistration, dispatches every host action through oneobsidian_actionnotification channel, and restores the files picker, Oil, Harpoon slot 1, vertical split, next-heading navigation, and lowercase:sidebarwhile Neovim owns editor keys. Lowercase commands register an uppercase Neovim command plus a start-of-command-line guarded abbreviation. Refresh and disconnect remove mappings, commands, abbreviations, and notification listeners before reinstalling.- Plugin:
src/rpc/obsidian-feature-bridge.ts,src/rpc/neovim-connection.ts,src/rpc/key-delegation.ts,src/vim/registration.ts,src/picker/picker.ts,src/main.ts
- Plugin:
- Neovim RPC workspace and navigation bridge (M4b Batch 1) — extends the registry-driven bridge with pane focus/cycling, horizontal splitting, tab close/cycle/target actions, counted
Ngt, previous-pane and alternate-file state, pane-to-tab moves, four go-to-definition variants, and 11 lowercase workspace ex callbacks. A general dispatch payload carries mapping counts and command arguments for later parameterized batches; guarded abbreviations keep command names inert inside substitutions.- Plugin:
src/rpc/obsidian-feature-bridge.ts,src/vim/registration.ts,src/keybindings/action-registry.ts
- Plugin:
- Neovim RPC picker bridge (M4b Batch 2) — generates the remaining built-in picker leader mappings and picker ex callbacks in Neovim. The existing general command payload preserves grep queries and named picker sources, source-specific marks/register pickers and resume reuse the ordinary picker entry point, modal keys remain owned by Obsidian, and file selection re-seeds Neovim through active-leaf activation.
- Plugin:
src/rpc/obsidian-feature-bridge.ts
- Plugin:
- Neovim RPC Harpoon, marks, and jumplist bridge (M4b Batch 3) — generates every remaining Harpoon mapping and ex callback,
:marks/:delmarks/:jumps, and host-owned<C-o>/<C-i>. Slot strings and mapping counts use the existing payloads; cross-note navigation waits for active-note re-seeding and restores the stored cursor in both CM6 and Neovim. Lowercase within-buffer marks remain Neovim-native, while uppercase cross-file mark motions are explicitly deferred.- Plugin:
src/rpc/obsidian-feature-bridge.ts,src/rpc/neovim-connection.ts,src/rpc/document-sync.ts,src/vim/harpoon-nav.ts,src/workspace/global-defaults.ts,src/main.ts
- Plugin:
- Neovim RPC folding and undo-tree integration (M4b Batch 5) — keeps all fold and undo operations native to Neovim, forwards visible fold state beside extmarks on the existing redraw pass, mirrors matching CM6 folds, supplies a Markdown-aware window-local
foldexpr, and bridges only the three Obsidian sidebar lifecycle commands. The sidebar renders Neovim's nativeundotree()result; 64-bit msgpack integers are decoded for its timestamps. Fold persistence is intentionally unavailable under RPC rather than restoring host offsets into Neovim-owned state.- Plugin:
src/rpc/companion.lua,src/rpc/decorations.ts,src/rpc/frontmatter-fold.ts,src/rpc/key-delegation.ts,src/rpc/msgpack-rpc.ts,src/rpc/obsidian-feature-bridge.ts,src/vim/undo-tree.ts,src/vim/undo-tree-view.ts,src/main.ts
- Plugin:
- Neovim RPC structural navigation and hard-wrap (M5a) — moves heading, level-specific heading, same-indent list, and Markdown-link motions out of the host feature bridge and into buffer-local companion mappings backed by Neovim's bundled Markdown treesitter parsers. Counts and operator-pending ranges match the bundled fork. The mirror receives the configured
textwidth, while nativegq/gwand the stock Markdown ftplugin own wrapping.- Plugin:
src/rpc/companion.lua,src/rpc/document-sync.ts,src/rpc/decorations.ts,src/rpc/neovim-connection.ts,src/rpc/obsidian-feature-bridge.ts,src/motions/register.ts,src/main.ts
- Plugin:
- Neovim RPC Markdown text objects (M5b) — installs buffer-local operator-pending and visual mappings for emphasis, inline code, math, strikethrough, Markdown links and wikilinks, fenced code blocks, nested blockquotes, callouts, HTML tags, table cells, and table rows. Native Markdown treesitter supplies structural ranges; native
it/atsupplies tag matching with fork-compatible count handling. Explicit visual ranges keep every operator bounded, and teardown removes every mapping.- Plugin:
src/rpc/companion.lua
- Plugin:
- Neovim RPC floating-window bridge (M6a) — enumerates floats during the existing redraw provider's
on_end, forwards per-window config, buffer content, and extmarks without grid events or polling, and renders positioned Obsidian overlays with border presence and Neovim z-index. CM6 character/line metrics map terminal cells approximately onto proportional Markdown typography; cursor- and window-relative origins are resolved separately. Closed windows and disconnected sessions remove their overlays.- Plugin:
src/rpc/companion.lua,src/rpc/decorations.ts,src/rpc/floating-windows.ts - Styles:
styles.css
- Plugin:
- Neovim RPC IME composition bridge (M6b) — a cursor-positioned input target outside CM6's
contentDOMowns native composition and keeps preedit out of Neovim. Commits enter throughnvim_input, preserving insert undo and dot-repeat; ordinary keys are suppressed during composition, while Escape, blur, mode changes, active-note changes, and disconnect cancel preedit and resynchronize ownership.- Plugin:
src/rpc/ime-input.ts,src/rpc/key-delegation.ts - Styles:
styles.css
- Plugin:
- Neovim RPC external-UI messages (M8a) — attaches the messages, command-line, and popup-menu UI extensions once, dispatches ordered redraw batches while rejecting unhandled grid events before further work, and routes D12 error, warning, and informational message kinds to severity-styled Obsidian Notices with a five-second duplicate cooldown. Routine undo, search, progress, completion, and command-list kinds remain silent; command-line and popup-menu rendering remain deferred.
- Plugin:
src/rpc/redraw.ts,src/rpc/messages.ts,src/rpc/decorations.ts,src/rpc/neovim-connection.ts
- Plugin:
- Neovim RPC external command line (M8b) — renders
cmdline_show, byte-correctcmdline_pos,cmdline_special_char, and level-scopedcmdline_hideevents in a themed editor overlay. First characters, prompts, confirm choices, and nested levels make typed commands, searches,vim.ui.input(), and genericvim.ui.select()visible without changing bundled-fork behavior; popup-menu completion remains deferred.- Plugin:
src/rpc/cmdline.ts,src/rpc/neovim-connection.ts - Styles:
styles.css
- Plugin:
- Neovim RPC popup-menu completion (M8c) — renders
popupmenu_show, selection updates, and hide events as a themed four-column completion list. Command-line wildmenu uses byte-correct command-line anchoring, while insert completion uses Neovim grid cells and measured CM6 editor metrics.- Plugin:
src/rpc/popupmenu.ts,src/rpc/cmdline.ts,src/rpc/neovim-connection.ts - Styles:
styles.css
- Plugin:
- Neovim RPC status-bar mode ownership (M8d) — routes
msg_showmodeinto the existing status bar, gives the externally supplied Neovim mode precedence over fork events while RPC is connected, and restores fork-driven mode text on disconnect.- Plugin:
src/rpc/mode-status.ts,src/rpc/neovim-connection.ts,src/vim/mode-tracker.ts,src/main.ts
- Plugin:
- The table-nav overlay was never broken under the Neovim backend, and the documentation saying otherwise is corrected — no code changed.
canActivate()gates onforkAvailable, which is!isBuiltinVimEnabled(app)— that Obsidian's own vim is off, not that the bundled engine is driving keys — so the overlay already activated under RPC, and reading that as "requires the bundled engine" was the error. Measured under a live connection rather than argued:lmoved the highlighted cell from column 0 to column 1 while Neovim's cursor row stayed at 3 and the buffer was byte-identical, because Obsidian's keymap scope consumes the key before the delegation listener on the editor sees it. The risk worth ruling out was the opposite one, a key driving both the overlay and Neovim, and it does not occur.- Docs:
docs/features/neovim-backend.md,KNOWN_LIMITATIONS.md
- Docs:
- Companion mappings and leader groups describe themselves to which-key plugins — the readable-description work covered the feature bridge but not the companion, which registers 18 structural motions, 26 Markdown text objects and the fold aliases as real Neovim mappings; all of them still carried ids like
vim-motions-rpc-textobject:iC. They now read as prose, and checking the object table while writing the labels caught two I would have guessed wrong:iCis the code fence andiothe callout, not the other way round. Leader prefixes are also registered as<Nop>mappings carrying only a group label, so which-key.nvim renders+findand+harpoonmenus rather than a flat list of leaves. Nothing executes those stubs — Neovim resolves the longer binding — and the leader itself is never mapped, since mapping it would make it a complete binding and break every leader sequence.- Plugin:
src/rpc/companion.lua,src/rpc/obsidian-feature-bridge.ts,src/rpc/neovim-connection.ts,src/main.ts
- Plugin:
- Hint mode works with the Neovim backend, and bridged bindings describe themselves — hint mode needed far less than expected: its overlay captures keys on the document in capture phase, ahead of the delegation listener on the editor, and stops propagation, so label keystrokes could never have reached Neovim. Only the trigger was missing, and the five
hint*ex callbacks plus thehintModeleader mapping are ordinary registrations the bridge already installs. Flash and EasyMotion share that samecaptureKeyspath, so their triggers are one allowlist entry away too; they are deliberately left out becausesand<leader><leader>already mean something in Neovim. Separately, every generated mapping and command now carries a readabledescprefixedVim Motions:rather thanvim-motions-rpc:<id>, so:map,:commandand which-key.nvim describe them in words. Which-key itself is not ported: it renders the plugin's leader registry from the fork's key stream, which is not the keymap in force under RPC, whereas which-key.nvim draws in a floating window the bridge already renders.- Plugin:
src/rpc/obsidian-feature-bridge.ts
- Plugin:
- Cursor shapes and input-method switching follow Neovim's mode — both read the bundled fork's own state or its
vim-mode-changeevent, which never moves while Neovim owns keys, so the per-mode cursor shape stayed on normal throughout an RPC session and automatic IM switching never fired.src/vim/external-mode.tsis one seam the connected backend publishes Neovim's mode into, mapped to the plugin's vocabulary; the animated cursor consults it ahead of the fork throughresolveVimModeWithExternal, and the input-method watcher subscribes alongside the adapter event. It publishes at connect as well as on every key — a gap the new lifecycle scenario caught, sinceonModeonly fires after a keystroke and the mode was null until the user typed — and clears on disconnect so the fork regains ownership. Which-key and hint mode stay on the fork's event stream: they need key events rather than a mode.- Plugin:
src/vim/external-mode.ts,src/vim/animated-cursor/controller.ts,src/im/im-mode-watcher.ts,src/rpc/neovim-connection.ts,src/main.ts
- Plugin:
- JSON snippets work with the Neovim backend through LuaSnip — the plugin's snippet JSON is plain VS Code format with the standard variable set and nothing Obsidian-specific, so LuaSnip reads it verbatim; verified before building by expanding
datefrom the bundledglobal.jsonthrough a real LuaSnip, which produced the resolved date rather than the literal variables. Set up Neovim now installs LuaSnip, writes the bundled snippets out ofmain.jstovim-motions-snippets/beside the generated config, points the loader at those and at the user's own snippet directory as an absolute path, and maps<Tab>/<S-Tab>to expand and jump — without an expansion key the block would load and do nothing. The Lua DSL remains incompatible: different namespace and registration signature, and its reactivef()/d()nodes call into Obsidian's vault.- Plugin:
src/rpc/config-export.ts,src/main.ts
- Plugin:
- The generated dial.nvim configuration restores the behaviours that made the feature worth having — its default augends are decimal, hex numbers, four date patterns and Japanese weekdays, so wiring only the keymaps would have lost booleans, hex colours and checkboxes, which are most of what the bundled increment adds over Vim's own. Those three augends are now registered explicitly. Verified against the real plugin rather than asserted:
flag = true→false,#aabbcc→#abbbcc,- [ ] task→- [x] task,41→42. This also corrects a documentation claim that dates were unsupported by default; they are.- Plugin:
src/rpc/config-export.ts
- Plugin:
- Set up Neovim installs and updates the plugins it configures, on request — the configuration export previously refused to install anything, on a reading of the Developer Policies that was stricter than the policy is: the "install or update their dependencies" clause targets a plugin pulling in what it needs unasked, not an install the user explicitly requested, which is why BRAT ships in the catalogue. Set up Neovim now previews the plugins Neovim will fetch or update and the configuration that will be written, and applies both only on confirmation, using Neovim's own
vim.packinto Neovim's data directory. Installing is deliberately kept on the button rather than written into the generated file, so a file you require stays configuration. Measured before building:vim.pack.addandvim.pack.updateare both non-interactive headless, the latter needingforce = trueto skip its review buffer.- Plugin:
src/rpc/config-export.ts,src/rpc/neovim-connection.ts,src/settings.ts,src/main.ts,styles.css
- Plugin:
--cleansilently disabled Neovim package activation — with a configured Neovim configuration path the backend spawns under--clean, which strips the user packpath.vim.packthen clones a plugin to disk and never puts it on the runtimepath, sorequirestill fails: an install that reports success and does nothing. Measured directly —rtp_has_plugin=falseunder--cleanagainsttruewithout it. The spawn now appends Neovim's ownstdpath('data')/siteback, which restores packages without reinstating the wrapper-injected runtimepath--cleanexists to exclude; a configuredinit.luastill loads.- Plugin:
src/rpc/neovim-connection.ts
- Plugin:
- Generate a Neovim configuration from your settings — most bundled-engine features were modelled on a Neovim plugin, so Vim engine → Generate a Neovim configuration translates the enabled ones into
nvim-surround,dial.nvim,spider.nvim,yanky.nvim,flash.nvim, andmini.operators, alongside the leader key andtextwidth. It writeslua/vim_motions.luabeside the user's Neovim configuration and does nothing until they addrequire('vim_motions')themselves; a Copy button serves the pick-and-choose case. The path must be underlua/to be requireable — a file besideinit.luais not, measured — and it is resolved by asking Neovim forstdpath('config')rather than reconstructing it fromXDG_CONFIG_HOMEandNVIM_APPNAME, which M2a measured as unreliable against wrapper installs. Every block is wrapped inpcall(require, …)so the file loads with none of the plugins present, and it emits no install call: this configures what the user already has, never fetches it. Regenerating refuses to overwrite a file whose generated header is gone. A changed setting marks the file stale in the settings tab, and Regenerate automatically (default off) applies it without asking. Both settings implementations render this through one shared method rather than two copies.- Plugin:
src/rpc/config-export.ts,src/util/external-fs.ts,src/settings.ts,src/main.ts,styles.css
- Plugin:
- Smart list continuation and yank highlight now work with the Neovim backend — both are driven by the existing plugin settings and need no Neovim plugin.
o/Ocontinues-,*, and+bullets, which the stock Markdown ftplugin does not do (formatoptions=jtcqlnomitsoandr). It cannot simply drop the ftplugin'sfcomment flag: that flag serves two behaviours at once — it givesgqits hanging indent while stoppingorepeating the marker — and removing it madegqre-bullet every wrapped line, whichrpc-structural-navcaught. The continuation form is swapped in only for the duration of ano/Oinsert, through anexprmapping returning the key so count, undo, and dot-repeat stay native; numbered lists remain out of reach becausecommentscannot increment a counter. Yank highlight is reported by aTextYankPostnotification and drawn by the host's own renderer, sofadeworks as well assolid. The obvious extmark route does not: a yank changes no text, so the decoration provider never re-runs and the mark reaches CM6 only on a later redraw, long after the highlight expired.textwidth, previously a lone positional argument, now travels with both as one editor-options object.- Plugin:
src/rpc/document-sync.ts,src/rpc/neovim-connection.ts,src/main.ts
- Plugin:
- Arbitrary Obsidian commands are reachable from Neovim —
:oband:obcommandjoin the feature bridge, so:ob theme:use-darkexecutes any Obsidian command by ID while RPC owns editor keys. The bridge is otherwise an allowlist of features this plugin registers, which by construction cannot enumerate commands owned by Obsidian itself or by other plugins; this is the one endpoint that escapes it. Both route through the existing{ id, args }payload and the start-of-command-line guarded abbreviation, so:%s/ob/…/stays a substitution.- Plugin:
src/rpc/obsidian-feature-bridge.ts
- Plugin:
- Neovim backend crash breadcrumb — a persisted
neovimToggleInFlightmarker is written before a real RPC connect or disconnect and cleared once it settles, so a marker that survives a restart means the renderer died mid-switch. The next start clears it and explains what happened instead of leaving an unexplained lost window. The renderer crash itself cannot be caught in-process: the plugin's JavaScript dies with the renderer, and Electron'sUtilityProcessis unavailable to plugins. Scoped to real transitions — a reload that neither connects nor disconnects writes nothing.- Plugin:
src/main.ts,src/settings.ts
- Plugin:
treesitter-handle-leakast-grep rule — flags aTreeCursorallocated with.walk()whose enclosing scope never callsdelete()on it.web-tree-sitterregisters every handle with aFinalizationRegistryanddelete()unregisters it first, so a dropped cursor is not a leak — it is a deferred free of its tree's pointer (the registry holdsthis.tree[0], not the cursor's own handle) from a GC callback, after the CM6 bridge has already freed that tree. Shown to fire on the real defect,getAllNodesOfTypeat88ec375~1, and to stay silent on the fixed tree, per the repo rule that a new pattern rule must be demonstrated against a defect that actually shipped.- Coverage was measured against a probe of every form rather than assumed: it fires on
const/let/varbindings never freed in scope, on a discardednode.walk();, and on a reassignment in a scope with nodelete()at all, with no false positives across four correctly-freed shapes. The discarded-value arm uses a direct-childhasdeliberately — withstopBy: endit also matchedcursor = node.walk();and flagged a correctly freed reassignment. It is flow-insensitive, so a reassignment in a scope that frees some other cursor is a known false negative, accepted because the alternative flagged correct code.TreeandQueryhandles are out of scope, sinceparse()andnew QueryWrapper()legitimately transfer ownership to a cache or caller; their owners are asserted by unit tests instead.
- Coverage was measured against a probe of every form rather than assumed: it fires on
Changed
- Minimum Obsidian version raised from 1.7.2 to 1.8.7 — RPC message severity styling applies its class to
Notice.messageEl, whichnoticeElpredates;messageElis available from 1.8.7. Vaults on 1.7.2 through 1.8.6 will not receive this release.README.mdand the installation guide state the new floor. - The bundled vim fork's own type declarations are used —
src/types/codemirror-vim.d.tsdeclaredVimasRecord<string, unknown>, shadowing the 166 members the fork's shipped.d.tsexports and reducing everyVim.*access across the 19 files that import it tounknown. All 15 fork-specific exports it hand-declared (foldopenAnnotation,setCursorSuppressed,setKeyInterceptActive, …) were already shipped, so deleting it reports zero errors insrc/andtest/while restoring real checking. The casts that erasure forced are gone:escape-guard.tsnow callsVim.setIdleEscapeCallbackdirectly, and the demand harness callsVim.map/unmap/getOperatorfuncwithout a hand-written view.getBundledVimApi()keeps its conversion — the plugin's ownVimApiinterface genuinely does not overlap the fork's shape.- Plugin:
src/types/codemirror-vim.d.ts(removed),src/vim/escape-guard.ts
- Plugin:
- RPC E2E provisioning is cross-platform and version-pinned — Linux uses the official Neovim 0.12.5 release tarball in the E2E container, while macOS and Windows install the matching official release in the workflow. Every installer prints
nvim --versionand rejects API levels below 12 before tests start.- CI:
.github/docker/e2e-runner/Dockerfile,.github/workflows/docker-e2e-runner.yml,.github/workflows/e2e.yml,.dockerignore - Scripts:
scripts/install-neovim.sh,scripts/install-neovim.ps1,scripts/neovim-version.txt
- CI:
- Neovim RPC frontmatter handling supports both properties modes — removes the Source-only connection refusal. Source frontmatter remains fully navigable; rendered frontmatter receives a dedicated-window
foldmethod=exprfold using the same start-of-document delimiter rule as CodeMirror, and post-key cursor synchronization resolvesfoldclosed()positions to the first body line. Property-widget focus remains owned by Obsidian, while API edits can still update the complete folded document.- Plugin:
src/fold/frontmatter.ts,src/fold/provider.ts,src/rpc/frontmatter-fold.ts,src/rpc/document-sync.ts,src/rpc/key-delegation.ts,src/rpc/neovim-connection.ts
- Plugin:
- The renderer no longer parses Markdown while the Neovim backend is connected — Neovim parses the same document natively, and the renderer consumers of its tree are dormant then: structural motions and Markdown text objects run as companion mappings inside Neovim, and fold state is mirrored back from redraw. The tree-sitter bridge now tracks the connection, so a connected session parses each change once instead of twice. It also removes the WASM heap growth that made retained tree-sitter nodes reachable — defence in depth, since that defect is fixed at the call sites. Verified both ways: 34 RPC tests pass with the bridge off, 33 non-RPC tests pass with it on.
- Plugin:
src/main.ts
- Plugin:
Fixed
]hand[hwaited a second before moving — M5a added]h1–]h6and[h1–[h6as descriptive aliases for the level-specific heading motions, which the plugin already binds to]1–]6and[1–[6. The aliases made]ha prefix of six longer mappings, so every press hit the fork's prefix-ambiguity deferral and waited outoperatorshadowtimeout(1000 ms) before moving. The redundant aliases are gone; the numeric bindings are unchanged.- Plugin:
src/motions/register.ts,src/rpc/companion.lua
- Plugin:
- A Neovim RPC request could stay pending forever — a request whose response never arrived left its promise unsettled with no bound, so a wedged Neovim silently hung whatever awaited it. Requests now reject after 30 seconds. The timer is taken from
window, not the Node global, so it also fires correctly in Obsidian popout windows.- Plugin:
src/rpc/msgpack-rpc.ts
- Plugin:
- A rapid Vim-mode toggle applied only its first half —
disableVimandenableVimcleared their in-progress flag from a 500 ms timer armed infinally, so the returned promise resolved while the flag was still set. The opposite toggle arriving inside that window hit its own guard and was discarded with nothing to retry it, leaving Vim off and every extension-slot feature unregistered until Obsidian reloaded. Toggles are now serialized so both halves apply.- Plugin:
src/main.ts
- Plugin:
- Heading motions became no-ops before the parser settled —
]h/[hand the level-specific motions consulted the treesitter tree and gave up when it existed but had produced no headings yet, which happens while parsing is still catching up. They now fall back to the regex scanner in that case, as they already did when no tree was available at all.- Plugin:
src/motions/headings.ts
- Plugin:
- Two treesitter nodes were held across traversals that can free them — snippet context detection read a fenced block's language from a node retained across a child walk, and the blockquote text object held a node across its
.parent()ancestor walk. Both are the use-after-free shape behind the renderer crash class: the handle survives the tree that owns it and then returns stale data. Both now read through a cursor while traversing.- Plugin:
src/snippets/context.ts,src/text-objects/blockquote.ts,src/treesitter/js-api.ts
- Plugin:
- RPC fold expression no longer rescans the complete document for every queried line — the window-local Markdown
foldexprpreviously fetched and rescanned all lines on each invocation, making an edit on a 2,004-line note effectively quadratic and pushing measured RPC operator latency to 145.9 ms p95. It now computes one linear fold-level table per Neovimchangedtickand reuses it for the remaining fold queries.- Plugin:
src/rpc/frontmatter-fold.ts
- Plugin:
- RPC requests wait for active-note re-seeding — programmatic Neovim requests issued immediately after returning from a host sidebar could race the asynchronous active-leaf activation and have their buffer update replaced by the later seed. Requests now await the document-sync activation promise before flushing keys or reaching Neovim.
- Plugin:
src/rpc/neovim-connection.ts
- Plugin:
- RPC notes now use a real Markdown buffer instead of the dashboard buffer — M2a previously wrote into Neovim's intro buffer by forcing its
modifiableoption. With alpha-nvim loaded that buffer remainedbuftype=nofileandfiletype=alpha, preventing Markdown plugins and filetype configuration from activating even though byte synchronisation passed. The backend creates one listed buffer, names it with the active note's absolute path, makes it current, explicitly runs filetype detection, and reuses it across active-leaf changes. The buffer is now finalized as theacwritemirror described above.- Plugin:
src/rpc/document-sync.ts,src/rpc/msgpack-rpc.ts
- Plugin:
- RPC acceptance tests no longer load the developer's Neovim configuration — both lifecycle and text-sync specs use the committed minimal fixture. A Lua marker assertion fails if the configured path is ignored or cleared.
- Tests:
test/fixtures/nvim/init.lua,test/specs/rpc-lifecycle.e2e.ts,test/specs/rpc-text-sync.e2e.ts
- Tests:
- RPC leaf changes no longer overwrite the newly active note with the previous note — the M2b one-shot seeding path reused the previous Neovim mirror on every later activation and dispatched it into the new CM6 editor, allowing Obsidian autosave to persist cross-note data loss. Activation now always reads the newly active editor, renames the Neovim buffer, detects its filetype, and repopulates it while line-event echo is suppressed. No activation path writes an existing mirror into a different editor.
- Plugin:
src/rpc/document-sync.ts
- Plugin:
- Harpoon same-leaf navigation preserves stored cursor positions — changing the file in one leaf fired
active-leaf-changeafter that leaf already represented the destination, so cursor tracking overwrote the destination pin with line 0, column 0. Same-leaf activations no longer treat the destination as the leaf being left, and navigation snapshots its target before asynchronous activation.- Plugin:
src/main.ts,src/vim/harpoon-nav.ts
- Plugin:
- Oil sort cycling changes the rendered order — directory rendering always read the configured default sort value, so
gsadvanced an internal key that was never used. The manager now initializes its active sort from the configured default and renders with the cycled value.- Plugin:
src/oil/manager.ts
- Plugin:
- Oil hidden-file toggle recognizes an unchanged buffer — dirty detection re-rendered the directory to compare strings, but rendering allocates fresh entry ids, making every unchanged buffer appear modified and blocking
g.. It now computes changes against the existing Oil snapshot.- Plugin:
src/oil/manager.ts
- Plugin:
- Oil path yanks update the unnamed register —
y.now writes the selected path to the bundled Vim engine's unnamed register as well as the system clipboard.- Plugin:
src/oil/manager.ts,src/oil/keybindings.ts
- Plugin:
g-andg+navigate the undo tree again — both actions capturedthis.undoTreein a local at registration time, butactivateUndoTreeForFile()swaps that field per note, so the capture was orphaned on the first file activation while edits kept recording into the live tree throughbuildUndoTreeExtension().g-walked the empty orphan and returned at itsif (!node)guard, doing nothing; where the orphan held history it applied those change sets and restored unrelated content. Both registration paths now resolve the field at call time.- Plugin:
src/main.ts
- Plugin:
- Renderer crash on Neovim RPC disconnect —
disconnectChild()sentnvim_command qa!and then awaited the child's'close'event, with aSIGKILLfallback, all inside the teardown path. That crashed Obsidian's renderer with a SIGSEGV on a corrupted V8 compressed pointer, reproducible in the CI container at 24 of 46 runs of a spec that cycles connect-traffic-disconnect 14 times. The crash requires RPC traffic and a disconnect: 216 non-RPC tests, 432 traffic-free connect cycles, and 2,400 toggle-free requests were all clean, and stubbing the extmark, float, buffer-line, and cursor handlers changed nothing, so it was never the message handling. Teardown now detaches from the child, unrefs it, and defersSIGTERMwith aSIGKILLescalation to a later turn; nothing is awaited and both timers re-checkexitCode/signalCode. This is a mitigation, not a cure — arms that drop the synchronous quit-and-wait total 7 of 38 runs against 24 of 46 for the rest (Fisher p = 0.002, roughly a 3x reduction), and a residual path survives. See known limitations.- Plugin:
src/rpc/neovim-connection.ts
- Plugin:
- Renderer crash from retained tree-sitter nodes —
getAllNodesOfType()collectedNodeobjects during a tree walk and read them afterwards, andheadingLevelFromNode()callednode.child()on those retained nodes. ANodeis a JavaScript object holding an address into WASM linear memory, so any parse that grows that memory replaces the backing buffer and leaves the retained node pointing into a detached one — a read atbase + garbage, which segfaults the renderer with no JavaScript frame. Structural heading motions (]h,[h, level variants) now walk with aTreeCursorand extract plain data, retaining no node and deleting the cursor. Measured against an isolated reproducer: 8 of 16 runs before, 0 of 16 after; the full spec went from a pooled 29% to 0 of 16.- Plugin:
src/treesitter/js-api.ts,src/motions/headings.ts
- Plugin:
vim.treesitter.query.parse()leaked its compiled query — every call built aQueryWrapperthat no cache owned, soNamedQueries.dispose()never saw it and the underlying WASM query survived for the life of the Lua state. Parsed queries are now tracked and deleted on state close alongside the named ones, throughrunCleanupsso one failing disposer cannot skip the rest.- Plugin:
src/lua/treesitter/query-api.ts
- Plugin:
- Query capture nodes carried no tree reference —
Query:iter_captures()andQuery:iter_matches()pushed nodes without a_treefield, sonode:tree()returnedniland every node reached through:parent()/:child()lost the reference too, unlike Neovim. Both iterators now hold the source node's tree table in the Lua registry and attach it to each capture, matching the existingiter_childrenshape and releasing the reference when the iterator is exhausted.- Plugin:
src/lua/treesitter/query-api.ts
- Plugin:
- The 3-second deferred Neovim shutdown is removed — it existed only as a mitigation for the renderer crash resolved below, adopted on a comparison that is now known to have been confounded because both of its arms contained the leak. With the leak fixed, the reproducer spec measures 0 segfaults in 16 runs in all three teardown shapes — the deferral, the original synchronous
qa!-and-wait that used to crash 24 of 46, and the immediate non-blockingSIGTERMthat now ships. 48 runs, 0 segfaults. Teardown keeps the non-blocking shape, which is better on its own merits and is also less code than the synchronous version: disconnect returns immediately instead of blocking for up to four seconds, andSIGTERMlets Neovim exit on its own terms whereqa!did not.- Plugin:
src/rpc/neovim-connection.ts
- Plugin:
- Intermittent renderer crash when disconnecting the Neovim backend — resolved, and by a fix that had already shipped for an apparently unrelated defect. The cause was a leaked
web-tree-sitterTreeCursor:getAllNodesOfTypeallocated one per structural motion and never deleted it, leaving it in the library'sFinalizationRegistry, which registers a cursor under its tree's pointer. The CM6 bridge frees that tree on the next re-parse, so GC later fired the finalizer against a dangling pointer and corrupted the WASM allocator from a GC callback — which is why it required RPC traffic and bundled-fork editing in the same session. The reproducer spec that measured 24 of 46 runs now measures 0 segfaults in 16. The deferred-teardown mitigation is retained but is no longer load-bearing; the measurement that justified it was confounded, because both of its arms contained the leak.- Docs:
KNOWN_LIMITATIONS.md,test/flaky-inventory.md
- Docs:
- Two trees handed to Lua had no owner —
vim.treesitter.get_string_parser()andTSTree:copy()each allocate a tree that no cache holds, so nothing freed them deterministically and their only reclaim was a GC finalizer at an unpredictable moment. That is the mechanism behind unpredictableTSNodestaleness, and it is the same handle-ownership class as the renderer crash. Both are now tracked and deleted when the Lua state closes.- Plugin:
src/lua/treesitter/api.ts,src/lua/treesitter/tree.ts
- Plugin:
- The Lua treesitter parser caches outlived the Lua state —
parserCacheandltreeCachewere module-level, solua_closeleft both populated: a reloaded configuration inherited the previous state's trees, and their WASM trees, parsers, and injection queries were never freed. Both are now per-state and disposed throughregisterStateCleanup, deleting cached trees and destroying cachedLanguageTrees.- Plugin:
src/lua/treesitter/api.ts
- Plugin:
Tests
test/now passes full strict type-checking —typecheck:testsrunstsc -p tsconfig.test.jsonunfiltered at zero errors, replacing the resolution-only allowlist (TS2304,TS2305,TS2307,TS2551,TS2552) that let nullability, argument-type, and missing-property errors through. The 19 errors the run reported againstsrc/were configuration gaps rather than defects and are resolved by including the existing ambient declarations; nosrc/code changed. The pass surfaced four defects no other gate could see: a vacuousexpect(result.flash).toBe(DEFAULT_SETTINGS.flash)comparingundefinedtoundefinedagainst a setting that does not exist, arunCleanups()call missing its requiredcontextargument, avimHandleKeys(string[])call whose thrownTypeErrorwas swallowed by the diagnostic's owncatch, and aProbeinterface missing the two fields its assertions read.- Tests:
tsconfig.test.json,package.json,test/unit/settings-resolution.test.ts,test/unit/lua/plugin-demand-harness.ts,test/specs/navigation.e2e.ts,test/specs/lua-plugin-flash-diagnostic.e2e.ts, and 42 further spec and unit files
- Tests:
- The
vim.treesittere2e suite now tests something — all 12 of its assertions readgetLuaConfigError(), which returnedplugin.luaLoadResult.error. No such member exists on the plugin, so the helper returnednullunconditionally and the whole suite passed against a Lua body ofthis is not valid lua @@@ ###; thevim.g.__*flags each test computed were never read. Each test now reads its flags back and asserts their values, and a config that fails part-way through drops the later flags rather than reporting nothing. Sabotaging the same test now fails it.- Tests:
test/specs/treesitter.e2e.ts
- Tests:
- Spec shape guards —
test/unit/spec-shape-guards.test.tscloses the class strict typing cannot: a hand-writtenRecord<string, T>plugin handle is an assertion about the plugin, so an invented member compiles and silently readsundefined. It cross-checks every declared plugin member intest/— 97 spec files declare one — against the class insrc/main.ts, and fails anyexecuteObsidiancallback whose{ error: … }branch is cast to a primitive that drops it (109 such callbacks exist; none currently does). Both guards are negative-controlled.- Tests:
test/unit/spec-shape-guards.test.ts
- Tests:
- Root configuration files are type-checked —
typecheck:configadds a seventh blocking gate overwdio.conf.mts,eslint.config.mts,vitest.config.ts, and the.mjsbuild, release and CI-reporting scripts, none of which sat inside either existing tsconfig include.wdio.conf.mtsreported 22 errors on first measurement, including in theafterTestcleanup hook whose failure surfaces as cross-test flakiness rather than a red test. It needsmoduleResolution: nodenextand type-only imports ofwebdriverioandwdio-obsidian-service, because adeclare globalaugmentation only loads if its package is imported. The.mjshalf reported 11 more:esbuild.config.mjswas clean, butreport-e2e-failures.mjs— the script that reports CI failures — carried an implicitany[], four uncheckedstring | undefinedarguments and an unguardedMap.get()dereference, andversion-bump.mjsreadprocess.env.npm_package_versionwithout a guard, so running it outsidenpm versionwrote a version-lessmanifest.jsonand a literal"undefined"key intoversions.json. All three scripts were re-run against sample input to confirm unchanged output.- Tests:
tsconfig.config.json,wdio.conf.mts,vitest.config.ts,esbuild.config.mjs,version-bump.mjs,scripts/report-e2e-failures.mjs,scripts/report-latency.mjs,package.json
- Tests:
- M7 latency certification harness —
test/specs/rpc-latency.e2e.tsmeasures real keydown to first rAF after the same CM6 transaction criterion for fork and production RPC over a 2,004-line runtime fixture (N=500 plus 75 warmups). It proves production fork interception and bridge engagement, enforces stable document size, and gates on the expected fork-faster p50 relationship. The certified run measured fork/RPC p50 15.3/17.3 ms, p95 39.5/36.2 ms, and p99 53.1/48.7 ms, for p95/p99 deltas of −3.3/−4.4 ms; delay, forced-layout, engagement, and drift controls are recorded inrpc-latency-negative-controls.md. - RPC prerequisite coverage — all 13 RPC specs use
test/specs/rpc-prerequisites.tsto warn and skip when Neovim is absent, below API level 12, or a required fetched fixture is missing. Oil now checks its fetched flash fixture, and only the POSIX old-API stub scenario skips on Windows. - RPC lifecycle acceptance coverage — 7 WDIO scenarios cover attach/API reporting, runtime disable, Vim disable, unexpected
SIGKILLand reconnect, plugin unload, missing binaries, and the API-level floor. PID liveness is checked directly, teardown/version-floor sabotages are recorded intest/specs/rpc-lifecycle.negative-control.md, and the settings-option inventory records both controls as process-backend settings rather than Vim options. Disposable.sisyphus/spike files are excluded from the production lint project. - RPC text synchronisation acceptance coverage —
test/specs/rpc-text-sync.e2e.tschecks isolated fixture loading, normal Markdown buffer identity, 210 boundary-valid API edits, cross-line collapse, end/start deletion, append, and the documented invalid UTF-8 boundary divergence against a raw-byte Lua oracle. Its active-leaf regression uses two known file contents, switches both ways, retains a Neovim key edit, and independently reads both files through the vault adapter. Restoring the one-shot seed reproducedTarget.mdasA original\nA second lineon first activation andrpc-A original\nA second lineafter the later switch, including on disk. - RPC key delegation acceptance coverage —
test/specs/rpc-keys.e2e.tsdrives 210 insert/operator/visual/count/dot-repeat/undo-redo/macro sequences containing ASCII, astral, combining, and CJK text through the editor DOM, comparesciwfoo<Esc>w.with live headless Neovim, checks bridged/unbridged buffer/cursor/mode/register identity, proves single insertion, and records the visible/source D7 measurement. Negative controls producedxxseedwithout fork interception,xxiseedwithoutpreventDefault, CM column 0 instead of 6 without cursor sync, and buffer/register divergence after a bridged-onlyx. - RPC frontmatter acceptance coverage — six M2c scenarios in
test/specs/rpc-keys.e2e.tscover visible-mode connection, repeated upward motion, frontmatter-preservingddwith an independent vault read, guardedgg, Source-mode navigation, and API edits inside the fold.test/specs/rpc-keys-negative-controls.mdrecords the no-fold, no-guard, and Source-fold sabotages and exact cursor/document outcomes. - RPC decoration acceptance coverage —
test/specs/rpc-decorations.e2e.tscompares every rendered flash.nvim label against flash's own all-namespace extmark query, verifies label selection in Neovim and CM6, statically excludes polling and grid consumption, and proves the idle bridge is empty. Negative controls observed 39/40 labels after dropping one forwarded extmark, all 40 offsets shifted by one after a byte-column error, and 0/40 decorations without the UI redraw clock. - RPC write/read acceptance coverage —
test/specs/rpc-write-routing.e2e.tsverifies Obsidian's save command was invoked, reads saved and deliberately stale content through the vault adapter, checks:e!preserves the unsaved editor document, and rejects a stale Neovimmodifiedflag.test/specs/rpc-write-routing-negative-controls.mdrecords the four required sabotages and observed values. - RPC Obsidian bridge acceptance coverage —
test/specs/rpc-obsidian-bridge.e2e.tscovers seven representative host scenarios plus teardown-aware refresh.test/specs/rpc-obsidian-bridge-negative-controls.mdrecords isolated missing-map, no-op-dispatch, unguarded-abbreviation, and skipped-teardown failures. - M4b registry and workspace bridge coverage —
test/unit/vim-registration-inventory.test.tsguards the measured 75-motion, 78-action, 164-map, and 102-ex registration surface and six M4a selections. The RPC bridge spec adds horizontal split, four-way pane focus, counted and explicit tab targeting, lowercase command/abbreviation safety, wikilink definition navigation, and expanded refresh inventory coverage. - M4b Batch 2 picker bridge coverage — the RPC bridge spec asserts every picker leader source, lowercase
:buffers, query-bearing:grep, named:Pickersource selection, modal-key file selection plus post-selection Neovim content, guarded substitution behavior, and duplicate-free refresh. Four subject sabotages prove query, mapping, guard, and re-seed assertions fail independently. - M4b Batch 3 Harpoon, marks, and jumplist coverage — the RPC bridge spec asserts slot mappings and arguments, optional versus explicit removal, three-file cursor-preserving cycling, cross-note and counted older jumps with independent file/cursor/content checks,
:jumps, mark-table plus sign-column deletion, substitution safety, and duplicate-free refresh. Four subject sabotages prove slot, host ownership, count, and gutter refresh independently. - M4b Batch 4 Oil isolation coverage —
test/specs/rpc-oil.e2e.tsdrives all 16 Oil mappings through the embedded editor's real DOM with RPC connected, verifies 14 observable host effects, explicitly skips the two OS-shelling actions, checks handler/interception state on Oil and Markdown, and proves Oil keys do not change Neovim's buffer. Forced interception removed the first character from Neovim's sentinel, while no-op'ingoilHelpfailed only its scenario. - M4b Batch 5 folding and undo-tree coverage —
test/specs/rpc-folds-undo.e2e.tscovers nine scenarios for the fold mirror, native fold motions/operator, raw-byte undo/redo and chronological navigation, native:earlier/:later, Neovim-backed sidebar data, and duplicate-free bridge refresh. Four subject sabotages prove fold forwarding, row mapping, sidebar data ownership, and command lifecycle independently. - M5a structural navigation and hard-wrap coverage —
test/specs/rpc-structural-nav.e2e.tsruns 14 fork-oracle parity scenarios across headings, levels, counts, list items, links,gq/gw, and seven operator-pending forms. It compares yank contents, rejects unexpected empty documents, and independently reads one edited note through the vault adapter. Five subject sabotages are recorded inrpc-structural-nav-negative-controls.md. - M5b Markdown text-object coverage —
test/specs/rpc-text-objects.e2e.tsruns 65 fork-oracle parity scenarios covering delete, change, yank/register, visual selection, and counted forms for 13 Markdown object shapes, including nested and adjacent delimiters. Every operator checks the document-wipe invariant, one edit is read through the vault adapter, and three subject sabotages are recorded inrpc-text-objects-negative-controls.md. - M6a floating-window coverage —
test/specs/rpc-floats.e2e.tscompares flash.nvim's prompt content and config-derived placement with Neovim, checks two-float z-index order, close and disconnect cleanup, and a custom float's extmark and border.rpc-floats-negative-controls.mdrecords missing-forwarding, one-cell offset, ignored-z-index, and stale-close failures. - M6b IME composition coverage —
test/specs/rpc-ime.e2e.tsdrives Chromium's native composition path through CDP, verifies byte-exact CJK commit, dot-repeat, cancellation, key suppression, and active-note switching with an independent vault-adapter read.rpc-ime-negative-controls.mdrecords CM6-only commit, buffer-API commit, and composing-key forwarding failures. - M8a external-UI message coverage —
test/specs/rpc-messages.e2e.tscovers eight scenarios for informational and error Notices, real-key Lua errors, silent undo/search kinds, one-per-message dispatch, duplicate limiting, and 200-key grid-event latency.rpc-messages-negative-controls.mdrecords missing-dispatch, noisy-kind, and removed-dedup failures with observed counts and values. - M8b external command-line coverage —
test/specs/rpc-cmdline.e2e.tsdrives all 11 command-line, caret, prefix, prompt, selection, cancellation, nesting, and bundled-fork-isolation scenarios through real editor key events.rpc-cmdline-negative-controls.mdrecords stale-hide, raw-byte-caret, and single-level-state failures with observed counts and values. - M8c popup-menu and M8d status-mode coverage —
test/specs/rpc-popupmenu.e2e.tsdrives insert completion and command-line wildmenu selection/hide through real editor key events, while four lifecycle scenarios cover insert, normal, visual-line, and disconnect arbitration.rpc-popupmenu-negative-controls.mdrecords ignored-selection, wrong-anchor, and suppressed-mode-handler failures with observed counts and values. - The
treesitter-handle-leakrule's own behaviour is locked by a test —test/unit/treesitter-handle-leak-rule.test.tsruns the rule against nine fixtures and asserts exactly which fire: all five leak shapes, none of the four correctly-freed ones, plus the shippedgetAllNodesOfTypedefect. Negative-controlled by restoring thestopBy: endthat produced the original false positive, which reports[ 'reassign-both.ts' ]against an expected[]. A blocking gate that flags correct code gets disabled, so the silent cases matter as much as the firing ones. - Unowned treesitter handle coverage —
test/unit/lua/treesitter-queries.test.tsasserts that aget_string_parser()tree and aTSTree:copy()are both deleted when the Lua state closes. Negative-controlled against the unowned state: 3 deletes with the owner installed, 1 without, the survivor being the document parser's cached tree. - Treesitter Lua memory-safety coverage —
test/unit/lua/treesitter-queries.test.tsadds three regressions against the real bundled grammars:query.parse()wrappers are deleted on state close, capture nodes from both iterators expose their tree, and the parser cache is freed on close without leaking into the next state. Each was negative-controlled against the defect it replaces — 0 deletes instead of 3,iter_captures node has no treeanditer_matches node has no tree, and 1 delete instead of 2. - Undo-tree navigation is asserted behaviourally — the existing
g-/g+scenarios asserted only that the keys did not crash and left the mode alone, both of which held for the entire timeg-was broken.test/specs/undo-tree.e2e.tsnow asserts that the live tree's current sequence moves, which fails against the previous code with the sequence unchanged at 20 instead of 19 while the two non-crash scenarios still pass. - The
pluginAutoFetchboundary is now held by a test rather than by an argument — the M0 policy determination rests on that fetch path never feeding the real Neovim runtime, since installing executable dependencies for a runtime is listed under Not allowed in the Developer Policies, and design risk R-5 required a test asserting it cannot.test/unit/rpc/plugin-autofetch-boundary.test.tsasserts the complete spawn argv, so any addedruntimepathentry fails it, and separately asserts that no file undersrc/rpc/imports the fetch or store modules or names their on-disk paths, which it reads fromplugin-store.tsrather than restating. Negative-controlled twice: prependinglua/.stagingto the runtimepath failed three of the five assertions, reporting a 7-element argv against the expected 5; adding aplugin-storeimport todecorations.tsfailed exactly one, reporting[ 'decorations.ts -> plugin-store' ]against[].- Tests:
test/unit/rpc/plugin-autofetch-boundary.test.ts,src/rpc/neovim-connection.ts
- Tests:
- Unit tests can now import any RPC module — anything reaching
src/rpc/companion.luafailed to load under Vitest, which parsed the Lua as JavaScript, so no test undertest/unit/rpc/could importneovim-connection.tsat all. AluaTextPlugin()mirrors esbuild's'.lua': 'text'loader, alongside the existing.wasmprecedent.- Tests:
test/helpers.ts,vitest.config.ts
- Tests:
- Table navigation under the Neovim backend is covered —
test/specs/rpc-table-nav.e2e.tsasserts the overlay activates while RPC is connected, thatlmoves the highlighted cell, and that neither Neovim's cursor nor the buffer changes. The two absence assertions carry a positive control rather than a sabotage: a sibling scenario presses the same key outside the table and requires the same probe to report movement, so "Neovim did not move" cannot hold for every implementation. Negative-controlled by forcingcanActivate()false, which reportedentered: false, navigated: falseagainsttrue, true.- Tests:
test/specs/rpc-table-nav.e2e.ts
- Tests:
- Companion-label and leader-group coverage — six unit cases cover
leaderGroupPrefixesandleaderGroupLabel, and two live scenarios assert the companion's operator-pending maps read as prose and that every registered group starts with the leader without ever equalling it. Negative-controlled: starting the prefix loop atleader.lengthinstead ofleader.length + 1failed three unit cases including the leader-exclusion one; removing the install call failed the live group scenario; renaming the group-label key failed the label case.- Tests:
test/unit/rpc/bridge-action-label.test.ts,test/specs/rpc-obsidian-bridge.e2e.ts
- Tests:
- Hint-mode and bridged-label coverage —
test/unit/rpc/bridge-action-label.test.tscovers the label derivation across 13 names; two scenarios inrpc-obsidian-bridge.e2e.tsactivate hint mode from Neovim and assert the buffer is byte-identical afterwards, and that no generateddescleaks an internal id. The buffer assertion is the load-bearing one — the overlay closing would pass even if the label key had also reached Neovim. Negative-controlled: removinghintactivatefrom the allowlist failed only the hint scenario; revertingDESC_PREFIXto the old id form failed three, including the refresh-inventory check. The refresh check now identifies leader mappings by their leader prefix rather than bydesc, since the description no longer encodes an action name.- Tests:
test/unit/rpc/bridge-action-label.test.ts,test/specs/rpc-obsidian-bridge.e2e.ts
- Tests:
- External mode coverage —
test/unit/vim/external-mode.test.tscovers the Neovimmode()mapping across 19 values plus the publish, dedup and clear semantics, and asserts the precedence rule directly rather than mirroring it. Two lifecycle scenarios drive real keys against a live Neovim for insert, normal, visual and visual-line, and check the seam clears on disconnect. Negative-controlled four ways: testingnoafternfailed the three operator-pending cases; removing the unchanged-mode dedup failed exactly one; dropping the visual-block mapping failed three; reversing??in the precedence helper failed two; and removing the per-key publish failed both e2e scenarios.- Tests:
test/unit/vim/external-mode.test.ts,test/specs/rpc-lifecycle.e2e.ts
- Tests:
- A bundled snippet was referenced with mixed path separators on Windows — the relative path is built with a literal
/and was then joined to the configuration directory with the platform separator, so the file was written to…\lua\vim-motions-snippets\global.jsonand referenced as…\lua\vim-motions-snippets/global.json. Windows opens either spelling, so nothing failed at runtime and the snippet still expanded; the only symptom was two spellings of one path. Relative segments are now split on either separator before rejoining, and a unit case pins it so the mismatch fails on every platform rather than on a Windows shard.- Plugin:
src/rpc/config-export.ts
- Plugin:
- A Windows-only assertion failure in the snippet-export spec — the scenario compared the generated configuration against a raw filesystem path, but the path is embedded in a Lua string literal, so on Windows every separator is doubled and
D:\a\…never appears verbatim. It passed on Linux and macOS and failed one Windows shard. The product was correct throughout, which the sibling scenario proved by expanding a snippet through LuaSnip on the same runner; only the assertion was wrong. It now unescapes the literals before comparing, and a platform-independent unit case pins the round-trip so the next regression of this class fails everywhere rather than on one shard.- Tests:
test/specs/rpc-config-export.e2e.ts,test/unit/rpc/config-export.test.ts
- Tests:
- Snippet expansion is covered end to end under the Neovim backend —
LuaSnipjoinstest/fixtures/test-plugins.jsonpinned at0abc8f39, which makes itrequire-able from a live Neovim through the sameruntimepathappend that already servesflash.nvimto the decoration and float oracles. The scenario drives the production path rather than the API: real keys into the Obsidian editor, forwarded to Neovim, expanded by LuaSnip through the generated configuration's own<Tab>mapping, and mirrored back. It skips with a message when the fixture is absent. Removing the expansion key from the generated block fails it, along with the two scenarios that load the configuration.- Tests:
test/fixtures/test-plugins.json,test/specs/rpc-config-export.e2e.ts,.gitignore
- Tests:
- Consent-modal and auto-regeneration coverage — three scenarios in
rpc-config-export.e2e.tsdrive the real settings UI: the preview lists repositories and the configuration before anything happens and cancelling writes nothing, auto-regeneration rewrites on a covered change only while the toggle is on, and a hand-edited file survives even with it on. First e2e in the suite to open the settings tab; post-1.13 renders the seven pages as navigable rows, so General has to be opened before the controls exist in the DOM, and the settings pane is itself a modal, so selecting "the first.modal" finds it rather than the preview. Negative-controlled three ways: forcing the auto-refresh gate open failed only the toggle scenario, neutering the sentinel failed both hand-edited-file scenarios, and making Cancel write failed the consent scenario. The first attempt at that last control had Cancel call the full install path and did not fail, because the write lands long after any window a test can wait on — the assertion covers the write rather than the install, and says so.- Tests:
test/specs/rpc-config-export.e2e.ts
- Tests:
- Snippet-export coverage — unit cases assert the block emits the paths it was given and registers an expansion key, and a live scenario asserts the bundled files are written where the generated config says they are. The expansion-key control initially did not fire: the assertion looked for
expand_or_jumpable, which survives on the following line when thevim.keymap.setcall is removed, so it was strengthened to assert the mapping itself. Removing the bundled write failed only the live scenario.- Tests:
test/unit/rpc/config-export.test.ts,test/specs/rpc-config-export.e2e.ts
- Tests:
- Package-activation and install-list coverage —
rpc-config-export.e2e.tsasserts the real spawn leaves Neovim able to activate a package, since the--cleanfailure looks like success and cannot be caught by inspecting a hand-built command line; removing the packpath restore fails it. Unit cases cover the repository list, including that flash is not requested twice when both features that use it are on. The R-5 argv assertion caught the new spawn element exactly as designed, and rather than bumping the expectation it now also asserts the added path resolves inside Neovim's own data directory, which is what keeps it clear of the vault fetch store.- Tests:
test/unit/rpc/plugin-autofetch-boundary.test.ts,test/unit/rpc/config-export.test.ts,test/specs/rpc-config-export.e2e.ts
- Tests:
- Configuration-export coverage —
test/unit/rpc/config-export.test.tsholds generation, the sentinel, the fingerprint and the module probe across 17 cases;test/specs/rpc-config-export.e2e.tsproves the written file is requireable by a live Neovim rather than inspecting the path we chose. Negative-controlled five ways: removing the sentinel check failed only the overwrite-refusal case; dropping thepcallguard failed two generation cases; making the fingerprint constant failed staleness; and dropping thelua/segment failed three e2e cases. The fourth control initially did not fire at all, becauseresolveGeneratedConfigPathhas two branches and the first sabotage hit the one the spec does not take — the control is what caught that, not review.- Tests:
test/unit/rpc/config-export.test.ts,test/specs/rpc-config-export.e2e.ts,test/unit/known-set-options.test.ts
- Tests:
- Editor-option projection coverage —
test/specs/rpc-editor-options.e2e.tsdrives five scenarios through real editor keydowns rather thannvim_input, covering bullet continuation on and off, yank highlight on and off, and reapplication after an activation reseeds the buffer. Negative-controlled three ways: restoring the ftplugin'sfflag producedhelloagainst- hello; removing theo/Omappings produced the same; suppressing the yank notification failed only the highlight scenario; and dropping the reseed call producedhellowith no marker while the other four passed. A fourth control was not needed —rpc-structural-navalready failed on the first implementation, reportinggqoutput re-bulleted as- to wrap with a hanging indentinstead of the hanging-indent form.- Tests:
test/specs/rpc-editor-options.e2e.ts
- Tests:
- Generic Obsidian-command coverage —
test/specs/rpc-obsidian-bridge.e2e.tsruns:ob workspace:toggle-pinthrough Neovim and asserts the leaf pins and unpins, having first confirmed the id is in Obsidian's registry and that no bridge-installed command could account for the effect. A second scenario holds the abbreviation guard. Negative-controlled in both halves: dropping'ob'from the allowlist failed only that scenario withobInstalled: falseagainsttrue, and neuteringexecuteCommandfailed it again at the pin wait while the guard scenario still passed. The bridge-installed probe is scoped by thevim-motions-rpc:marker because matching every Neovim user command picked up the developer config'sDapStepInto, which lowercases to containpin.- Tests:
test/specs/rpc-obsidian-bridge.e2e.ts
- Tests:
Documentation
README.md,docs/configuration/settings.md: desktop/arbitrary-code/FFI/external-file/no-sandbox/no-install disclosure and Milestone 1 scope.KNOWN_LIMITATIONS.md: current lifecycle-only boundary and separation from fengari plugin auto-fetching.AGENTS.md,CONTRIBUTING.md: RPC source ownership and lifecycle test locations.README.md,KNOWN_LIMITATIONS.md,docs/features/neovim-backend.md: M8a message routing, duplicate limiting, and deferred command-line/popup rendering.AGENTS.md,CONTRIBUTING.md: M8a redraw/message source ownership and acceptance/negative-control locations.README.md,KNOWN_LIMITATIONS.md,docs/features/neovim-backend.md: M8b command-line, prompt, byte-caret, nested-level behavior, and deferred popup-menu boundary.AGENTS.md,CONTRIBUTING.md: M8b command-line source ownership and acceptance/negative-control locations.README.md,KNOWN_LIMITATIONS.md,docs/features/neovim-backend.md: M8c popup-menu completion and M8d Neovim-owned status-bar mode behavior.AGENTS.md,CONTRIBUTING.md: popup-menu/mode-status source ownership and acceptance/negative-control locations.CHANGELOG.md: M8c/M8d implementation, tests, and documentation coverage.CHANGELOG.md: Milestone 1 implementation and test coverage.README.md,docs/configuration/settings.md,KNOWN_LIMITATIONS.md: Milestone 2a text scope, active-editor/single-buffer boundary, and deferred key/frontmatter/decorations work.AGENTS.md,CONTRIBUTING.md: document-sync source ownership and text-sync acceptance/negative-control locations.test/specs/rpc-text-sync-negative-controls.md: observed line-event, offset, trailing-newline, decoded-oracle, and intro-buffer identity failures.CHANGELOG.md: Milestone 2a implementation and test coverage.README.md,docs/configuration/settings.md,KNOWN_LIMITATIONS.md: explicit Neovim configuration selection, isolated startup behavior, production default, and measured startup benefit.AGENTS.md,CONTRIBUTING.md: fixture-backed RPC test contract and configuration-aware connection ownership.README.md,docs/configuration/settings.md,KNOWN_LIMITATIONS.md: Milestone 2b key ownership, Source-frontmatter requirement, cursor/mode synchronization, and deferred IME/decorations scope.AGENTS.md,CONTRIBUTING.md: key-delegation source ownership and RPC key acceptance coverage..sisyphus/plans/neovim-rpc-backend-design.md: D7 decision and measured visible/source cursor behavior.CHANGELOG.md: Milestone 2b implementation, tests, and negative controls.KNOWN_LIMITATIONS.md,.sisyphus/plans/neovim-rpc-backend-design.md: corrected per-activation seeding semantics and explicit prohibition on writing a stale mirror into another note.AGENTS.md,CONTRIBUTING.md: document-sync ownership and the independent two-file disk-integrity regression.test/specs/rpc-text-sync-negative-controls.md: exact editor and on-disk values from restoring the cross-note overwrite defect.CHANGELOG.md: cross-note data-loss fix and negative-control evidence.README.md,docs/configuration/settings.md,KNOWN_LIMITATIONS.md: Milestone 2c support for both properties modes and rendered-frontmatter behavior.AGENTS.md,CONTRIBUTING.md: shared delimiter, RPC fold ownership, cursor guard, widget-focus exclusion, and acceptance coverage.test/specs/rpc-keys-negative-controls.md: M2c fold, guard, and Source-mode negative-control evidence.CHANGELOG.md: Milestone 2c implementation, tests, and documentation.README.md,docs/configuration/settings.md,KNOWN_LIMITATIONS.md: Milestone 3 decoration scope, persistent-extmark limits, redraw clock, and remaining float/IME boundaries.AGENTS.md,CONTRIBUTING.md: bundled companion/decorations ownership and RPC decoration acceptance coverage..sisyphus/plans/neovim-rpc-backend-design.md: resolved D-E companion packaging decision.test/specs/rpc-decorations-negative-controls.md: exact missing-label, shifted-offset, missing-redraw-clock, and assertion-reachability failures.CHANGELOG.md: Milestone 3 implementation, tests, and negative controls.README.md,docs/configuration/settings.md,KNOWN_LIMITATIONS.md:acwritesave ownership and Obsidian-backed reload semantics.AGENTS.md,CONTRIBUTING.md: companion/document-sync ownership and write/read acceptance coverage..sisyphus/plans/neovim-rpc-backend-design.md: deferred M2a write/read item completion and verification evidence.test/specs/rpc-write-routing-negative-controls.md: exact missing-autocmd, missing-acwrite, stale-disk, and dirty-flag failures.CHANGELOG.md: write/read routing implementation, tests, and documentation.README.md,docs/configuration/settings.md,KNOWN_LIMITATIONS.md: M4a representative surface, guarded lowercase commands, and remaining M4b boundary.AGENTS.md,CONTRIBUTING.md: feature-bridge source ownership and acceptance/negative-control locations.test/specs/rpc-obsidian-bridge-negative-controls.md: exact M4a mapping, dispatch, abbreviation, and teardown sabotage outcomes.CHANGELOG.md: M4a implementation, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/features/workspace-navigation.md: M4b Batch 1 workspace/navigation scope, parameter forwarding, and remaining batches.AGENTS.md,CONTRIBUTING.md: expanded bridge ownership and registry inventory/RPC acceptance coverage.test/specs/rpc-obsidian-bridge-negative-controls.md: exact M4b count, pane-map, abbreviation-guard, and refresh-teardown failures..sisyphus/plans/neovim-rpc-backend-design.md: M4b Batch 1 implementation and verification result.CHANGELOG.md: M4b Batch 1 implementation, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/features/ex-commands.md: M4b Batch 2 picker scope, argument forwarding, modal ownership, and remaining batches.AGENTS.md,CONTRIBUTING.md: expanded RPC picker acceptance coverage.test/specs/rpc-obsidian-bridge-negative-controls.md: exact Batch 2 query, mapping, abbreviation, and re-seed sabotage outcomes..sisyphus/plans/neovim-rpc-backend-design.md: M4b Batch 2 implementation and empirical result.CHANGELOG.md: M4b Batch 2 implementation, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/features/harpoon.md,docs/features/workspace-navigation.md: M4b Batch 3 scope, host-owned cross-note jumplist, cursor restoration, and uppercase cross-file mark gap.AGENTS.md,CONTRIBUTING.md: expanded feature-bridge ownership and RPC acceptance coverage.test/specs/rpc-obsidian-bridge-negative-controls.md: exact Batch 3 slot, jumplist ownership/count, and gutter-refresh failures..sisyphus/plans/neovim-rpc-backend-design.md: M4b Batch 3 implementation and explicit marks/jumplist ownership decision.CHANGELOG.md: M4b Batch 3 implementation, fix, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/features/oil-explorer.md: M4b Batch 4 native Oil ownership, RPC exclusion, and path-register behavior.AGENTS.md,CONTRIBUTING.md: RPC Oil acceptance and negative-control locations.test/specs/rpc-oil-negative-controls.md: exact forced-interception corruption and isolated action failure..sisyphus/plans/neovim-rpc-backend-design.md: corrected Class-B total and empirical Oil result.CHANGELOG.md: native Oil fixes, RPC isolation tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/features/workspace-navigation.md,docs/features/undo-tree.md: Batch 5 fold/undo ownership, rendering, sidebar data, and fold-persistence boundary.AGENTS.md,CONTRIBUTING.md: RPC fold/undo source ownership and acceptance/negative-control locations.test/specs/rpc-folds-undo-negative-controls.md: exact forwarding, row-mapping, sidebar-source, and missing-command failures..sisyphus/plans/neovim-rpc-backend-design.md: corrected Class-B total from 121 to 80 and per-endpoint Batch 5 disposition.CHANGELOG.md: Batch 5 implementation, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/reference/keybindings.md,docs/features/structural-navigation.md,docs/features/hardwrap.md: M5a native/ported ownership, aliases, operator parity, andtextwidthwiring.AGENTS.md,CONTRIBUTING.md: M5a RPC acceptance and negative-control locations.test/specs/rpc-structural-nav-negative-controls.md: exact missing-map, missing-width, level, motion-kind, and count failures.CHANGELOG.md: M5a implementation, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/reference/keybindings.md,docs/features/text-objects.md: M5b native/ported ownership, supported object list, bounded ranges, and the unparsed highlight-delimiter gap.AGENTS.md,CONTRIBUTING.md: M5b RPC acceptance and negative-control locations.test/specs/rpc-text-objects-negative-controls.md: exact range-end, missing-map, and count-forwarding failures.CHANGELOG.md: M5b implementation, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/configuration/settings.md: M6a float support, approximate CM6 cell mapping, relative origins, and remaining IME boundary.AGENTS.md,CONTRIBUTING.md: floating-window source ownership and RPC acceptance/negative-control locations.test/specs/rpc-floats-negative-controls.md: exact forwarding, position, z-index, and stale-close sabotage outcomes..sisyphus/plans/neovim-rpc-backend-design.md: M6a implementation and empirical result.CHANGELOG.md: M6a implementation, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md,docs/configuration/settings.md: M6b composition ownership, commit path, cancellation lifecycle, and remaining RPC boundaries.AGENTS.md,CONTRIBUTING.md: IME input source ownership and CDP acceptance/negative-control locations.test/specs/rpc-ime-negative-controls.md: exact CM6-only, buffer-API, and composing-key forwarding sabotage outcomes..sisyphus/plans/neovim-rpc-backend-design.md: M6b implementation and empirical result.CHANGELOG.md: M6b implementation, tests, negative controls, and documentation.README.md,KNOWN_LIMITATIONS.md: M7's certified latency measurements and deltas.AGENTS.md,CONTRIBUTING.md: production latency harness, p50 sanity gate, negative-control locations, and cross-platform RPC test tooling..sisyphus/plans/neovim-rpc-backend-design.md: M7 implementation, fold-expression repair, measurements, controls, and certification verdict.test/specs/rpc-latency-negative-controls.md: exact delay, layout, engagement/isolation, size-drift, and sanity-gate output.CHANGELOG.md: M7 harness, fold-expression performance repair, negative controls, and documentation.docs/features/neovim-backend.md,docs/features/index.md: opt-in Neovim backend setup, security and ownership boundaries, shared keybindings, and limitations.docs/configuration/settings.md: removes the stale milestone scope label.README.md,KNOWN_LIMITATIONS.md: removes stale milestone wording while preserving the certified M7 measurements.AGENTS.md,CONTRIBUTING.md: source-tree gaps, pinned installer/workflow ownership, RPC prerequisite guard, and current latency gate.CHANGELOG.md: cross-platform CI provisioning, shared skip behavior, Windows scenario scope, and documentation updates.test/flaky-inventory.md: the WASM heap-move mechanism is refuted by measurement — 84 probe readings across master and the reverted retaining walk, including failing runs, all report zero heap growth. Pre-growing the heap is dropped, and the four places that stated the mechanism as established are corrected. The retained-node fix and its measurements stand; only the stated reason was wrong.KNOWN_LIMITATIONS.md: LuaTSNodehandles become stale after a re-parse, and lifetime management is declined rather than deferred. Also records that dropping thedelete()calls is not a safe alternative, becauseweb-tree-sitter's ownFinalizationRegistrystill frees the handle at a GC-determined moment.test/flaky-inventory.md: the renderer segfault's root cause, established by a 2x2. The fix changed two things — it stopped retainingNodes and added the missingcursor.delete()— and only the second mattered: the leak alone measured 5 of 8 container runs with no nodes retained, while the original retaining walk with the cursor freed measured 0 of 6. A leakedTreeCursorstays inweb-tree-sitter'sFinalizationRegistryholding its tree's pointer, so GC later frees a tree the bridge already deleted. This also invalidates the arm that retired the use-after-free class: neutralising the twelve in-repodelete()calls never disabled the registry, so "nothing is ever freed" was never achieved.docs/features/neovim-backend.md,KNOWN_LIMITATIONS.md: the Neovim plugin-compatibility boundary is published, closing design risk R-6. Compatibility is decided by mechanism rather than by plugin name: extmarks, virtual text, and floating windows cross, whilescreenpos()and the screen-cell query class are unbridgeable by construction — the bridge transports buffer coordinates, grid events are discarded, and proportional Markdown typography has no stable cell grid to answer with.matchadd()is unreachable separately, being window-local rather than an extmark. flash.nvim is named as the measured bridged case rather than presented as a certification of any plugin.docs/features/neovim-backend.md,KNOWN_LIMITATIONS.md,README.md: the plugin's own Lua and vimrc config does not bind editor keys while the Neovim backend owns them..obsidian.init.luastill loads, but avim.keymap.setthere targets the bundled fork, which key delegation stands down, so editor mappings never fire and their equivalents belong in the user'sinit.lua. Settings and host-rendered features are unaffected, and the bridge's generated Obsidian action mappings follow the configured leader but not arbitrary remaps.docs/features/neovim-backend.md,KNOWN_LIMITATIONS.md: a What changes in RPC mode section records which bundled-engine features Neovim replaces, which need a Neovim equivalent, and which host features read the fork's event stream rather than bridge state. The Class-A disposition was a design decision but had never been written down for users, and the host-feature group was assumed unaffected because it only renders: which-key and hint mode subscribe to the adapter'svim-keypress/vim-command-done, yank highlight tovim-yank, input-method switching tovim-mode-change, and the animated cursor resolves its per-mode shape fromadapter.state.vim, so the shape stays on normal while Neovim is in insert. Measured on 0.12.5 rather than assumed:gris unmapped, andoon- item oneyieldshellobecause the stock Markdown ftplugin setsformatoptions=jtcqln. Corrects a claim added earlier in this same section, which listed snippets, which-key, and the animated cursor among features that still apply —Tabis forwarded to Neovim before CM6's snippet keymap sees it.AGENTS.md: re-measures thees2021build-target rationale, which was argued against the mobile floor "at 1.7.2" and so no longer matched the manifest. The conclusion is unchanged and the basis is now correct: Android 5.1 is the binding constraint and has not moved — still the Play Store minimum at 1.8.10 and at 1.13.6 — so raisingminAppVersiondoes not raise it, while iOS went 13 → 14.5 → 15. Ates2022esbuild emits 8static {}blocks wherees2021emits none, and those need Safari 16.4, above iOS 15 and far above the Android 5.1 WebView. The saving is 402 bytes gzipped of 729,321, not the 31 recorded; the bundle has grown since. The claims about native class fields and#privatemethods are dropped as unevidenced —#privateoccurs identically in both builds and no source file uses it.README.md,docs/getting-started/index.md: state the 1.8.7 minimum, which the manifest had already been raised to.docs/configuration/settings.md: adds the Grep binary mode and Picker keymap rows, which had no documentation at all, and corrects the claim that cursor shapes require the bundled fork.docs/configuration/status-bar.md,docs/getting-started/recommended-setup.md: the extended mode indicators and per-mode cursor shapes were listed as fork-only; both are also provided by the Neovim backend, and only built-in vim mode lacks them.docs/index.md: adds thetagsfrontmatter every other page carries.KNOWN_LIMITATIONS.md: the async-Lua row and therequire()status both still said "deferred" long aftersrc/lua/coroutine-runner.tsandsrc/lua/module-snapshot.tsshipped.CONTRIBUTING.md: records the two new modules in its source-tree list, which mirrors AGENTS.md and had been updated only there.docs/configuration/vimrc.md: theguicursorexample was annotated "bundled fork mode only", which stopped being true once per-mode cursor shapes started following the backend's mode.KNOWN_LIMITATIONS.md: the LuaTextYankPostautocmd limitation now records that it also does not fire while the Neovim backend owns keys — the event comes from the bundled engine, which is stood down — and that the yank highlight feature is unaffected, being driven by a Neovim notification instead. Also adds the two RPC behaviours verified by hand rather than in CI, with the reason and the check for each.AGENTS.md: recordssrc/rpc/config-export.ts,src/vim/external-mode.ts, and the three new RPC specs, which the source-tree and test-organisation sections had not been updated for.docs/configuration/settings.md: adds the Set up Neovim and Regenerate automatically rows, and points the Neovim backend note at the RPC-mode feature table.docs/features/quality-of-life.md,docs/features/tables.md: corrects two qualifications that predate the Neovim backend. Yank highlight was documented as requiring the bundled fork engine, which is now misleading — it works under the backend too, where the fork is stood down and aTextYankPostnotification drives the same renderer. Table-nav's "requires the bundled vim engine" was accurate about built-in vim but read as excluding the backend, under which it is measured working.docs/features/neovim-backend.md: removes a table left over from before the configuration export existed, which duplicated four rows of the generated set and listed snippets beside them — implying snippets were generated when no such block exists, and none is cheap, because the bundled snippets are vault-stored JSON and Lua-DSL definitions rather than asetup()call. Adds a warning that the generated file is a starting point rather than parity:dial.nvim's default augends do not cover hex colours, booleans, dates or checkboxes, andnvim-surround's defaults omitdsf/csfand insert-mode<C-G>s.README.md: the third-party Lua compatibility paragraph is scoped to the bundled fengari runtime. It described the shim while reading as a statement about the plugin, which understated the Neovim backend in particular — that runs the user's own Neovim, where LuaJIT FFI is available and flash.nvim works.
Full Changelog: 0.150.0...1.0.0