Skip to content

Conversation

@lawrence-forooghian
Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian commented Jan 16, 2026

This implements the spec changes from ably/specification#414 at 3f86319. That is, the update events emitted by a sync are now calculated from the before/after diff of the object state, as opposed to just being calculated from the createOp.

Summary by CodeRabbit

  • New Features
    • Replace operations now emit precise change deltas: counters report exact amount changed; maps report which keys were added, removed, or updated.
  • Tests
    • Added and expanded unit tests validating diff calculations and updated expectations across replace and sync scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

Walkthrough

The PR changes replaceData flows in LiveCounter and LiveMap to capture previous data, update state, and return calculated diffs (counter or map). It adds ObjectDiffHelpers for diff computation and updates/extends unit and integration tests to validate diff outputs (RTLC6h / RTLM6h).

Changes

Cohort / File(s) Summary
Core diff computation
Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
New internal enum with calculateCounterDiff(previousData:newData) and calculateMapDiff(previousData:newData) returning LiveObjectUpdate diffs (counter delta; map removals/adds/updates; skips tombstoned entries where appropriate).
LiveCounter implementation
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
In replaceData, store previousData before mutating, ignore mergeInitialValue return when createOp exists, and return a counter diff via ObjectDiffHelpers.calculateCounterDiff.
LiveMap implementation
Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
In nosync_replaceData, store previousData before state changes, ignore mergeInitialValue return when createOp exists, and return a map diff via ObjectDiffHelpers.calculateMapDiff.
Counter diff tests
Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift
Adds DiffCalculationTests verifying RTLC6h diff amount correctness with and without createOp (note: block duplicated in two test contexts).
Map diff tests
Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift
Adds DiffCalculationTests under ReplaceDataTests validating RTLM6h diff outputs for adds, removes, and updates, including cases with createOp.
Diff helper unit tests
Tests/AblyLiveObjectsTests/ObjectDiffHelpersTests.swift
Extensive unit tests for calculateCounterDiff and calculateMapDiff covering removals, additions, updates, tombstoned-entry scenarios, and multiple transition cases.
Integration test updates
Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift
Adjusts assertions to validate per-diff emissions (individual map key statuses and counter amount) instead of prior merge-return expectations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I stored "before" in my little paw,

then watched the "after" hop in with a gnaw,
I tally the change and map every key,
a whiskered cheer for diffs that set data free,
tiny rabbit, big updates—hip hooray!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'AIT-255 Fix events emitted upon sync' is directly related to the main objective of fixing events emitted during sync operations for LiveObjects, matching the linked issue AIT-255.
Linked Issues check ✅ Passed The PR implementation fully addresses the AIT-255 objectives: it captures previous state, calculates diffs before/after replacement, emits diff-based updates, and aligns Swift behavior with JS and the spec.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing events emitted upon sync; no unrelated modifications detected outside the diff calculation and event emission requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch AIT-255-fix-replace-data-events


📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 97e805d and 1185b08.

📒 Files selected for processing (7)
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
  • Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift
  • Tests/AblyLiveObjectsTests/ObjectDiffHelpersTests.swift
  • Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift
🚧 Files skipped from review as they are similar to previous changes (3)
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift
  • Tests/AblyLiveObjectsTests/ObjectDiffHelpersTests.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/swift.mdc)

**/*.swift: Specify an explicit access control level (SwiftLint explicit_acl) for all declarations in Swift code (tests are exempt)
When extending a type, put the access level on the extension declaration rather than on each member (tests are exempt)
Prefer implicit .init(...) when the type can be inferred in initializer expressions
Prefer enum case shorthand (.caseName) when the type can be inferred
For JSONValue or WireValue, prefer using literal syntax via ExpressibleBy*Literal where possible
Prefer Swift raw string literals for JSON strings instead of escaping double quotes
When an array literal begins with an initializer expression, place the initializer on the line after the opening bracket

Files:

  • Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
Tests/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

Tests/**/*.swift: Use the Swift Testing framework (import Testing), not XCTest, in test files
Do not use fatalError for expectation failures; prefer Swift Testing’s #require
Only add labels to test cases or suites when the label differs from the suite struct or test method name
Tag tests per CONTRIBUTING.md’s "Attributing tests to a spec point" with exact comment format; distinguish @spec vs @specPartial; do not repeat @spec for the same spec point
Add comments in tests to clarify when certain test data is irrelevant to the scenario
In tests, import Ably using import Ably
In tests, import AblyLiveObjects using @testable import AblyLiveObjects
In tests, import _AblyPluginSupportPrivate using import _AblyPluginSupportPrivate (do not use internal import)
When passing a logger to internal components in tests, use TestLogger()
When unwrapping optionals in tests, prefer #require over guard let

