Commit graph

17079 commits

Author SHA1 Message Date
Johannes Millan
ab0371fac6 fix(e2e): improve supersync test stability and build compatibility
E2E test improvements:
- Increase timeouts for sync button and add task bar visibility checks
- Add retry logic for sync button wait in setupSuperSync
- Handle dialog close race conditions in save button click
- Fix simple counter test to work with collapsible sections and inline forms

Build fixes:
- Add es2022 lib/target and baseUrl to electron tsconfig
- Include window-ea.d.ts for proper type resolution
- Add @ts-ignore for import.meta.url in reminder service for Electron build
2025-12-19 09:59:44 +01:00
Johannes Millan
d54156dda3 refactor(sync): extract server migration logic to dedicated service
Extract server migration detection and handling from OperationLogSyncService
to a new ServerMigrationService for improved maintainability.

Extracted functionality:
- checkAndHandleMigration: Detects empty server with previously synced ops
- handleServerMigration: Creates SYNC_IMPORT with full validated state
- _isEmptyState: Checks if state has meaningful data to sync
- _hasNoUserCreatedTags: Excludes system tags from empty state check

Changes:
- Create ServerMigrationService (257 lines)
- Create server-migration.service.spec.ts (19 tests)
- Update OperationLogSyncService to delegate to new service
- Remove ~200 lines from OperationLogSyncService (1714 → 1521 lines)

All existing tests continue to pass. Conflict resolution logic untouched.
2025-12-18 18:01:49 +01:00
Johannes Millan
d7ae49f75c chore(lint): add eslint-disable for intentional selector-based effect
The applyLanguageFromState$ effect is intentionally designed to fire
during sync - UI language config should apply regardless of source.
Safe because it uses dispatch: false and is idempotent.
2025-12-18 17:49:13 +01:00
Johannes Millan
e538ce41ab refactor(sync): extract SYNC_IMPORT filter logic to dedicated service
Extract _filterOpsInvalidatedBySyncImport from OperationLogSyncService
into a new SyncImportFilterService for better testability and separation
of concerns.

- Create SyncImportFilterService with 143 LOC for filter logic
- Add comprehensive tests (16 tests) for the new service
- Update OperationLogSyncService to delegate to the new service
- Remove old tests from OperationLogSyncService spec (moved to new file)
2025-12-18 17:42:48 +01:00
Johannes Millan
4367fe2109 feat(lint): add ESLint rule to enforce hydration guards on selector effects
Add local-rules/require-hydration-guard ESLint rule that warns when
selector-based NgRx effects lack hydration guards (skipDuringSync() or
isApplyingRemoteOps()).

The rule correctly identifies:
- Effects that START with this.store.select() as primary source
- Does NOT flag selectors in withLatestFrom (secondary sources)
- Does NOT flag selectors inside operator callbacks (already guarded)

This prevents duplicate operations during sync replay where selector-based
effects would fire on intermediate states.

Install eslint-plugin-local-rules to enable the rule.
2025-12-18 17:33:37 +01:00
Johannes Millan
c6215aab46 fix(sync): notify user on validation failure and prevent data loss on import
- Show error snackbar when server migration validation fails, informing
  user to try importing a backup instead of silently failing
- Skip clearing operations when backup fails during import, keeping old
  ops as fallback to prevent potential data loss
- Update tests to verify both behaviors
2025-12-18 17:29:25 +01:00
Johannes Millan
87c296a3d5 refactor(operation-log): extract shared utility and standardize JSDoc
- Extract extractActionPayload() to operation.types.ts to eliminate
  duplication between operation-converter.util.ts and
  dependency-resolver.service.ts

- Standardize archive-operation-handler.service.ts JSDoc with consistent
  @localBehavior/@remoteBehavior tags documenting when each handler
  executes vs skips for local/remote operations
2025-12-18 17:23:56 +01:00
Johannes Millan
b7012e0248 feat(sync): add per-user storage quota enforcement (100MB)
Add storage quota system to prevent single users from consuming
excessive disk space on the sync server.

Changes:
- Add storageQuotaBytes and storageUsedBytes fields to User model
- Add STORAGE_QUOTA_EXCEEDED error code for quota violations
- Add quota calculation and enforcement methods to SyncService
- Block uploads (POST /ops, POST /snapshot) when quota exceeded
- Allow downloads to continue when over quota (so users can delete data)
- Return storage usage/quota in GET /status response

Default quota: 100MB per user
2025-12-18 17:17:52 +01:00
Johannes Millan
0d7b38db2d fix(sync): use absolute values for simple counter sync
- Click counters now sync immediately with absolute values instead of
  being batched every 5 minutes
- Stopwatch counters now sync absolute values instead of relative
  durations, fixing issue where remote clients would add duration to
  their existing value (e.g., 0:20 became 0:23)
- Remove _modifiedClickCounters batching mechanism (no longer needed)
- Add comprehensive unit tests for immediate sync behavior
- Add e2e test for simple counter sync between multiple clients
2025-12-18 17:15:17 +01:00
Johannes Millan
8c1d5c6c75 feat(sync): auto-reload archive data when changed during sync
Trigger WorklogService.refreshWorklog() after remote sync operations
that affect archive data (moveToArchive, restoreTask, updateTask,
flushYoungToOld, deleteProject, deleteTag, etc.).

- Use isArchiveAffectingAction() helper to detect archive operations
- Only trigger reload for remote operations, not local hydration
- Use lazy injection to avoid circular dependency
- Add unit tests for archive reload trigger behavior
2025-12-18 16:45:58 +01:00
Johannes Millan
ea7e6cb8c9 fix(supersync): resolve e2e test failures and flakiness
- Disable server rate limiting in E2E test mode to prevent sync timeouts
- Improve SuperSync configuration dropdown stability in page object
- Add explicit waits for UI elements in daily summary tests
- Handle rate limit errors in sync wait logic defensively
2025-12-18 16:36:58 +01:00
Johannes Millan
6b907f65f7 test(sync): add tests for force download and clock merging
Add comprehensive tests for the new concurrent modification handling:

1. VectorClockService tests:
   - Test getFullVectorClock() method that queries all ops from seq 0
   - Verify it includes clock entries missing from snapshot

2. OperationLogDownloadService tests:
   - Test forceFromSeq0 option downloads from seq 0
   - Test allOpClocks is populated with all clocks (including duplicates)
   - Test allOpClocks includes clocks from filtered duplicate ops

3. OperationLogSyncService tests:
   - Test force download is triggered when normal download returns 0 ops
   - Test allOpClocks are passed to _resolveStaleLocalOps
   - Add getCurrentVectorClock to spy setup
2025-12-18 16:24:56 +01:00
Johannes Millan
ef0a84f290 fix(sync): force full download to rebuild clocks on concurrent modification
When the server rejects operations with "Concurrent modification" but
normal download returns 0 new ops, the client's computed vector clock
is likely missing entries that the server has (due to compaction or
snapshot issues).

This fix adds:
1. forceFromSeq0 option to downloadRemoteOps that downloads from seq 0
   and captures ALL op clocks (including duplicates)
2. When concurrent modification persists after normal download,
   trigger a force download to get all op clocks from the server
3. Merge all downloaded clocks into the global clock before creating
   merged operations
4. Add getFullVectorClock() to VectorClockService as a fallback

This ensures merged operations have clocks that dominate the server's
frontier, fixing the issue where Today list reordering wasn't syncing.
2025-12-18 16:07:03 +01:00
Johannes Millan
976040d2f3 chore(sync): add clock logging to debug concurrent modification rejections 2025-12-18 15:53:43 +01:00
Johannes Millan
76bfe36821 fix(sync): loop re-upload until all merged ops are accepted
The previous fix only did ONE re-upload after creating merged ops.
If that re-upload also failed due to concurrent modification, the
newly created merged ops would not be uploaded, leaving changes unsynced.

The fix adds a loop that continues re-uploading until either:
1. No more merged ops are created (server accepted all ops)
2. Max attempts (5) reached (safety limit to prevent infinite loops)

This ensures that even with persistent concurrent modification conflicts,
the client will keep creating merged ops with incrementing clocks until
the server accepts them.
2025-12-18 15:52:36 +01:00
Johannes Millan
0dcbe2dab1 fix(sync): use global vector clock for merged ops to ensure clock dominance
The previous fix used getEntityFrontier() which only includes ops after
the last snapshot. If the conflicting remote op was compacted into the
snapshot, its clock would NOT be included in the entity frontier.

