docs(sync): align SYNC_IMPORT scenarios with current gate semantics

Rename stale _hasMeaningfulLocalData() refs to _hasMeaningfulStoreData()
and remove the dead Encryption-only flowchart node — PASSWORD_CHANGED
SYNC_IMPORTs without pending ops now fall through the standard gate.
This commit is contained in:
Johannes Millan 2026-04-29 13:38:40 +02:00
parent a217206a44
commit 9eaf266e3d
2 changed files with 10 additions and 13 deletions

View file

@ -41,11 +41,9 @@ flowchart TD
%% SYNC_IMPORT handling
IS_IMPORT{Contains SYNC_IMPORT?}
IS_IMPORT -->|No| CONFLICT_CHK
IS_IMPORT -->|Yes| ENC_ONLY{Encryption-only change<br/>+ no pending ops?}
ENC_ONLY -->|Yes| APPLY
ENC_ONLY -->|No| IMPORT_CONFLICT{Meaningful<br/>pending ops?}
IS_IMPORT -->|Yes| IMPORT_CONFLICT{Meaningful<br/>pending ops?}
IMPORT_CONFLICT -->|Yes| IMPORT_DLG[ImportConflictDialog:<br/>import reason shown,<br/>Use Server Data recommended]
IMPORT_CONFLICT -->|No| APPLY_IMPORT[Apply full state replacement<br/>silently — already-synced<br/>store data is not a conflict]
IMPORT_CONFLICT -->|No| APPLY_IMPORT[Apply full state replacement<br/>silently — already-synced<br/>store data is not a conflict<br/>PASSWORD_CHANGED falls through here]
IMPORT_DLG -->|Use Server| FORCE_DL
IMPORT_DLG -->|Use Local| FORCE_UP
IMPORT_DLG -->|Cancel| CANCELLED
@ -107,7 +105,6 @@ flowchart TD
**Notes:**
- The `Enter Password` and `Decrypt Error` dialogs correspond to `DecryptNoPasswordError` and `DecryptError` respectively — they are distinct components with different options.
- `Encryption-only change` bypass: when an incoming SYNC_IMPORT has `syncImportReason === 'PASSWORD_CHANGED'` and there are no meaningful pending ops, the dialog is skipped (data is identical, only encryption changed).
- `IMPORT_CONFLICT` gate uses pending ops only, not store contents (`_hasMeaningfulPendingOps()`). "Meaningful" = TASK/PROJECT/TAG/NOTE create/update/delete or full-state ops — config-only ops don't count. Already-synced store data is not a conflict with the incoming SYNC_IMPORT — the user-facing warning happens on the originating device. Including store contents in the gate would let an old client pick `USE_LOCAL` and force-upload its stale pre-import state, rolling back the remote import for everyone.
- `IMPORT_CONFLICT` gate uses pending ops only, not store contents (`_hasMeaningfulPendingOps()`). PASSWORD_CHANGED SYNC_IMPORTs without pending ops fall through this gate naturally to silent acceptance — the data is identical, only the encryption changed. "Meaningful" = TASK/PROJECT/TAG/NOTE create/update/delete or full-state ops — config-only ops don't count. Already-synced store data is not a conflict with the incoming SYNC_IMPORT — the user-facing warning happens on the originating device. Including store contents in the gate would let an old client pick `USE_LOCAL` and force-upload its stale pre-import state, rolling back the remote import for everyone.
- LWW tie-breaking: on equal timestamps, remote wins (server-authoritative). `moveToArchive` operations always win regardless of timestamp.
- Re-download retry limit: max 3 resolution attempts per entity (`MAX_CONCURRENT_RESOLUTION_ATTEMPTS`); if exceeded, ops are permanently rejected.

View file

