Fix cross-tab sync for collection-root subscribers - #815
Conversation
Collection-root callbacks are now automatic for collection keys, so the option no longer exists on ConnectOptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f4587d6582
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
Eyyy i'm traveling a bit today so won't be able to get to this review till tomorrow, @mountiny looks like you're assigned to the issue Expensify/App#94839, do you want to review today? |
|
@mountiny Could you assign me as a reviewer since I was assigned to Expensify/App#94839? |
4f91425 to
f4587d6
Compare
|
Happy to review, @situchan can you work on the checklist? |
yes |
| function raiseStorageSyncManyKeysEvent(onyxKeys: StorageKeyList) { | ||
| if (onyxKeys.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| let chunk: StorageKeyList = []; | ||
| let chunkLength = 2; // accounts for the surrounding `[]` | ||
| for (const onyxKey of onyxKeys) { | ||
| raiseStorageSyncEvent(onyxKey); | ||
| const keyLength = onyxKey.length + 3; // quotes + comma separator | ||
| if (chunk.length > 0 && chunkLength + keyLength > MAX_SYNC_PAYLOAD_LENGTH) { | ||
| emitSyncEvent(JSON.stringify(chunk)); | ||
| chunk = []; | ||
| chunkLength = 2; | ||
| } | ||
| chunk.push(onyxKey); | ||
| chunkLength += keyLength; | ||
| } | ||
| if (chunk.length > 0) { | ||
| emitSyncEvent(JSON.stringify(chunk)); |
There was a problem hiding this comment.
Chunking can split a collection across two events.
Chunks are cut purely by payload size, so if one collection's members land in two different chunks, the receiver sees two storage events → two keysChanged calls → two collection notifications instead of one.
This partially undoes the O(N²)→O(1) win exactly in the large-batch case chunking exists for.
Grouping by collection before chunking would avoid it.
Not blocker though
There was a problem hiding this comment.
I think its an edge case , maybe not worth to fix for now.
what do you think @fabioh8010 ?
| Onyx.disconnect(connection2); | ||
| }); | ||
|
|
||
| it('should notify collection-root and collection member subscribers when a collection member syncs from another instance', async () => { |
There was a problem hiding this comment.
A couple of test gaps worth filling.
The two new tests are good, but I'd add:
- A deletion test (member removed in another tab → subscribers get
undefined). Not only verifying this manually. - A chunking test around the size limit — that logic is currently untested.
- Optionally, a test for the legacy raw-key path in
parseSyncOnyxStorageEventValue.
There was a problem hiding this comment.
I added few tests now.
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppAndroid: mWeb ChromeiOS: HybridAppiOS: mWeb SafariMacOS: Chrome / Safaritest.mov |
…y parsing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Details
Supersedes #803 (same changes with latest
mainmerged in — re-opened from a branch I can maintain). Originally coming from here, authored by @fabioh8010.Cross-tab (multi-instance) storage sync events were not delivered to collection-root subscribers — e.g. the LHN subscribing to the whole
report_collection — so actions like pin/unpin, drafting a message, or marking a report read/unread performed in one tab did not sync to other open tabs.This PR:
Note: this fix depends on the OnyxDerived recompute coalescing that landed in E/App via Expensify/App#96785 (now on staging). Without coalescing, the extra collection-root notifications caused a derived-value recompute storm. Re-verified against latest E/App
main(which includes #96785) — see Manual Tests.Related Issues
Expensify/App#94839
Linked E/App PR
Expensify/App#97218
Automated Tests
Added tests in
tests/unit/onyxTest.tsverifying that both collection-root subscribers (waitForCollectionCallback: true) and collection member subscribers are notified when a collection member syncs from another instance. Existing multi-instance sync tests were updated to the new batched event shape.Manual Tests
Tested on web against latest E/App
main(includes the OnyxDerived microtask coalescing from Expensify/App#96785), with Onyx pinned to this PR's head (latestmainmerged in, v3.0.94):OnyxDerivedcompute counts in the idle tab — only ~3–6 recomputes per action (previous failure mode without coalescing was thousands of recomputes and a frozen tab).Author Checklist
### Related Issuessection above### Linked E/App PRsection above, and verified this change against it (E/App CI passed and manual testing completed)TestssectiontoggleReportand notonIconClick)myBool && <MyComponent />.STYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)/** comment above it */thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor)thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)Avataris modified, I verified thatAvataris working as expected in all cases)mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2026-07-28.at.11.30.11.mov
Screen.Recording.2026-07-28.at.11.31.40.mov
Screen.Recording.2026-07-28.at.11.39.21.mov