From f6a8247c48b6a64dc9c4c7c557ff5559c48d3397 Mon Sep 17 00:00:00 2001 From: Olivier Lambert Date: Tue, 10 Feb 2026 00:16:40 +0100 Subject: [PATCH] fix: cursor jumps to end of text when editing a comment (#1924) * fix: cursor jumps to end of text when editing a comment When editing a comment mid-text, the cursor would jump to the end after every keystroke, making it impossible to insert text at any position other than the end. Root cause: on each keystroke, the comment editor's onUpdate callback updated parent state (setContent), which changed the defaultContent prop passed back to CommentEditor. A useEffect watching defaultContent then called commentEditor.commands.setContent(), which reset the entire editor content and moved the cursor to the end. Fix: - Store in-progress edits in a ref instead of state to avoid triggering React re-renders and the prop->effect->setContent cascade - Read from the ref when saving the comment - Sync the ref back into state after a successful save so the read-only view updates immediately - Guard the setContent useEffect to only run for read-only editors, so websocket-driven updates from other browsers still work Fixes #1791 Functionally tested on Firefox and Chrome: mid-text editing, saving, cross-browser live updates via websocket. Co-Authored-By: Claude Opus 4.6 * fix stale content on edit cancel --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com> --- .../features/comment/components/comment-editor.tsx | 9 +++++++-- .../comment/components/comment-list-item.tsx | 12 +++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/client/src/features/comment/components/comment-editor.tsx b/apps/client/src/features/comment/components/comment-editor.tsx index a0489cdc..efb0c586 100644 --- a/apps/client/src/features/comment/components/comment-editor.tsx +++ b/apps/client/src/features/comment/components/comment-editor.tsx @@ -84,9 +84,14 @@ const CommentEditor = forwardRef( autofocus: (autofocus && "end") || false, }); + // Sync content from props for read-only editors (e.g. when updated via + // websocket on another browser). Skip for editable editors to avoid + // resetting the cursor position on every keystroke. useEffect(() => { - commentEditor.commands.setContent(defaultContent); - }, [defaultContent]); + if (!editable && commentEditor && defaultContent) { + commentEditor.commands.setContent(defaultContent); + } + }, [defaultContent, editable, commentEditor]); useEffect(() => { setTimeout(() => { diff --git a/apps/client/src/features/comment/components/comment-list-item.tsx b/apps/client/src/features/comment/components/comment-list-item.tsx index ebf27196..738f2e4f 100644 --- a/apps/client/src/features/comment/components/comment-list-item.tsx +++ b/apps/client/src/features/comment/components/comment-list-item.tsx @@ -1,5 +1,5 @@ import { Group, Text, Box, Badge } from "@mantine/core"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import classes from "./comment.module.css"; import { useAtom, useAtomValue } from "jotai"; import { timeAgo } from "@/lib/time"; @@ -40,6 +40,7 @@ function CommentListItem({ const [isLoading, setIsLoading] = useState(false); const editor = useAtomValue(pageEditorAtom); const [content, setContent] = useState(comment.content); + const editContentRef = useRef(null); const updateCommentMutation = useUpdateCommentMutation(); const deleteCommentMutation = useDeleteCommentMutation(comment.pageId); const resolveCommentMutation = useResolveCommentMutation(); @@ -56,9 +57,13 @@ function CommentListItem({ setIsLoading(true); const commentToUpdate = { commentId: comment.id, - content: JSON.stringify(content), + content: JSON.stringify(editContentRef.current ?? content), }; await updateCommentMutation.mutateAsync(commentToUpdate); + if (editContentRef.current) { + setContent(editContentRef.current); + editContentRef.current = null; + } setIsEditing(false); emit({ @@ -128,6 +133,7 @@ function CommentListItem({ setIsEditing(true); } function cancelEdit() { + editContentRef.current = null; setIsEditing(false); } @@ -194,7 +200,7 @@ function CommentListItem({ setContent(newContent)} + onUpdate={(newContent: any) => { editContentRef.current = newContent; }} onSave={handleUpdateComment} autofocus={true} />