diff --git a/static/js/document.js b/static/js/document.js index ec9d79755..e2d2e7608 100644 --- a/static/js/document.js +++ b/static/js/document.js @@ -9196,6 +9196,23 @@ import * as Modals from './modalManager.js'; return oldId; } + function _isMarkdownPreviewVisible() { + const preview = document.getElementById('doc-md-preview'); + return !!(preview && preview.style.display !== 'none'); + } + + function _refreshMarkdownPreviewIfVisible(docId, content) { + if (!_isMarkdownPreviewVisible()) return false; + const doc = docs.get(docId); + const lang = ((doc && doc.language) || document.getElementById('doc-language-select')?.value || '').toLowerCase(); + if (lang !== 'markdown') return false; + const textarea = document.getElementById('doc-editor-textarea'); + if (textarea) textarea.value = content; + syncHighlighting(); + _setMarkdownPreviewActive(true, { remember: false }); + return true; + } + /** Handle SSE doc_update event from AI */ export function handleDocUpdate(data) { const streamingId = streamDocFinalize(); @@ -9305,6 +9322,7 @@ import * as Modals from './modalManager.js'; if (docLang && langSelect) langSelect.value = docLang; if (!docLang) attemptAutoDetect(); const isEmailUpdate = (docLang || '').toLowerCase() === 'email'; + const markdownPreviewWasVisible = _isMarkdownPreviewVisible(); // Animate content update for edits; apply directly for creates/streaming const isEdit = !isEmailUpdate && isExistingDoc && oldContent && oldContent !== newContent && !streamingId; @@ -9318,7 +9336,10 @@ import * as Modals from './modalManager.js'; if (oldLines[li] !== newLines[li]) changedLines++; } if (changedLines >= DIFF_MODE_THRESHOLD) { + if (markdownPreviewWasVisible) _setMarkdownPreviewActive(false, { remember: false }); enterDiffMode(oldContent, newContent); + } else if (markdownPreviewWasVisible && _refreshMarkdownPreviewIfVisible(docId, newContent)) { + // Preview is the visible surface, so refresh it instead of animating a hidden editor. } else { _animateDocEdit(textarea, newContent); } @@ -9332,6 +9353,7 @@ import * as Modals from './modalManager.js'; } else { if (textarea) textarea.value = newContent; syncHighlighting(); + _refreshMarkdownPreviewIfVisible(docId, newContent); } } diff --git a/tests/test_document_ai_preview_refresh_js.py b/tests/test_document_ai_preview_refresh_js.py new file mode 100644 index 000000000..4dda69c31 --- /dev/null +++ b/tests/test_document_ai_preview_refresh_js.py @@ -0,0 +1,53 @@ +"""Regression guards for AI document updates while Markdown Preview is visible (#2182).""" + +import re +from pathlib import Path + + +SRC = Path(__file__).resolve().parent.parent / "static/js/document.js" + + +def _function_body(name: str) -> str: + text = SRC.read_text(encoding="utf-8") + match = re.search(rf"\n\s*(?:export\s+)?(?:async\s+)?function\s+{name}\([^)]*\)\s*\{{", text) + assert match, f"{name} not found" + + start = match.end() + depth = 1 + i = start + while i < len(text) and depth: + if text[i] == "{": + depth += 1 + elif text[i] == "}": + depth -= 1 + i += 1 + assert depth == 0, f"{name} body did not close" + return text[start : i - 1] + + +def test_markdown_preview_refresh_rerenders_visible_preview(): + body = _function_body("_refreshMarkdownPreviewIfVisible") + + assert "_isMarkdownPreviewVisible()" in body + assert "lang !== 'markdown'" in body + assert "textarea.value = content;" in body + assert "syncHighlighting();" in body + assert "_setMarkdownPreviewActive(true, { remember: false });" in body + + +def test_doc_update_refreshes_preview_instead_of_hidden_editor_animation(): + body = _function_body("handleDocUpdate") + + visible = "const markdownPreviewWasVisible = _isMarkdownPreviewVisible();" + exit_preview = "if (markdownPreviewWasVisible) _setMarkdownPreviewActive(false, { remember: false });" + diff = "enterDiffMode(oldContent, newContent);" + refresh = "markdownPreviewWasVisible && _refreshMarkdownPreviewIfVisible(docId, newContent)" + animate = "_animateDocEdit(textarea, newContent);" + + assert visible in body + assert exit_preview in body + assert diff in body + assert body.index(exit_preview) < body.index(diff) + assert refresh in body + assert body.index(refresh) < body.index(animate) + assert "_refreshMarkdownPreviewIfVisible(docId, newContent);" in body