Conflict Resolution
Conflict Resolution
Resolve cloud sync conflicts with a guided merge wizard
Overview
The Conflict Resolution wizard activates when two versions of the same data arrive from different sources during cloud sync — typically when the same project is edited on multiple devices or by multiple collaborators while offline. The wizard presents both versions side-by-side and lets you choose how to merge them.
Requirements
Functional Requirements
| ID | Requirement | Acceptance Criteria | Priority | Source |
|---|---|---|---|---|
| REQ-CFR-001 | The system shall detect field-level conflicts when syncing data from multiple sources | Sync detects a conflict → the wizard opens by itself, wherever you are; field-level diff displayed | Must | useConflictResolverState |
| REQ-CFR-002 | The system shall present conflicting fields side-by-side (local vs. remote) with visual diff highlighting | Open conflict → two columns shown; changed fields highlighted in yellow/red; identical fields dimmed | Must | useConflictResolverState |
| REQ-CFR-003 | The system shall support resolution strategies: Keep Local, Keep Remote, Merge (field-by-field), Auto-merge | Click Keep Local → local version saved; Keep Remote → remote saved; Merge → field-level picker; Auto → non-overlapping resolved | Must | useConflictResolverState |
| REQ-CFR-004 | The system shall queue unresolved conflicts and surface them via a notification badge — not shipped as stated; see the 2026-09-10 note | A skipped conflict does stay queued in the engine and the wizard reopens next sync — but there is no badge count and no list screen | Must | |
| REQ-CFR-005 | The system shall support batch resolution: apply the same strategy to all conflicts at once — not shipped as stated; see the 2026-09-10 note | No "Resolve All" and no cross-conflict strategy picker exist | Should | |
| REQ-CFR-006 | The system shall log all conflict resolutions for audit trail purposes — not shipped as stated; see the 2026-09-10 note | No resolution log is written and no viewer exists; the backup_conflict_… safety backup is the recoverable record | Should | |
| REQ-CFR-007 | The system shall prevent data loss by always preserving both versions until the user explicitly resolves | Conflict pending → both local and remote versions saved; no auto-overwrite; cancel resolution → both preserved | Must | useConflictResolverState |
| REQ-CFR-008 | The system shall support exporting conflict resolution history as a JSON/CSV report for audit purposes — removed 2026-05-30 (dead code; never wired into the live wizard) | Click export → file downloads; contains timestamps, entity IDs, resolution choices, user info | Should | |
| REQ-CFR-009 | The system shall support keyboard shortcuts for conflict resolution (Accept Local, Accept Remote, Skip) — removed 2026-05-30 (dead code; never wired into the live wizard) | Press L → accept local; press R → accept remote; press S → skip; shortcuts shown in toolbar | Should | |
| REQ-CFR-010 | The system shall support searching and filtering conflicts by entity type, date, or field name — removed 2026-05-30 (dead code; never wired into the live wizard) | Type search term → conflicts filtered; clear → all shown; filter by entity type → matching conflicts only | Should |
Note (2026-05-30): REQ-CFR-008 (export), REQ-CFR-009 (keyboard shortcuts), and REQ-CFR-010 (search/filter) describe surfaces that shipped as code but were never connected to the live conflict wizard. The unused code was removed; these requirements are retained as planned/future scope.
Note (2026-09-10): REQ-CFR-004, REQ-CFR-005 and REQ-CFR-006 are not shipped as stated, and are retained as planned scope on the same basis. All three describe a queue of conflicts; the engine surfaces one conflict at a time.
- REQ-CFR-004 (queue + notification badge).
sync.conflictis a single object, not a list —ConflictResolverModalContainerrenders whichever one conflict is currently raised. There is no badge count to increment and no conflict-list screen to open:SyncStatusBadgehas four states (Synced / Syncing… / Offline / Sync Error), carriesrole="status", and has no click handler. What is true is that nothing is lost — a skipped conflict stays queued inside the engine and the wizard reappears on the next sync pass.- REQ-CFR-005 (batch resolution). There is no "Resolve All" and no strategy picker across conflicts.
useConflictResolverStatetakes oneconflictand oneonResolve.- REQ-CFR-006 (audit log). No resolution log is written and there is no viewer for one. The recoverable record of a resolution is the
backup_conflict_…safety backup described under Resolution Strategies, which is a data backup rather than a chronological log of who chose what.
Non-Functional Requirements
| ID | Requirement | Metric | Priority |
|---|---|---|---|
| NFR-CFR-001 | Conflict diff view shall render within 1 second for projects with ≤500 items | Diff render | Should |
| NFR-CFR-002 | Batch resolution shall process ≤50 conflicts within 2 seconds — planned scope; batch resolution is not built (REQ-CFR-005) | Batch perf | Should |
Data Requirements
- Conflict record:
{ entityType, entityId, localVersion, remoteVersion, conflictingFields[], timestamp } - Resolution log: planned scope, not written — see REQ-CFR-006 above
- Persistence: what is written on resolution is a pair of
backup_conflict_<type>_…safety backups, one per side, cleaned up later byconflictBackupCleanup. There is no conflict-queue store and no resolution log; an unresolved conflict lives in the sync engine and is re-raised on the next sync pass
Constraints & Limits
- Only appears when cloud sync is enabled and conflicts are detected
- Auto-merge resolves non-overlapping changes automatically (only true conflicts need manual resolution)
- The sidebar sync indicator shows status only — Synced / Syncing… / Offline / Sync Error. It carries no conflict count and is not clickable; the wizard is what surfaces a conflict
Offline Behavior
- Conflicts are not generated offline (sync is paused)
- Conflict resolution UI works offline once conflicts are loaded
- Resolved conflicts sync to cloud when connectivity is restored
Dependencies
- Requires: Collaboration (REQ-COL-006) — conflicts arise from collaboration sync
When Conflicts Occur
Conflicts arise in these scenarios:
- Multi-device editing: Same item edited on two devices before sync completes
- Collaborative offline: Two collaborators edit the same project while both offline
- Concurrent category moves: Item moved to different categories by different users
- Edit-delete conflicts: Item deleted by one user while being edited by another
Internal sync bookkeeping is not a conflict. The engine's own lineage counter (syncRev) advances
on every upload, so two devices that have synced a different number of times always hold different
values. That difference alone carries no user content and is excluded from conflict detection and
from the diff — see INTERNAL_FIELDS in app/src/data/collab/collabUtils.js.
Resolving Conflicts
Opening the Wizard
The wizard opens by itself the moment the sync engine detects a conflict — you do not navigate
to it. ConflictResolverModalContainer is mounted app-wide and renders as soon as a conflict is
raised, whichever screen you are on.
You cannot open it on demand, and there is no conflict queue screen. This guide told you to click a "sync conflict badge" in the top bar, or to navigate to "Settings → Cloud Sync → Conflicts", until 2026-09-08. Neither exists: the sidebar's sync badge shows status only (Synced / Syncing… / Offline / Sync Error) and is not clickable, and there is no Cloud Sync tab in Settings. If you dismiss a conflict with Skip This Conflict it stays queued and the wizard reappears on the next sync pass.
Resolution Strategies
| Strategy | Description | Reversible? |
|---|---|---|
| Merge | Choose field-by-field which version to keep — keeps both sides | Yes — nothing is discarded |
| Auto-merge | System resolves non-overlapping changes automatically | Yes — nothing is discarded |
| Keep Local | Discard remote changes; your version wins | No — replaces the whole record |
| Keep Remote | Discard local changes; other device/user's version wins | No — replaces the whole record |
Keep Local / Keep Remote are document-scoped and irreversible. Accepting one side drops the
queued upload for that record and overwrites the other side wholesale, so everything the losing side
changed since the last successful sync goes with it — not just the field on screen. Simple view
therefore states the count up front ("… discards N cloud change(s)" / "… discards N change(s) made on
this device since your last sync") and makes Review Field by Field the primary button. Safety
backups of both variants are still written first (backup_conflict_…), so a wrong pick is
recoverable, but recovery is manual.
When the Whole Record Is the Conflict
A few conflicts arrive as two complete versions of a record rather than as a list of differing
fields. Two engine branches produce that shape: a stale local lineage (this device's copy is
older than the last state synced to the cloud) and the record types that are never merged
automatically (dataProjects, shootingDays, transferSessions, folderTemplates, offloads).
The wizard now splits those two versions into individual fields itself, so Review Field by Field works on them like any other conflict and both sides can be kept. The engine's own reason for refusing the automatic merge is shown at the top of the dialog — a stale local copy is a caveat to read each field against, not a reason to abandon the dialog.
Where field-by-field is genuinely absent, it is no longer offered. If the two versions differ only in internal sync bookkeeping there is nothing to choose per field. The wizard then says the conflict cannot be split, drops the Field-by-Field tab and the "Review field by field" button, and leaves only the two whole-record choices. Until 2026-09-09 it advertised the tab anyway and led to a single "Entire record" row carrying the same irreversible choice the Simple view already offered.
Step-by-Step Merge
- Review the conflict summary showing affected fields
- For each field, view local and remote values side-by-side
- Click Use Local or Use Remote per field
- Click Apply Resolution to finalize
- Repeat for remaining conflicts
Tips
- Prefer Review Field by Field: it is the only resolution that keeps both sides — and it is offered whenever the conflict can actually be split (see above)
- Resolve quickly: Unresolved conflicts can block future syncs
- Communicate with team: When conflicts arise in shared projects, coordinate before resolving
- Use auto-merge first: Let the system handle non-overlapping changes automatically
- Read the discard counts: they tell you how much each whole-record choice throws away
Related Documentation
Last Updated: 2026-09-10 Version: 0.790.2
