Skip to content

[AIT-1019] feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc#1224

Open
sacOO7 wants to merge 12 commits into
feature/path-based-liveobjects-implementationfrom
feature/liveobjects-remaining-kotlin-implementation
Open

[AIT-1019] feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc#1224
sacOO7 wants to merge 12 commits into
feature/path-based-liveobjects-implementationfrom
feature/liveobjects-remaining-kotlin-implementation

Conversation

@sacOO7

@sacOO7 sacOO7 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Completes the path-based LiveObjects public API in the liveobjects Kotlin module. The sync/CRDT engine and serialization layers landed earlier on the base branch; this PR fills in everything between them and the public interfaces: value resolution, reads, writes, identity-bound instances, parent references, and subscription event bubbling. ably-js liveobjects plugin is the source of truth throughout, with spec points (objects-features.md, plus the unmerged typed-SDK points from ably/specification#491) anchored inline as code comments.

What was missing before

The module had a complete sync engine but the path/instance layers were skeletons: ResolvedValue wrapped the wrong types (public blueprints instead of the internal CRDT objects), every read/write/subscribe method was a TODO(), and update events carried no source message, no tombstone signal and no path information.

Changes

Internal graph bridge

  • ResolvedValue is now a sealed interface over the internal graph: MapRef(InternalLiveMap) / CounterRef(InternalLiveCounter) / Leaf(WireObjectData), resolved per call via LiveMapEntry.getResolvedValue() (tombstone- and dangling-ref-aware, RTLM5d2).
  • PathSegments handles dot-delimited path ↔ segment conversion with escape-aware parsing (RTPO4, RTPO6). Deviation from ably-js: join escapes backslashes as well as dots, because here the joined string is the storage and gets re-parsed on every resolution (ably-js stores segment arrays and only renders the escaped string). Without this, a key ending in \ collides with the escaped-dot separator and breaks lookups/subscriptions.

Typed update model

  • ObjectUpdate is a sealed class (NoOp / MapUpdate / CounterUpdate) carrying the source WireObjectMessage and a tombstone flag (RTLO4b4). Both managers thread the message through every apply/merge/diff path (RTLM6h, RTLC6h, RTLM23c).

Parent references & event bubbling

  • Every object tracks its parents (parentReferences, keyed by parent objectId per RTLO3f), maintained at all mutation points and rebuilt wholesale after sync (RTO5c10, ordered before the RTO5c7 notifications).
  • getFullPaths() computes all simple paths from root with cycle safety (RTLO4f); notifyUpdated fans out to instance listeners, then bubbles to path subscribers via PathObjectSubscriptionRegister (RTO24: prefix + depth coverage, most-preferred candidate path, at-most-once per subscription, per-listener error isolation), then tears down instance listeners on tombstone (RTLO4b4c3).
  • Sync-time messages are filtered from public events (operation == null), and surfaced messages are converted once via toPublicMessage (PAOM3).

Reads, writes and instances

  • RealtimeObject#get() returns the root LiveMapPathObject after attach + sync (RTO23), bridged from coroutines with sequentialScope.future {}.
  • All path reads re-resolve on every call (RTPO3) — typed value()s, map iteration, compactJson() with cycle markers (RTPO13/14).
  • Writes evaluate value-type blueprints recursively (LiveMapValueType/LiveCounterValueType, RTLMV4/RTLCV4): nested create messages first, own MAP_CREATE last, published together with the MAP_SET in a single protocol message (RTLM20h1).
  • All 8 Instance types are value-bound (RTINS2a): live map/counter instances delegate to the internal objects; primitives capture the extracted value (binary decoded once, defensively copied out).

Fix found during cross-validation against ably-js history

  • handleStateChange(ATTACHED) now clears buffered operations (RTO4d) and starts a new sync unconditionally (RTO4c). The previous conditional was a port of pre-e280bff1 ably-js code that upstream has since removed.

Testing & validation

  • New PathSegmentsTest covers the stored-vs-supplied path invariants and the backslash round-trip regression.
  • runUnitTests, runLiveObjectsUnitTests, checkWithCodenarc, checkstyleMain/Test all green; zero TODOs remain in the module.
  • Every spec anchor in the module (683 unique points) was verified against objects-features.md / spec PR README: add a note about the push example/test app #491; behavioral markers (boundary comparisons, error codes, skip predicates, message ordering) were cross-checked against ably-js.
  • Proguard: no new reflective entry points; blueprint constructor signatures ((Number) / (Map)) unchanged, existing keep rules remain valid.

Notes for reviewers

  • UTS LiveObjects suites are re-enabled in uts/build.gradle.kts but failures there are pending triage in a follow-up — check the spec anchor before assuming an engine bug.
  • RTTS/typed-SDK spec points reference the unmerged Add typed-SDK LiveObjects API spec section (RTTS1-RTTS10) specification#491; anchors should be re-verified once it merges.
  • Known deliberate deviations from ably-js (each documented at the code site): empty map keys are rejected with 40003 (ably-js accepts them), instance-layer entries() skips dangling references (RTLM11d3a) while keys() does not, and the backslash escaping described above.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added path-based LiveObjects support, including updated object resolution, path navigation, and sync status visibility in the sample apps.
    • Introduced a new syncing indicator in the UI for LiveObjects screens.
  • Bug Fixes

    • Improved LiveObjects updates so changes, resets, and nested objects stay in sync more reliably.
    • Fixed timeout handling in proxy integration tests to use real elapsed time where needed.
  • Tests

    • Added coverage for async behavior, path handling, and updated LiveObjects sync scenarios.

…ubscriptions and event bubbling

Completes the path-addressed LiveObjects API on top of the sync engine:

- Bridge ResolvedValue (MapRef/CounterRef/Leaf) to the internal graph
  objects so path resolution returns live references
- Replace the untyped ObjectUpdate with a sealed model
  (MapUpdate/CounterUpdate/NoOp) carrying the source objectMessage and
  tombstone flag (RTLO4b4, RTLM18, RTLC11)
- Track parent references on every graph object and derive
  getFullPaths() for event bubbling (RTLO3f, RTLO4f-RTLO4h)
- Implement RTPO3 path resolution and the read APIs: RealtimeObject#get
  (RTO23), PathObject value/exists/instance/compactJson (RTPO8,
  RTPO13/RTPO14)
- Add creation value-type evaluation and the path write APIs
- Add value-bound typed Instances and PathObject#instance (RTINS5,
  RTTS6/RTTS7)