Files:

  • Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift
Sources/AblyLiveObjects/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/swift.mdc)

In AblyLiveObjects library (non-test) code, import modules as: Ably with import Ably, and _AblyPluginSupportPrivate with internal import _AblyPluginSupportPrivate

Files:

  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
Sources/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

For testsOnly_ property declarations, do not add generic explanatory comments (their meaning is understood)

Files:

  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
🧬 Code graph analysis (3)
Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift (4)
Tests/AblyLiveObjectsTests/Helpers/Subscriber.swift (1)
  • getInvocations (23-33)
Sources/AblyLiveObjects/Utility/ExtendedJSONValue.swift (1)
  • map (71-92)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift (1)
  • entries (143-150)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (1)
  • value (100-104)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (2)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift (1)
  • mergeInitialValue (516-574)
Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (1)
  • calculateCounterDiff (11-17)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift (2)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (1)
  • mergeInitialValue (354-371)
Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (1)
  • calculateMapDiff (25-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: SPM (Xcode 16.4)
  • GitHub Check: Xcode, tvOS (Xcode 16.4)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 16.4)
  • GitHub Check: Xcode, macOS (Xcode 16.4)
  • GitHub Check: Xcode, iOS (Xcode 16.4)
  • GitHub Check: Example app, tvOS (Xcode 16.4)
  • GitHub Check: Example app, macOS (Xcode 16.4)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 16.4)
  • GitHub Check: Example app, iOS (Xcode 16.4)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 16.4)
  • GitHub Check: SPM, release configuration (Xcode 16.4)
  • GitHub Check: Generate code coverage
🔇 Additional comments (6)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift (1)

471-512: LGTM! Diff-based update implementation is correct.

The changes correctly implement RTLM6g/RTLM6h:

  1. previousData is captured at the right point—after tombstone handling but before data mutation
  2. mergeInitialValue result is appropriately discarded since the diff is computed separately
  3. The diff is calculated between previousData and the final data state (post-merge)

This aligns with the counter implementation pattern and the PR objective of emitting diffs representing the full before-and-after state change.

Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (1)

334-350: LGTM! Consistent diff-based update implementation.

The changes correctly implement RTLC6g/RTLC6h with the same pattern as the map implementation:

  1. previousData captured before state mutation
  2. mergeInitialValue result discarded in favor of computed diff
  3. Diff returned via ObjectDiffHelpers.calculateCounterDiff

The implementation correctly produces the full before-and-after diff (e.g., 0 → 15 yields amount=15).

Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift (3)

122-126: LGTM! Test correctly validates diff-based map updates.

The test properly verifies RTLM6h semantics—the emitted update reflects the diff from empty state to final state containing both key1 (from entries) and createOpKey (from createOp), both marked as .updated.


163-165: LGTM! Test correctly validates diff-based counter updates.

The test properly verifies RTLC6h semantics—the diff from 0 (initial zero-valued counter) to 15 (10 from state + 5 from createOp) is correctly expected as amount: 15.


374-387: LGTM! Complex scenario test validates diff semantics correctly.

Both map and counter diff validations in this comprehensive test align with the RTLM6h/RTLC6h specifications, verifying the full before-and-after diff behavior for existing objects during sync.

Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (1)

1-57: LGTM! Clean implementation of diff calculation helpers.

The implementation correctly follows the spec:

  • calculateCounterDiff (RTLC14b): Simple subtraction produces the delta
  • calculateMapDiff (RTLM22b): Properly handles removed/added/changed keys using set operations on non-tombstoned entries

