Undo where a delete occurs after modification drops data on synced document

I have two Y.Doc I am trying to keep in sync. In the example below, written as a test, session1 will initiate all changes and session2 will be syncing to it.

If session1 updates an element and then deletes it within the span of UndoManager.captureTimeout, this becomes one undo step as expected. However, calling undo will cause session2 to lose data while session1’s data is restored correctly.

import * as Y from 'yjs'

/** Utility class to quickly access a doc, its top level data, associated undo, and grab its JSON representation. */
class Session {
    yDoc = new Y.Doc()
    yArray = this.yDoc.getArray('top') as Y.Array<Y.Map<number>>
    undoManager = new Y.UndoManager(this.yArray, { trackedOrigins: new Set(['local']) })
    getJSON = () => this.yArray.toJSON()
}

/** Model to start with */
const originalModel = [{ X: 1, Y: 1, Z: 0 }]

/** Returns true if vertices exist and some are missing a key. */
const hasCorruptVertices = (json: Array<any>) => {
    return json.some((v: any) => v.X === undefined || v.Y === undefined || v.Z === undefined)
}

/** Expect that the JSON matches, we have no missing changes, and that there are no corrupt vertices. */
const expectMatch = (sessions: Session[], expected: any) => {
    for (const session of sessions) {
        expect(session.yDoc.store.pendingStructs?.missing).toBe(undefined)
        const json = session.getJSON()
        const isCorrupted = hasCorruptVertices(json)
        expect(isCorrupted).toBe(false)
        expect(json).toEqual(expected)
    }
}

describe('Y.UndoManager Example', () => {
    it('will ❌ the synced doc if delete the element in the same undo step as an update', async () => {
        // Create two sessions that stay in sync.
        const session1 = new Session()
        const session2 = new Session()

        session1.yDoc.on('update', (update) => {
            session2.yDoc.transact(() => {
                Y.applyUpdate(session2.yDoc, update)
            }, 'remote')
        })
        session2.yDoc.on('update', (update) => {
            session1.yDoc.transact(() => {
                Y.applyUpdate(session1.yDoc, update)
            }, 'remote')
        })

        // Load model into session1
        originalModel.forEach((v: { X: number, Y: number, Z: number }) => {
            const vertex = new Y.Map<number>()
            vertex.set('X', v.X)
            vertex.set('Y', v.Y)
            vertex.set('Z', v.Z)
            session1.yArray.push([vertex])
        })

        // Expect that the model is correct in both sessions
        expectMatch([session1, session2], originalModel)

        // Update the vertex in session1
        session1.yDoc.transact(() => {
            const vertex = session1.yArray.get(0)
            vertex.set('X', 10)
        }, 'local')

        // Delete the parent of the vertex in session1
        session1.yDoc.transact(() => {
            session1.yArray.delete(0)
        }, 'local')

        // We expect that the two transactions above collapsed into one undo
        expect(session1.undoManager.undoStack.length).toBe(1)

        // Expect that the model in both sessions are updated to reflect the result of modification + deletion, which means deletion
        expectMatch([session1, session2], [])

        // Undo the change
        session1.undoManager.undo()

        // Expect that the model in session 1 is back to its original state
        expectMatch([session1], originalModel)

        // We would want the second model to also match the original model, but instead it looks like this:
        // [{ Y: 1, Z: 0 }]
        expect(hasCorruptVertices(session2.getJSON())).toBe(true)
    })
})