This caused the merged op's clock to potentially NOT dominate the server's
clock, leading to another rejection and an infinite loop of:
1. Upload merged op
2. Server rejects (clock doesn't dominate)
3. Create another merged op
4. Repeat...

The fix uses getCurrentVectorClock() which includes the snapshot clock
plus all subsequent ops - the complete knowledge of all known clocks.
This ensures the merged op's clock will ALWAYS dominate when uploaded.
2025-12-18 15:45:36 +01:00
Johannes Millan
be490c88c8 fix(sync): trigger follow-up upload after creating merged ops for concurrent modifications
The previous fix created merged update operations when concurrent modification
rejections occurred, but these ops just sat in the log waiting for the next
sync cycle. This meant the Today list reorder would not sync until some other
change triggered a sync.

The fix:
1. _resolveStaleLocalOps now returns the count of merged ops created
2. _handleRejectedOps now returns the total count of merged ops
3. uploadPendingOps adds this count to localWinOpsCreated
4. ImmediateUploadService already triggers a follow-up upload when
   localWinOpsCreated > 0, so merged ops get uploaded immediately

This ensures Today list reordering (and any other concurrent modifications)
sync immediately after conflict resolution, not on the next sync cycle.
2025-12-18 15:40:52 +01:00
Johannes Millan
dc4debfa76 fix(sync): fix concurrent modification handling for Today list reordering
The previous fix incorrectly checked `localWinOpsCreated === 0` to determine
if download returned new ops. This was wrong because:
- `localWinOpsCreated` indicates LWW conflict resolution creating local-win ops
- Download returning ops where remote wins all conflicts = 0 local-win ops
- This caused the code to incorrectly try to "resolve locally" even when
  download had already processed remote ops

The fix:
1. Add `newOpsCount` to downloadRemoteOps return value
2. Check `newOpsCount === 0` to detect when download got no new ops
3. Also check for still-pending ops AFTER download with new ops, in case
   downloaded ops were for different entities than our pending ops

This ensures that Today list reordering conflicts are properly resolved
when one client's changes are rejected due to concurrent modification.
2025-12-18 15:27:00 +01:00
Johannes Millan
49eb7d05a7 fix(sync): handle concurrent modification rejections for conflict resolution
When multiple clients modify the same entity (e.g., TODAY_TAG), the server
may reject operations with "Concurrent modification detected". Previously,
these were marked as rejected and silently discarded, causing user changes
to be lost.

Now concurrent modification rejections are handled properly:
1. Try downloading new remote ops first
2. If download returns new ops, normal conflict detection handles them
3. If download returns nothing (we already have the conflicting ops):
   - Mark old pending ops as rejected (their clocks are stale)
   - Create NEW ops with current state and merged vector clocks
   - The new ops will be uploaded on next sync cycle

This ensures user changes are preserved by creating new operations with
vector clocks that dominate all existing clocks (both local and remote).

Changes:
- Add _resolveStaleLocalOps() to create merged update ops
- Make getCurrentEntityState() public in ConflictResolutionService
- Await download before checking if local resolution is needed
- Add unit tests for concurrent modification handling
2025-12-18 14:54:21 +01:00
Johannes Millan
97145580a5 refactor(persistence): remove legacy persistence services
Remove unused persistence services that have been replaced by the
operation log system:
- android-db-adapter.service.ts
- database.service.ts
- db-adapter.model.ts
- indexed-db-adapter.service.ts
- persistence.service.spec.ts

Also remove related references from migration service, startup service,
and storage keys.
2025-12-18 14:15:14 +01:00
Johannes Millan
ede5bfc7ee test(sync): add comprehensive tests for import backup
Add edge case tests for import backup functionality:
- Complex nested data structures preservation
- Backup survives clearAllOperations
- Independence from state_cache
- clearAllOperations doesn't affect import_backup
- Continue import even if backup save fails
2025-12-18 14:11:02 +01:00
Johannes Millan
fb0d87a739 refactor(sync): use dedicated import_backup store instead of tmpBackupService
Replace tmpBackupService with a dedicated import_backup object store in
the SUP_OPS IndexedDB database. This avoids triggering the stray backup
recovery prompt on startup.

Changes:
- Add import_backup store to SUP_OPS schema
- Add saveImportBackup/loadImportBackup/clearImportBackup/hasImportBackup methods
- Update pfapi.service.ts to use new backup mechanism
- Add tests for import backup functionality
2025-12-18 14:07:48 +01:00
Johannes Millan
59ec39dc34 fix(sync): clear old operations on import to prevent IndexedDB bloat
SYNC_IMPORT operations contain the full app state (10-30MB+) and
accumulate in IndexedDB because imports don't delete old operations.
This causes IndexedDB to grow to 29MB+ even with minimal actual data.

Changes:
- Add clearAllOperations() method to OperationLogStoreService
- Before import, backup current state to tmpBackupService
- Clear all old operations before creating new SYNC_IMPORT
- Add tests for new functionality

The backup allows recovery if something goes wrong during import.
2025-12-18 13:57:09 +01:00
Johannes Millan
831e78e9bb perf(sync): optimize startup speed for SuperSync
- Delay initial sync by 500ms for SuperSync only (data already local)
- Pre-warm privateCfg cache in setActiveSyncProvider (reduces IDB reads)
- Add unit tests for new behavior
- Fix tag.effects.spec.ts missing provider mocks
2025-12-18 13:26:44 +01:00
Johannes Millan
17b396b64f fix(sync): validate state before creating SYNC_IMPORT
Add validation and repair before creating SYNC_IMPORT operations
during server migration. This prevents corrupted state (e.g.,
orphaned menuTree references) from propagating to other clients.

- If state is invalid and can't be repaired, abort SYNC_IMPORT
- If state was repaired, dispatch loadAllData to update local store
- Ensures SYNC_IMPORT only contains valid, consistent state
2025-12-18 13:21:23 +01:00
Johannes Millan
26eb1536c5 fix(sync): repair TODAY_TAG consistency after sync via effect
Add post-sync repair for TODAY_TAG.taskIds divergence caused by per-entity
conflict resolution. When "Add to today" and "Snooze" operations conflict,
the TASK entity may resolve to one client's values while TODAY_TAG gets
different values, causing persistent state divergence.

Implementation follows existing pattern in tag.effects.ts:
- selectTodayTagRepair selector detects inconsistencies between
  TODAY_TAG.taskIds and tasks with dueDay=today
- repairTodayTagConsistency$ effect watches for inconsistencies and
  dispatches updateTag to repair, protected by skipDuringSync()

This approach is cleaner than the previous service-based implementation
as it consolidates sync-related repairs in tag.effects.ts alongside
the existing preventParentAndSubTaskInTodayList$ effect.
2025-12-18 13:16:58 +01:00
Johannes Millan
781898b572 fix(e2e): use !override to replace ports instead of merge
Docker-compose merges arrays by default, so both port 1900 and 1901
were being mapped. Using !override ensures only port 1901 is used
for e2e tests, allowing them to run alongside the dev server on 1900.
2025-12-18 13:02:52 +01:00
Johannes Millan
2c7c29666e fix(sync): throttle sync triggers to prevent rapid consecutive syncs
Add 2-second throttle to sync effect to prevent blur events from
triggering a second sync immediately after initial sync completes.
2025-12-18 12:55:30 +01:00
Johannes Millan
bf65350dca fix(e2e): only treat snackbars with error keywords as sync errors
Informational snackbars like "Deleted task X Undo" and "addCreated task X"
were being incorrectly detected as sync errors. Now only snackbars containing
actual error keywords (error, failed, problem, could not, unable to) are
treated as sync failures.
2025-12-18 12:28:23 +01:00
Johannes Millan
ac36f5af67 fix(e2e): use correct nav-link selector for project navigation
The nav-item component renders a button with class 'nav-link', not an anchor tag.
Changed locators from '.nav-sidenav nav-item a:has-text(...)' to
'.nav-sidenav .nav-link:has-text(...)'.
2025-12-18 10:56:32 +01:00
Johannes Millan
144f16ae04 test(e2e): improve flaky edge case tests stability
- Use more specific nav-sidenav locators for project navigation
- Add retry logic for marking tasks as done
- Add waitForTask after task creation before syncing
- Increase settling time between operations
- Add debug logging throughout tests
2025-12-18 10:42:40 +01:00
Johannes Millan
b024d47c8b test(e2e): improve sync test stability and coverage
- Add wait calls after task creation for UI stability
- Add debug logging for test troubleshooting
- Add timeout after sync for UI to settle
- Improve test assertions with better waits
2025-12-18 10:36:22 +01:00
Johannes Millan
de26eae809 test(sync): add unit tests for critical testing gaps
Add comprehensive unit tests identified in code review:

- SyncStateCorruptedError: tests for error creation, context, and instanceof checks
- issue-provider-shared.reducer: tests for deleteIssueProvider/deleteIssueProviders actions
- task-repeat-cfg-shared.reducer: tests for deleteTaskRepeatCfg action
- operation-applier.service: error path tests including:
  - archiveOperationHandler errors
  - extractDependencies errors
  - checkDependencies errors
  - non-Error throwable conversion
  - hydration state tracking (start/end, cleanup on error)
  - isLocalHydration option (skipping dependency checks)

Total: 34 new test cases
2025-12-18 10:33:18 +01:00
Johannes Millan
7a0858d67c fix(sync): address race conditions, error handling, and TODAY_TAG consistency
- Add single-tab fallback mutex for LockService when Web Locks unavailable
- Add 30s timeout to daily summary sync wait to prevent hanging
- Move conflict detection inside lock scope to prevent race condition
- Fix null check in archive service for missing parent tasks
- Add TODAY_TAG filtering in convertToMainTask and handleTagUpdates
- Extract mapTasksToArchiveFormat helper to reduce duplication
- Add consecutive failure tracking in operation capture meta-reducer
- Improve error handling in SimpleCounterService and BatchedTimeSyncAccumulator
2025-12-18 10:18:11 +01:00
Johannes Millan
0b998459aa fix(e2e): fix failing tags test and flaky late-join test
- Tags test: Use right-click context menu approach instead of 'g' shortcut
  to avoid typing into editable task title
- Tags test: Add robust dismissAllOverlays helper with multiple Escape
  presses and backdrop click fallback
- Late-join test: Improve conflict dialog handling with retry loop and
  wait for dialog to close
- Late-join test: Add extra sync cycle after conflict resolution for
  more reliable data propagation
- All files: Fix incorrect port in warning messages (1900 -> 1901)

All 48 supersync e2e tests pass consistently.
2025-12-17 21:56:53 +01:00
Johannes Millan
380fc9127e fix(sync): address critical and high priority issues from code review
- SimpleCounterService: fix memory leak by cleaning up accumulators on counter
  deletion, flush pending data in ngOnDestroy, clear accumulators on type change
- BatchedTimeSyncAccumulator: add clearOne() method for cleanup without dispatch
- getRepeatableTaskId: add input validation with descriptive error messages
- ArchiveService: fix race condition by reusing loaded archive data instead of
  reloading, add logging for missing parent task edge case, clarify lastFlush
  semantics with comments
- DailySummaryComponent: add try/catch for sync operations with error snackbar
- LockService: add graceful degradation when Web Locks API unavailable with
  clear warning about multi-tab data loss risks

All fixes include comprehensive test coverage (518+ lines added).
2025-12-17 21:14:25 +01:00
Johannes Millan
4b8edc91ab test(sync): add compaction, performance, and network failure tests
Add comprehensive tests for operation log sync functionality:
- Compaction integration tests: snapshot creation, tail replay,
  unsynced op preservation, emergency compaction, vector clock
  preservation
- Performance integration tests: large operation handling,
  hydration speed, sync batch performance, concurrent writes
- E2E network failure tests: upload/download failure recovery,
  server error handling, pending operations sync after recovery
2025-12-17 20:13:22 +01:00
Johannes Millan
cda5e1fe1f test(e2e): add task deletion sync test and improve documentation
- Add test for task deletion syncing between clients
- Document that scheduled task tests use dueDay, not actual repeat configs
- Reference integration tests for full repeat config sync testing
2025-12-17 18:24:19 +01:00
Johannes Millan
37b779c0e7 test(sync): add integration tests for repeat task sync scenarios
Add TaskRepeatCfg operation factories and comprehensive integration tests
covering multi-client sync scenarios for repeatable tasks:
- Concurrent instance creation with deterministic IDs
- Deleted instance tracking (deletedInstanceDates sync)
- Config modification during instance creation
- Late-joining client receiving repeat configs
2025-12-17 18:22:19 +01:00
Johannes Millan
95e513f45b refactor(sync): extract shared BatchedTimeSyncAccumulator utility
Extract common batched time sync logic from TaskService and
SimpleCounterService into a shared utility class.

- Create BatchedTimeSyncAccumulator with accumulate/flush/flushOne/shouldFlush methods
- Add 15 unit tests for the utility
- Refactor SimpleCounterService to use the shared accumulator
- Refactor TaskService to use the shared accumulator
- Context tracking (_unsyncedContexts) remains domain-specific in TaskService
2025-12-17 18:21:28 +01:00
Johannes Millan
9df9af2b29 fix(simple-counter): batch click counter sync to 5 minutes
Click counters now sync every 5 minutes like stopwatch counters,
instead of immediately after each click. This reduces sync traffic
and aligns with task time tracking behavior.

- Modified increaseCounterToday/decreaseCounterToday to track IDs
- Updated _flushAccumulatedTime to flush click counter changes
- Updated tests to verify batched behavior
2025-12-17 18:03:27 +01:00
Johannes Millan
b255a4e7ce test(simple-counter): add service tests for click counter sync
Add tests to verify that increaseCounterToday and decreaseCounterToday:
- Dispatch the local UI update action (non-persistent)
- Dispatch setSimpleCounterCounterToday with absolute value (persistent)
- Actions are dispatched in correct order
2025-12-17 17:56:03 +01:00
Johannes Millan
3c68471d11 fix(simple-counter): use absolute values for click counter sync
Simplify click counter sync by using setSimpleCounterCounterToday
(absolute value) instead of increment operations:

- Make increaseSimpleCounterCounterToday non-persistent (local UI only)
- Make decreaseSimpleCounterCounterToday non-persistent (local UI only)
- Service now dispatches setSimpleCounterCounterToday after increment
  to sync the absolute value
- Remove isRemote checks from reducer (no longer needed)

This fixes the issue where click counters weren't syncing properly
because increment operations are relative, not absolute.
2025-12-17 17:49:54 +01:00
Johannes Millan
aaf7d3b553 fix(task-repeat): use deterministic IDs to prevent duplicate tasks during sync
When two devices create a repeatable task for the same day simultaneously,
they now generate the same task ID (rpt_{repeatCfgId}_{dueDay}), allowing
conflict resolution to work correctly instead of creating duplicates.

Also adds hydration guards to TaskDueEffects to prevent selector-based
effects from firing during sync replay.
2025-12-17 17:41:43 +01:00
Johannes Millan
9c8526e89d fix(simple-counter): optimize sync with batched stopwatch updates
- StopWatch counters now use batched sync (every 5 min) instead of
  syncing on every 1-second tick, reducing sync operations by 99.7%
- Add tickSimpleCounterLocal (non-persistent) for immediate UI updates
- Add syncSimpleCounterTime (persistent) for batched sync
- ClickCounter clicks still sync immediately
- Make isOn state local-only (toggle/on/off actions non-persistent)
- Fix double-counting bug for click counters on remote clients by
  checking isRemote flag in increase/decrease reducers
- Add flush on counter stop, visibility change, and app close
2025-12-17 17:39:20 +01:00
Johannes Millan
0b101ffb9a fix(schedule): use time left instead of total estimate for drag preview size
The drag preview was showing larger than the actual event duration because
it used task.timeEstimate (total) instead of event.timeLeftInHours (which
accounts for timeSpent).
2025-12-17 17:16:52 +01:00
Johannes Millan
109be9e7a4 fix(sync): apply language changes from remote sync without reload
Replace action-based language effects with selector-based effect that
fires on state changes regardless of source (local or remote sync).
2025-12-17 17:02:58 +01:00
Johannes Millan
99dd4b4be4 fix(simple-counter): persist isEnabled changes from config form
The updateAllSimpleCounters action was missing isPersistent metadata,
causing simple counter settings (including isEnabled) to not persist
across page reloads when changed via the config form.
2025-12-17 16:54:21 +01:00
Johannes Millan
5992fad51b fix(sync): perform archive flush synchronously to prevent DB lock error
Previously, flushYoungToOld was dispatched as an action and handled by
an NgRx effect. This caused a race condition during finish day:
1. Action dispatched, effect queued
2. Method returned, sync started, DB locked
3. Effect ran, tried to write, blocked by DB lock

Fix follows the same pattern as moveToArchive:
- Perform the flush synchronously in ArchiveService before dispatching
- Dispatch action for op-log capture only (syncs to other clients)
- Handler skips local operations (only runs for remote)

Also adds comprehensive unit tests and e2e test for this scenario.
2025-12-17 16:42:59 +01:00
Johannes Millan
02d80249af fix(sync): prevent SYNC_IMPORT for fresh clients syncing to empty server
The server migration check was incorrectly creating a SYNC_IMPORT when
a fresh client (with local data but no sync history) synced to an empty
server. This caused operations from other clients to be filtered out as
"invalidated by SYNC_IMPORT" because they were CONCURRENT with it.

Now _checkAndHandleServerMigration() checks for previously synced ops
before triggering migration, correctly distinguishing between:
- Fresh client (only local ops) → uploads ops normally
- Server migration (has sync history) → creates SYNC_IMPORT

Also adds npm scripts for debugging supersync E2E tests.
2025-12-17 15:56:17 +01:00