The force unwraps on lines 47-48 are safe since the keys are guaranteed to exist in both dictionaries (they come from the intersection of both key sets).

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lawrence-forooghian lawrence-forooghian marked this pull request as draft January 16, 2026 11:09
@github-actions github-actions bot temporarily deployed to staging/pull/111/AblyLiveObjects January 16, 2026 11:10 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift`:
- Around line 1-4: Replace the unused Foundation import at the top of the file
with the required module imports: remove "import Foundation" and add "internal
import _AblyPluginSupportPrivate" and "import Ably" so the file follows the
AblyLiveObjects coding guidelines; update the header above the internal enum
ObjectDiffHelpers accordingly.
🧹 Nitpick comments (1)
Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (1)

96-109: Consider simplifying using Optional's built-in equality.

Since JSONObjectOrArray conforms to Equatable, the entire function can be simplified to a single expression, as Swift's Optional automatically provides == when the wrapped type is Equatable.

♻️ Simplified implementation
     private static func areJSONEqual(
         _ json1: JSONObjectOrArray?,
         _ json2: JSONObjectOrArray?
     ) -> Bool {
-        switch (json1, json2) {
-        case (nil, nil):
-            return true
-        case (nil, .some), (.some, nil):
-            return false
-        case let (.some(j1), .some(j2)):
-            // JSONObjectOrArray conforms to Equatable, so we can use == directly
-            return j1 == j2
-        }
+        json1 == json2
     }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8114e17 and b59a67f.

📒 Files selected for processing (7)
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
  • Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift
  • Tests/AblyLiveObjectsTests/ObjectDiffHelpersTests.swift
  • Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/swift.mdc)

**/*.swift: Specify an explicit access control level (SwiftLint explicit_acl) for all declarations in Swift code (tests are exempt)
When extending a type, put the access level on the extension declaration rather than on each member (tests are exempt)
Prefer implicit .init(...) when the type can be inferred in initializer expressions
Prefer enum case shorthand (.caseName) when the type can be inferred
For JSONValue or WireValue, prefer using literal syntax via ExpressibleBy*Literal where possible
Prefer Swift raw string literals for JSON strings instead of escaping double quotes
When an array literal begins with an initializer expression, place the initializer on the line after the opening bracket

Files:

  • Tests/AblyLiveObjectsTests/ObjectDiffHelpersTests.swift
  • Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
Tests/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

Tests/**/*.swift: Use the Swift Testing framework (import Testing), not XCTest, in test files
Do not use fatalError for expectation failures; prefer Swift Testing’s #require
Only add labels to test cases or suites when the label differs from the suite struct or test method name
Tag tests per CONTRIBUTING.md’s "Attributing tests to a spec point" with exact comment format; distinguish @spec vs @specPartial; do not repeat @spec for the same spec point
Add comments in tests to clarify when certain test data is irrelevant to the scenario
In tests, import Ably using import Ably
In tests, import AblyLiveObjects using @testable import AblyLiveObjects
In tests, import _AblyPluginSupportPrivate using import _AblyPluginSupportPrivate (do not use internal import)
When passing a logger to internal components in tests, use TestLogger()
When unwrapping optionals in tests, prefer #require over guard let

Files:

  • Tests/AblyLiveObjectsTests/ObjectDiffHelpersTests.swift
  • Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift
  • Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift
Sources/AblyLiveObjects/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/swift.mdc)

In AblyLiveObjects library (non-test) code, import modules as: Ably with import Ably, and _AblyPluginSupportPrivate with internal import _AblyPluginSupportPrivate

Files:

  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
Sources/**/*.swift

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

For testsOnly_ property declarations, do not add generic explanatory comments (their meaning is understood)

Files:

  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
🧬 Code graph analysis (6)
Tests/AblyLiveObjectsTests/ObjectDiffHelpersTests.swift (2)
Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (2)
  • calculateCounterDiff (11-17)
  • calculateMapDiff (25-62)
Tests/AblyLiveObjectsTests/Helpers/TestFactories.swift (1)
  • internalMapEntry (408-418)
Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift (4)
Tests/AblyLiveObjectsTests/Helpers/Subscriber.swift (1)
  • getInvocations (23-33)
Sources/AblyLiveObjects/Utility/ExtendedJSONValue.swift (1)
  • map (71-92)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift (1)
  • entries (143-150)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (1)
  • value (100-104)
Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift (1)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (3)
  • createZeroValued (66-81)
  • nosync_replaceData (203-216)
  • value (100-104)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (1)
Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (1)
  • calculateCounterDiff (11-17)
Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (2)
Sources/AblyLiveObjects/Internal/InternalLiveObject.swift (1)
  • tombstone (17-41)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift (1)
  • keys (152-155)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift (2)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (1)
  • mergeInitialValue (354-371)
Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (1)
  • calculateMapDiff (25-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Example app, tvOS (Xcode 16.4)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 16.4)
  • GitHub Check: Xcode, tvOS (Xcode 16.4)
  • GitHub Check: Xcode, macOS (Xcode 16.4)
  • GitHub Check: Xcode, iOS (Xcode 16.4)
  • GitHub Check: Example app, macOS (Xcode 16.4)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 16.4)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 16.4)
  • GitHub Check: SPM (Xcode 16.4)
  • GitHub Check: Example app, iOS (Xcode 16.4)
  • GitHub Check: SPM, release configuration (Xcode 16.4)
  • GitHub Check: Generate code coverage
🔇 Additional comments (11)
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift (1)

334-351: Diff-based update path looks correct.

Storing previousData before mutation and returning the calculated diff ensures a single full delta for replaceData.

Tests/AblyLiveObjectsTests/InternalDefaultLiveMapTests.swift (1)

296-486: DiffCalculationTests provide solid RTLM6h coverage.

The scenarios exercise expected diffs for createOp merges, tombstones, and unchanged entries.

Tests/AblyLiveObjectsTests/ObjectsPoolTests.swift (3)

121-127: Map diff emission assertions align with RTLM6h.

These checks correctly validate the full-diff update for existing maps.


163-166: Counter diff emission expectation matches RTLC6h.

The assertion correctly validates the single diff update.


374-388: Complex sync scenario diff assertions look correct.

The emitted updates now reflect the full before/after deltas as intended.

Tests/AblyLiveObjectsTests/InternalDefaultLiveCounterTests.swift (1)

153-233: RTLC6h diff tests are well covered.

Positive, negative, and createOp-merged deltas are exercised cleanly.

Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift (1)

471-512: LGTM! Clean implementation of RTLM6g/RTLM6h spec changes.

The implementation correctly:

  1. Captures previousData before any data mutations (RTLM6g)
  2. Discards the LiveMapUpdate from mergeInitialValue when createOp is present (RTLM6d)
  3. Returns a unified diff computed via ObjectDiffHelpers.calculateMapDiff (RTLM6h)

This ensures a single event representing the full before-and-after state change during sync, matching the behavioral change described in the PR objectives.

Sources/AblyLiveObjects/Internal/ObjectDiffHelpers.swift (4)

11-17: LGTM!

Simple and correct implementation of RTLC14b - the diff amount is computed as newData - previousData.


66-72: LGTM!

Correctly compares only the data field for diff purposes, ignoring metadata fields (timeserial, tombstonedAt) as documented.


75-93: LGTM!

Comprehensive nil handling and field-by-field comparison. The comparison covers all ObjectData fields appropriately.


25-62: The current implementation is correct. The tests explicitly expect calculateMapDiff to return .update(DefaultLiveMapUpdate(update: [:])) even when there are no changes—not .noop. The test assertion #expect(update.update?.update.isEmpty == true) confirms this: it expects the .update case with an empty dictionary, which would fail if .noop were returned instead. This behavior is intentional and consistent with calculateCounterDiff, which also always returns .update regardless of whether the diff amount is zero.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@lawrence-forooghian lawrence-forooghian force-pushed the AIT-255-fix-replace-data-events branch 2 times, most recently from f1d5b97 to 8fc5d09 Compare January 16, 2026 11:39
@github-actions github-actions bot temporarily deployed to staging/pull/111/AblyLiveObjects January 16, 2026 11:41 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/111/AblyLiveObjects January 16, 2026 12:05 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the AIT-255-fix-replace-data-events branch from 175e957 to b1b0427 Compare January 16, 2026 12:59
@github-actions github-actions bot temporarily deployed to staging/pull/111/AblyLiveObjects January 16, 2026 13:00 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/111/AblyLiveObjects January 16, 2026 13:11 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the AIT-255-fix-replace-data-events branch from 2cea0d1 to 31bf8db Compare January 16, 2026 13:15
@lawrence-forooghian lawrence-forooghian changed the title Implement spec changes from 4eda10d: fix events emitted upon sync [AIT-255] Fix events emitted upon sync Jan 16, 2026
@github-actions github-actions bot temporarily deployed to staging/pull/111/AblyLiveObjects January 16, 2026 13:17 Inactive
@lawrence-forooghian lawrence-forooghian force-pushed the AIT-255-fix-replace-data-events branch from 31bf8db to 97e805d Compare January 16, 2026 13:17
@github-actions github-actions bot temporarily deployed to staging/pull/111/AblyLiveObjects January 16, 2026 13:19 Inactive
@lawrence-forooghian lawrence-forooghian marked this pull request as ready for review January 16, 2026 13:20
This implements the spec changes from [1] at 3f86319. That is, the
update events emitted by a sync are now calculated from the before/after
diff of the object state, as opposed to just being calculated from the
createOp.

Written by Claude based on the spec.

[1] ably/specification#414
Copy link
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@lawrence-forooghian lawrence-forooghian merged commit d1047bc into main Jan 19, 2026
18 checks passed
@lawrence-forooghian lawrence-forooghian deleted the AIT-255-fix-replace-data-events branch January 19, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants