- Fix 403 on save for public collection viewers (return role in GET board response)
- Add read-only status indicator (StatusBar + StatusIndicator)
- Fix beforeunload save to use fetch+keepalive with auth header
- Socket reconnect now rejoins board room automatically
- Canvas setup uses polling instead of brittle 200ms timer
- Fix double user:left on disconnect (use disconnecting event, snapshot rooms)
- Thread + comment creation wrapped in db.transaction
- Prevent owner downgrade via addCollectionMember (check existing member)
- Bound redirect depth in downloadImage to 5
- Arrangement operations anchor to bounding box top-left (no drift)
- Distribute H/V also anchor to top-left
- Fix annotations fetch to use axios api instance (401 interceptor)
- Replace require() with static import in shortcut-definitions
Video memory:
- Lazy <video> creation: constructor makes placeholder only, initVideo()
creates element with preload='metadata' when near viewport
- Tiered budgets: 1 playing / 6 initialized / 4 poster textures max
- Aggressive teardown: offscreen videos lose <video> element, poster,
and all textures — zero memory for offscreen videos
- Poster textures dropped while playing (don't keep both resident)
- Dimension caching in scene data (nativeW/nativeH) avoids re-init
Server:
- HTTP Range support (206 Partial Content) for video seeking
- Proper end clamping and invalid range rejection (416)
Sync:
- Remove redundant broadcastSceneDebounced() from broadcastTransform()
- Remove duplicate broadcastElements() from drag-end handler
VideoControls:
- Event-driven (timeupdate/play/pause/seeked) instead of rAF polling
- Tracks actual HTMLVideoElement reference, rebinds on init/teardown
- Seek uses ref-based commit on pointerUp, not conditional onChange
- Batch broadcastTransform: sends all selected items in one emit instead
of throttle-dropping all but the first (multi-select drag now syncs all)
- TransformBox onDragEnd: persist+sync final state after resize/rotate
(was only broadcasting ephemeral transforms, never the final position)
- Layer lock toggle: add missing onCanvasChange() call for sync+save
- Receiver handles both batched and legacy single-item transform formats
Rewrite sync module to work with SceneManager and v2 scene format
instead of Fabric.js. Key changes:
- setupSync takes SceneManager instead of Canvas
- broadcastScene uses sceneManager.serialize() for v2 SceneData
- broadcastTransform reads from item.data (x,y,sx,sy,angle)
- onSceneReceived uses diff-based loadScene (no flicker, no full reload)
- onTransformReceived updates item.data + displayObject directly
- Remove _suppress/resumeBroadcasts (diff-based loading is safe)
- Remove userInteracting/pendingScene deferral (not needed with diffs)
- Remove all Fabric.js imports and types
Full-featured PureRef-style collaborative canvas for game dev teams:
- Layer panel with visibility, lock, drag reorder, group/ungroup (Ctrl+G/Shift+G)
- Arrangement tools (grid, row, column) via right-click context menu
- Copy to system clipboard (Ctrl+C writes PNG for external paste in Paint etc.)
- Number shortcuts (1-5) for tool selection with visible shortcut badges
- Premium dark UI across all pages (Login, Collections, Boards, Editor)
- Socket.IO rooms for cursors, transforms, and presence notifications
- MinIO image storage with backend proxy, drag/drop and paste upload