From cabc44b16b6e25bdded7967bff621b12a01ca3a6 Mon Sep 17 00:00:00 2001 From: crazy-explore-r Date: Wed, 12 Aug 2026 17:38:20 +0530 Subject: [PATCH 1/2] fix(stats): refresh word/char/reading-time on every render updateDocumentStats() only ran from inside the preview post-processing path, which is skipped whenever renderMarkdown() detects the preview HTML is already up to date for the active tab/content. Closing the only open tab reset the stats to zero via clearActiveDocument(), and reopening that same tab hit the cache-hit early return, leaving the footer stuck at 0 Min Read / 0 Words / 0 Chars until a full page reload forced a fresh render. Call updateDocumentStats() unconditionally at the top of renderMarkdown() so it stays in sync with editor content regardless of preview caching decisions. --- desktop-app/resources/js/script.js | 4 ++++ script.js | 4 ++++ tests/e2e/tab-split-sidebar-update.spec.js | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index 80594c39..3271bf21 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -11188,6 +11188,10 @@ ${selector} .arrowheadPath { options = options || {}; const rawVal = markdownEditor.value; const force = options.force === true; + // Stats must reflect the current editor content on every render call, even when + // the preview HTML itself is skipped below because it's already up to date + // (e.g. switching back to a tab whose content didn't change). + updateDocumentStats(); const previewDocumentId = getActivePreviewDocumentId(); const hasCurrentPreview = previewHasCommittedRender && diff --git a/script.js b/script.js index 80594c39..3271bf21 100644 --- a/script.js +++ b/script.js @@ -11188,6 +11188,10 @@ ${selector} .arrowheadPath { options = options || {}; const rawVal = markdownEditor.value; const force = options.force === true; + // Stats must reflect the current editor content on every render call, even when + // the preview HTML itself is skipped below because it's already up to date + // (e.g. switching back to a tab whose content didn't change). + updateDocumentStats(); const previewDocumentId = getActivePreviewDocumentId(); const hasCurrentPreview = previewHasCommittedRender && diff --git a/tests/e2e/tab-split-sidebar-update.spec.js b/tests/e2e/tab-split-sidebar-update.spec.js index 2879b8d4..75fbe452 100644 --- a/tests/e2e/tab-split-sidebar-update.spec.js +++ b/tests/e2e/tab-split-sidebar-update.spec.js @@ -257,6 +257,24 @@ test('closing a tab keeps the document in Files and reopening restores the tab', await expect(reopenedRow).toHaveCount(0); }); +test('reopening the only closed tab restores the word/char/reading-time stats', async ({ page }) => { + const closedTabId = await page.locator('#tab-list .tab-item.active').getAttribute('data-tab-id'); + await setEditorContent(page, '# Kept document\n\nSome words to count here.'); + + await expect(page.locator('#word-count')).toHaveText('8'); + await expect(page.locator('#char-count')).toHaveText('42'); + + await page.locator('#tab-list .tab-item.active .tab-close-btn').click(); + await expect(page.locator('#word-count')).toHaveText('0'); + await expect(page.locator('#char-count')).toHaveText('0'); + + await page.locator(`.document-tree-row[data-document-id="${closedTabId}"] .document-tree-main`).click(); + await expect(page.locator('#markdown-editor')).toHaveValue('# Kept document\n\nSome words to count here.'); + await expect(page.locator('#word-count')).toHaveText('8'); + await expect(page.locator('#char-count')).toHaveText('42'); + await expect(page.locator('#reading-time')).toHaveText('1'); +}); + test('format toolbar consolidates heading, case, alignment, and insert actions', async ({ page }) => { await expect(page.locator('[data-md-action="clear-formatting"]')).toHaveCount(0); await expect(page.locator('[data-md-action="help"]')).toHaveCount(0); From 4625ca40a994a52f7a568e6e401c966ea89eeff1 Mon Sep 17 00:00:00 2001 From: crazy-explore-r Date: Thu, 13 Aug 2026 09:45:24 +0530 Subject: [PATCH 2/2] fix(stats): drop redundant postProcessPreview stats call updateDocumentStats() now runs unconditionally at the top of renderMarkdown(), so the copy inside postProcessPreview() was double-counting words/chars on every non-cached render. The guard in executeMainThreadRender/executeWorkerRender already skips postProcessPreview when editor content has moved on since the render started, and a fresh renderMarkdown() call follows any such edit anyway, so dropping the duplicate call doesn't leave stats stale. --- desktop-app/resources/js/script.js | 1 - script.js | 1 - 2 files changed, 2 deletions(-) diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index 3271bf21..49db4386 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -11139,7 +11139,6 @@ ${selector} .arrowheadPath { renderMathJaxNodes(roots, rawVal, context, { snapshotReviewTargets: true }); decorateReviewTargets(); - updateDocumentStats(); updateFindHighlights(); scheduleLineNumberUpdate(); scheduleAdvancedPostProcessRecovery(rawVal, context); diff --git a/script.js b/script.js index 3271bf21..49db4386 100644 --- a/script.js +++ b/script.js @@ -11139,7 +11139,6 @@ ${selector} .arrowheadPath { renderMathJaxNodes(roots, rawVal, context, { snapshotReviewTargets: true }); decorateReviewTargets(); - updateDocumentStats(); updateFindHighlights(); scheduleLineNumberUpdate(); scheduleAdvancedPostProcessRecovery(rawVal, context);