Skip to content

Repository files navigation

OVE — совместный визуальный редактор

Высокопроизводительный браузерный редактор для командной работы (схемы, вайрфреймы, доски). Стек зафиксирован спайками S1–S4: Pixi.js v8 (рендер), Yjs (CRDT-синк), ECS (модель), Node y-websocket + Go API (бэкенд). Контекст и решения — implementation-plan.md, spikes/DECISIONS.md.

Статус: M5 — Производительность, QA, бета ✅ (механика)

M0–M4 закрыты. В M5 — перф-пасс (viewport-culling/LOD), воспроизводимый бенч против реального движка, нагрузочный тест синка и стресс больших документов.

Новое в M5 (оба трека):

  • Viewport-culling + LOD. Рендер держит Pixi-узел только для сущностей, чей мировой AABB пересекает видимую область камеры (+margin) и крупнее ~0.5px на экране; при сдвиге/зуме множество узлов пересогласуется, пока камера неподвижна — обрабатываются только дельты. Стоимость кадра ∝ числу видимых объектов, а не размеру документа. Геометрия (packages/engine/src/cull.ts) покрыта юнит-тестами. fit считает границы по всем сущностям (не по видимым узлам).
  • Перф-бенч против реального движкаtools/bench (@ove/perf-bench): перепрогон «облёта» камеры из спайка S1, но через продакшен-рендер с culling, плюс статистика видимо/всего. pnpm --filter @ove/perf-bench bench (headed = реальный GPU). Закрывает риск «перф на интегрированном GPU» (остаётся прогон на Intel UHD/Iris).
  • Нагрузочный тест синка (10 одновременных)server/sync/loadtest.mjs (pnpm --filter @ove/sync-server loadtest): N клиентов на комнату, замер латентности, пропускной способности и сходимости под нагрузкой (все документы идентичны). Проверено: 10×200 правок → сходятся, документы идентичны, латентность sub-ms локально.
  • Стресс больших документов + перф-HUD. Офлайн ?doc=demo&stress=N грузит N сгенерированных объектов и показывает FPS + culling-статистику; E2E m5-perf.spec.ts проверяет инвариант culling (видимо ≪ всего при зуме) на 1500 объектах.

Что было в M4 (бэкенд-хардненинг и инфра)

M0–M3 закрыты (Foundation → однопользовательский канвас → совместное редактирование → фичи редактора и экспорт). В M4 бэкенд получил аутентификацию, права доступа, S3 и воспроизводимый прод-деплой.

Новое в M4 (Track B):

  • Аутентификация. Регистрация/вход по email+паролю (bcrypt), stateless access-токены (HMAC-SHA256, без внешних зависимостей). Фронт хранит токен и шлёт Authorization: Bearer; протухший токен → автоматический возврат на экран входа. Офлайн-демо (?doc=demo) работает без auth.
  • Права доступа. Документы scoped по владельцу/участникам: список показывает только свои и расшаренные. Семантика «ссылка = доступ» — открытие документа по ссылке добавляет аутентифицированного пользователя в участники (сохраняет M3-UX шаринга, но уже под auth). Переименование/удаление — только владелец. Join-токен синка привязан к пользователю и требует доступа к документу.
  • S3 (Yandex Object Storage). Presigned PUT/GET по AWS SigV4 (реализован на stdlib, совпадает с эталонным AWS-вектором; path-style для Yandex). Клиент грузит ассеты/экспорты напрямую в хранилище, API лишь подписывает URL и хранит метаданные (assets). Без ключей загрузки отключаются (503), остальное работает.
  • Хардненинг. Rate limiting на IP (токен-бакет), CORS по списку доверенных origin, request-id в логах, /metrics (Prometheus text), healthchecks всех сервисов.
  • Инфра. docker-compose.prod.yml: nginx раздаёт собранный редактор и проксирует /api→Go и /sync→y-websocket (единый origin). Образ API — distroless с self-health. Окружения dev/stage/prod через infra/.env (см. infra/.env.example).

Что было в M3 (фичи редактора и экспорт)

M0–M2 закрыты (Foundation → однопользовательский канвас → совместное редактирование). В M3 редактор получил полный набор инструментов работы с фигурами и экспорт.

Новое в M3 (Track A):

  • Группировка с вложенностью. Группа — логический контейнер (рендер её не видит, порядок задают z дочерних фигур); хранится отдельно от фигур и со-редактируется/ персистится наравне с ними. Выделение группы раскрывается в её листья, поэтому весь мультитрансформ M1 работает без изменений. Ctrl+G / Ctrl+Shift+G, двойной клик «проваливается» в группу, рамка захватывает группу целиком. Один шаг undo на всю операцию (UndoManager отслеживает и фигуры, и группы).
  • Панель свойств выделения: заливка/обводка/толщина/прозрачность, позиция/размер, кегль/шрифт; правка всего выделения одной транзакцией.
  • Панель слоёв = дерево групп с раскрытием, drag-reorder верхнего уровня (z), переименованием групп и удалением по строке.
  • Контекстное меню (ПКМ без сдвига — меню, right-drag остаётся паном): дублировать (Ctrl+D), группировать/разгруппировать, на передний/задний план, удалить.
  • Экспорт PNG/SVG всей сцены из модели: viewBox по границам, многострочный текст через <tspan> с корректной базовой линией, привязка font-family; PNG — растеризация того же SVG (кадр не зависит от текущего zoom/pan).

Track B: шаринг доски ссылкой (копирование ?doc=…) на стартовом экране и в шапке редактора. Аутентификация и права доступа к комнатам — M4.

Что было в M2 (совместность)

Канвас (M1, @ove/engine + apps/editor): инструменты rect/ellipse/line/arrow/ text (V/R/O/L/A/T); продакшен-выделение/трансформ (маркеры постоянного экранного размера, неравномерный resize, rotate, мультивыделение, снэппинг с направляющими); текст-оверлей с IME; слои/z-order; палитра; nudge.

Совместность (M2):

  • Yjs = источник истины. Редактор мутирует через адаптер SceneStore, который делегирует в @ove/sync SyncEngine; ECS-World — проекция, в которую пишет только наблюдатель (петли структурно невозможны). UI не переписан.
  • Троттлинг drag: патчи жеста коалесцируются в одну Yjs-транзакцию на кадр (оптимистичный рендер локально).
  • Awareness: живые курсоры соавторов с именами, presence и контуры их выделения; индикатор соединения.
  • Scoped undo/redo через Y.UndoManager (отмена правит только свои изменения).
  • Персистентность Yjs→Postgres (server/sync): журнал апдейтов + дебаунс-снапшот; документ переживает отключение всех клиентов и рестарт сервера (холодный late-join).
  • Доски и доступ: стартовый экран со списком досок (создание/переименование/ удаление); открытие доски запрашивает комнату и HMAC join-токен у Go API и подключается к синку по авторизованному WebSocket (сервер синка проверяет токен на ws-upgrade).
  • Go API: документы (CRUD), комнаты, выпуск/проверка join-токенов.

Exit M3 достигнут: функционален весь список MVP из брифа — примитивы, co-edit, слои + группировка (с вложенностью), экспорт PNG/SVG. Проверено автотестами (node --test в @ove/sync/@ove/engine/@ove/export, go test) и headless-E2E.

Структура (монорепо, pnpm-workspaces)

