From 837b76ade27cdbda5c8f9249b73fd3e265d80288 Mon Sep 17 00:00:00 2001 From: PathGao <42336971+PathGao@users.noreply.github.com> Date: Sat, 25 Jul 2026 06:05:15 +0900 Subject: [PATCH] fix: ignore stale preview renders --- scripts/previewRenderRevision.test.ts | 17 ++++++++++++++++ src/lib/MarkdownViewer.svelte | 28 +++++++++++++++++++++------ 2 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 scripts/previewRenderRevision.test.ts diff --git a/scripts/previewRenderRevision.test.ts b/scripts/previewRenderRevision.test.ts new file mode 100644 index 00000000..64a39105 --- /dev/null +++ b/scripts/previewRenderRevision.test.ts @@ -0,0 +1,17 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import test from 'node:test'; + +const viewer = readFileSync('src/lib/MarkdownViewer.svelte', 'utf8'); + +test('preview rendering uses a revision and cleans up its debounce timer', () => { + assert.match(viewer, /let previewRenderRevision = 0;/); + assert.match(viewer, /const renderRevision = \+\+previewRenderRevision;/); + assert.match(viewer, /return \(\) => clearTimeout\(timer\);/); +}); + +test('a completed preview render verifies its tab, content, and revision', () => { + assert.match(viewer, /previewRenderRevision !== renderRevision/); + assert.match(viewer, /tabManager\.activeTabId !== tabId/); + assert.match(viewer, /currentTab\?\.rawContent !== rawContent/); +}); diff --git a/src/lib/MarkdownViewer.svelte b/src/lib/MarkdownViewer.svelte index f2a0cd52..0509f11b 100644 --- a/src/lib/MarkdownViewer.svelte +++ b/src/lib/MarkdownViewer.svelte @@ -2353,17 +2353,33 @@ import { t } from './utils/i18n.js'; } } - let debounceTimer: number; + let previewRenderRevision = 0; $effect(() => { const tab = tabManager.activeTab; + const renderRevision = ++previewRenderRevision; if (tab && (tab.isSplit || (isEditing && settings.showToc)) && tab.rawContent !== undefined) { - if ((tab as any)._lastRenderedRawContent === tab.rawContent) return; - - clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => { - renderTabPreviewFromRaw(tab).catch(console.error); + const tabId = tab.id; + const rawContent = tab.rawContent; + if ((tab as any)._lastRenderedRawContent === rawContent) return; + + const timer = setTimeout(() => { + renderMarkdownPreview(rawContent, tab.path) + .then((processed) => { + const currentTab = tabManager.activeTab; + if ( + previewRenderRevision !== renderRevision || + tabManager.activeTabId !== tabId || + currentTab?.rawContent !== rawContent + ) return; + tabManager.updateTabContent(tabId, processed); + (currentTab as any)._lastRenderedRawContent = rawContent; + tick().then(renderRichContent); + }) + .catch(console.error); }, 16); + + return () => clearTimeout(timer); } });