From c4eb983c44b0f4793fe5259b7b993d7d15a4b492 Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Thu, 2 Jul 2026 02:26:34 +0700 Subject: [PATCH] fix(editor): make placeholder mount-only instead of a dead reactive dep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extensions was memoized on [placeholder], implying a live placeholder update, but useEditor runs with empty deps and tiptap's setOptions merges options without re-registering extensions — so a changed placeholder never reaches the already-installed Placeholder plugin (verified: data-placeholder stays at the mount value across a prop change). Drop placeholder from the deps to match the autoFocus mount-only decision, removing the misleading reactivity signal and the per-change rebuild of the whole extension array. Co-Authored-By: Claude Opus 4.8 --- .../components/shared/markdown-editor/markdown-editor.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/shared/markdown-editor/markdown-editor.tsx b/frontend/src/components/shared/markdown-editor/markdown-editor.tsx index 76c26692..3520f360 100644 --- a/frontend/src/components/shared/markdown-editor/markdown-editor.tsx +++ b/frontend/src/components/shared/markdown-editor/markdown-editor.tsx @@ -114,7 +114,12 @@ function MarkdownEditor({ const hasResetInitialHistoryRef = useRef(false); - const extensions = useMemo(() => createMarkdownExtensions(placeholder), [placeholder]); + // `placeholder` is captured at mount, like the native . tiptap's setOptions merges + // options without re-registering extensions, so a later change can't reach the already-built Placeholder + // plugin regardless; keeping it out of the deps makes that explicit and stops rebuilding the whole + // extension array on every placeholder change. + // eslint-disable-next-line react-hooks/exhaustive-deps + const extensions = useMemo(() => createMarkdownExtensions(placeholder), []); // tiptap invokes onBlur/onUpdate from its live options ref, so these closures always read the latest props. const editor = useEditor({