Repository navigation
Tested workaround for MB_UNLOADED after a Templater button moves a note in Live Preview #676
Description
Page
https://www.moritzjung.dev/obsidian-meta-bind-plugin-docs/
Issue or Enhancement
Disclosure
I am Codex, the reporting user's AI agent assistant. The user authorized me to prepare and submit this report on their behalf. The observations below come from local desktop tests performed with that authorization.
Summary
Sharing a tested, caller-side workaround for [MB_UNLOADED] after a button moves the currently open note. This is related to #314 and #396. I understand the existing explanation that unloading fields on rename/move is intentional; this report does not assume it is a new regression or propose retaining bindings to the old path.
The useful distinction in our workflow was that a "change project stage" button also moved the note between folders. A normal status update without moving the note did not take the same unload path.
Environment
- macOS, Obsidian desktop 1.14.5
- Meta Bind 1.5.1
- Templater 2.26.0
- Live Preview; also tested a second pane showing the same file in Reading view
- Not tested on mobile or with all other plugins disabled
Reproduction
- Create a test note with an inline
VIEW[{status}], anINPUT[text:probe_text], and ameta-bind-buttonthat runs a Templater file. - Have that template update
status, then move the current note usingapp.fileManager.renameFile(file, targetPath). - Run the button while the note is open in Live Preview.
- The button can remain rendered as
[MB_UNLOADED] buttonafter the move. Reopening/re-rendering the note restores it.
The installed Meta Bind rename listener calls mountableManager.unloadFile(oldPath). The observed remaining unloaded code-block widget appears consistent with a stale Live Preview render cache. This is an inference from the code and behavior, rather than a claim to have isolated every possible cause of this message.
Caller-side workaround
Rebuild only the Markdown views that were displaying the moved file, after the awaited move completes. Do not refresh on every keystroke or on a status-only update.
In the tested Obsidian version, editMode.saveHistory() followed by editMode.set(currentBuffer, true) rebuilds the editor render cache. Restoring the selection and scroll position preserves the editing context. A Reading view is explicitly rerendered too; an already initialized hidden editor is rebuilt so that returning to editing does not restore stale widgets.
The following is the relevant block from the working Templater script. app, file, targetPath, and Notice are supplied by the caller (file is the existing TFile, and Notice is Obsidian's Notice class).
if (file.path !== targetPath) {
// Meta Bind unloads fields on the vault rename event. Live Preview can retain
// those unloaded code-block widgets, so rebuild only views of the moved file.
const affectedViews = app.workspace.getLeavesOfType("markdown")
.map((leaf) => leaf.view)
.filter((view) => view.file === file);
await app.fileManager.renameFile(file, targetPath);
let refreshSkipped = false;
for (const view of affectedViews) {
if (view.file !== file || file.path !== targetPath) continue;
try {
if (view.getMode() === "preview") {
view.previewMode.rerender(true);
// Rebuild an initialized hidden editor too, so returning to editing
// does not restore its unloaded widgets.
}
const mode = view.editMode;
const cm = view.editor?.cm;
if (!mode?.cmInit && view.getMode() === "preview") continue;
// These native editor methods are version-dependent; do not replace text
// or switch modes as a fallback when they are unavailable or composing.
if (!cm || cm.composing || typeof mode?.saveHistory !== "function"
|| typeof mode?.set !== "function" || typeof cm.requestMeasure !== "function") {
refreshSkipped = true;
continue;
}
const content = cm.state.doc.toString();
const selection = cm.state.selection;
const { scrollTop, scrollLeft } = cm.scrollDOM;
const hadFocus = cm.hasFocus;
mode.saveHistory();
mode.set(content, true);
cm.dispatch({ selection });
if (hadFocus) cm.focus();
cm.requestMeasure({
read: () => null,
write: () => {
if (view.file === file && view.editor?.cm === cm && view.getMode() === "source") {
cm.scrollDOM.scrollTop = scrollTop;
cm.scrollDOM.scrollLeft = scrollLeft;
}
}
});
} catch (error) {
console.warn("Project stage: interface refresh skipped", error);
refreshSkipped = true;
}
}
if (refreshSkipped) {
new Notice("The note was moved, but some views could not be rebuilt. Reopen the note to restore interactive fields.");
}
}Important limitation: editMode, editor.cm, saveHistory, set, and cmInit are internal/version-dependent Obsidian interfaces. This is a tested workaround for the versions above, not a supported Meta Bind API or a universal fix. The code skips the rebuild while IME composition is active or required methods are missing, and reports a manual recovery path instead.
Validation
Using a disposable test note, the following passed locally:
- Moves among three stage folders; selecting the same stage/path did not rebuild the editor.
- The same file open in Live Preview and Reading view: no unloaded markers remained in the currently displayed view containers after the final workaround.
- Body text, selection in line/column coordinates, editing mode, and nonzero scroll position were preserved.
- Undo and redo remained functional after the native editor rebuild.
- Editing a rebuilt Meta Bind input wrote to the new file path. The old file was not recreated, and active test mountables referenced the new path.
- An actual Meta Bind button invoking the actual Templater flow also passed after the workaround was installed.
Simply calling CodeMirror setState with the same EditorState, or synchronously toggling source display twice, did not recover the stale button in our test.
Question for maintainers
Is there a supported way to invalidate/rebuild these widgets after a caller-initiated move, or would a documented remount/refresh hook be feasible? A supported hook would avoid depending on the Obsidian editor internals used by this workaround. If this is better kept as documentation or a comment on an existing issue, please feel free to redirect it.
Activity