Commit graph

17022 commits

Author SHA1 Message Date
Johannes Millan
e0786e4e2e test(sync-server): add comprehensive edge case tests for snapshot skip
Add 11 additional edge case tests for the server-side snapshot skip
optimization:
- SYNC_IMPORT at seq 1
- sinceSeq exactly equal to snapshotSeq
- Empty ops when all before snapshot
- excludeClient interaction with snapshot detection
- Gap detection: intentional skip vs real gaps
- Pagination after snapshot
- Multiple snapshots (use latest)
- Empty server state
- sinceSeq slightly before snapshotSeq
- sinceSeq at exactly snapshotSeq-1

Total: 17 unit tests covering the optimization thoroughly.
2025-12-17 11:42:32 +01:00
Johannes Millan
e36ba3f47d test(sync-server): add tests for snapshot skip optimization
Unit tests (6):
- Returns latestSnapshotSeq when SYNC_IMPORT exists
- Skips operations before SYNC_IMPORT for fresh clients (sinceSeq=0)
- Does NOT skip when sinceSeq is after SYNC_IMPORT
- Returns undefined latestSnapshotSeq when no full-state ops exist
- Handles BACKUP_IMPORT as a full-state operation
- Handles REPAIR as a full-state operation

Integration tests (5):
- HTTP endpoint includes latestSnapshotSeq in response
- HTTP endpoint skips pre-import ops for fresh clients
- HTTP endpoint does NOT skip when sinceSeq > SYNC_IMPORT seq
- HTTP endpoint omits latestSnapshotSeq when no full-state ops
- HTTP endpoint uses the latest full-state op when multiple exist
2025-12-17 11:36:04 +01:00
Johannes Millan
0bacefa58a perf(sync-server): skip pre-import operations when downloading
When a fresh client downloads operations (sinceSeq=0), the server now
finds the latest full-state operation (SYNC_IMPORT, BACKUP_IMPORT, REPAIR)
and starts from there instead of from the beginning.

This optimization:
- Reduces bandwidth by not sending superseded operations
- Reduces server and client processing time
- Works with the client-side filtering fix (pre-import ops are filtered anyway)

The response now includes latestSnapshotSeq to indicate where the
effective state starts, which clients can use for optimization decisions.
2025-12-17 11:31:04 +01:00
Johannes Millan
d60ff3fd3a fix(sync): filter pre-import ops from same client in SYNC_IMPORT handling
Remove flawed same-client exception in _filterOpsInvalidatedBySyncImport().
The bug allowed pre-import operations from the importing client to bypass
timestamp filtering, causing state corruption when a fresh client joined
a sync group after a data import.

The assumption that "ops from the same client have knowledge of the import"
was wrong for pre-import operations. They reference the old state that the
import replaced. Now all pre-import ops are filtered by timestamp regardless
of which client created them.
2025-12-17 11:24:27 +01:00
Johannes Millan
8ac9f4deee fix(config): handle startOfNextDay value of 0 correctly
The effect filter used a truthy check which filtered out 0 values,
preventing setStartOfNextDayDiff(0) from being called when resetting
the setting to its default value.
2025-12-17 11:19:53 +01:00
Johannes Millan
20bad157db fix(sync): recognize bulk ID-based update payloads in validation
Add support for {entityType}Ids pattern (e.g., taskIds) in
validateUpdatePayload to prevent false "unusual structure" warnings
for actions like planTasksForToday and removeTasksFromTodayTag.
2025-12-17 10:56:35 +01:00
Johannes Millan
ee7e0750da test(sync): add tests for offline transitions, LWW edge cases, and concurrency
Add comprehensive tests for identified coverage gaps:
- Offline/online transition scenarios (5 tests)
- LWW same-timestamp tie-breaking edge cases (4 tests)
- Lock service high contention and error recovery (7 tests)
- Concurrent operation race conditions (7 tests)
2025-12-17 10:42:13 +01:00
Johannes Millan
b08774d7e6 test(worklog): update test expectations for alphabetical sorting
Update test expectations in map-archive-to-worklog.spec.ts and E2E tests
to match the new alphabetical sorting order for worklog entries.

The sorting feature sorts tasks alphabetically while keeping subtasks
grouped with their parent tasks. This changes the expected order in tests:
- Parent tasks now sorted alphabetically (MT1 before PT1)
- Subtasks sorted alphabetically within their group (SUB_B before SUB_C)
2025-12-16 21:03:13 +01:00
Johannes Millan
ff9012b724 fix(sync): handle LWW Update actions and immediate re-upload
Two key fixes for LWW conflict resolution sync flow:

1. Add lwwUpdateMetaReducer to handle [ENTITY_TYPE] LWW Update actions
   - When Client B receives an LWW Update from Client A, the meta-reducer
     updates the NgRx store so the UI reflects changes without reload
   - Supports TASK, PROJECT, TAG, NOTE, SIMPLE_COUNTER, TASK_REPEAT_CFG

2. Propagate needsReupload flag through sync flow
   - autoResolveConflictsLWW now returns { localWinOpsCreated: number }
   - downloadRemoteOps returns needsReupload flag when local-win ops created
   - sync.service triggers second upload when needsReupload is true
   - Ensures local-win operations are uploaded in the same sync cycle

Tests added:
- Unit tests for lwwUpdateMetaReducer (10 tests)
- Integration tests for LWW Update store application (12 tests)
- E2E tests for LWW conflict resolution scenarios (6 tests)
2025-12-16 20:37:14 +01:00
Johannes Millan
a9df156049 fix(short-syntax): update parent timeEstimate when subtask estimate changes
Added parent task timeEstimate recalculation using
reCalcTimeEstimateForParentIfParent() when a subtask's timeEstimate
is updated via short syntax.
2025-12-16 20:23:17 +01:00
Johannes Millan
612cb3d201 fix(short-syntax): calculate timeSpent when timeSpentOnDay is updated
The short-syntax meta-reducer was updating timeSpentOnDay directly via
taskAdapter.updateOne() without recalculating the timeSpent total. This
caused the UI to display stale time values.

Now uses updateTimeSpentForTask() which:
- Calculates timeSpent from timeSpentOnDay
- Handles parent task time aggregation for subtasks
2025-12-16 20:18:18 +01:00
Johannes Millan
736ce872d8 perf(sync): add incremental caching for operation log queries
- Add caching for getUnsynced() with cache invalidation on markSynced/markRejected
- Optimize getAppliedOpIds() to incrementally update cache instead of full rebuild
- When new ops are added, only scan the new entries instead of the entire table
- Initial cache build still requires full scan, but subsequent calls are O(new_ops)

This significantly reduces sync latency by avoiding multiple full IndexedDB table
scans on each sync cycle. Before: 2-3 full scans × 300-500ms. After: Only scan
newly added operations (typically 0-5 ops = ~1ms).

Also fix lint errors in spec files (client-B -> clientB naming convention).
2025-12-16 19:18:54 +01:00
Johannes Millan
df26464cb9 fix(sync): move operationCaptureMetaReducer to first position for correct state capture
The meta-reducer was incorrectly positioned LAST in the array, but NgRx
composes meta-reducers such that FIRST = OUTERMOST. This caused
operationCaptureMetaReducer to receive already-modified state from
taskSharedCrudMetaReducer, resulting in beforeState === afterState
and no entity changes being detected for delete operations.

Moving it to FIRST position ensures it captures the original state
before any other meta-reducer modifies it.