- Add path/instance subscriptions with depth windows and event bubbling
  via PathObjectSubscriptionRegister (RTO24, RTPO19)
- RTO4c fix: always start a new sync sequence on ATTACHED regardless of
  the HAS_OBJECTS flag, matching current ably-js
- Make stored path strings round-trip-safe by escaping backslashes as
  well as dots in PathSegments#join
- Remove dead createMap/createCounter code paths and re-enable the UTS
  liveobjects test suites
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR migrates the LiveObjects module to a path-based object model. It implements value resolution for typed instances and path objects, adds parent-reference tracking, threads WireObjectMessage through update pipelines, introduces a path subscription registry and PathSegments escaping utility, migrates example apps to the new API, and updates UTS test infrastructure and proxy documentation references.

Changes

LiveObjects Core Migration

Layer / File(s) Summary
Async future helpers and root retrieval
liveobjects/.../DefaultRealtimeObject.kt, liveobjects/.../DefaultLiveObjectsPlugin.kt, liveobjects/.../Helpers.kt, tests
Adds asyncFuture/asyncVoidFuture, changes get()/getRootAsync to return path objects, simplifies attach state handling and ensureAttached, wires pathObjectSubscriptionRegister.
PathSegments encoding and subscription register
liveobjects/.../path/PathSegments.kt, liveobjects/.../path/PathObjectSubscriptionRegister.kt, tests
Adds dot-path escape/join/parse utilities and a per-object registry dispatching path change events by prefix/depth matching.
ResolvedValue, ObjectUpdate contracts and typed instance factory
liveobjects/.../value/ResolvedValue.kt, value/ObjectUpdate.kt, instance/DefaultInstance.kt, message/WireObjectMessage.kt, instance/DefaultInstanceSubscriptionEvent.kt
Introduces ObjectUpdate sealed hierarchy, updates ResolvedValue to wrap internal live types, adds toInstance() factory and compact JSON leaf conversion.
Default*Instance implementations
instance/types/*.kt
Replaces TODO stubs with concrete access/write-validated read/write logic for binary, boolean, JSON, number, string, counter, and map instances.
DefaultPathObject and typed path object implementations
path/DefaultPathObject.kt, path/types/*.kt
Implements resolveValueAtCurrentPath, typed value()/increment/decrement/set/remove/subscribe across all path object types.
BaseRealtimeLiveObject parent references, tombstone, notifyUpdated
value/BaseRealtimeLiveObject.kt
Adds parent-reference tracking/getFullPaths, message-carrying tombstone, and centralized notifyUpdated dispatching instance/path subscriptions.
ObjectsPool.all() and rebuildAllParentReferences
ObjectsPool.kt, ObjectsManager.kt
Rebuilds parent references from pooled maps during sync before dispatching updates.
LiveCounter creation, internal counter, coordinator, manager
value/livecounter/*.kt
Builds COUNTER_CREATE messages, switches to InstanceListener subscriptions, threads WireObjectMessage through apply/merge logic.
LiveMap creation, internal map, coordinator, manager, entry
value/livemap/*.kt, Utils.kt
Builds nested MAP_CREATE messages, adds compactJson/nested-create set, InstanceListener subscriptions, and message-stamped parent-reference-aware map updates.

Estimated code review effort: 5 (Critical) | ~120 minutes

Example App Migration to Path Objects

Layer / File(s) Summary
Utils.kt path-object helpers
examples/.../Utils.kt
Migrates counter/map creation and observation helpers to LiveMapPathObject/LiveCounterPathObject, adds observeObjectsSyncState.
ObjectsSyncStatusRow component
examples/.../ObjectsSyncStatus.kt
New composable showing sync spinner/checkmark based on observed sync state.
ColorVotingScreen and TaskManagementScreen wiring
examples/.../ColorVotingScreen.kt, TaskManagementScreen.kt
Screens call new path-object APIs directly, removing coroutine wrappers, and add sync status rows.

Estimated code review effort: 3 (Moderate) | ~25 minutes

UTS Test Infra and Proxy Doc Updates

Layer / File(s) Summary
Test filters, real timeout helper, proxy session fix
uts/build.gradle.kts, uts/.../infra/Utils.kt, uts/.../ProxySession.kt
Removes LiveObjects test exclusions, adds withRealTimeout wall-clock helper, stringifies proxy match action values.
ObjectsFaultsTest real-timeout and cleanup updates
uts/.../ObjectsFaultsTest.kt
Switches to withRealTimeout, adds closeClient helper, reworks RTO20e fault-injection assertions.
Proxy documentation path updates
.claude/skills/uts-to-kotlin/SKILL.md, uts/.../AuthReauthTest.kt
Updates proxy doc references to uts/docs/proxy.md.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • ably/ably-java#1216: Example code's observeObjectsSyncState and sync UI depend on the same ChannelBase.object/ObjectStateEvent API introduced there.
  • ably/ably-java#1225: Overlaps directly with the same example migration to LiveMapPathObject/LiveCounterPathObject in Utils.kt, ColorVotingScreen.kt, and TaskManagementScreen.kt.
  • ably/ably-java#1221: Shares the same SKILL.md proxy integration-test guidance and generated KDoc template updates.

Suggested reviewers: ttypic

Poem

A rabbit hops through paths anew,
No more coroutines, just set and get() too.
Counters increment, maps resolve with grace,
Parent refs rebuilt, each one in its place.
🐇 Sync status glows green — the burrow's complete!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main theme: completing the path-based LiveObjects API with parent references and event bubbling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/liveobjects-remaining-kotlin-implementation

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.

@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 3, 2026 09:54 Inactive
@sacOO7 sacOO7 changed the title feat(liveobjects): Implement remaining PathObject implementation from new API i.e. bubbling events and parentReferenes feat(liveobjects): Implement remaining Path based API i.e. parentReferenes and bubbling events Jul 3, 2026
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 3, 2026 09:56 Inactive
@sacOO7 sacOO7 changed the title feat(liveobjects): Implement remaining Path based API i.e. parentReferenes and bubbling events feat(liveobjects): implement remaining path-based API — parent references, event bubbling, reads/writes and instances Jul 3, 2026
The example app targeted the removed io.ably.lib.objects callback API
(getRootAsync, createCounterAsync, LiveMapValue.asLiveCounter). Rewrite
its bridge layer (Utils.kt) against the public path-based interfaces in
io.ably.lib.liveobjects, entered via channel.object.get() which returns
the root LiveMapPathObject once objects are SYNCED.

PathObjects reference a location and re-resolve on every call, which
simplifies the app considerably:

- get-or-create is a single atomic publish: root.set(key,
  LiveMapValue.of(LiveCounter.create())) replaces the old free-floating
  create + link two-step
- the root-map subscription that watched for "Reset all" replacing a
  counter (and rebound to the new instance) is gone entirely - a path
  subscription follows the replacement automatically
- mutations call the CompletableFuture API directly, fire-and-forget:
  the path subscription refreshes Compose state on ack, so the
  *Coroutine wrapper extensions and their scope.launch call sites are
  removed

UI, test tags, channel setup and the instrumentation tests are
unchanged. Verified with :examples:assembleDebug,
:examples:compileDebugAndroidTestKotlin and manually on an emulator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sacOO7 sacOO7 changed the title feat(liveobjects): implement remaining path-based API — parent references, event bubbling, reads/writes and instances feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc Jul 3, 2026
sacOO7 and others added 2 commits July 3, 2026 17:05
Review fixes for #1225:

- getOrCreateCounter/getOrCreateMap now check the resolved type
  (getType() != LIVE_COUNTER/LIVE_MAP) instead of exists(), so a
  wrong-typed value at the key is replaced rather than returned as a
  view whose reads yield null and writes fail; the LWW resolution of
  the inherent check-then-set race is documented on both helpers
- observeCounter/observeMap effects keyed on (root, key) so a changed
  key rebinds
- Red vote card enable state now tracks redCounter (was greenCounter,
  a pre-existing copy/paste bug)
- Add Task is disabled until the tasks map is bound, so input is never
  cleared without the task being enqueued
- new ObjectsSyncStatusRow on both screens surfaces objects sync
  progress ("Objects syncing..." -> "Objects synced") via the
  channel.object.on(SYNCING/SYNCED) state API, with the initial state
  derived from root readiness since state events fire on transitions
  only

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rrected spec

Proxy session creation failed with HTTP 400 for any rule using a frame-action
match: the uts-proxy's MatchConfig.match.action is a Go string (action name or
numeric string), but the rule builders serialised messageAction as a JSON
number. wsFrameToClientRule/wsFrameToServerRule now send it as a string.

Add withRealTimeout to the shared infra: inside runTest, a bare withTimeout
measures virtual (kotlinx.coroutines.test) time, which fast-forwards while the
test idles - a timeout wrapping a real network await fires instantly. All
real-network waits in integration tests must run on a real-thread dispatcher,
matching the existing awaitState/awaitChannelState/pollUntil helpers.

Rework ObjectsFaultsTest against the corrected source spec
(objects/integration/proxy/objects_faults.md, ably/specification#501):

- bound every object.get() with withRealTimeout instead of virtual-time
  withTimeout (all five tests previously failed instantly)
- RTO7/RTO8: fetch the channel handle once and reuse it - re-calling
  Channels.get() with mode options on an attached channel throws
- RTO20e: exercise the RTO20e1 sequence (mutation in flight while SYNCING,
  then the channel enters FAILED -> 92008/400 with the injected 90000 channel
  error as cause), mirroring ably-js's sync-wait rejection test; the spec's
  original mutate-after-FAILED steps hit the RTO26b precondition (90001)
  instead and were fixed at source
- implement the spec's Common Cleanup: close clients and await CLOSED (with
  the connection-state guard) before tearing down the proxy session

Update proxy spec path references (ObjectsFaultsTest/AuthReauthTest KDocs,
uts-to-kotlin skill) to uts/docs/proxy.md per the relocation in
ably/specification#501; uts/README.md's GitHub URLs are left until that PR
reaches main. See PROXY_UTS_SPEC_FIX.md for the full evidence chain.

All five ObjectsFaultsTest tests pass against the sandbox through uts-proxy.
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 4, 2026 17:38 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 4, 2026 17:39 Inactive
[AIT-1038] chore(examples): migrate LiveObjects example app to the path-based API
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 8, 2026 12:47 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 8, 2026 12:48 Inactive
…align ensureAttached to RTL33

Rebuild PathObjectSubscriptionRegister on ably-java's EventEmitter — the SDK's
thread-safe registration/dispatch primitive already used by the LiveMap/LiveCounter
change coordinators — replacing the hand-rolled ConcurrentHashMap + AtomicLong id
counter. PathEvent carries the broadcast payload, PathSubscription the per-subscription
state, and PathEventEmitter.apply resolves each listener's own covered path. Dispatch
behaviour (coverage, first-covered candidate, per-listener error isolation) is unchanged.

Update ensureAttached to the ensure-active-channel procedure (RTL33, specification
PR #472), matching the ably-js source:
- ATTACHED/SUSPENDED return without attaching (RTL33a)
- INITIALIZED/DETACHED/DETACHING/ATTACHING trigger an implicit attach and wait; the
  attach failure's ErrorInfo is propagated unchanged (RTL33b/b1)
- FAILED (and any other state) throws code 90001 / statusCode 400 via objectException (RTL33c)

Refresh HelpersTest to cover the full RTL33 state matrix.
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 8, 2026 18:20 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 8, 2026 18:21 Inactive
…LiveObject

Move the ObjectUpdate sealed hierarchy (NoOp/MapUpdate/CounterUpdate) and its
noOp extension into a dedicated value/ObjectUpdate.kt, leaving BaseRealtimeLiveObject
focused on the base class. ObjectType stays with the base class as the broad object
discriminator used across the package.

Relocate the LiveMap-specific MapChange enum from the generic value package into
value.livemap alongside the other map types; it is a per-key diff type owned by
LiveMap, and value already depends on value.livemap (BaseRealtimeLiveObject uses
InternalLiveMap), so this introduces no new package coupling.

The sealed subclasses remain in the value package, so exhaustive when-handling
(e.g. tombstone) still compiles. Pure code move, no behaviour change.

Also drop a stale @Suppress(RedundantNullableReturnType) on
DefaultLiveMapInstance.get, whose nullable return is genuine.
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 9, 2026 10:28 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 9, 2026 10:29 Inactive
…s type's file

The toCompactJsonElement() extension only reads WireObjectData's own fields and is
used across packages (path, livemap), so it belongs with the type it extends rather
than in the livemap feature file. Move it into message/WireObjectMessage.kt alongside
the other WireObjectData extensions (size, isInvalid); retarget the DefaultPathObject
import and drop the now-unused gson imports from InternalLiveMap. Pure move, no
behaviour change; message still has no dependency on value/path/livemap.
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 9, 2026 10:47 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 9, 2026 10:48 Inactive
…lution

resolveValueAtPath always received the PathObject's own path property at every
call site, so the parameter was redundant. Make it a no-arg method that resolves
the instance's stored path directly, and rename it to resolveValueAtCurrentPath
to reflect that it only ever resolves this node's current path.

This also removes a latent footgun: DefaultLiveMapPathObject.at(path) shadows the
path property, so a future resolveValueAtPath(path) call inside it would have
resolved the argument instead of the node's path. The no-arg form makes that
mistake impossible. Pure refactor, no behaviour change.
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 9, 2026 11:00 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 9, 2026 11:01 Inactive
…er overloads

The parameterless increment()/decrement() on both DefaultLiveCounterPathObject and
DefaultLiveCounterInstance duplicated the full body of their Number counterparts with
a hardcoded 1. Make them delegate to increment(1)/decrement(1) instead. All spec
checks live in the parameterized overload, and the default-of-1 spec points
(RTPO17a1/RTPO18a1, RTINS14a1/RTINS15a1) are preserved on the delegating methods.
Pure refactor, no behaviour change.
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 9, 2026 11:07 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 9, 2026 11:09 Inactive
…opagation

Rename the DefaultRealtimeObject async bridges for clarity: asyncApi -> asyncFuture
(generic, value-returning) and asyncVoidApi -> asyncVoidFuture (the Unit->Void adapter
required by the Java write APIs), updating all call sites. Behaviour is unchanged; the
thenApply { null } bridge stays because kotlinx's future builder cannot produce a
CompletableFuture<Void> (its type param is non-null, Void's only value is null).

Add DefaultRealtimeObjectAsyncTest asserting result and exception propagation for both
helpers, and - crucially - that a failed operation does not cancel the shared sequential
scope for subsequent operations, locking in the SupervisorJob failure-isolation guarantee.
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 9, 2026 11:35 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 9, 2026 11:36 Inactive
Base automatically changed from feature/liveobjects-kotlin-implementation to feature/path-based-liveobjects-implementation July 9, 2026 11:49
getOrPut on a ConcurrentHashMap may invoke its factory on a losing thread during a
concurrent first-time getInstance, constructing a duplicate DefaultRealtimeObject whose
init-started coroutine would then leak. computeIfAbsent runs the factory at most once per
channel, so exactly one instance is created. It needs Java 8 / Android API 24, already
required by this module's CompletableFuture-based public API.
@sacOO7 sacOO7 changed the title feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc [AIT-1019] feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc Jul 9, 2026
@sacOO7 sacOO7 marked this pull request as ready for review July 9, 2026 12:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultJsonObjectInstance.kt`:
- Around line 14-31: `DefaultJsonObjectInstance.value()` is exposing the
internal mutable Gson `JsonObject` directly, which can let callers mutate the
instance state; update `DefaultJsonObjectInstance` to return a defensive copy
(for example via `deepCopy()`) the same way this is handled in
`DefaultJsonArrayInstance`, unless JSON values are intentionally exempted by
spec. Keep `compactJson()` and the access checks in
`throwIfInvalidAccessApiConfiguration()` unchanged, and make the fix only in the
`value()` path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09797bbc-a124-42d3-a2da-ffbf16798f66

📥 Commits

Reviewing files that changed from the base of the PR and between 2ea697c and 0f0075a.

📒 Files selected for processing (53)
  • .claude/skills/uts-to-kotlin/SKILL.md
  • examples/src/main/kotlin/com/ably/example/Utils.kt
  • examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt
  • examples/src/main/kotlin/com/ably/example/screen/ObjectsSyncStatus.kt
  • examples/src/main/kotlin/com/ably/example/screen/TaskManagementScreen.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/DefaultLiveObjectsPlugin.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/DefaultRealtimeObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/Helpers.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/ObjectsManager.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/ObjectsPool.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/Utils.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/DefaultInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/DefaultInstanceSubscriptionEvent.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultBinaryInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultBooleanInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultJsonArrayInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultJsonObjectInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultLiveCounterInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultLiveMapInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultNumberInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultStringInstance.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/message/WireObjectMessage.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/DefaultPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/PathObjectSubscriptionRegister.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/PathSegments.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultBinaryPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultBooleanPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultJsonArrayPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultJsonObjectPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultLiveCounterPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultLiveMapPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultNumberPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultStringPathObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/BaseRealtimeLiveObject.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/ObjectUpdate.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/ResolvedValue.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livecounter/DefaultLiveCounter.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livecounter/InternalLiveCounter.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livecounter/LiveCounterChangeCoordinator.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livecounter/LiveCounterManager.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/DefaultLiveMap.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/InternalLiveMap.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/LiveMapChangeCoordinator.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/LiveMapEntry.kt
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/LiveMapManager.kt
  • liveobjects/src/test/kotlin/io/ably/lib/liveobjects/unit/DefaultRealtimeObjectAsyncTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/liveobjects/unit/HelpersTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/liveobjects/unit/PathSegmentsTest.kt
  • uts/build.gradle.kts
  • uts/src/test/kotlin/io/ably/lib/uts/infra/Utils.kt
  • uts/src/test/kotlin/io/ably/lib/uts/infra/integration/proxy/ProxySession.kt
  • uts/src/test/kotlin/io/ably/lib/uts/integration/proxy/liveobjects/ObjectsFaultsTest.kt
  • uts/src/test/kotlin/io/ably/lib/uts/integration/proxy/realtime/AuthReauthTest.kt
💤 Files with no reviewable changes (2)
  • liveobjects/src/main/kotlin/io/ably/lib/liveobjects/Utils.kt
  • uts/build.gradle.kts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Completes the Kotlin liveobjects module’s public, path-based LiveObjects API by wiring the existing sync/CRDT engine up through path resolution, typed reads/writes, instance binding, parent references, and path subscription event bubbling; also updates UTS proxy tests and example apps to exercise the new behavior.

Changes:

  • Implemented path resolution (PathSegments), typed PathObject operations, compact JSON snapshots, and value-type evaluation for nested object creation.
  • Added typed update model (ObjectUpdate), parent-reference tracking/rebuild, and event dispatch (instance listeners + bubbling to path subscriptions).
  • Re-enabled/extended UTS LiveObjects suites, improved proxy integration test timeout behavior, and migrated example apps to the new API (including sync-status UI).

Reviewed changes

Copilot reviewed 53 out of 53 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
uts/src/test/kotlin/io/ably/lib/uts/integration/proxy/realtime/AuthReauthTest.kt Updates proxy KDoc to point at the new proxy docs path.
uts/src/test/kotlin/io/ably/lib/uts/integration/proxy/liveobjects/ObjectsFaultsTest.kt Improves proxy LiveObjects fault tests (real-time timeouts, cleanup, new FAILED-during-sync-wait scenario).
uts/src/test/kotlin/io/ably/lib/uts/infra/Utils.kt Adds withRealTimeout helper for runTest + real network waits.
uts/src/test/kotlin/io/ably/lib/uts/infra/integration/proxy/ProxySession.kt Ensures proxy match config action is encoded as a string.
uts/build.gradle.kts Re-enables LiveObjects UTS tests by removing exclusions.
liveobjects/src/test/kotlin/io/ably/lib/liveobjects/unit/PathSegmentsTest.kt Adds unit tests for stored vs user-supplied path parsing/joining (incl. backslash cases).
liveobjects/src/test/kotlin/io/ably/lib/liveobjects/unit/HelpersTest.kt Updates/expands ensureAttached tests for additional channel states and error propagation.
liveobjects/src/test/kotlin/io/ably/lib/liveobjects/unit/DefaultRealtimeObjectAsyncTest.kt Adds tests for sequential-scope future bridging and exception propagation behavior.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/ResolvedValue.kt Switches ResolvedValue to reference internal CRDT objects (map/counter) or leaf wire data.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/ObjectUpdate.kt Introduces a typed update model carrying source message + tombstone signal.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/LiveMapManager.kt Threads source messages through apply paths; maintains parent refs on mutation; produces typed diffs.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/LiveMapEntry.kt Resolves entries to internal refs or wire leaves; filters invalid/tombstoned refs.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/LiveMapChangeCoordinator.kt Adds MapChange enum and proper NoOp update marker; adds listener teardown helper.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/InternalLiveMap.kt Implements internal map read APIs, compact JSON snapshots, write evaluation (nested creates), and instance subscription dispatch.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livemap/DefaultLiveMap.kt Implements value-type evaluation to [nested creates..., MAP_CREATE] plus primitive-to-wire conversion helper.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livecounter/LiveCounterManager.kt Threads source messages; produces typed counter updates and no-op behavior for diffs.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livecounter/LiveCounterChangeCoordinator.kt Switches no-op representation to ObjectUpdate.NoOp and adds listener teardown helper.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livecounter/InternalLiveCounter.kt Implements decrement path, subscription dispatch, and ties into new update model.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/livecounter/DefaultLiveCounter.kt Implements value-type evaluation to COUNTER_CREATE with validation and objectId derivation.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/value/BaseRealtimeLiveObject.kt Adds parent reference tracking, getFullPaths, tombstone semantics, and update dispatch (instance + bubbling path events).
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/Utils.kt Removes invalidInputError from this file (now provided elsewhere).
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultStringPathObject.kt Implements typed leaf read for strings.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultNumberPathObject.kt Implements typed leaf read for numbers.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultLiveMapPathObject.kt Implements map navigation and read/write operations via fresh path resolution.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultLiveCounterPathObject.kt Implements counter read/write operations via fresh path resolution.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultJsonObjectPathObject.kt Implements typed leaf read for JSON objects.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultJsonArrayPathObject.kt Implements typed leaf read for JSON arrays.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultBooleanPathObject.kt Implements typed leaf read for booleans.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/types/DefaultBinaryPathObject.kt Implements typed leaf read for binary (base64 decode).
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/PathSegments.kt Adds stored-path vs user-path parsing/joining utilities with escape handling.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/PathObjectSubscriptionRegister.kt Adds path subscription registry and coverage-based bubbling dispatch.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/path/DefaultPathObject.kt Implements core path resolution, compact JSON, existence, and subscription registration.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/ObjectsPool.kt Exposes all() to support parent-reference rebuild.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/ObjectsManager.kt Rebuilds parent references after sync before dispatching updates.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/message/WireObjectMessage.kt Adds WireObjectData.toCompactJsonElement() for compact snapshots.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultStringInstance.kt Implements value-bound primitive instance behavior.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultNumberInstance.kt Implements value-bound primitive instance behavior.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultLiveMapInstance.kt Implements identity-bound map instance operations and subscriptions.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultLiveCounterInstance.kt Implements identity-bound counter instance operations and subscriptions.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultJsonObjectInstance.kt Implements value-bound JSON object instance behavior.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultJsonArrayInstance.kt Implements value-bound JSON array instance behavior.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultBooleanInstance.kt Implements value-bound boolean instance behavior.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/types/DefaultBinaryInstance.kt Implements value-bound binary instance behavior with defensive copying.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/DefaultInstanceSubscriptionEvent.kt Makes subscription event compatible with both map/counter change emitters.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/instance/DefaultInstance.kt Adds ResolvedValue.toInstance() to construct typed instances (including primitives).
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/Helpers.kt Simplifies/updates ensureAttached logic across channel states and error semantics.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/DefaultRealtimeObject.kt Implements get() via sequential scope futures; adds path subscription registry; adjusts attach/sync transitions.
liveobjects/src/main/kotlin/io/ably/lib/liveobjects/DefaultLiveObjectsPlugin.kt Uses computeIfAbsent for channel object caching.
examples/src/main/kotlin/com/ably/example/Utils.kt Migrates example bridge to new PathObject API and adds sync-state observation helpers.
examples/src/main/kotlin/com/ably/example/screen/TaskManagementScreen.kt Migrates task management UI to new LiveObjects API; adds sync status row.
examples/src/main/kotlin/com/ably/example/screen/ObjectsSyncStatus.kt Adds UI component for “syncing/synced” indication.
examples/src/main/kotlin/com/ably/example/screen/ColorVotingScreen.kt Migrates color voting UI to new LiveObjects API; adds sync status row.
.claude/skills/uts-to-kotlin/SKILL.md Updates proxy doc path references in the skill documentation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sacOO7 sacOO7 requested a review from ttypic July 10, 2026 10:37
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.

2 participants