Commit Graph
31 Commits
Author SHA1 Message Date
Hiren Kangad b081ed7169 refactor(sticky): fixed-width card with S/M/L/XL/XXL font presets
Stickies are now fixed-width cards — no resize handles shown.
Text size controlled via 5 presets (10/14/18/24/32px).
On creation, zoom-aware screenToWorld snaps to nearest preset.
Height auto-adjusts from content. Plain text resize unchanged.

Removes: sticky width bake, live reflow drag, min width constraint.
Backward compatible — existing stickies map to nearest preset.
2026-03-13 00:18:41 +05:30
Hiren Kangad 1631185e4f feat(sticky): S/M/L text size toggle, live reflow resize, min width
Sticky UX overhaul — clear separation between card resize and text size:

- S/M/L toggle (12/14/18px) in contextual toolbar for sticky text size
- TransformBox: horizontal-only resize for stickies with live text
  reflow during drag (no more stretch-then-snap)
- Hide vertical handles (tc/bc) for sticky-only selections
- Minimum sticky width of 80px enforced in both drag and bake
- TextEditor: sticky background auto-resizes as user types
- textLimits.ts comment corrected to match actual usage

Plain text unchanged: resize gesture scales fontSize via bake.
Fully backward compatible — existing stickies map to nearest preset.
2026-03-13 00:12:23 +05:30
Hiren Kangad a20cab3fcd refactor: extract TextCore shared text engine + sticky width-only resize
TextCore.ts: shared text rendering with dirty-check caching, zoom-bucket
resolution, and optional word-wrap. TextSprite and StickySprite now
compose TextCore instead of duplicating text logic.

Sticky resize now bakes width only (fontSize stays stable), so dragging
a sticky wider/narrower reflows text with auto-adjusted height — like
a resizable text box.
2026-03-12 23:53:06 +05:30
Hiren Kangad d9c1f4c600 fix(sticky): bake scale into fontSize + width on sticky resize
Sticky text was blurry after scaling because sx/sy stretched the
texture without re-rasterizing. Now bakes fontSize and card width
on drag-end (same as plain text), so StickySprite recomputes layout
at native resolution.
2026-03-12 23:44:43 +05:30
Hiren Kangad eb4591dca0 fix: remove font-size clamp from resize-bake path
Direct manipulation (scaling text up/down) should respect user intent
without artificial caps. Clamps remain on creation defaults only.
2026-03-12 23:38:47 +05:30
Hiren Kangad 150bb91a27 refactor: centralize font-size clamps in textLimits.ts
Single source of truth for font-size constraints (text 6–160, sticky
8–96) used by creation defaults, resize-bake, and any future mutation
paths. Replaces scattered hardcoded min/max values.
2026-03-12 23:35:25 +05:30
Hiren Kangad 1ac727ea60 feat: zoom-bucket text sharpness + remove fontSize toolbar controls
Add TextSharpnessManager for crisp text rendering at all zoom levels:
- Discrete zoom buckets (0.5–3x) avoid texture churn on small zoom changes
- Initial bucket applied to all items on setup and newly created items
  via SceneManager.onItemCreated callback (fixes blurry-on-load)
- Visibility check uses getItemWorldBounds for correct grouped item coords
- TextSprite/StickySprite gain setZoomBucket() for resolution control

Remove fontSize +/- controls from contextual toolbar — font size is now
controlled purely through direct manipulation (resize → bake).
2026-03-12 23:23:30 +05:30
Hiren Kangad 9fac16e6a4 feat: contextual text formatting toolbar + resize fontSize baking
- Remove persistent font-size slider from main toolbar (tools only)
- Lock in zoom-aware creation defaults as named constants
- Split contextual toolbar by object type: text vs sticky
  - Text: fontSize + fontFamily + text color
  - Sticky: fontSize + fontFamily + text color + note fill color
  - Mixed selection: hide toolbar
- Bake scale into fontSize on text resize (reset sx/sy to 1)
- Sticky resize stays layout-driven (no font change)
2026-03-12 22:47:48 +05:30
Hiren Kangad 83e753bb69 perf: remove thumbnail generation from hot autosave path
extract.canvas(viewport) snapshots the entire viewport to a WebGL
texture on the main thread. When viewport dimensions × devicePixelRatio
exceed GPU limits, this causes texImage2D errors and ~1000ms stalls
that block focus, typing, and scroll. Thumbnails can be generated on
idle or server-side instead.
2026-03-12 22:24:59 +05:30
Hiren Kangad e5cf3d3632 feat(review): add useAnchoredOverlayPosition hook for world→screen conversion 2026-03-12 22:09:12 +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 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 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 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 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
Hiren Kangad 205ce65a8b fix(refboard): drag-drop opening in new tab — use ref for drop target
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.
2026-03-10 11:20:26 +05:30
Hiren Kangad 21ee4b2601 feat(refboard): incremental sync — stop full-scene broadcast on edits
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.
2026-03-10 10:59:04 +05:30
Hiren Kangad 451ec339a5 fix(refboard): prevent WebGL OOM on large scenes
- Skip thumbnail generation when scene has >50 items (extract.canvas
  on full viewport with many textures causes GPU OOM and context loss)
- Add WebGL context lost/restored handlers — auto-reload scene on recovery
- Reduce texture memory budget from 512MB to 256MB for GPU headroom
2026-03-10 10:45:14 +05:30
Hiren Kangad cebe4a3eb9 fix(refboard): full sync audit — batch multi-drag, persist resize, sync lock
- 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
2026-03-10 04:16:14 +05:30
Hiren Kangad e9509ef7b0 feat(refboard): canvas polish — transform box sync, dot grid, snap tuning, perf fixes
- Fix transform box not updating after alignment/arrangement/normalize/flip operations
  (shortcuts, context menu, and selection toolbar all fixed with DRY _opUpdate helper)
- Replace 100ms polling loop with event-driven viewport updates (moved + wheel-scroll)
- Add adaptive dot grid background that responds to zoom/pan
- Reduce snap guide threshold from 8px to 4px for subtler snapping
- Remove PresenceOverlay (remote selection highlighting) — too heavy for minimal benefit
- Offset multiple dropped images so they don't overlap
- Add new canvas modules: SnapGuides, clipboard, grouping, FrameSprite, DrawingSprite,
  LaserPointer, context-menu-items, SelectionToolbar, Minimap
- Extract Editor hooks into dedicated files (useBoardLoader, useCanvasSetup,
  useShortcutHandler, useLayerPanel, useSaveManager, useFollowMode)
- Sync improvements: real-time transform broadcast, board rooms, viewport sync
2026-03-10 03:58:53 +05:30
Hiren Kangad 8a32119997 Add inline text editing on double-click for RefBoard text elements
Introduces TextEditor utility that overlays a styled <textarea> matching
the PixiJS Text object's font, size, and color. Hides the PixiJS text
while editing, commits on blur/Enter, cancels on Escape, and updates
dimensions from measured bounds on save.
2026-03-10 02:50:47 +05:30