Adds integration tests to verify correct ordering and prevent regression.
2025-12-16 19:07:39 +01:00
Johannes Millan
f89feed8b7 fix(docs): correct late-joiner replay section to describe UUIDv7 filtering
The documentation incorrectly described "vector clock dominance" filtering,
but the actual implementation uses UUIDv7 timestamp comparison. Updated
Section 2c to accurately reflect the implementation and explain why UUIDv7
is used (SYNC_IMPORT operations break vector clock causality detection).
2025-12-16 18:50:59 +01:00
Johannes Millan
9f1d68ea40 Merge branch 'master' into feat/operation-logs
* master:
  fix(electron): use includes() instead of in operator for hostname check
  fix(docker): use Debian-based nginx for ARM64 QEMU compatibility
  16.6.1
  build: add resolved URL and integrity for ical.js version 2.1.0
  fix(ui): align time tracking button overlay (#5720)
  fix(calendar): handle Office 365 updateTimezones crash (#5722)
  fix(repeat): use fallback for undefined startDate (#5724)
  build(welcome): update wording for issue claiming instructions
  fix(docker): drop arm/v7 platform to fix QEMU build failure
2025-12-16 18:14:12 +01:00
Johannes Millan
d0ce292ac5 refactor(sync): remove manual conflict dialog in favor of LWW auto-resolution
The conflict resolution dialog and presentConflicts method have been removed
as all conflicts are now automatically resolved using Last-Write-Wins (LWW)
strategy. This removes the need for manual user intervention during sync.

Changes:
- Remove DialogConflictResolutionComponent and related files
- Remove presentConflicts method from ConflictResolutionService
- Remove presentConflicts tests and dialog timeout tests
- Remove D_CONFLICT_RESOLUTION translations from en.json
- Update spec files to reference autoResolveConflictsLWW instead

The remaining ConflictResolutionService now only contains:
- isIdenticalConflict (for detecting when both sides made same change)
- autoResolveConflictsLWW (automatic timestamp-based conflict resolution)
2025-12-16 17:30:11 +01:00
Johannes Millan
4d96c8ffff feat(sync): implement LWW conflict auto-resolution
Replace manual conflict resolution dialogs with automatic Last-Write-Wins
(LWW) resolution based on operation timestamps:

- Add autoResolveConflictsLWW() to ConflictResolutionService
- When remote timestamp >= local: remote wins, local op rejected
- When local timestamp > remote: local wins, create new UPDATE op
  with current entity state and merged vector clock
- Show non-blocking snackbar notification after resolution
- Create safety backup before resolving conflicts

Includes comprehensive test coverage:
- Unit tests for ConflictResolutionService
- Integration tests for LWW timestamp comparison and vector clock merging
- E2E tests for multi-client convergence scenarios

Add detailed LWW documentation with Mermaid diagrams explaining the
algorithm, outcomes, and tradeoffs vs manual resolution.
2025-12-16 16:22:53 +01:00
Johannes Millan
404973ef72 fix(test): update service-logic integration tests for LWW auto-resolve
- Add autoResolveConflictsLWW to ConflictResolutionService spy
- Mock window.confirm to return true for fresh client confirmation
- Update conflict test assertion to check autoResolveConflictsLWW
2025-12-16 15:19:12 +01:00
Johannes Millan
d58b5f8fc5 perf(sync): optimize operation log hydration for faster startup
Skip dependency checking and archive handling during local hydration
since operations were already validated when created and archive data
is already persisted. Also removes debug console.log statements and
reduces logging verbosity.
2025-12-16 14:23:13 +01:00
Johannes Millan
cb04277daa fix(docs): correct gap detection and server migration flow in diagrams
Diagram 2 corrections:
- Gap detection does NOT fetch snapshot - it resets sinceSeq=0 and
  re-downloads all ops from the server
- Add "Server Migration" case: gap + empty server triggers SYNC_IMPORT
- Update Key Implementation Details with correct behavior

Diagram 1 cleanup:
- Remove misleading "OpEffects -->|flushYoungToOld| ArchiveYoung" arrow
  (flush is handled by ArchiveOperationHandler, not direct OpEffects write)
2025-12-16 14:10:35 +01:00
Johannes Millan
c68a56eb47 docs(sync): add fresh client safety, piggybacked ops, gap detection to diagram
Download Flow additions:
- Gap detection check → triggers GET /api/sync/snapshot
- Fresh client check with confirmation dialog before accepting remote data

Upload Flow additions:
- Fresh client check → blocks upload (must download first)
- Piggybacked ops in response → routed to conflict detection

Update Key Implementation Details with new features.
2025-12-16 14:06:03 +01:00
Johannes Millan
99fed15d06 docs(sync): add auto-resolve conflict methods to master diagram
Update Conflict Management section to show auto-resolve flow:
- Both DELETE same entity → auto-resolved as "remote"
- Identical payload (same opType + payload) → auto-resolved as "remote"
- Real conflicts → show user dialog

Add auto-resolve note to Key Implementation Details.
2025-12-16 14:01:07 +01:00
Johannes Millan
a9e647c19d fix(docs): fix Mermaid parse error in sync architecture diagram
Remove invalid :::class syntax from subgraph declarations (not supported).
Use style commands at end of diagram instead.
2025-12-16 13:57:06 +01:00
Johannes Millan
2dbbb2016c docs(sync): consolidate into master sync architecture diagram
Replace fragmented sections 2, 2.1, 2.2, 2.3 with single comprehensive
master diagram showing:
- Client-side: sync loop, download/upload flow, conflict management
- Server-side: all 6 API endpoints with request/response details
- Server processing: validation, conflict detection, persistence
- PostgreSQL: all 4 tables with columns and write operations
- Connections: API -> processing -> database with operation types

Add quick reference tables for API endpoints and PostgreSQL tables.
2025-12-16 13:52:45 +01:00
Johannes Millan
69f7706348 docs(sync): add API endpoints and PostgreSQL operations to diagram
- Add Section 2.1: Server API Endpoints & Database Operations
  - Shows all 7 API endpoints (upload/download ops, snapshot, status, restore)
  - Maps each endpoint to PostgreSQL tables it reads/writes
- Add Section 2.2: Detailed Database Writes Per Action
  - Table showing exact Prisma operations for each API action
- Add Section 2.3: Operation Processing Flow (Server-Side)
  - Validation, conflict detection, and persistence steps
  - Documents transaction isolation and race condition guards
2025-12-16 13:48:44 +01:00
Johannes Millan
0f1d3d1ee1 docs(sync): clarify archive storage structure in diagrams
- Update first diagram to show ArchiveModel contains both task: TaskArchive
  and timeTracking: State (not just time tracking data)
- Add flushYoungToOld action flow from OpEffects to archive
- Expand Archive Data Flow Notes with flush mechanism details
- Update Section 8.2 dual-database diagram with full ArchiveModel structure
2025-12-16 13:47:29 +01:00
Johannes Millan
3405999aba fix(docs): correct archive flush thresholds in diagrams
Fix incorrect "6 months" references:
- ARCHIVE_TASK_YOUNG_TO_OLD_THRESHOLD = 21 days (task age)
- ARCHIVE_ALL_YOUNG_TO_OLD_THRESHOLD = 14 days (flush interval)
2025-12-16 13:35:32 +01:00
Johannes Millan
5ba97128ec fix(docs): correct multi-tab section in architecture diagram
Replace incorrect "Multi-Tab Coordinator" with actual behavior:
- Single instance enforcement via BroadcastChannel (startup.service.ts)
- Web Locks API for critical sync operations (lock.service.ts)
2025-12-16 13:31:33 +01:00
Johannes Millan
ef8b54f423 fix(sync): add missing TranslateService mock to integration tests
OperationLogSyncService injects TranslateService, but the integration
test configurations were missing the mock provider, causing 8 test
failures.
2025-12-16 12:57:25 +01:00
Johannes Millan
6d242332c6 docs(sync): add dual-database architecture diagram for PFAPI storage
Add section 8.2 showing the two separate IndexedDB databases:
- SUP_OPS: Operation log (ops table, state_cache)
- PFAPI: Archive data (archiveYoung, archiveOld), time tracking, legacy sync

Update archive operations flow diagram to explicitly show:
- PFAPI IndexedDB as the storage destination for archive data
- SUP_OPS IndexedDB for operation log entries
- Clear separation between the two storage systems
2025-12-16 12:44:28 +01:00
Johannes Millan
68a105616f fix(docs): fix Mermaid parse error in vector clock diagram
Replace curly braces with equals notation for vector clocks
to avoid conflicts with Mermaid's diamond shape syntax.
2025-12-16 12:42:26 +01:00
Johannes Millan
c930fab74b docs(sync): add archive and time tracking storage to architecture diagram
Add Archive Storage section to the first architecture diagram showing:
- ArchiveService writing to IndexedDB BEFORE dispatch
- Two-tier archive storage (archiveYoung/archiveOld)
- Time tracking data (timeSpentOnDay entries)
- Notes explaining the archive data flow and sync handling
2025-12-16 12:34:10 +01:00
Johannes Millan
a9becc2058 fix(sync): improve sync reliability with bug fixes and tests
- Fix silent save failures during sync by adding snackbar notification
- Fix fresh client race condition by using synchronous window.confirm()
- Simplify LockService to Web Locks API only (97%+ browser support)
- Add monotonicity warning when server sequence decreases
- Document vector clock corruption limitation and ModelCtrl cache bypass
- Add unit tests for UUID timestamp extraction from UUIDv7
2025-12-16 12:28:49 +01:00
Johannes Millan
f928e6b18e docs: cleanup 2025-12-16 12:28:04 +01:00
Johannes Millan
642f8d3984 docs(sync): improve and consolidate operation log documentation
- Update hybrid-manifest-architecture.md status to Implemented
- Add documentation index (README.md) for operation-log docs
- Add index for docs/ai/sync/ with document categorization
- Replace duplicate docs with redirect stubs to canonical locations
- Add effect rules, multi-entity rules, and config constants to operation-rules.md
- Update vector-clocks.md with operation log integration section
- Expand super-sync-server README with API details and security features
- Update pfapi sync README with operation log integration info
2025-12-16 10:45:20 +01:00
Johannes Millan
425aee6de9 refactor(sync): apply code review improvements across sync layer
- Extract `filterNewOps()` to OperationLogStoreService for batch duplicate filtering
- Refactor `detectConflicts()` into smaller helpers: `_checkOpForConflicts()`,
  `_checkEntityForConflict()`, `_buildEntityFrontier()`, `_adjustForClockCorruption()`
- Add UUID validation to `_extractTimestampFromUuidv7()` with clear error messages
- Extract `_isValidBackupId()` helper in SyncSafetyBackupService
- Add `hasTask()` method to TaskArchiveService, replace exception-based control flow
- Cache reducer chain in TaskArchiveService to avoid recreation per call
- Add `SYSTEM_TAG_IDS` constant to tag.const.ts for filtering system tags
- Extract `_hasNoUserCreatedTags()` helper using SYSTEM_TAG_IDS
- Add iteration limit (MAX_DOWNLOAD_ITERATIONS) to download loop
- Add user notification for archive operation failures
- Add JSDoc for SyncSafetyBackup interface and backup slot strategy comments
- Use i18n keys for error messages in restore dialog
2025-12-15 21:38:48 +01:00
Johannes Millan
a85b8e65b4 feat(archive): add manual archive compression feature
Add ability to compress archived tasks by:
- Deleting subtask entities and merging timeSpent/timeSpentOnDay to parent
- Clearing notes from tasks older than 1 year
- Clearing non-essential issue fields (keeps issueId and issueType)

The compression operation is synced across clients via the operation log.
Users can trigger compression from Settings → Import/Export with a preview
showing affected tasks and estimated space savings.
2025-12-15 21:34:30 +01:00
Johannes Millan
ffa485f4b7 fix(reminders): clear remindAt when adding today-scheduled tasks to today
When tasks scheduled for today with a time triggered the reminder dialog,
clicking "Add all to today" did not clear remindAt, causing the reminder
to repeatedly pop up. Now remindAt is always cleared when explicitly
adding tasks to today.
2025-12-15 21:08:08 +01:00
Johannes Millan
815c86c10d fix(migration): read legacy data from 'pf' database, handle orphan operations
Two bugs prevented legacy data from being migrated to operation logs:

1. Migration services called getAllSyncModelData() which uses NgRx store
   delegate. Since NgRx store is empty during init, this returned empty data.
   Fix: Use getAllSyncModelDataFromModelCtrls() to read from 'pf' database.

2. Effects dispatching actions during app init created orphan operations
   before migration ran. The lastSeq > 0 check then returned early, thinking
   migration already happened.
   Fix: Check for snapshot first, then check for Genesis operation, and
   clear orphan operations if found before proceeding with migration.
2025-12-15 21:02:04 +01:00
Johannes Millan
f51973ee92 fix(sync): update vector clock tests to expect error on overflow
Tests were expecting silent reset to 1 on overflow, but the implementation
correctly throws an error since resetting would break causality.
2025-12-15 20:00:58 +01:00
Johannes Millan
e7c8534a17 feat(sync): add double checkmark indicator for Super Sync
Show single checkmark when local ops are synced to remote, and double
checkmark (done_all icon) when we also recently checked remote for
updates (within 1 minute). This applies only to Super Sync.

- Add SuperSyncStatusService to track remote check freshness and
  pending ops status
- Update OperationLogDownloadService to mark remote checks
- Update OperationLogSyncService to track pending ops status
- Update SyncWrapperService to expose combined status observable
- Update main-header component to conditionally show done_all vs check
2025-12-15 18:41:42 +01:00
Johannes Millan
d6db1c2885 fix(sync): use timestamp comparison for same-millisecond UUIDv7 operations
When filtering operations after a SYNC_IMPORT, the previous string
comparison `op.id > latestImport.id` could incorrectly filter out
operations created in the same millisecond due to random bits in UUIDv7.

This caused repeatable task instances to not sync when created
immediately after an import operation.

Fix: Extract the 48-bit timestamp from UUIDv7 and use >= comparison
to ensure same-millisecond operations are kept.
2025-12-15 18:27:44 +01:00
Johannes Millan
b1ff5b9722 fix(sync): persist archive data to IndexedDB on SYNC_IMPORT
When receiving a SYNC_IMPORT operation from another client (e.g., after
a backup import), the archive data (archiveYoung, archiveOld) was only
loaded into NgRx state but never persisted to IndexedDB. On app restart,
IndexedDB had the old data, causing time tracking loss.

Fix adds _handleLoadAllData handler to ArchiveOperationHandler that
writes archive data to IndexedDB for remote SYNC_IMPORT operations.

Fixes: 1594 hours on Client A -> 70 hours on Client B after sync
2025-12-15 18:00:16 +01:00
Johannes Millan
c12900329c refactor(sync): remove dead file-based sync code and add hydration tests
- Remove ~750 lines of unused file-based sync code from upload/download services
- Delete OperationLogManifest interface (no longer needed)
- Remove manifest service references from integration tests
- Add unit tests for HydrationStateService
2025-12-15 17:41:36 +01:00
Johannes Millan
3f907b384e test(sync): update effects tests for updateTask as archive-affecting
updateTask is now archive-affecting since it can update archived tasks.
Updated tests to reflect this change.
2025-12-15 17:36:54 +01:00
Johannes Millan
c1d71a432c test(sync): add tests for archived task update sync
- Test TaskArchiveService.updateTask dispatches persistent action
- Test TaskArchiveService.updateTasks dispatches actions for each update
- Test isSkipDispatch option prevents dispatch
- Test isIgnoreDBLock is passed through to save
- Test ArchiveOperationHandler.handleUpdateTask for remote operations
- Test updateTask is now archive-affecting action
2025-12-15 17:30:45 +01:00
Johannes Millan
d55ec85777 fix(sync): sync archived task updates (e.g., time spent edits)
TaskArchiveService.updateTask() and updateTasks() now dispatch
persistent actions after saving to IndexedDB, enabling sync.
ArchiveOperationHandler handles remote updateTask operations
by updating the archive with isSkipDispatch to prevent loops.
2025-12-15 17:24:39 +01:00
Johannes Millan
9a18a8194c fix(sync): respect action opType for archive operations and improve sync robustness
Fix moveToArchive operations being rejected by server:
- deriveOpType() now uses action's declared opType instead of deriving from entity changes
- moveToArchive declares opType: Update but was being changed to Delete based on entity changes
- Server rejected because DEL operations require singular entityId, but bulk ops use entityIds

Additional sync improvements:
- Use OpLog.warn instead of devError for old snapshot format (expected transitional state)
- Add lock-based synchronization for conflict detection (replaces flushPendingWrites)
- Handle TODAY_TAG correctly when checking if client state is empty
- Add partial apply failure handling with validation and user notification
- Server: Add race condition prevention with REPEATABLE_READ isolation and re-check after seq allocation
- Server: Add concurrent snapshot generation lock to prevent duplicate work

Update tests to use LockService instead of OperationWriteFlushService
2025-12-15 17:10:25 +01:00
Johannes Millan
4d632aba3b feat(sync): auto-resolve identical sync conflicts
When both local and remote operations have the same effect (e.g., both
DELETE the same entity or both UPDATE with identical payloads), the
conflict is now auto-resolved as "remote" without showing the dialog
to the user. This reduces unnecessary user interaction when conflicts
are semantically identical.

- Add isIdenticalConflict() method to detect identical conflicts
- Add _deepEqual() helper for payload comparison
- Modify presentConflicts() to separate and auto-resolve identical conflicts
- Add comprehensive tests for identical conflict detection
2025-12-15 16:02:39 +01:00
Johannes Millan
55cdc18a24 fix(sync): use UUIDv7 comparison for replay filtering after restore
Replace vector clock comparison with UUIDv7 timestamp comparison in
_replayLocalSyncedOpsAfterImport. This fixes state divergence after
restore/import operations.

When restoring with isForceConflict=true, a fresh vector clock with a
new client ID is created. Comparing clocks with no overlapping client
IDs resulted in CONCURRENT (not LESS_THAN), causing old ops to be
incorrectly replayed on top of restored state.

UUIDv7 IDs are time-ordered, so op.id < syncImportOp.id correctly
identifies ops created before the restore/import that should be
filtered out.
2025-12-15 15:43:49 +01:00