Skip to content

fix: skip stale da-admin reload when client pushes Y.js state after reconnect#136

Merged
kptdobe merged 2 commits intomainfrom
fix/image-upload-reconnect2
May 5, 2026
Merged

fix: skip stale da-admin reload when client pushes Y.js state after reconnect#136
kptdobe merged 2 commits intomainfrom
fix/image-upload-reconnect2

Conversation

@kptdobe
Copy link
Copy Markdown
Contributor

@kptdobe kptdobe commented May 4, 2026

User scenario

A user uploads several images one after another in the DA editor. After uploading the 4th image, the real image briefly appears then reverts to the FPO/upload-icon placeholder (/blocks/edit/img/fpo.svg).

The same issue was reported for a single image upload by a second user.

Root cause

The chain of events that triggers the regression:

  1. The client uploads an image. da-live inserts a temporary FPO placeholder, sends the file to da-admin, then replaces the FPO with the real image URL.
  2. The Y.js updateHandler debounces (2 s, max 10 s) saving the updated doc state to da-admin.
  3. da-admin returns 412 on the debounced PUT (version conflict). da-collab reacts by clearing its worker storage (storage.deleteAll()) and closing all WebSocket connections.
  4. The client reconnects in ~85 ms. bindState runs and, finding empty worker storage, fetches the current document from da-admin. Because the 2 s debounce has not yet flushed, da-admin still holds the stale snapshot (image URL not yet written).
  5. bindState schedules a 1-second deferred reload to avoid a race with Y.js sync. After 1 s the doc is overwritten with the stale snapshot, replacing the real image URL with the FPO placeholder.

Fix

Snapshot Y.encodeStateVector(ydoc) before the 1-second timeout. At fire time, if the state vector has advanced (the reconnected client already pushed its authoritative Y.js state), the da-admin reload is skipped — the client state takes precedence over the stale snapshot.

Related PRs

Tests

  • Test bindState skips da-admin reload when client sends Y.js update before timeout
  • Test bindState still reloads from da-admin when no client update arrives before timeout

…econnect

When a debounced PUT to da-admin returns 412, the collab DO clears its
worker storage and closes all WebSocket connections.  On reconnect a fresh
bindState runs and, finding no worker storage, schedules a 1-second deferred
reload from da-admin.  Because the debounce (2 s) fires after the timeout
(1 s), the client's latest changes (e.g. a newly uploaded image URL) had not
yet been written to da-admin, so the reload was overwriting them with a stale
snapshot.

Fix: snapshot Y.encodeStateVector(ydoc) before the timeout.  At fire time,
if the state vector has advanced (the client already synced its authoritative
state via Y.js), the da-admin reload is skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kptdobe kptdobe merged commit a8ddc23 into main May 5, 2026
5 checks passed
@kptdobe kptdobe deleted the fix/image-upload-reconnect2 branch May 5, 2026 08:02
adobe-bot pushed a commit that referenced this pull request May 5, 2026
## [1.2.2](v1.2.1...v1.2.2) (2026-05-05)

### Bug Fixes

* skip stale da-admin reload when client pushes Y.js state after reconnect ([#136](#136)) ([a8ddc23](a8ddc23))
@adobe-bot
Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 1.2.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants