Commit graph

11554 commits

Author SHA1 Message Date
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
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
Johannes Millan
56d490ed35 test(sync): add tests for restore feature and safety backup service
Add comprehensive tests for:
- SuperSyncRestoreService (14 tests): isAvailable, getRestorePoints, restoreToPoint
- DialogRestorePointComponent (21 tests): initialization, selection, restore flow
- SyncSafetyBackupService (20 tests): lazy injection, backup CRUD, event handling
- Server sync.service: getRestorePoints, generateSnapshotAtSeq
- Server sync.routes: restore-points and restore/:serverSeq endpoints
2025-12-15 13:37:01 +01:00
Johannes Millan
082aca26e8 fix(sync-server): reduce operation and tombstone retention to 45 days
Reduces storage per user by ~50% at steady state. Devices offline
longer than 45 days will need a full resync via gap detection.
2025-12-15 13:28:58 +01:00
Johannes Millan
ef92a12667 feat(sync): add restore from history for Super Sync
- Add server endpoints for restore points (GET /api/sync/restore-points, GET /api/sync/restore/:serverSeq)
- Add RestorePoint types and RestoreCapable interface
- Add getRestorePoints() and generateSnapshotAtSeq() to SyncService
- Add SuperSyncRestoreService for client-side restore orchestration
- Add DialogRestorePointComponent for restore point selection UI
- Add "Restore from History" button to sync settings (visible when Super Sync is active)
- Fix circular dependency in SyncSafetyBackupService using lazy injection
- Update docker-compose.yml to support local development (configurable NODE_ENV, PUBLIC_URL)
2025-12-15 13:18:15 +01:00
Johannes Millan
6470725eaf fix(sync): improve robustness and safety of sync operations
- Fix race condition in sync-wrapper.service.ts with early return check
- Convert async subscribe anti-patterns to firstValueFrom for proper error handling
- Add atomic flag to conflict dialog timeout to prevent race conditions
- Create safety backup before conflict resolution via SyncSafetyBackupService
- Add SyncAlreadyInProgressError class to replace fragile string-based detection
- Redact sensitive fields (passwords, encryption keys) in sync config logging
- Add type-safe toSyncProviderId helper to replace unsafe double assertions
- Add defensive null check for vector clocks in sync status comparison
- Extract magic numbers to documented constants (SYNC_WAIT_TIMEOUT_MS, etc.)
- Add placeholder test file for sync-wrapper.service.ts documenting expected tests
2025-12-15 10:47:35 +01:00
Johannes Millan
67d175bdf1 fix(restore): read from updatedState when restoring project associations
When restoring a deleted task, we must read from the updated state
(after tag associations are restored) to get the current project
entity, not the original state.
2025-12-13 20:42:14 +01:00
Johannes Millan
5c8fcdf8e5 fix(sync): sanitize access token to remove non-ASCII characters
The HTTP Headers API requires ISO-8859-1 characters. Tokens copied from
web pages may contain invisible Unicode characters (zero-width spaces,
etc.) that cause "Failed to execute 'set' on 'Headers'" errors.
2025-12-13 20:37:16 +01:00
Johannes Millan
baa3e0fae1 test(restore): add tests for adjacent task and project state restoration
Tests that:
- Multiple adjacent tasks can be restored to original positions
- Task restoration reads current project state, not stale captured state
2025-12-13 20:17:54 +01:00
Johannes Millan
b1e1c1d10b fix(sync): resolve WebDAV sync regression for multi-client scenarios
- Await all saves in updateLocalMainModelsFromRemoteMetaFile() using
  Promise.all() to ensure data is fully persisted before sync completes
- Remove unreliable early return optimization in sync() that compared
  metaRev timestamps, causing incorrect InSync returns
- Add cache-busting (cache: 'no-store') to WebDAV HTTP adapter to
  prevent stale responses
- Fix E2E tests by removing page reloads that broke sync provider
  re-initialization
- Improve waitForSync test helper to properly detect new sync cycles
- Add debug logging for vector clock comparisons in sync status checks

The sync now properly detects remote changes via vector clock comparison
and uploads/downloads data correctly between multiple clients.
2025-12-13 20:10:07 +01:00
Johannes Millan
3b42fb4974 test(operation-log): speed up download service tests with injectable delay
Add RETRY_DELAY_BASE_MS injection token to make retry delays configurable.
Tests now provide 0ms delay for instant retries, reducing test time from
7+ seconds to ~0.06 seconds for the download failure handling tests.
2025-12-13 19:37:06 +01:00
Johannes Millan
c93714bd0e test(validate): add state validity tests for all state-changing actions
Add comprehensive test suite that validates state remains valid after
every state-changing action. Tests check Typia schema validation,
cross-model relationship validation, and entity state consistency.

Includes:
- Test utilities for building valid AppDataCompleteNew states
- 30 tests covering TaskSharedActions, project, tag, and note actions
- Complex scenario tests for multi-step operations
2025-12-13 19:19:08 +01:00
Johannes Millan
82870601a6 test(sync): add comprehensive tests for undo task delete sync
Unit tests (8 new tests in task-shared-crud.reducer.spec.ts):
- Preserve timeSpentOnDay data when restoring
- Preserve attachments when restoring
- Preserve notes, issueId, reminderId, dueWithTime
- Idempotent project restore (no duplicates on replay)
- Subtasks with their own tags restored correctly
- Position-aware restore (middle position)
- Position clamping when array shrinks

E2E test (supersync-edge-cases.spec.ts):
- Undo task delete syncs restored task to other client
- Tests full flow: create→sync→delete→undo→sync→verify
2025-12-13 17:57:30 +01:00
Johannes Millan
5c217e2dff fix(sync): use merge semantics for task restore to prevent data loss
The restore logic was REPLACING taskIds arrays entirely with captured
snapshots, which could cause data loss if tasks were added between
delete and undo (e.g., during sync replay on another device).

Changes:
- Add mergeTaskIdsAtPositions() helper for position-aware merging
- Restore inserts at original position instead of replacing arrays
- Only restore task IDs that were in the captured array for each tag
- Add 5 tests for merge behavior (preserving new tasks, position, idempotency)
2025-12-13 17:45:24 +01:00
Johannes Millan
61ab6c4568 fix(sync): make undo task delete sync across devices
The previous implementation used a payload-less undoDeleteTask action that
relied on locally cached state. When synced, other devices received an empty
operation and couldn't restore the task.

Changes:
- Add restoreDeletedTask action with full payload (task, project/tag/parent context)
- Simplify meta-reducer to capture-only, export getLastDeletePayload() getter
- Move restore logic to shared reducer (sync-aware)
- Update snackbar effect to use actionFn with captured payload
- Remove obsolete undoDeleteTask action
- Add comprehensive tests (17 new tests for restore, 15 for capture)
2025-12-13 17:03:19 +01:00
Johannes Millan
ba49276791 test(sync): add comprehensive edge case tests for cascading deletes and data integrity
- Add unit tests for deleteTag/deleteTags in tag-shared.reducer.spec.ts:
  - Orphaned task deletion when tag is sole assignment
  - Cascade delete subtasks of orphaned parents
  - Cleanup task repeat configs referencing deleted tags
  - Cleanup time tracking state for deleted tags

- Add unit tests for project time tracking cleanup in project-shared.reducer.spec.ts

- Add E2E tests in supersync-advanced-edge-cases.spec.ts:
  - Bulk task creation (10 tasks) syncs without data loss
  - Stale client reconnection after many changes
  - Special characters in task names sync correctly

Note: Complex cascading delete E2E tests were removed as the scenarios
are better covered by unit tests due to UI timing fragility.
2025-12-13 15:13:01 +01:00
Johannes Millan
b79e8f8838 fix(sync): route ImmediateUploadService through SyncService for migration detection
When a client switches to a new/empty server, the full state snapshot
(SYNC_IMPORT) was not being uploaded because ImmediateUploadService
was calling OperationLogUploadService.uploadPendingOps() directly,
bypassing the migration detection callback in OperationLogSyncService.