@ -136,7 +136,7 @@ Comprehensive spec of all scenarios that can occur during SuperSync synchronizat
**Expected:**
1. `isWhollyFreshClient()` = true
2. `_hasMeaningfulLocalData()` = false
2. `_hasMeaningfulStoreData()` = false
3. Show native `confirmDialog()`: "Initial Sync — This appears to be a fresh installation. Remote data with X changes was found. Do you want to download and overwrite your local data with it?"
4. If confirmed → download and apply all remote ops
5. If cancelled → snackbar "Sync cancelled", no data applied
@ -150,7 +150,7 @@ Comprehensive spec of all scenarios that can occur during SuperSync synchronizat
**Expected:**
1. `isWhollyFreshClient()` = true
2. `_hasMeaningfulLocalData()` = true (checks for tasks, non-INBOX projects, non-system tags, notes)
2. `_hasMeaningfulStoreData()` = true (checks for tasks, non-INBOX projects, non-system tags, notes)
3. Throw `LocalDataConflictError`
4. Show full conflict dialog: USE_LOCAL / USE_REMOTE / CANCEL
5. USE_LOCAL → `forceUploadLocalState()` (creates SYNC_IMPORT)
@ -169,7 +169,7 @@ Comprehensive spec of all scenarios that can occur during SuperSync synchronizat
3. If meaningful → throw `LocalDataConflictError` → full conflict dialog
4. If only config/system ops → proceed without dialog
**Note:** This op-content check only applies to the file-based snapshot path. For SuperSync (incremental ops path), the fresh client check uses `_hasMeaningfulLocalData()` (store-based check) instead.
**Note:** This op-content check only applies to the file-based snapshot path. For SuperSync (incremental ops path), the fresh client check uses `_hasMeaningfulStoreData()` (store-based check) instead.
**User sees:** Conflict dialog only when real user data would be lost. ✓
@ -186,7 +186,7 @@ Comprehensive spec of all scenarios that can occur during SuperSync synchronizat
1. Download batch contains SYNC_IMPORT/BACKUP_IMPORT/REPAIR
2. Check pending local ops → no meaningful pending changes (`_hasMeaningfulPendingOps()` = false)
3. **Apply silently via `processRemoteOps()`** — no dialog. Already-synced store data is not a conflict here; the SYNC_IMPORT is the new authoritative state.
4. The reason `_hasMeaningfulLocalData()` is intentionally NOT checked: prompting an old client whose only "data" is already-synced state would let the user pick `USE_LOCAL` and force-upload that stale state as a new SYNC_IMPORT, rolling back the remote import for everyone.
4. The reason `_hasMeaningfulStoreData()` is intentionally NOT checked: prompting an old client whose only "data" is already-synced state would let the user pick `USE_LOCAL` and force-upload that stale state as a new SYNC_IMPORT, rolling back the remote import for everyone.
5. The dialog **does** appear only when there are unsynced pending user changes that would actually be discarded — see D.2.
**User sees:** Nothing. Data updates seamlessly to the new authoritative state. The user-facing warning happened on the originating device (`D_SERVER_MIGRATION_CONFIRM` / encryption flow), not here.
@ -563,7 +563,7 @@ Comprehensive spec of all scenarios that can occur during SuperSync synchronizat
1. Same setup flow as I.1 (config + encryption prompt)
2. `sync()` fires → download from server
3. Server is empty (`latestServerSeq === 0`) AND `newOps.length === 0`
4. Pre-op-log detection: `isWhollyFreshClient()` = true AND `_hasMeaningfulLocalData()` = true
4. Pre-op-log detection: `isWhollyFreshClient()` = true AND `_hasMeaningfulStoreData()` = true
5. `downloadRemoteOps()` calls `serverMigrationService.handleServerMigration()` to create a SYNC_IMPORT from local state
6. Returns `serverMigrationHandled: true` → upload phase proceeds
7. SYNC_IMPORT gets uploaded to server → other clients can download it
@ -590,7 +590,7 @@ Comprehensive spec of all scenarios that can occur during SuperSync synchronizat
5. Two paths depending on whether server sends snapshot or incremental ops:
- **Snapshot path (file-based):** `isWhollyFreshClient()` = true → show `confirmDialog` with count=1 ("Remote data with 1 changes was found")
- **Incremental ops path (SuperSync):** `isWhollyFreshClient()` = true → show `confirmDialog` with actual op count ("Remote data with N changes was found")
6. `_hasMeaningfulLocalData()` = false (brand new client) → simple confirmation, not conflict dialog
6. `_hasMeaningfulStoreData()` = false (brand new client) → simple confirmation, not conflict dialog
7. If confirmed → apply all remote ops → upload phase (nothing to upload) → `IN_SYNC`
8. If cancelled → snackbar "Sync cancelled"
@ -604,7 +604,7 @@ Comprehensive spec of all scenarios that can occur during SuperSync synchronizat
1. Client B: setup → server probe → correct encryption prompt (enter or create) → `sync()`
2. Download remote ops → `isWhollyFreshClient()` = true (empty op log)
3. `_hasMeaningfulLocalData()` = true (has tasks/projects/tags)
3. `_hasMeaningfulStoreData()` = true (has tasks/projects/tags)
4. Throw `LocalDataConflictError` → full conflict dialog: USE_LOCAL / USE_REMOTE / CANCEL
5. USE_LOCAL → `forceUploadLocalState()` → creates SYNC_IMPORT, overwrites server
6. USE_REMOTE → `forceDownloadRemoteState()` → clears local, downloads everything