Fixed
- A
FileTypehandler in your own Neovim config silently disabled the rendered-frontmatter fold from the second note onward —activateDocument()runsfiletype detecton every activation, which re-fires the user'sFileTypehandlers, and setting a window-local fold expression there is ordinary Neovim configuration.NeovimFrontmatterFold.sync()installed its expression once at connect and then early-returned for the rest of the session, so the handler landed after it on every subsequent activation and nothing put it back: the first note was folded correctly, and every note after it was not. The frontmatter guard is a correctness property rather than a preference — the properties widget stays owned by Obsidian and Neovim's cursor must not enter the region — so it silently stopped holding after the first pane switch. Measured on Neovim 0.12.5 against both routes into that handler:g:markdown_folding, where the stock Markdown ftplugin setsfoldexpr=MarkdownFold(), and a treesitterfoldexprin a personal ftplugin, which is the commoner one.MarkdownFold()is additionally wrong for frontmatter rather than merely different —tag: alphafollowed by the closing---matches its setext-H2 rule, measuredfoldlevel(2)=2, so the properties line starts a fold instead of sitting inside a protected one. The fold expression is now reapplied per activation.foldlevelandfoldenabledeliberately are not: noFileTypehandler writes them, and rewriting them there would undo a user's ownzm/zMon every pane switch.sync()keeps its early return becauseprepareKeyInput()awaits it before every delegated keystroke; the reapply lives on a separate activation-path method so the per-keystroke cost is unchanged. (#199)- Plugin:
src/rpc/frontmatter-fold.ts(syncForActivation(), splitting the connect-time install from the per-activation expression reapply),src/rpc/document-sync.ts(activation call site)
- Plugin:
- Every fold in the focused pane arrived closed under the Neovim backend, and appeared to spring open again when focus moved to another pane — the mirror window was activated with
foldlevelset to0whenever Settings → Editor → Properties in document was anything other than Source, which is Obsidian's default, so a note opened as ifzMhad been pressed.0was chosen to close the frontmatter fold, and it does, but it closes every heading fold with it. The expression fold marks frontmatter with a sentinel written as100, and Vim caps an expression fold atMAX_LEVEL,20—foldnestmaxdoes not move that cap, measured at5and10, both still reporting20— so the level that closes the frontmatter and nothing else is19, not0. Raising it to99instead is the other wrong answer: the sentinel never reaches99either, so the frontmatter fold opens and Neovim's cursor gets the properties widget back. The sentinel is now written as the cap it resolves to and the window's level as one below it. The apparentzRon the pane being left needs no separate cause and is not a defect: CM6 folds are per-pane and the decoration bridge unfolds the pane it stops mirroring, so an all-closed pane visibly reopens as focus leaves it. Reproduced on Linux against Neovim 0.12.5; never platform-specific. (#199)- Plugin:
src/rpc/frontmatter-fold.ts
- Plugin:
Tests
- A new spec measures fold state as the activation leaves it, which no existing spec did.
rpc-folds-undo.e2e.tssetsfoldmethod,foldexpr,foldlevel=99andfoldenableand runszRin itsbeforeEach, so the window it measures is never the window the product produced — the same fixture-hides-product shape as thevim.opt.swapfile = falseline removed for the swap-file defect two entries above. Nothing in the new spec sets a fold option or runszR. It also runs withpropertiesInDocumentset tovisible; every other RPC spec setssource, which is the branch that already worked, and the mode has to be in place before connecting becauseNeovimFrontmatterFoldcaches it and reapplies nothing once it matches. Red first: all four scenarios failed,foldclosed()per line returning[1, 1, 1, 1, 1, 1, 7, 7]over the eight-line heading fixture where-1was expected — both top-level heading folds closed on arrival. Two further controls, each proven independently. Setting the window level to99failed the frontmatter scenario and nothing else, atclosed: -1, closedEnd: -1against1and3, which is what makes that scenario the guard against the tempting fix rather than a duplicate of the others. Settingfoldmethodtomanual, so no folds exist at all, failed three of the four and left the pane-focus scenario passing vacuously — deliberate, and recorded:foldclosed()cannot tell a pane with no folds from a pane with no closed folds, and the level-ladder assertion[1, 1, 1, 1, 2, 2, 1, 1]in the first scenario is what refuses to let folding be absent. The pane-focus scenario seeds each leaf through its own editor rather thansetupEditor, which re-focuses until it sees any focused CM6 editor and with two visible panes can already be the other one; seeding through it settled the mirror on the wrong pane and reported six lines where eight were expected. (#199)- Tests:
test/specs/rpc-fold-focus.e2e.ts(new),test/specs/rpc-fold-focus-negative-controls.md(new)
- Tests:
- Two further scenarios in the same spec are opposing controls on the per-activation reapply, because restoring the fold expression and leaving the fold level alone are separable and a fix that does one without the other is wrong in a different direction. Red first for the first of them: with a
FileTypehandler installed and a pane switched, the frontmatter line returnedlevel: 0, closed: -1, closedEnd: -1against20,1and3— the plugin's sentinel replaced by the user's expression. The handler is a constant0rather than a real treesitter expression so the effect is unambiguous and no parser has to be installed on the test machine; both real routes were measured separately against Neovim 0.12.5 and reach the window the same way. Each sabotage then failed exactly one scenario and left the other green: deleting theapplyFoldExpression()call failed only the handler scenario, and addingfoldlevelto the reapply failed onlyleaves a user fold level alone across a pane switch, at[-1, -1, -1, -1, -1, -1, -1, -1]against the[1, 1, 1, 1, 1, 1, 7, 7]azMhad produced. That second scenario is green both before and after the reapply was added — its job is to stay green, and the sabotage is what shows it is not decorative. (#199)
Documentation
AGENTS.md,CONTRIBUTING.md: the new spec and its negative-control file, and why it does not overlaprpc-folds-undo.e2e.ts.KNOWN_LIMITATIONS.md: the RPC frontmatter paragraph now states that the window's fold level sits one below the frontmatter fold so the body arrives unfolded, that Vim'sMAX_LEVELcap is what decides that number, and that the fold expression is reapplied per activation becausefiletype detectre-fires the user'sFileTypehandlers.docs/features/neovim-backend.md: a sentence in the properties-mode paragraph stating that only the frontmatter fold is closed on activation, which pairs with the existing note that fold persistence is unavailable in RPC mode, and a note that the plugin reclaimsfoldmethod/foldexpron the mirror window while leavingfoldlevelto you.
Full Changelog: 1.3.0...1.3.1