Если хранить состояние с помощью Immutable.js, можно сохранять не мутации, а сами состояния (Immutable.js хитрый, и это, скорее всего, не вызовет большого оверхеда по памяти), и всё станет сильно проще. Но возникнут трудности с сериализацией.
Поправьте меня, если я ошибаюсь: если мутации юзера не коммутативны с мутациями сервера, то всё так и так плохо. А если коммутативны, то можно выделить отдельное подмножество состояния, за которое отвечает юзер и которое можно сохранять в истории для undo/redo.
Логично. И как в таком случае вы себе представляете undo/redo? Получается, максимум можно откатиться до последней правки, пришедшей из другого источника. Этот же результат вполне достижим с Immutable.js.
Ключевое в undo/redo — глагол do. Пользователь совершает какие-то действия, отменяет их и повторяет. Именно собственные действия, а не действия других пользователей и не состояние всего мира, которое меняется не только и не столько пользователем.
В принципе, эта проблема решена в MobX State Tree «из коробки». Изменения можно хранить в виде набора immutable снапшотов (как в Redux). Или в виде потока операций JSON Patch: можно применить операцию к стору, или применить обратную операцию, или отпарвить на сервер, и т.д. Жаль, что для Vue нет подобной библиотеки.
Создаем плагин Vuex Undo/Redo для VueJS