Commit Graph
107 Commits
Author SHA1 Message Date
Hiren Kangad 6e1d9df7f0 feat(review): add InlineCommentComposer presentational component 2026-03-12 22:09:12 +05:30
Hiren Kangad 7aa55b66f0 refactor(review): extract shared anchor math into reviewAnchors.ts 2026-03-12 22:09:12 +05:30
Hiren Kangad 318b7358e1 fix(review): address code review findings — tool conflicts, dual inputs, pin animation
1. TEXT and ERASER tool clicks now suppressed during review mode via
   reviewMode flag on ToolContext (read from ref for live value)
2. Object-comment input hidden when draftPin is active — only one
   creation input visible at a time
3. PinOverlay entrance animation removed — focus styling (scale 1.15x,
   alpha changes) no longer fights with fade-in animation loop
2026-03-12 22:09:12 +05:30
Hiren Kangad 16a677dc89 fix: remove entrance animation so dropped items are instantly draggable
Items were fading in from alpha 0 via spring animation, preventing
immediate interaction. Removed the entrance animation entirely — items
now appear at full opacity and are movable on first frame.
2026-03-12 22:09:12 +05:30
Hiren Kangad 5f60cc826a fix(review): gate pin visibility on review mode per spec 2026-03-12 22:09:12 +05:30
Hiren Kangad c6b2c325a5 feat(review): wire FeedbackPanel with draft pin, expandRequest, and point-aware jump-to-object
- Pass expandRequest, focusedThreadId, draftPin, onCreatePointThread to FeedbackPanel
- Lift thread detail state via onThreadDetailChange → setOpenThreadDetailId
- Jump-to-object centers on pin location for point-pinned threads
- Pan-only by default; zoom in if object appears <50px on screen
- Show toast when jumping to deleted object
2026-03-12 22:09:12 +05:30
Hiren Kangad a1efdedb2a feat(review): add focused thread highlight and headerSlot to ThreadList
- FOCUSED_BG and FOCUSED_BORDER tokens in feedbackStyles
- headerSlot prop for draft pin comment input above thread list
- focusedThreadId prop highlights active thread row with accent border
- ThreadListItem gains focused prop with left border + subtle background
2026-03-12 22:09:12 +05:30
Hiren Kangad 9bd2810f5a feat(review): update FeedbackPanel with expandRequest, draftPin input, and lifted thread detail state
- Replace expandThreadId with expandRequest pulse pattern (seq counter)
- Add draftPin, onCreatePointThread, focusedThreadId, onThreadDetailChange props
- Lift expandedThreadId changes to parent via onThreadDetailChange callback
- Draft pin comment input section shown above thread list
- Thread detail collapse signal via expandRequest with null threadId
- Pass thread object to onJumpToObject for point-aware navigation
2026-03-12 22:09:12 +05:30
Hiren Kangad 3b15166c32 feat(review): wire PinOverlay ghost/focus from Editor state 2026-03-12 22:09:12 +05:30
Hiren Kangad f05a2c8c34 feat(review): add Escape handler and point thread submit
- Escape priority: draft pin → thread detail collapse → review mode exit
- Draft/detail clearing works even when focus is in input field
- Review mode exit respects input/textarea focus context
- handleCreatePointThread: REST POST with auto-focus on success
- Clear draft/focus/expandRequest when review mode exits
2026-03-12 22:09:12 +05:30
Hiren Kangad 08fe062b92 feat(review): add review-mode pointer handlers for pin click + draft placement
- pointerdown records position, pointermove detects drag (>5px)
- pointerup fires review logic only if no drag occurred
- Hit-test priority: existing pin → scene object → empty canvas
- 10px screen-space click radius for forgiving object detection
- Permission gating: viewer role cannot place draft pins
2026-03-12 22:09:12 +05:30
Hiren Kangad e854ad59e2 feat(review): add ghost pin rendering and focused pin visuals to PinOverlay
- setGhostPin(): renders white semi-transparent "+" bubble at relative coords
- setFocusedThread(): scales focused pin 1.15x, dims others to 0.7 alpha
- Ghost and focus visuals reapplied on refresh()
- Ghost container cleaned up on destroy()
2026-03-12 22:09:12 +05:30
Hiren Kangad 358d30beb3 refactor(review): split focusedThreadId into persistent focus + expandRequest pulse
- Add DraftPin interface (exported for FeedbackPanel)
- Add expandRequest state with seq counter for re-trigger
- Add draftPin, openThreadDetailId, expandSeqRef state
- Remove old pointerdown pin-click handler with requestAnimationFrame clear
2026-03-12 22:09:12 +05:30
Hiren Kangad 2d1c06cec9 feat(review): make review mode independent of tool selection
- Tool button clicks no longer exit review mode
- Review button moved outside tool group with divider separator
- Tool active state no longer dimmed during review mode
- Updated hint text for review mode
2026-03-12 22:09:12 +05:30
Hiren Kangad d3f7c45dda fix(crop): ensure overlay renders on top of scene items
CropOverlay was hidden behind images because SceneManager._applyZOrder()
reorders viewport children by z-index, pushing non-scene children behind.
Now start() moves itself to the top of the viewport child list.
2026-03-11 18:17:43 +05:30
Hiren Kangad 3413d58f83 fix(crop): return cropOverlayRef instead of .current to fix null timing
The cropOverlay was created asynchronously inside a polling interval but
returned synchronously as .current (null). By returning the ref itself,
Editor.tsx reads .current at call time when the overlay actually exists.
2026-03-11 17:39:34 +05:30
Hiren Kangad 01f22c9d43 fix: address code review findings from crop/text commit
- CropOverlay: register move/up handlers dynamically on drag start (single
  handler instead of 8x per-handle), remove on drag end. Override destroy()
  to call _cleanup() preventing keyboard listener leaks.
- Text format toolbar: re-measure text bounds (w/h) after fontSize/fontFamily
  changes, update spatial index and transform box.
- TextEditor: add clearText() method; tools.ts uses it instead of fragile
  document.querySelector('textarea').
- SceneManager._updateItem: apply crop mask on remote sync for image items.
- useCanvasSetup: stop TextEditor on unmount to prevent orphaned textarea.
- Double-click zoom: use item.data dimensions instead of getBounds() (which
  includes shadow offset).
2026-03-11 17:28:52 +05:30
Hiren Kangad 58da934ec1 feat: text tool UX, image crop, text format toolbar, double-click focus
- Text tool: click-to-place immediately opens inline editor, auto-switches
  back to select tool. Empty text cleanup on save.
- Crop: select image + press C (or right-click > Crop) to enter crop mode.
  8 drag handles with rule-of-thirds grid, dimmed outside area.
  Enter confirms, Escape cancels. Non-destructive (stored as normalized rect).
- Text format toolbar: appears when text items selected, with font size +/-,
  font family dropdown, and color picker with presets.
- Double-click image: zoom-to-fit (PureRef-style focus)
- Entrance animation: replaced bounce with simple fade-in (no delay before
  items become interactive)
- TextEditor: allow empty text (caller handles cleanup)
2026-03-11 17:22:25 +05:30
Hiren Kangad 4ed475c62e fix(canvas): zoom-stable handles and group-scale multi-selection
- Transform handles counter-scale by 1/zoom so they stay constant screen size
- Selection border stroke also counter-scales
- Multi-select resize scales items as a unit from bounding box anchor
- Redraw transform box on viewport zoom/pan via 'moved' listener
2026-03-11 16:59:10 +05:30
Hiren 155dc6ac01 - Add mattermost_id column to RefBoard users for OAuth linking
- Admin panel shows per-user RefBoard login status (Active vs Not signed in)
- Add RefBoard config: REFBOARD_PUBLIC_URL, REFBOARD_INTERNAL_URL, REFBOARD_API_KEY
- Update .env.example with RefBoard section
2026-03-11 13:40:00 +05:30
Hiren Kangad 6518ed6763 fix: hardening pass — permissions, socket reconnect, canvas setup, arrangements
- 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
2026-03-11 08:08:21 +05:30
Hiren Kangad fc2d9df741 feat: annotation UX overhaul, animated packing, toolbar redesign
- PinOverlay: zero-allocation rendering with in-place Graphics/Text updates,
  fix WebGL texture crash (addressModeU null) from orphaned Text objects
- Pins always visible on media, track during drag via updatePositions() fast path
- Replace eraser with Review mode in toolbar tool group (orange gradient)
- Add tool instruction hints bar below toolbar (context-sensitive per tool)
- Feedback panel: glassmorphism, improved ThreadDetail/CommentItem/ThreadListItem
- Timestamps: relative with "ago" suffix, full date on hover tooltip
- De-emphasize pin numbers, show status text in thread list
- Animated arrangement transitions (easeOutCubic, 320ms) for pack/grid/row/column
- Disable snap guides (user requested)
- Paste now selects newly created items
- Remove eraser keyboard shortcuts, remap tool shortcuts
- Orphaned thread cleanup: safe destroy check for deleted media pins
2026-03-11 01:53:56 +05:30
Hiren Kangad ed8424d9a1 feat(annotations): Figma-style UI overhaul
- Redesign PinOverlay: teardrop pins with author initials, colors, numbers
- Split FeedbackPanel into modular components:
  - feedback/FeedbackPanel.tsx (orchestrator)
  - feedback/ThreadList.tsx (list view with filters)
  - feedback/ThreadListItem.tsx (individual thread row)
  - feedback/ThreadDetail.tsx (expanded thread with comments)
  - feedback/CommentItem.tsx (single comment)
  - feedback/CommentInput.tsx (reusable input with Enter/Shift+Enter)
  - feedback/feedbackStyles.ts (shared design tokens)
