fix(streaming): re-resolve the leaf per delta so deltas stay in the right file
This commit is contained in:
+25
-21
@@ -252,9 +252,14 @@ export default class BeaverPlugin extends Plugin {
|
||||
const filename = this.gatewayFilename(file);
|
||||
if (!filename) return;
|
||||
|
||||
const { editor, view } = this.findEditorFor(file);
|
||||
const content = editor
|
||||
? editor.getValue()
|
||||
// Snapshot the buffer if the file is open, otherwise read disk. We
|
||||
// don't keep the editor: an ``Editor`` belongs to a leaf, not a
|
||||
// file, so if the user opens another note in the same tab mid-
|
||||
// stream the old handle now points at the wrong document. Every
|
||||
// delta and the final write re-resolve the leaf by ``file.path``.
|
||||
const initialEditor = this.findEditorFor(file);
|
||||
const content = initialEditor
|
||||
? initialEditor.getValue()
|
||||
: await this.app.vault.read(file);
|
||||
|
||||
const notice = new Notice(`Beaver: sending to ${agent}…`, 0);
|
||||
@@ -269,7 +274,10 @@ export default class BeaverPlugin extends Plugin {
|
||||
// write the partial to disk. The gateway also skips its own
|
||||
// intermediate file writes on this endpoint, so the local
|
||||
// file (and Obsidian Sync's copy) only sees the final state
|
||||
// once.
|
||||
// once. Re-resolved per delta: if ``file`` is no longer
|
||||
// open anywhere, the delta is dropped and the final
|
||||
// ``vault.modify`` catches the buffer up.
|
||||
const editor = this.findEditorFor(file);
|
||||
if (editor) spliceIntoEditor(editor, newContent);
|
||||
},
|
||||
onDone: (resp) => {
|
||||
@@ -305,7 +313,7 @@ export default class BeaverPlugin extends Plugin {
|
||||
}
|
||||
|
||||
if (typeof resp.new_content === "string") {
|
||||
await this.writeBack(file, editor, view, resp.new_content);
|
||||
await this.writeBack(file, resp.new_content);
|
||||
}
|
||||
new Notice(`Beaver: ${agent} replied`);
|
||||
}
|
||||
@@ -324,29 +332,22 @@ export default class BeaverPlugin extends Plugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
private findEditorFor(file: TFile): {
|
||||
editor: Editor | null;
|
||||
view: MarkdownView | null;
|
||||
} {
|
||||
private findEditorFor(file: TFile): Editor | null {
|
||||
// Walk open markdown views — we want the editor instance that owns
|
||||
// ``file`` so we can use setValue (keeps the buffer's edit history
|
||||
// intact) instead of falling back to vault.modify.
|
||||
// ``file`` *right now* so we can splice (keeps the buffer's edit
|
||||
// history intact) instead of falling back to vault.modify. Never
|
||||
// cached by callers: ``view.file`` changes when the user navigates
|
||||
// within the same leaf, and is ``null`` between switches.
|
||||
const leaves = this.app.workspace.getLeavesOfType("markdown");
|
||||
for (const leaf of leaves) {
|
||||
const view = leaf.view as MarkdownView;
|
||||
if (view?.file?.path === file.path) {
|
||||
return { editor: view.editor, view };
|
||||
if (view?.file?.path === file.path) return view.editor;
|
||||
}
|
||||
}
|
||||
return { editor: null, view: null };
|
||||
return null;
|
||||
}
|
||||
|
||||
private async writeBack(
|
||||
file: TFile,
|
||||
editor: Editor | null,
|
||||
_view: MarkdownView | null,
|
||||
newContent: string,
|
||||
): Promise<void> {
|
||||
private async writeBack(file: TFile, newContent: string): Promise<void> {
|
||||
const editor = this.findEditorFor(file);
|
||||
if (editor) {
|
||||
// Reuse the splice path so the final write (frontmatter refresh
|
||||
// at the top + USER_SCAFFOLD appended at the bottom) preserves
|
||||
@@ -354,6 +355,9 @@ export default class BeaverPlugin extends Plugin {
|
||||
spliceIntoEditor(editor, newContent);
|
||||
return;
|
||||
}
|
||||
// File not open (anymore): write to disk. Obsidian refreshes any
|
||||
// buffer that opens it later, and a leaf that shows it in another
|
||||
// pane gets the update through the vault event.
|
||||
await this.app.vault.modify(file, newContent);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user