- UploadManager store: tracks jobs through uploading → processing → done/failed
- UploadPanel component: floating bottom-left widget showing upload progress,
file names, sizes, status with auto-dismiss for completed items
- axios onUploadProgress wired for real-time upload percentage
- Video jobs transition to "processing" after upload, then "done" when
media:job:update socket event arrives (reuses existing pipeline)
- Failed uploads show error message from server response
- Clear button to dismiss finished/failed items
- Background media worker: polls media_jobs table, runs ffprobe+ffmpeg
with concurrency limit, generates video posters, emits socket events
- Non-blocking video upload: stores file + enqueues job, returns immediately
- Poster hydration on board load: GET /boards/:id injects poster/dimensions
from DB into canvas_state video objects
- SpatialGrid: fixed-cell (512px) spatial hash for O(nearby) culling instead
of O(all) item scanning, eliminates setTimeout violations
- Canvas-based video rendering: draws video frames to offscreen canvas then
uploads to GPU, completely eliminates GL_INVALID_OPERATION errors from
PixiJS VideoSource auto-update mechanism
- Server poster upgrade path: culling ticker and applyProcessedMedia() both
upgrade client-captured posters to server posters when available
- Pause restores server poster (paused video behaves like an image)
- Selection drag-end persistence: onObjectDragEnd broadcasts + saves + undo
- Live media:job:update socket handler patches scene data + VideoSprite
dimensions without broadcast fanout
- Add onDragOver/onDrop preventDefault on container div (React level)
to always block browser default file-open behavior
- Add DOM fallback in useCanvasSetup if ref is somehow null
- setupDragDrop's native listeners handle the actual upload logic
The drag/drop setup was searching for a <canvas> element to find its
parent, which was fragile and could fail (returning null). When it
failed, document-level dragover/drop prevention was never registered,
so the browser's default behavior (open file in new tab) kicked in.
Fix: pass a direct ref to the outer canvas container div from Editor
to useCanvasSetup. No more DOM querying for the drop target.
Replace broadcastSceneNow() with incremental element:update for all
property-modifying operations (align, arrange, flip, resize, rotate,
normalize, frame color). Full scene sync now only fires via debounced
fallback (500ms) for structural changes (add/remove/paste/group).
Changes:
- onCanvasChange() accepts optional changedIds for incremental sync
- SelectionToolbar, context menu, shortcuts all pass item IDs
- TransformBox.onDragEnd passes item IDs instead of full scene
- ShortcutContext, MenuContext, hook interfaces updated
This dramatically reduces sync traffic during normal editing — only
the changed elements are sent instead of the entire board state.
- Make PixiJS canvas background transparent so SVG dot grid shows through
- Move dot grid SVG before canvas in DOM with zIndex 0 (behind images)
- Set wrapper div background to #1e1e1e
- Remove 600px cap on pasted images — preserve native dimensions
onCanvasChange now calls broadcastSceneNow() so rearrangement,
alignment, and other bulk operations sync to other tabs in real-time
instead of waiting for the next periodic sync or page reload.
Floating HTML overlay with play/pause, seekbar, time display, and
mute toggle that appears when a single video element is selected.
Positioned at the bottom of the video using viewport.toScreen()
coordinate conversion, polled at 100ms for position and 250ms for
playback progress.
dashed border and "Inbox" label. Auto-synced media slides in with
spring animations and random offset/rotation for a natural look.
MattermostImport: modal dialog for pulling images from a MM thread
URL and managing linked channels for auto-sync. Uses the existing
dark modal styling pattern from ShortcutsHelp.
Editor wiring: toolbar button for MM import, InboxZone added to
viewport on init, socket listener for board:media-arrived events
that routes assets into the InboxZone with a toast notification.
- Rewrite Editor.tsx: swap FabricCanvas for PixiCanvas, wire
SelectionManager, SceneManager, and all Pixi-based APIs
- Port tools.ts from Fabric Canvas API to PixiJS Viewport/Scene
- Update LayerPanel type icons for PixiJS scene types (text, video)
- Remove FabricCanvas.tsx (dead code after swap)
- Uninstall fabric npm package
- All operations, shortcuts, sync, history, drag/drop, paste now
flow through the PixiJS pipeline end-to-end
- Auto-generate board thumbnail from canvas content on save (webp, 400px)
- Collection cards show stacked/fanned board thumbnails with random offsets
- Three-dot menu on all cards with Rename, Share, Delete actions
- Inline rename for collections (header + card) and boards
- Collection cards show public/private status and member count
- Share button on collection cards for quick access
- Add updateCollection/updateBoard API functions
- Add thumbnail + object_count columns to boards table with migration
- Fix zoom drift: use element-relative coords (offsetX/Y) not getScenePoint
- Fix thumbnail generation breaking viewport: use finally block for restore
- Fix thumbnail bounds: use scene-space obj coords not screen-space getBoundingRect
- Fix object counter: live count from canvas instead of stale DB images table
- Fix three-dot menu clipping by removing overflow:hidden from card containers
- Status bar shows "objects" instead of "images", updates on add/delete
Viewport transform (zoom level + pan offset) is saved to localStorage
keyed by board ID. Restored automatically when reopening the same board.
Debounced 300ms save on zoom/pan to avoid excessive writes.
- Copy/paste/duplicate now uses obj.clone() instead of broken fromObject() serialization
- Ctrl+C stores actual object refs, Ctrl+V clones them — works for images, groups, paths, text
- System clipboard copy (Ctrl+C / Ctrl+Shift+C) properly re-selects after screenshot
- Layer panel: double-click to rename, collapsible groups with expand/collapse arrow
- Auto-indexed names: Image 1, Image 2, Drawing 1, Drawing 2, etc.
- Group children visible in layer panel when expanded
- Copy/paste works on groups and mixed multi-selections
- Bot auto-replies in DMs without @root mention (dm_history.py for rolling context)
- chat_with_history() in llm_client for multi-turn DM conversations
- Fixed MM v11 healthcheck (curl removed from image, now uses mmctl)
- RefBoard PUBLIC_URL set to refboard.unleashtheavatargame.com
- Added RefBoard to cloudflared dependencies
- Updated ROOT-AI-STACK.md: MM 11.4, 19 services, RefBoard, DM support
- RefBoard PRD (docs/plans/refboard-prd.md)
- Editor.tsx: improved clone logic using Fabric.js enqueueObject
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