Skip to content

Desktop: Resolves #16134: Add data loader and word-level diff for Conflict resolution UI - #16135

Open
varunkumar-22 wants to merge 7 commits into
laurent22:devfrom
varunkumar-22:feat/conflict-data-loader
Open

Desktop: Resolves #16134: Add data loader and word-level diff for Conflict resolution UI#16135
varunkumar-22 wants to merge 7 commits into
laurent22:devfrom
varunkumar-22:feat/conflict-data-loader

Conversation

@varunkumar-22

Copy link
Copy Markdown
Contributor

Fixes #16134

PR Summary

Two things are covered in this PR:

  • Conflict Data Loader : Returns all the data required for conflict resolution UI
  • Word-level diff: An independent algorithm built using library jsdiff which diffs word-level for a given two texts as input

Conflict Data loader

  • This PR adds the shared data layer that the future conflict resolution UI will use. There is no UI in this, it's just the data layer required for further implementation of UI.

  • loadConflictData(noteId) reads the three versions of a conflict note, runs them through the merge engine, and returns:

    • Sections, each marked unchanged, auto-merged, or conflict
    • Both titles, plus a flag for whether they differ (a title can conflict on its own, even when the body doesn't)
  • In some cases there wil be nothing to merge. Then the loader returns a status of Unavailable, which tells the caller to fall back to the read-only conflict view. That covers:

    • No row in conflict_note_states
    • The note doesn't exist
    • The note is still encrypted or locked, so there's no readable text to compare
    • For encrypted notes, once the note is decrypted it gets saved again, and since the loader recomputes each time, the merge just works on the next call
    • A note with no base is not treated as unavailable. This still falls back to a simple two-way diff.

As mentioned in #16134 :
Sections are worked out fresh on every call instead of being read from storage. Sync never saves them — it only keeps the merged text — and it doesn't compute them at all when there's no base version. Recomputing means every conflict note works the same way, old or new.

Word-level diff

library used : jsdiff
Although there is diff-match-patch already in repo, which can be used for word-level diff is no longer a active and maintained repo. So I chose jsdiff library for this as it is very much active and well maintained and also used by many other products

  • wordDiff(local, remote) takes the two texts [ local & remote in this case] of a single conflict section and returns which words differ. Each of the side comes back as its own text split into segments, each flagged highlighted or not.
  • then the UI simply goes through the returned segments and highlights the ones marked as changed. No merge markers are included heree
  • If any character in a word changes, the whole word is highlighted, never part of it [ added a test for it as well]
  • Punctuation and runs of whitespace are separate tokens, which means that the markdown syntax stays unhighlighted when only the text inside it changes
  • If we put all the returned pieces of text back together, we will get the same original text exactly as it was. This lets the UI simply display each piece in order and highlight only the marked ones, without worrying about character positions or any alignment related stuff
  • The diff runs with one second timeout and will falls back to highlighting the whole section if that is hit, so a very large conflict can't block the UI

Also, while reviewing my changes with AI, AI found this bug: jsdiff's built-in word splitter only knows Latin letters. So, for Cyrillic, Greek, Arabic and Hebrew , it fell back to comparing single characters and highlighted partial words

  • so I wrote a unicode aware tokenizer that treats words correctly in languages like Arabic, Russian, Greek, and Hebrew.
  • Chinese and Japanese are intentionally compared character by character because those languages don't separate words with spaces, making character-level highlighting is more useful

Additional implementation details

  1. Feature flag
    Everything is behind the existing hidden featureFlag.conflictResolution. When it's off the loader returns Unavailable.
  2. About diffNotes.ts
    This branch includes the merge engine unchanged from #16023 so it builds and its tests run on their own. It is not part of this PR and will be removed once #16023 merges.

Testing:

Tests are added

  • Loader: three-way merge, no-base fallback, titles, recomputing on demand, and also every unavailable case including the flag being off
  • Word diff: whole-word highlighting, the join-back-to-input across several kinds of input, non-Latin scripts, and the Chinese/Japanese per-character behaviour

AI Assistance Disclosure:

AI was used to review the implemented code and also to add some of the tests [ Only after mentor approval ]. All the code was reviewed by me before submitting.

@coderabbitai coderabbitai Bot added the enhancement Feature requests and code enhancements label Aug 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Comment thread packages/lib/services/conflict/diffNotes.ts
@laurent22

Copy link
Copy Markdown
Owner

@varunkumar-22 please fix the conflict

@varunkumar-22
varunkumar-22 force-pushed the feat/conflict-data-loader branch from ff09173 to 7e52721 Compare August 8, 2026 10:17
@varunkumar-22

Copy link
Copy Markdown
Contributor Author

Conflict is resolved now.

Recent commits ensures that: diffNotes is up-to-date with PR: #16023 , and also it was agreed to use original note instead of remote columns & that update is made in loadConflictData to read remote body and title directly from remoteNote

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

@joplin-coderabbit-cleanup joplin-coderabbit-cleanup Bot deleted a comment from coderabbitai Bot Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Feature requests and code enhancements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Conflict resolution: add data loader and word-level diff for UI

2 participants