- Add status colors: red (open), green (resolved)
- Add relative timestamps (2h ago, 3d, etc.)
- Wire pin clicks to expand threads in panel
- Pins follow media transforms in real-time
- Add delete thread with inline confirmation
- Register review mode shortcut (. key)
- Better empty states and filter bar (Open/Resolved/All/Mine)
2026-03-11 01:08:05 +05:30
Hiren Kangad f24414e25c refactor: remove voting system, add author colors + pin numbering
- Remove votes backend (route, db functions, server mount)
- Remove votes frontend (store, socket, PinOverlay badges, FeedbackPanel UI)
- Add authorColors utility (8-color palette, deterministic hash)
- Add getPinNumber() to annotationStore
- Fix JWT to include username/display_name
- Add resolveAuthorName DB fallback for old tokens
2026-03-11 00:54:48 +05:30
Hiren Kangad f7a39a1726 fix(annotations): address code review findings (1-8, 10)
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'
2026-03-10 21:16:08 +05:30
Hiren Kangad 303124f518 feat(annotations): comments, threads, voting system with review mode
Backend:
- comment_threads + comments + object_votes tables with indexes
- Thread/comment CRUD endpoints with socket broadcast
- Vote toggle endpoint with socket broadcast

Frontend:
- AnnotationStore for reactive thread/comment/vote state
- FeedbackPanel with thread list, expanded view, replies, filtering
- Vote toggle buttons in panel
- PinOverlay for canvas pin markers + vote badges
- Review Mode toggle in toolbar (comment bubble icon)
- Jump-to-object from thread view
- Orphaned thread detection for deleted objects
- New comment creation from panel when object selected
- Socket event wiring for real-time sync
2026-03-10 21:04:38 +05:30
Hiren Kangad b6b615957b feat: show queued upload count in panel header 2026-03-10 20:51:25 +05:30
Hiren Kangad 9dc0815a12 feat: upload queue with queued status and cancel button
Multi-file drops now show queued/uploading distinction. Jobs start
as 'queued' and transition to 'uploading' when the HTTP request
begins. Queued items show a cancel button in the upload panel.
Cancelled jobs are skipped in the sequential upload loop.
2026-03-10 20:42:17 +05:30
Hiren Kangad 9af4051b67 fix: release GIF ref on sprite destroy to prevent gifCache leak
Override destroy() in AnimatedGifSprite to call releaseGif() when
a loaded GIF item is deleted from the board. Without this, the
gifCache refCount stays elevated until full canvas teardown.
2026-03-10 20:09:24 +05:30
Hiren Kangad 1f5ab883ce fix: file drop uses extension fallback when MIME type is empty
Browsers sometimes report empty or non-standard MIME types for
dragged files. Now checks file extension as fallback when MIME
doesn't match the allowlist. Only shows rejection feedback for
files that are recognizably media but unsupported.
2026-03-10 20:03:18 +05:30
Hiren Kangad bc2c5fb974 fix: reject unsupported media URL drops with feedback
URLs with media-like extensions (.avif, .tiff, .heic, .bmp, etc.)
that aren't in our supported set now show a failed entry in the
upload panel. Plain webpage URLs without media extensions are still
silently ignored (not our concern).
2026-03-10 19:49:05 +05:30
Hiren Kangad 065762d273 fix: synthesize readable filename for rejected clipboard pastes
Rejected clipboard items now show as "pasted-item.tiff" instead
of raw MIME subtype, so the upload panel entry is readable.
2026-03-10 19:46:17 +05:30
Hiren Kangad 45891a278c fix: reject unsupported file types with explicit feedback
Replace broad image/*/video/* prefix matching with explicit MIME
allowlist matching backend. Unsupported files (BMP, TIFF, AVIF,
HEIC, PDF, etc.) now show as failed rows in the upload panel
with a clear "Unsupported format: .ext" message instead of being
silently ignored. Applies to both drag-drop and clipboard paste.
2026-03-10 19:44:04 +05:30
Hiren Kangad e45fce3e67 fix: harden GIF support — ref-counted sources, defensive detection, play budget
1. Move GIF loading behind TextureManager.loadGif/releaseGif with
   ref-counting (fixes shared-source invalidation on duplicate GIFs)
2. Strip query strings/fragments before .gif extension check
3. GIFs now load paused — separate MAX_PLAYING_GIFS=8 budget in
   culling system, only nearest N animate (others show static frame)
2026-03-10 19:41:50 +05:30
Hiren Kangad 5a64708bd4 feat: add animated GIF support using PixiJS built-in GifSprite
Register GifAsset extension for .gif loading, create AnimatedGifSprite
wrapper with lazy load/unload matching ImageSprite pattern, and detect
.gif assets in SceneManager to use animated rendering. GIFs stored as
type 'image' in scene format — no schema changes needed.
2026-03-10 19:26:41 +05:30
Hiren Kangad 51e53a323b feat: add export-as-image with format, scale, and background options
New ExportDialog component with scope (selection/all), filename,
format (PNG/JPEG/WebP), quality slider, scale multiplier, and
background color picker. Wired into Toolbar and Editor.
2026-03-10 19:16:05 +05:30
Hiren Kangad c126885e53 fix: upload manager — URL imports, active count, board reset
1. URL imports now participate in upload manager pipeline:
   addUrlJob() creates a job row, uploadComplete/setFailed called
   on success/failure — consistent with local file uploads

2. activeCount only counts uploading/processing jobs, not failed.
   Failed jobs no longer inflate the "Uploads (n)" header count.

3. uploadManager.clear() called on boardId change so stale jobs
   from a previous board don't linger in the next board's panel.
2026-03-10 19:10:14 +05:30
Hiren Kangad 784554e40d fix: video size error handling — validation, timeouts, failure surfacing
Backend:
- Increase ffmpeg/ffprobe timeouts from 15s to 60s for large videos
- Classify processing errors (timeout, OOM, corrupt) with user-facing messages
- Emit failure details via media:job:update socket event (error field)
- Bump refboard container memory 512M → 1G

Frontend:
- Client-side file size validation (200MB) before upload starts
- Oversized files show immediate error in upload manager, skip upload
- Handle media:job:update status='failed' — surface error in upload panel
- Add processingFailed() to UploadManager for video processing errors
2026-03-10 19:04:42 +05:30
Hiren Kangad 38fb3edf14 feat: add Upload Manager with progress tracking and status panel
- 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
2026-03-10 18:56:25 +05:30
Hiren Kangad 26527006a6 feat: auto-select imported items after upload
Pass SelectionManager to setupDragDrop and setupPaste. After each
upload completes, the newly created scene items are selected:
- Single file/URL: selectOnly
- Multi-file drop/paste: select all new items
2026-03-10 18:51:28 +05:30
Hiren Kangad 3c6a5bf29e docs: add team feedback tracker with prioritized issues
5 items: auto-select after import, upload manager, video size handling,
export as image, GIF support (product decision pending).
2026-03-10 18:46:40 +05:30
Hiren Kangad d10010c4d2 docs: add annotations & voting implementation plan
14-task phased plan covering backend (DB schema, REST endpoints, socket events),
frontend state management, FeedbackPanel, canvas PinOverlay, voting, and polish.
2026-03-10 17:28:05 +05:30
Hiren Kangad 3a6cfd59b5 docs: annotations & voting system design
Thread-centric model: one pin = one thread with flat replies.
Server-authoritative real-time sync (all clients receive canonical
events). Soft Review Mode for pin visibility without locking editing.
Denormalized thread summaries for efficient panel queries.
Public boards: read-only feedback, auth required to participate.
Phased: comments first, then canvas pins, then voting.
2026-03-10 17:17:38 +05:30
Hiren Kangad de418b2ba5 feat: media processing pipeline, spatial indexing, canvas-based video rendering
- 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
2026-03-10 14:30:01 +05:30
Hiren Kangad fdcee3536f fix(refboard): guard loadServerPoster against repeated culling calls
Add _serverPosterLoading flag so repeated 200ms culling ticks don't
call textures.load() multiple times before the first resolves. Also
release the texture ref if the sprite was destroyed or started playing
during the async load.
2026-03-10 12:24:17 +05:30
Hiren Kangad e3c3aaf558 fix(refboard): server posters respect culling budget + fix temp dir leak
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.
2026-03-10 12:19:43 +05:30
Hiren Kangad 198497381f feat(refboard): server-generated video posters via ffmpeg
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.
2026-03-10 12:10:18 +05:30
Hiren Kangad 9ecb287976 fix(refboard): force seek to decode poster frame with preload='metadata'
preload='metadata' doesn't decode video frames, so readyState stays
at 1 (HAVE_METADATA) and loadeddata never fires. capturePoster() now
detects this state and forces a seek to 0.1s via _trySeekCapture(),
which triggers frame decode and captures on the 'seeked' event.
2026-03-10 12:02:47 +05:30
Hiren Kangad 89010335be feat(refboard): video budget system, lazy lifecycle, incremental sync fixes
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
2026-03-10 11:56:33 +05:30
Hiren Kangad ae050d2119 fix(refboard): restore drag-drop with fallback + React event handlers
- 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
2026-03-10 11:22:36 +05:30