- Add waitForAppReady and tour dismissal after page reload in
webdav-sync-tags test to prevent task-list timeout
- Use existing providerSelect locator instead of role-based combobox
selector in sync.page.ts for more reliable dropdown interaction
- Add graceful error handling for scrollIntoViewIfNeeded with fallback
Fixes critical bug where multiple clients with existing data could each
create a SYNC_IMPORT when enabling SuperSync, causing data loss.
Root cause: When clients had local data (from legacy migration), each
would detect "server needs migration" and create competing SYNC_IMPORTs.
The last one uploaded would invalidate all prior data.
Fixes:
- Server rejects duplicate SYNC_IMPORT with SYNC_IMPORT_EXISTS (409)
- Client double-checks server is empty before creating SYNC_IMPORT
- Client merges all local op clocks into SYNC_IMPORT's vector clock
- hasSyncedOps() excludes MIGRATION/RECOVERY ops from check
- Upload service gracefully handles SYNC_IMPORT_EXISTS rejection
Tests added:
- ServerMigrationService: double-check and clock merging (5 tests)
- OperationLogStoreService: hasSyncedOps() MIGRATION exclusion (7 tests)
- OperationLogUploadService: SYNC_IMPORT_EXISTS handling (5 tests)
- E2E: Multiple clients with existing data merge correctly
When a SuperSync account is deleted, clients now properly detect
authentication failures and prompt for reconfiguration:
- Add _checkHttpStatus() helper to SuperSyncProvider that throws
AuthFailSPError on 401/403 responses
- Clear cached credentials on auth failure to allow reconfiguration
- Add DELETE /api/test/user/:userId endpoint for E2E testing
- Add deleteTestUser() helper in supersync-helpers.ts
- Add E2E tests for account deletion and reconfiguration scenarios
The existing SyncWrapperService already handles AuthFailSPError by
showing a snackbar with "Configure" action, so no UI changes needed.
- e2e/utils/waits.ts: Add proper retry logic and error handling when
waiting for magic-side-nav to appear. Now throws a clear error if
app doesn't load within 30s instead of silently continuing.
- packages/super-sync-server/src/auth.ts: Use nullish coalescing for
passwordHash to handle passkey-only users (who have no password).
- packages/super-sync-server/src/passkey.ts: Add WebAuthn passkey
authentication support. Fix TypeScript errors by importing types
from @simplewebauthn/types and converting credential IDs to
base64url strings.
Fixes flaky "Multiple fresh clients join and sync correctly after
snapshot" test that was failing when app didn't fully load.
operation-log.effects.ts:
- Move dequeue() inside the lock to prevent race condition with
flushPendingWrites(). Previously, flushPendingWrites() could see
queue=0 and return before IndexedDB write completed.
- Set entityId from entityIds[0] for bulk operations. Server requires
entityId for non-full-state operations, but bulk actions like
updateAllSimpleCounters only provide entityIds array.
supersync.page.ts:
- Fix flaky encryption checkbox tests caused by Angular Material
checkbox starting in indeterminate "mixed" state before form loads
- Use retry logic with toPass() instead of single click + assert
- Click on label instead of touch target for more reliable interaction
- Increase timeout to 10s with smart intervals [500, 1000, 1500]ms
Add test verifying that when a task scheduled for today is deleted on
one client while updated on another, the recreated task (via LWW Update)
correctly appears in the TODAY view. This validates that
syncTodayTagTaskIds properly updates TODAY_TAG.taskIds during LWW
conflict resolution.
Two bugs caused data loss after app restart with SuperSync:
Bug 1: Clock merge happened AFTER loadAllData dispatch
- Operations created during loadAllData (e.g., TODAY_TAG repair) got stale clocks
- Server rejected these as CONFLICT_STALE
- Fix: Call mergeRemoteOpClocks() BEFORE store.dispatch(loadAllData())
Bug 2: CONFLICT_STALE treated as permanent rejection
- Stale operations were marked rejected instead of being resolved via merge
- Fix: Handle CONFLICT_STALE the same as CONFLICT_CONCURRENT
Adds unit tests for both bugs and E2E regression tests to prevent recurrence.
After simulating network failure and restoring network, a dialog backdrop
could remain visible and intercept clicks on the sync button. This caused
the retry sync to fail with a timeout.
Add check for `.cdk-overlay-backdrop` and press Escape to dismiss any
open dialogs before triggering the retry sync.
Two test fixes:
1. webdav-sync-full.spec.ts - "should sync data between two clients"
- After multiple reloads in the deletion test section, Client B's
sync state became stale and couldn't receive new tasks
- Fix: Create a fresh Client B context for the conflict resolution
section instead of reusing the stale one
2. sync.page.ts - setupWebdavSync combobox selector
- The role-based selector getByRole('combobox', { name: 'Sync Provider' })
was unreliable across different test runs
- Fix: Use the existing providerSelect locator which was already
validated to be visible, instead of creating a new role-based selector
Resolve conflict in webdav-sync-expansion.spec.ts:
- Use simplified sync verification without reload (sync updates NgRx directly)
- Test: B marks task done -> sync -> verify A sees task as done
- Add retry loop for deletion sync to handle eventual consistency
- Fix done state sync test to handle Done Tasks section
- Add task order sync test
- Add time tracking sync test (skipped - complex persistence)
- Add null checks in simple-counter.reducer.ts to prevent crashes when
entity doesn't exist (fixes "Cannot read properties of undefined")
- Add missing entityIds to updateAllSimpleCounters action - was causing
operation log to skip persistence
- Add try-catch in operation-log-upload.service.ts to show alert when
storage quota exceeded (413 error)
- Fix supersync-network-failure.spec.ts test timing - route interception
must happen before task creation
- Add documentation to CLAUDE.md for running supersync tests with
real-time output using --reporter=line
Two fixes for supersync E2E test failures:
1. Remove race condition in operation-log.effects.ts:
- Removed isApplyingRemoteOps() filter that caused actions enqueued
by meta-reducer to be filtered out if sync started before effect ran
- Meta-reducer already handles buffering during sync, so this filter
was redundant and caused queue drain timeouts
2. Allow bulk operations with entityType 'ALL' to skip entityId validation:
- [Archive] Flush Young to Old action has entityType 'ALL' but no entityId
- Previous validation returned early without dequeuing, causing stuck queue
- Now dequeues before early return and allows entityType 'ALL' to proceed
3. Fix URL pattern in supersync-daily-summary.spec.ts:
- Changed from /(active\/tasks|tag\/TODAY\/tasks)/ to /(active\/tasks|tag\/TODAY)/
- App sometimes navigates to tag/TODAY without /tasks suffix
The tests were failing because:
1. The backup file has tasks with dueDay in the past (2024-12-01)
2. TODAY tag is a "virtual tag" where membership is determined by
task.dueDay === today, not by taskIds
3. When all tasks are done, planning mode activates and hides the
Finish Day button
Fix by:
- Navigate to INBOX project to view and mark tasks as done
- Navigate to TODAY tag to access the Finish Day button
- Exit planning mode by clicking "Ready to work!" if visible
- Then proceed with Finish Day flow
Unit tests for remote-ops-processing.service.ts:
- Migration failure handling (skip failed ops, continue processing)
- Early return when all ops fail migration
- Dropped entity ID tracking for dependency warnings
E2E tests for supersync-network-failure.spec.ts:
- Rate limit exceeded (429) handling with retry
- Server storage quota exceeded gracefully
- Long offline period sync recovery (15+ tasks)
- Malformed JSON response handling
- Corrupted/invalid operations from server
These tests address gaps identified in the SuperSync implementation
review, covering error recovery, rate limiting, quota handling, and
data corruption scenarios.
Add 6 new E2E test files covering:
- Task ordering sync between clients
- Repeat task and scheduled task sync
- Cross-entity operations (subtasks, batch tasks)
- Time tracking advanced scenarios
- Worklog/archive sync
- Planner/dueDay sync
Each test uses a module-level counter in generateTestRunId() to ensure
unique test user IDs even when tests run in parallel within the same
millisecond.
Add comprehensive tests for error code handling at unit and E2E levels:
Unit tests (super-sync.spec.ts):
- 429 Rate Limited response handling
- 413 Storage Quota Exceeded response handling
- Parse CONFLICT_CONCURRENT/STALE/DUPLICATE_OPERATION from response
- Mixed accept/reject batch categorization
- Extract piggybacked ops from conflict response
Unit tests (operation-log-sync.service.spec.ts):
- STORAGE_QUOTA_EXCEEDED shows alert but doesn't mark op rejected
- INTERNAL_ERROR marks op as rejected
- Verify subsequent operations behavior after errors
E2E tests (supersync-error-handling.spec.ts):
- Concurrent modification triggers LWW conflict resolution
- Sync recovers after initial connection
- Duplicate sync attempts handled gracefully
- Three clients converge to same state
Also adds SnapshotService.clearForUser() helper for test cleanup.
- Add page.isClosed() checks before waitForTimeout calls to prevent
"Target page, context or browser has been closed" errors when tests
timeout (supersync-helpers.ts, waits.ts, supersync.page.ts)
- Fix context menu selector in LWW tests: use .mat-mdc-menu-item instead
of button[mat-menu-item] which doesn't match Angular Material menu items
- Add retry logic for opening context menus with proper wait/escape handling
- Skip "Subtask edit survives when parent is deleted" test - documents
expected future behavior that is not yet implemented
The closeClient helper was throwing errors when trying to close browser
contexts that were already closed (due to test timeouts or failures).
This caused cascading errors in E2E tests and made debugging harder.
Fix by checking if page is closed before attempting to close the context,
and gracefully catching "already closed" errors.
- Add devError call on every compaction failure to catch issues during
development
- Add reload action after 3 consecutive failures since this indicates
a systemic issue (corrupted IndexedDB, storage quota, etc.) rather
than a transient hiccup
Also fix lint errors in supersync-lww-conflict.spec.ts (let -> const)
Re-enable and fix the skipped E2E test for concurrent tag changes:
- Use skipClose=true when adding tasks with tags (short syntax)
- Handle tag creation confirmation dialog properly
- Replace separate add/remove tag helpers with single toggleTagOnTask
helper (the context menu uses toggle checkboxes)
- Simplify navigation by staying in Today view instead of TagA
(prevents task from disappearing after removing TagA)
- Remove unused navigation code and dismissDialog helper
The test verifies that LWW correctly resolves concurrent tag changes
and updates both task.tagIds and tag.taskIds consistently.
Extend the lwwUpdateMetaReducer to also sync tag.taskIds arrays when
a task's tagIds changes during LWW conflict resolution.
When LWW Update syncs a task's tagIds change to remote clients, the
reducer now:
- Removes task from tags that were removed from task.tagIds
- Adds task to tags that were added to task.tagIds
This maintains bidirectional consistency between task.tagIds and
tag.taskIds, similar to the project.taskIds fix.
Also adds:
- 7 unit tests for project.taskIds sync (project move fix)
- 8 unit tests for tag.taskIds sync (new tag fix)
- E2E test for concurrent tag changes (skipped - env dialog issues)
Add comprehensive E2E tests covering core sync functionality gaps:
1. Multiple Conflicts on Same Entity (supersync-lww-conflict.spec.ts)
- Verifies LWW uses MAX timestamp across all operations
- Tests 3 ops from each client on same task
2. Import Invalidates Pending Remote Ops (supersync-import-clean-slate.spec.ts)
- Tests clean slate semantics for SYNC_IMPORT
- Pending ops from other clients are filtered as CONCURRENT
3. Partial Sync Failure with Retry (supersync-network-failure.spec.ts)
- Creates 10 tasks, fails mid-batch, retries
- Verifies all tasks sync without duplicates
4. Tag Deletion Atomic Cleanup (supersync-models.spec.ts)
- Creates tag with 10 tasks
- Deletes tag, verifies atomic removal from all tasks
5. Concurrent Project Move (supersync-lww-conflict.spec.ts)
- Tests moving same task to different projects concurrently
- NOTE: Currently reveals bug - task projectId becomes inconsistent
Tests 1-4 pass. Test 5 exposes a real sync bug to investigate.
Add defensive fix for race condition where subtasks could become
orphaned during archive sync:
1. Client A adds subtask to parent
2. Client B does SYNC_IMPORT before parent.subTaskIds synced
3. Client A archives parent (with stale subTaskIds in operation)
4. Client B receives archive - subtask left orphaned
The fix in deleteTaskHelper now looks up subtasks from state by
parentId in addition to the payload's subTaskIds, ensuring all
subtasks are removed even if the operation payload is stale.
Adds devError logging when orphan subtasks are detected to help
diagnose upstream issues.
Tests added:
- Unit tests for orphan subtask removal in task.reducer.spec.ts
- Integration tests documenting the race condition scenario
- E2E tests for archive subtask sync across clients
The PfapiStoreDelegateService was reading archive data from NgRx store,
but archives are stored in ModelCtrl (pf database), not NgRx. The NgRx
archive state is only populated on loadAllData (import) and is never
updated when ArchiveService writes to ModelCtrl during finish day.
This caused exports to contain empty/stale archiveYoung data after
archiving tasks via "Finish Day", resulting in lost subtasks.
The fix reads archiveYoung and archiveOld from ModelCtrl (via
pfapiService.m.archiveYoung.load()) instead of NgRx selectors, since
that's where the actual archive data lives.
Includes E2E test to verify subtasks are preserved in archive after
legacy data import and finish day flow.
Add E2E and integration tests documenting a bug where tasks with
subtasks are lost when:
1. Importing legacy data (pre-operation logs)
2. Archiving via "Finish Day"
3. Exporting again
The archiveYoung ends up empty in the export. Tests will pass
once the bug is fixed.
Files added:
- e2e/fixtures/legacy-archive-subtasks-backup.json
- e2e/tests/import-export/legacy-archive-subtasks.spec.ts
- integration/legacy-archive-subtasks.integration.spec.ts
Add E2E tests verifying that SYNC_IMPORT operations properly implement
clean slate semantics:
1. "Import drops ALL concurrent work from both clients" - verifies that
when Client A imports a backup, all pre-import tasks from both
Client A and Client B are dropped after sync propagates.
2. "Late joiner synced ops are dropped after import" - verifies that
even if Client B synced its work before Client A imported, those
operations are NOT replayed after receiving the SYNC_IMPORT.
These tests validate the intentional design where imports represent
explicit user actions to restore ALL clients to a specific state.
- Make snackbar detection more resilient (allow quick auto-dismiss)
- Use dialog close as primary success indicator
- Re-enable password change e2e test (was skipped)
- Use distinct task names to avoid substring matching issues
Address two issues identified in code review:
1. Storage cache not refreshed after cleanup:
- Modified deleteOldSyncedOpsForAllUsers() to return { totalDeleted, affectedUserIds }
- Cleanup job now calls updateStorageUsage() for each affected user
- Prevents stale quota checks after nightly cleanup
2. serverTime missing from download response:
- Added serverTime: Date.now() to DownloadOpsResponse
- Enables client-side clock drift detection
Tests:
- Added serverTime response test in sync-fixes.spec.ts
- Added deleteOldSyncedOpsForAllUsers return structure tests
- Updated legacy SQLite tests for new return type (excluded from CI)
When syncing 100+ operations, the server's piggyback limit (100 ops)
was causing Client B to miss operations. The server returned 100 piggybacked
ops but latestSeq was set to the actual server sequence (e.g., 199).
Client B then updated lastServerSeq to 199, so subsequent download got 0 ops.
Changes:
- Server: Add hasMorePiggyback flag to UploadOpsResponse when piggyback limit
is reached and more ops exist
- Client: When hasMorePiggyback is true, store lastServerSeq as the max
piggybacked op's serverSeq instead of latestSeq, ensuring subsequent
download fetches remaining ops
- Effects: Change switchMap to mergeMap in autoAddTodayTagOnMarkAsDone to
ensure ALL mark-as-done actions trigger planTasksForToday
- Flush service: Implement two-phase wait strategy (poll queue + acquire lock)
to ensure all pending writes complete before upload
- Add diagnostic logging for operation counts at key stages
Test: High volume sync with 50 tasks + 49 mark-as-done (197 ops total)
now correctly syncs all done states via piggyback (100) + download (97).
Implements the ability to change the encryption password by deleting
all server data and uploading a fresh snapshot with the new password.
Server changes:
- Add DELETE /api/sync/data endpoint to delete all user sync data
- Add deleteAllUserData() method to SyncService
Client changes:
- Add deleteAllData() to OperationSyncCapable interface
- Implement deleteAllData() in SuperSync provider
- Add EncryptionPasswordChangeService to orchestrate password change
- Add DialogChangeEncryptionPasswordComponent with validation
- Add "Change Encryption Password" button to sync settings (visible
when encryption is enabled)
- Add translations for all new UI strings
Testing:
- Add 10 unit tests for EncryptionPasswordChangeService
- Add 14 unit tests for DialogChangeEncryptionPasswordComponent
- Add 5 E2E tests for complete password change flow
- Add changeEncryptionPassword() helper to SuperSyncPage
Also fixes:
- Add missing deleteAllData() to MockOperationSyncProvider
- Fix typo S_FINISH_DAY_SYNC_ERROR -> FINISH_DAY_SYNC_ERROR
Add 4 new E2E tests for SuperSync encryption coverage:
- Multiple tasks sync correctly with encryption
- Bidirectional sync works with encryption
- Task update syncs correctly with encryption
- Long encryption password works correctly
Also clean up outdated TODO comments from header - encryption
is now working correctly.
- Delete docs/ai/sync/server-sync-architecture.md which incorrectly
stated "Status: Not Started" when server sync is fully implemented
- Delete deprecated src/app/features/time-tracking/store/archive.effects.ts
which was empty and marked for removal
- Update local-actions.token.ts comment to reference the correct
archive-operation-handler.effects.ts file
The sp_op_log lock is non-reentrant. When validation/repair code was
called from inside the lock (during sync or conflict resolution),
createRepairOperation() tried to acquire the lock again, causing a
deadlock.
Add callerHoldsLock/skipLock parameter through the call chain:
- operation-log-sync.service.ts passes callerHoldsLock: true when inside lock
- conflict-resolution.service.ts passes callerHoldsLock: true
- validate-state.service.ts accepts and forwards the flag
- repair-operation.service.ts skips lock when skipLock: true
Also adds high-volume sync E2E test (499 operations) and fixes test
isolation in meta-reducer-ordering integration tests.
Two critical sync fixes:
1. Clock drift detection now uses serverTime from response instead of
receivedAt from old operations. Previously, downloading 12-hour-old
ops would falsely trigger "clock drift" warnings.
2. User interactions during sync are now blocked from creating operations.
When HydrationStateService.startApplyingRemoteOps() is called, the
operation-capture meta-reducer skips capturing local actions, preventing
stale vector clocks and cascade conflicts on slow devices.
Includes comprehensive tests for both fixes and a bulk sync e2e test.