Details
-
Bug
-
Resolution: Unresolved
-
Major
-
None
-
17.10.11, 16.10.0
-
None
-
Unknown
-
Description
Analysis and text by Claude Code with Opus 5.5
Steps to reproduce
- Two users edit in realtime, with the WYSIWYG editor, a page made of many similar paragraphs, e.g. 60 paragraphs "Paragraph 1" to "Paragraph 60" (similar table cells or list items have the same effect).
- User A places the caret at the start of "Paragraph 40".
- User B adds a new paragraph with some text after "Paragraph 50", e.g. by typing /info to insert a macro.
- User A types "X".
Expected: "XParagraph 40".
Actual: "XParagraph 41": the caret of user A moved to the next paragraph.
The caret can move much further: in a functional test on Firefox, adding a paragraph with text after "Paragraph 10" moved a caret placed at the end of "Paragraph 40" to the end of "Paragraph 4". The local content of that editor apparently differed slightly from the synchronized content, which changed how DiffDOM matched the paragraphs (the exact difference wasn't identified). Combined with XWIKI-25611, such jumps look like the editor scrolling to random places.
When the caret of user A is in the middle or at the end of the paragraph, the editor notices that the paragraph holding the caret was rewritten and restores the caret based on the text instead, which is less precise and, combined with XWIKI-25611, scrolls user A to their caret.
Analysis
The realtime WYSIWYG editor applies remote changes by computing a patch with DiffDOM. DiffDOM considers two elements "roughly equal" when they have the same tag, attributes and child structure, whatever their text, so all the paragraphs above are roughly equal. To match the old and new children it looks for the longest run of roughly equal siblings, and among runs of the same length it keeps the last one (>= in findCommonSubsets). Adding one paragraph with text among N similar paragraphs therefore aligns the N old paragraphs with the last N new ones instead of skipping the added paragraph. Patch computed for the steps above:
addElement [0], modifyTextElement [1,0] "Paragraph 1" -> "Paragraph 2", modifyTextElement [2,0] "Paragraph 2" -> "Paragraph 3", ..., modifyTextElement [50,0] "Paragraph 50" -> "/info"
instead of addElement [50]. Every paragraph before the added one is reused for the next paragraph and its text rewritten. The selection of user A follows its text node, which now holds "Paragraph 41" (the text changed only after the caret), hence the caret move. An empty paragraph (<p><br></p>) doesn't have the same structure, so pressing Enter alone doesn't trigger the problem; typing in it does.
This is the "remaining known weakness" mentioned in XWIKI-25071. Besides moving or losing the caret of the other users, these patches update far more nodes than needed (e.g. reinitializing the widgets they contain).
Possible directions
- Match the siblings that are exactly equal first, and only then the roughly equal ones. In the example above the exactly equal runs are "Paragraph 1" to "Paragraph 50" and "Paragraph 51" to "Paragraph 60", which gives the expected patch. Breaking the ties in favour of the earliest run instead would only move the problem to the paragraphs after the added one.
- This is a change to the DiffDOM matching algorithm (upstream, or by overriding it), whose effect on other patches and on performance with large content needs to be evaluated, hence no fix is proposed yet.
Attachments
Issue Links
- is related to
-
XWIKI-25611 Remote changes keep scrolling the realtime WYSIWYG editor to the caret once the edited content has been refreshed
-
- Open
-
- relates to
-
XWIKI-25071 Concurrent file uploads in the realtime WYSIWYG editor can move the caret or the uploaded file into the other user's paragraph
-
- Closed
-