Roadmap: Совместная синхронизация в реальном времени (как в Figma)
Документ описывает целевую модель live-совместного редактирования холста по образцу Figma, текущее состояние реализации и план развития.
Как это устроено в Figma (референс)
Клиент-серверная модель поверх WebSocket. Под каждый документ поднимается отдельный серверный процесс-арбитр. Ключевые принципы (по материалам официального блога Figma «How Figma's multiplayer technology works»):
- Не CRDT и не OT. Подход вдохновлён CRDT, но без накладных расходов — потому что есть центральный сервер-арбитр. Документ — комбинация идей (last-writer-wins register и др.).
- Модель данных — дерево объектов. Каждый объект =
ID + набор свойств. Удобно представлять какMap<ObjectID, Map<Property, Value>>. - Синхронизация на уровне свойств. Передаются отдельные изменения
(objectId, property, value), а не весь документ. Правки разных свойств/объектов не конфликтуют. - Конфликт одного свойства → побеждает последнее значение, дошедшее до сервера (порядок задаёт сервер, а не клиентские часы).
- Оптимистичное применение. Клиент применяет правки мгновенно и отбрасывает приходящие с сервера изменения, конфликтующие с ещё не подтверждёнными локальными — без «мерцания».
- Presence — отдельный эфемерный канал. Курсоры и выделения не сохраняются в документ.
- Порядок детей — через fractional indexing; сервер отклоняет изменения, создающие циклы.
Содержание источника переформулировано для соблюдения лицензионных ограничений.
Текущее состояние в проекте
| Аспект | Реализация |
|---|---|
| Что синхронизируется | Вся модель BotDataWithSheets целиком (снапшот), не дельты |
| Транспорт | BroadcastChannel (вкладки) + WebSocket /api/canvas (устройства) |
| Троттлинг отправки | 120 мс (leading + trailing edge) |
| Разрешение конфликтов | Last-write-wins по timestamp на всю модель |
| Роль сервера | Чистый relay/broadcast, документ не хранит |
| Presence | Только плашка «кто изменил» на 12 сек |
| Управляющие сигналы | canvas-reset (reset / saved) — синхронизация плашки изменений |
Ключевые файлы:
client/hooks/use-canvas-sync.ts— клиентская синхронизацияshared/canvas-sync/— типы сообщений (canvas-sync-message.ts,canvas-actor.ts,canvas-reset-message.ts)server/canvas/initializeCanvasWebSocket.ts,server/canvas/broadcastCanvasSync.ts— серверclient/pages/editor.tsx— интеграция (handleBotDataUpdate,handleRemoteCanvasSync)
Главный архитектурный разрыв
У нас синхронизируется состояние целиком с LWW по времени, а у Figma — отдельные свойства с сервером-арбитром. Сейчас при одновременном редактировании двумя пользователями последний снапшот затирает чужую работу.
План развития
Быстрые победы (не трогают модель данных)
| # | Задача | Сложность | Статус |
|---|---|---|---|
| 1 | Presence-курсоры коллабораторов (эфемерный канал поверх WS) | Низкая–средняя | ⬜ Планируется |
| 2 | Индикаторы выделения/перемещения нод другими участниками | Средняя | ⬜ Планируется |
| 3 | Список «кто онлайн» (аватары участников) | Низкая | ⬜ Планируется |
CanvasActor(kind: user/agent/guest, displayName, username, userId) уже готов под presence.
Стратегический рефакторинг (ядро подхода Figma)
| # | Задача | Сложность | Статус |
|---|---|---|---|
| 4 | Гранулярные дельты (nodeId, property, value) вместо снапшота | Высокая | ⬜ Планируется |
| 5 | Разрешение конфликтов на уровне свойств (LWW-register per property) | Высокая | ⬜ Планируется |
| 6 | Сервер как источник истины + арбитраж порядка событий | Высокая | ⬜ Планируется |
| 7 | Оптимистичное применение без «мерцания» (отбрасывание конфликтующих серверных правок) | Средняя | ⬜ Планируется |
| 8 | Полноценный оффлайн (журнал правок + переприменение при реконнекте) | Средняя–высокая | ⬜ Планируется |
| 9 | Multiplayer-undo/redo (undo не затирает чужие правки) | Средняя–высокая | ⬜ Планируется |
| 10 | Fractional indexing для порядка/иерархии нод | Средняя | ⬜ Планируется |
| 11 | Бинарный/компактный протокол вместо JSON-снапшота | Средняя | ⬜ Планируется |
Рекомендуемая последовательность
- Сначала быстрые победы (#1–#3) — совместный UX появляется сразу, без риска большого рефакторинга и без изменения модели данных.
- Затем операционная модель (#4 → #5 → #6) — это «ядро» подхода Figma; без неё одновременное редактирование теряет данные.
- Остальное (#7–#11) логично навешивается поверх операционной модели.