- Remove Mattermost integration (OAuth, channel bridge, file sync watcher,
frontend import modal). RefBoard now ships as a self-contained app.
- Replace SSO Login screen with email/password form (+ optional register link
gated by ALLOW_SELF_REGISTRATION).
- Add SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD env-var bootstrap so a fresh
install ships with an admin account on first boot (idempotent).
- ALLOW_SELF_REGISTRATION flag (default false) gates POST /api/auth/register.
First user can always register (auto-promoted to admin).
- Drop mattermost_id and mm_file_id columns + board_channel_links table
from the schema; remove related db helpers and exports.
- Add MIT LICENSE, comprehensive README, .env.example, docker-compose.yml
(bundles MinIO so one command boots a working stack).
- Expand .gitignore for typical Node + Docker dev artefacts.
- Move PDF page count check before MinIO upload and DB record creation
to prevent orphaned objects when >500 page PDFs are rejected
- Use job type label (PDF page / Video) in media-worker error messages
instead of hardcoded "Video processing failed"
- Replace LIKE-based idempotency check with exact JSON match to prevent
page 1 matching page 11/12/etc substring collisions
- 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
1. toggleVote wrapped in transaction (race condition fix)
2. FeedbackPanel fetch calls now surface errors via onError/toast
3. Extracted resolveBoard/hasCollectionRole to shared board-access.js
4. AnnotationStore uses monotonic version counter for snapshots
5. PinOverlay uses object pool instead of destroy/recreate on refresh
6. canvasObjects prop memoized with useMemo
7. PinOverlay store subscription cleaned up on unmount
8. Comment content capped at 5000 chars (backend validation)
10. anchor_type validated to 'object' or 'point'
- 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
1. Server posters no longer auto-load in VideoSprite constructor.
loadServerPoster() is now public and called by the culling system
only when within MAX_POSTER_VIDEOS budget. Videos outside the
budget stay as placeholders regardless of server poster availability.
2. video-utils cleanup() now uses statSync to distinguish files from
directories, unlinks files first, then rmdirs. Previously tmpDir
was passed through path.dirname() which resolved to /tmp instead
of the created temp directory.
Upload pipeline now extracts poster frame (JPEG) and metadata
(width, height, duration) from videos at upload time using ffmpeg.
Server:
- Add ffmpeg to Docker image (Alpine)
- video-utils.js: probeVideo() and extractPoster() using ffprobe/ffmpeg
- Upload response includes poster_asset_key and duration
Frontend:
- VideoObject gains poster and duration fields in scene format
- VideoSprite accepts posterAssetKey + TextureManager, loads poster
as a regular image texture on construction (no <video> needed)
- Server poster loaded/released via TextureManager ref counting
- addVideoFromUpload passes poster and duration through to scene data
- image-drop.ts forwards poster_asset_key from upload response
Result: videos with server posters render as images by default.
Zero <video> elements needed for thumbnails. Only explicit play
creates a media element.
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
Remove server-side Sharp resize and texture dimension cap — PixiJS
handles large textures natively. Keep the proper Assets.unload() fix
to prevent "TextureSource destroyed instead of unloaded" warnings.
- Add mm_file_id column to images table for dedup tracking
- mm-pull fetches posts from MM API, downloads media files, uploads with LOD to MinIO
- Register bridge routes in server.js under /api/boards
When a board is loaded via GET /api/boards/:id, if the canvas_state
lacks a v2 marker, it is converted in-place and persisted back to DB
so subsequent loads skip conversion.
Queries DB for images where asset_key IS NULL, downloads each from
MinIO, generates LOD tiers (thumb/medium/full), uploads them, and
updates the DB record. Skips LOD for videos, SVGs, and GIFs. Logs
per-image progress and continues on individual failures.
- Upload route now generates 3-tier LOD (thumb/medium/full) for images via
lod-generator service, storing each tier at boards/{boardId}/{imageId}/
- Video uploads (mp4, webm, quicktime) supported as single-file storage
- New DB columns: asset_key (prefix path) and media_type ('image'/'video')
- Backward compatible: minio_path and public_url still populated (point to full tier)
- SVG and GIF skip LOD processing (single file stored)
- Added putBuffer() and MIME_TO_EXT export to minio.js
Sharp-based service that produces thumb (256px), medium (1024px),
and full (pass-through) tiers from an uploaded image buffer.
Skips upscaling when the source is smaller than a tier's width.
- 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
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