apps/editor        React-оболочка: тулбар, палитра, панель свойств, дерево слоёв, меню
packages/model     Контракт: типы Shape/Component/Group, фабрики                (граница треков)
packages/ecs       ECS-проекция (типизированные массивы + dirty-tracking)      ← seed s2/world
packages/sync      Yjs-биндинг (источник истины): фигуры + группы, публичный API ← seed s2/sync
packages/engine    Pixi-сцена, transform-tool, группировка, экспорт, viewport-culling/LOD ← seed s1
packages/export    SVG из модели (viewBox/tspan/baseline) + PNG-растеризация     ← seed s4
server/sync        Node y-websocket + персистентность Yjs→Postgres + ws-auth     ← seed s2/server
server/api         Go REST API: auth/документы/комнаты/ассеты + права + S3 presign ← ref s3
infra              docker-compose (dev + prod=nginx) / nginx-прокси / .env-окружения
tools/bench        Перф-бенч против реального движка (culling) — перепрогон S1 на целевом GPU (M5)

Зависимости направлены к контракту: ecs → model, sync → ecs, model, engine → ecs, model, sync, export, export → model, editor → engine, model.

Разработка

pnpm install
pnpm build        # tsc-эмит всех пакетов (topo) + vite-сборка редактора
pnpm lint
pnpm typecheck
pnpm dev          # редактор на http://localhost:5173
pnpm sync-server  # y-websocket на ws://localhost:1234 (нужен для M1+)

Браузерные E2E-тесты M3 (Pixi/WebGL + React в headless Chromium, офлайн-демо без бэкенда):

pnpm --filter @ove/editor exec playwright install chromium  # один раз: браузер
pnpm --filter @ove/editor e2e                               # vite build + playwright test

Покрывают: загрузку сцены (init WebGL), панель свойств, группировку/разгруппировку (тулбар + Ctrl+G/Ctrl+Shift+G), undo, контекстное меню → дублирование, экспорт PNG/SVG. E2E M5 (m5-perf.spec.ts) — стресс 1500 объектов + инвариант viewport-culling.

Перф и нагрузка (M5):

pnpm --filter @ove/perf-bench bench       # FPS-бенч против реального движка (headed = реальный GPU)
pnpm --filter @ove/sync-server loadtest   # нагрузочный тест синка: 10 клиентов, сходимость, латентность
# ручной стресс-просмотр: открыть редактор на ?doc=demo&stress=2000 (перф-HUD: FPS + culling)

Требуется Node ≥ 22 и pnpm 9.

Бэкенд (Track B) — dev:

docker compose -f infra/docker-compose.yml up -d --build  # postgres + sync + api
# API на http://localhost:8080 (healthz/readyz/metrics, /api/v1/auth/*, /api/v1/documents)
# Все /api/v1/* (кроме auth/register|login) требуют Authorization: Bearer <token>.

Прод (единый origin: nginx раздаёт редактор и проксирует API/синк):

cp infra/.env.example infra/.env   # заполнить секреты (openssl rand -hex 32) и S3
docker compose --env-file infra/.env -f infra/docker-compose.prod.yml up -d --build
# web на :80 → /  (редактор), /api → Go, /sync → y-websocket

Требуется Go ≥ 1.24 (для запуска API без контейнера) и Docker.

Go-тесты бэкенда (auth-токены, SigV4-presign, rate-limit, CORS/auth-middleware):

cd server/api && go test ./...

Граница контракта (заморожено в M0, согласованно расширено в M3)

  • @ove/model — единственный источник типов сцены: Shape (rect/ellipse/line/ arrow/text) как объединение компонентов (Transform, Geometry, Style, TextContent, ZIndex). M3 добавил опциональный ShapeBase.parent и тип Group (группа без геометрии) — обратносовместимо. Без рантайм-зависимостей.
  • @ove/sync — публичный API: SyncEngine (addShape / patch / moveTo / removeShape / clear / undo + addGroup / patchGroup / removeGroup / allGroups), сериализация Shape ↔ Y.Map. Паттерн «Yjs = источник истины, ECS = проекция; единственный writer-наблюдатель». Undo отслеживает и фигуры, и группы.

Эти две поверхности — граница между треками Front-graph и Sync+Backend; менять их можно только согласованно.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages