From e8b8e20eaae24b71ab0d9936c602d231345040a5 Mon Sep 17 00:00:00 2001 From: Eve <92513238+1320209572@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:48:47 +0800 Subject: [PATCH] fix(editor): isolate new query tab position --- .../src/components/editor/QueryEditor.vue | 17 +++++++++-------- .../editorTabSwitchViewportRestore.test.ts | 10 ++++++++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index b6fa5d40f5..dae590cbcb 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -6709,8 +6709,10 @@ function activateTabDocument(prevTabId: string | undefined, tabId: string | unde // beyond MAX_CACHED_TAB_STATES): restore the tab's saved cursor and scroll // position exactly like the cached-state branch, otherwise the swapped-in // document keeps whatever scroll offset the dispatch left behind (#8374). - restoreEditorSelection(); - restoreEditorViewport(); + // A brand-new tab has no saved state, so reset it instead of falling back + // to the previous tab's latest position (#8378). + restoreEditorSelection(props.initialSelection ?? { anchor: 0, head: 0 }); + restoreEditorViewport(props.initialViewport ?? { scrollTop: 0, scrollLeft: 0 }); return; } // setState swaps doc, selection, undo history and all fields at once, but it @@ -7066,10 +7068,10 @@ function flushEditorSelection() { if (latestSelection) emitEditorSelection(latestSelection); } -function restoreEditorSelection() { - const selection = normalizedEditorSelection(props.initialSelection ?? latestSelection, props.modelValue.length); - if (!view.value || !selection) return; - view.value.dispatch({ selection }); +function restoreEditorSelection(selection = props.initialSelection ?? latestSelection) { + const normalizedSelection = normalizedEditorSelection(selection, props.modelValue.length); + if (!view.value || !normalizedSelection) return; + view.value.dispatch({ selection: normalizedSelection }); } function restoreEditorFocus() { @@ -7108,8 +7110,7 @@ function flushEditorViewport() { if (latestViewport) emitEditorViewport(latestViewport); } -function restoreEditorViewport() { - const viewport = props.initialViewport ?? latestViewport; +function restoreEditorViewport(viewport = props.initialViewport ?? latestViewport) { if (!view.value || !viewport) return; const restoreScroll = () => { if (!view.value) return; diff --git a/packages/app-tests/editorTabSwitchViewportRestore.test.ts b/packages/app-tests/editorTabSwitchViewportRestore.test.ts index 1261e3c695..ecdfcd03d9 100644 --- a/packages/app-tests/editorTabSwitchViewportRestore.test.ts +++ b/packages/app-tests/editorTabSwitchViewportRestore.test.ts @@ -23,7 +23,13 @@ function activateTabDocumentSource(): string { test("uncached tab activation restores the saved cursor and scroll position", () => { const source = activateTabDocumentSource(); - assert.match(source, /if \(!cached\) \{[\s\S]*?swapEditorDocument\(doc\);[\s\S]*?restoreEditorSelection\(\);[\s\S]*?restoreEditorViewport\(\);[\s\S]*?return;/, "the swapEditorDocument branch must restore selection and viewport before returning"); + assert.match(source, /if \(!cached\) \{[\s\S]*?swapEditorDocument\(doc\);[\s\S]*?restoreEditorSelection\([^;]*\);[\s\S]*?restoreEditorViewport\([^;]*\);[\s\S]*?return;/, "the swapEditorDocument branch must restore selection and viewport before returning"); +}); + +test("uncached tabs without saved state do not inherit the previous tab position", () => { + const source = activateTabDocumentSource(); + assert.match(source, /restoreEditorSelection\(props\.initialSelection \?\? \{ anchor: 0, head: 0 \}\);/); + assert.match(source, /restoreEditorViewport\(props\.initialViewport \?\? \{ scrollTop: 0, scrollLeft: 0 \}\);/); }); test("cached tab activation keeps restoring selection and viewport", () => { @@ -34,5 +40,5 @@ test("cached tab activation keeps restoring selection and viewport", () => { test("viewport restore prefers the per-tab saved viewport", () => { const source = readFileSync(path.resolve("apps/desktop/src/components/editor/QueryEditor.vue"), "utf8"); - assert.match(source, /function restoreEditorViewport\(\) \{[\s\S]*?const viewport = props\.initialViewport \?\? latestViewport;/); + assert.match(source, /function restoreEditorViewport\(viewport = props\.initialViewport \?\? latestViewport\) \{/); });