Changes:
- Update OperationLogSyncService.uploadPendingOps() to return UploadResult | null
- Change ImmediateUploadService to call _syncService.uploadPendingOps()
  instead of _uploadService.uploadPendingOps() directly
- Update unit tests to reflect the new call chain
- Re-enable server-migration E2E tests (now passing)

This ensures the migration detection callback runs during immediate upload,
creating a SYNC_IMPORT when switching to a new/empty server so that other
clients joining the new server receive all data.
2025-12-13 13:54:02 +01:00
Johannes Millan
ef40c7ba6a fix(sync): ensure pending writes complete before conflict detection
Fix race condition where sync could start before local operation writes
completed to IndexedDB, causing conflict detection to miss pending ops.

- Add OperationWriteFlushService that uses sp_op_log lock to ensure all
  pending writes complete before reading from IndexedDB
- Call flushPendingWrites() before conflict detection in sync service
- Add retry with exponential backoff to createTestUser for rate limiting
- Add unit tests for flush service and sync ordering

The flush mechanism works by acquiring the same lock used by writeOperation().
Since effects use concatMap and Web Locks API guarantees FIFO ordering,
acquiring the lock means all prior writes have completed.
2025-12-13 11:50:36 +01:00
Johannes Millan
240581d046 post merge fix 2025-12-12 20:50:30 +01:00
Johannes Millan
bf7db5515e fix(sync): handle large payload transaction timeouts
Problem: Importing backups (~8.5MB) failed with "Transaction rolled back
due to internal error". The Prisma transaction had no explicit timeout,
defaulting to 5 seconds which was insufficient for large JSON payloads.
The client then marked the operation as permanently rejected instead of
retrying.

Server-side fix:
- Add 60s timeout to uploadOps transaction (matching generateSnapshot)
- Improve error message to indicate timeout vs other errors
- Include actual error details for debugging

Client-side fix:
- Extend transient error detection to include server errors:
  - "transaction rolled back"
  - "internal server error"
  - "server busy" / "please retry"
  - HTTP 500/502/503/504 errors
- Transient errors are NOT marked as rejected, allowing retry

Tests:
- Add 6 tests for transient vs permanent error handling
- Verify network errors, timeouts, and server errors are retriable
- Verify validation errors remain permanently rejected
2025-12-12 20:48:40 +01:00
Johannes Millan
04043e3964 docs(sync): document late-joiner replay with vector clock dominance
- Add Section 2c to architecture diagrams with mermaid diagrams
  explaining the late-joiner problem and solution
- Add Section C.7 to main architecture doc with code examples
- Add Example 4 to vector-clocks.md explaining dominance filtering
- Update Last Updated dates to December 12, 2025
2025-12-12 20:48:40 +01:00
Johannes Millan
ee6a766a46 fix(sync): add vector-clock dominance check to late-joiner replay
- Add missing vector-clock comparison in _replayLocalSyncedOpsAfterImport
  that filters ops dominated (LESS_THAN) by SYNC_IMPORT's clock
- Replace local MAX_REJECTED_OPS_BEFORE_WARNING with imported constant
- Fix CLAUDE.md doc path reference to operation-log-architecture-diagrams.md
- Add 6 comprehensive tests for vector-clock dominance behavior
2025-12-12 20:48:40 +01:00
Johannes Millan
db1a118846 fix(sync): initialize hardDependencies before extracting deps
Split the operation processing into two passes:
1. First pass: Initialize hardDependencies and opById for ALL operations
2. Second pass: Extract dependencies and build the graph

This fixes a bug where when processing operation A, if its dependency
entity is being deleted by operation B that comes LATER in the array,
hardDependencies.get(deleterOp.id) would be undefined because B hadn't
been processed yet.

Fixes: TypeError: Cannot read properties of undefined (reading 'add')
2025-12-12 20:48:40 +01:00