Перейти к основному содержимому

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 — отдельные свойства с сервером-арбитром. Сейчас при одновременном редактировании двумя пользователями последний снапшот затирает чужую работу.

План развития

Быстрые победы (не трогают модель данных)

#ЗадачаСложностьСтатус
1Presence-курсоры коллабораторов (эфемерный канал поверх 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Полноценный оффлайн (журнал правок + переприменение при реконнекте)Средняя–высокая⬜ Планируется
9Multiplayer-undo/redo (undo не затирает чужие правки)Средняя–высокая⬜ Планируется
10Fractional indexing для порядка/иерархии нодСредняя⬜ Планируется
11Бинарный/компактный протокол вместо JSON-снапшотаСредняя⬜ Планируется

Рекомендуемая последовательность

  1. Сначала быстрые победы (#1–#3) — совместный UX появляется сразу, без риска большого рефакторинга и без изменения модели данных.
  2. Затем операционная модель (#4 → #5 → #6) — это «ядро» подхода Figma; без неё одновременное редактирование теряет данные.
  3. Остальное (#7–#11) логично навешивается поверх операционной модели.