Repository navigation
[Due for payment 2026-10-05] [Proposal] API.writeWhenReady — safe delayed optimistic writes across flows #96010
Description
Initial proposal and discussion in Slack
Background
API.write() is the standard write path in the App. It takes command params plus Onyx data, applies optimistic updates, and sends the request through SequentialQueue for persistence, retries, and ordering.
Those optimistic updates are useful because the destination can show the expected result before the server responds. The trade-off is that applying optimistic Onyx data can trigger a large re-render wave across subscribed components. If that happens while a modal is dismissing or a destination is becoming visible, the JS thread can spend transition-frame time processing those updates instead of keeping navigation smooth.
I fixed this for the submit-expense flow by delaying the expensive API.write() call until after the destination is visually ready.
This PR alone reduced the span from 2408 ms to 893 ms. After combining other improvements, the span is now under 300 ms. The user sees the destination first, and the write-driven re-render wave happens afterward. This kept the normal API.write() and SequentialQueue behavior intact, while improving perceived submit speed.
Problem
When a single user interaction both navigates (e.g. dismisses a modal) and triggers an optimistic API.write(), the re-render cascade from applying optimistic Onyx data can compete with the navigation transition for main-thread time. The write itself is cheap. The cost is subscribed components re-rendering while the animation still needs frames. That makes dismiss feel slow/choppy.
Solution
Add an API-owned deferred write primitive:
API.writeWhenReady(command, params, onyxData, barrier?).
The first three parameters match API.write. The fourth parameter is optional. If omitted, the write waits for the navigation transition to finish. If provided, it is a promise-like barrier that releases the write when the destination is ready for the optimistic Onyx update.
API.writeWhenReady does not replace the normal write pipeline. It waits for the default transition readiness or the passed barrier, then calls normal API.write(). Optimistic data, success data, failure data, persistence, retry behavior, and request ordering stay owned by the existing API and network layers.
The default transition behavior covers the common modal-dismiss case without any extra argument. Passing a barrier is only needed when transition completion is not the right readiness point. Search can pass the readiness signal it already uses today from layout, focus, fallback, background, or timeout handling.
The first migration should move submit-expense to this API, then remove submit-expense API-write coordination from deferredLayoutWrite. Once submit-expense is fully migrated, we can document the standard in contributingGuides/PERFORMANCE.md and update reviewer guidance.
Alternative
One suggestion was to send the network request immediately and delay only optimisticData, since the render cascade comes from the optimistic Onyx update.
That is risky in the current pipeline because successData, failureData, finallyData, and response-applied Onyx updates can arrive before the delayed optimistic base exists. To make that safe, we would need to buffer response/failure side effects behind the same readiness point too. At that point it is no longer a small scheduling wrapper over API.write; it becomes a new two-phase write pipeline. Delaying the whole API.write() keeps the existing ordering intact.
Telemetry
Each migrated flow should measure perceived latency separately from delayed-write completion:
ToDestinationVisible: action to first meaningful destination paint.
WriteWhenReadyWait: barrier creation to barrier release.
ToFullyInteractive: optional, if deferred work affects interaction readiness.
ToOnyxUpdateVisible: from barrier release to delayed Onyx update paint
Timeouts and background flushes should be logged separately because they mean the intended barrier did not release the write.
Context
This proposal is part of the broader standardization effort tracked in #88596.
Related PR that demonstrated the gains: #85629 (2408 ms → 893 ms on the submit-expense span; combined improvements now under 300 ms).
Activity
melvin-bot commentedon Jul 17, 2026
@JakubKorytko Whoops! This issue is 2 days overdue. Let's get this updated quick!
JakubKorytko commentedon Jul 20, 2026
Not OD, was OOO (sick), back now and working on it.
74 remaining items
[-][Proposal] API.writeWhenReady — safe delayed optimistic writes across flows[/-][+][Due for payment 2026-10-05] [Proposal] API.writeWhenReady — safe delayed optimistic writes across flows[/+]melvin-bot commentedon Sep 28, 2026
@brunovjk
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.4.94-0 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
deferredLayoutWrite#100169If no regressions arise, payment will be issued on 2026-10-05. 🎊
The following checklist (instructions) will need to be completed before the issue can be closed. Please copy/paste the Contributor+ Checklist from here into a new comment on this GH and complete it. If you have the K2 extension, you can simply click: [this button]. If no checklist is needed for this issue, you can click: [no checklist button]
melvin-bot commentedon Oct 1, 2026
@brunovjk Whoops! This issue is 2 days overdue. Let's get this updated quick!
JakubKorytko commentedon Oct 2, 2026
I am working on the #102324 and it blocks the docs PR, not OD
melvin-bot commentedon Oct 4, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)brunovjk commentedon Oct 6, 2026
Contributor+ Checklist:
[Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to the comment on the PR: N/A - The issue is a planned performance improvement/architecture update rather than a regression fix.
Link to the comment on the Issue:
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion:
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
Regression Test Proposal
Precondition:
Test:
Do we agree 👍 or 👎
brunovjk commentedon Oct 6, 2026
Contributor+ Summary
So, as I understand it, the previous $125 payment does not cover my review of this PR. I should still be eligible for the C+ payment for this PR, but with the regression reduction applied, which would make the payment $125 via NewDot.
I believe this is the correct interpretation, but I’m open to any discussion if I missed something.
cc: @ mallenexpensify
mallenexpensify commentedon Oct 6, 2026
Payment Summary
Contributor+: @brunovjk due $125 via NewDot
@brunovjk makes sense to me, thx for the detailed breakdown. cc @Julesssss @roryabraham in case either of you disagree
laurenreidexpensify commentedon Oct 7, 2026
Approved $125 @brunovjk New Expensify