Commit Graph
66 Commits
Author SHA1 Message Date
Hiren Kangad 479feb991c fix: block flip/rotate in SelectionToolbar for sticky, text, markdown, pdf-page
The toolbar buttons called ops directly without type filtering.
Now filters NON_TRANSFORMABLE types before applying flip/rotate ops.
2026-03-14 16:51:02 +05:30
Hiren Kangad 9c8b3cc5f5 feat: wire PdfPickerModal into Editor with hires texture upgrade via socket 2026-03-14 11:31:45 +05:30
Hiren Kangad a901833b72 fix: rotated transform box, video texture crash, paste duplication, and loading UX
- TransformBox rotates with single-item selection (Figma-style), with
  handles and resize math projected into rotated coordinate space
- Fix VideoSprite alphaMode crash by swapping texture to EMPTY before
  destroying, preventing PixiJS render loop from reading null source
- Fix Ctrl+V double-paste: internal clipboard now always takes priority
  over system clipboard PNG, with wasRecentInternalPaste() guard
- Add asset loading progress bar and suppress "Drop images here" flash
  during initial scene load
2026-03-13 17:37:21 +05:30
Hiren Kangad 05118330d6 feat: unified composition renderer for clipboard/export
Replace the split snapshot/native renderer paths with a single
composition pipeline (compositionRenderer.ts) that:

- Loads actual source images and uses naturalWidth/Height for
  correct full-resolution sampling (fixes top-left-corner-only bug
  caused by data.w/h being capped to 600px display dimensions)
- Routes image-only selections through native Canvas 2D composition
- Falls back to viewport snapshot for mixed/unsupported selections
  with explicit warnings instead of silent degradation
- Resolves group children via itemResolver for proper group export
- Rejects group children from native composition (local coords
  incompatible with world-space drawing)
- Adds canvas size safety limits with auto-downscale
- Guards VideoSprite._drawFrame against null texture source race
  condition during zoom-triggered culling

New files:
- compositionRenderer.ts — unified composition module
- compositionRenderer.test.ts — 16 tests for entry flattening,
  bounds, dimensions, group handling

Modified:
- clipboard.ts — uses composeSelection() instead of direct renderers
- export.ts — uses composeSelection() + getCompositionDimensions()
- Editor.tsx, useShortcutHandler.ts — pass scene for group resolution
- VideoSprite.ts — null guard on texture source in frame loop
2026-03-13 16:59:11 +05:30
Hiren Kangad ae9b1ecefe refboard: avoid eager extract fallback for canvas snapshots 2026-03-13 15:36:12 +05:30
Hiren Kangad cd8207e984 refboard: capture board previews from rendered canvas 2026-03-13 15:23:08 +05:30
Hiren Kangad 98fb7be77e refboard: add visible rotate controls 2026-03-13 15:01:25 +05:30
Hiren Kangad accbe2e0f3 fix(markdown): polish editor UX — side panel, overlay sync, paste segregation
- Move markdown editor from canvas overlay to side panel (70% width)
  for better performance and editing experience
- Add @blocknote/mantine for full BlockNoteView with default UI components
- Fix socket reconnect loop caused by unstable pasteOpts object reference
  (memoize with useMemo)
- Add ResizeObserver to markdown overlay cards for automatic height sync
- Call mdOverlay.refreshAll() on every canvas change so overlays track
  pack/grid/arrange/save operations
- Paste goes to BlockNote editor when contentEditable is focused
- Simplify toolbar: single color picker (accent + auto-derived bg),
  title/name field, width presets S/M/L
- Guard normalize/flip operations to skip markdown and sticky items
- Fix title not updating on card preview (pass name prop, bump revision)
- Fix preview not refreshing after editor save (revision counter + overlay refresh)
2026-03-13 11:21:28 +05:30
Hiren Kangad a7579a9ff5 refboard: add manual board preview refresh button 2026-03-13 11:21:28 +05:30
Hiren Kangad b350951bf1 refboard: merge crop mode polish with markdown editor 2026-03-13 11:21:28 +05:30
Hiren Kangad dfdfd07a1d feat(markdown): add contextual toolbar for background color, accent color, and width presets 2026-03-13 11:21:28 +05:30
Hiren Kangad aa3112a5bc feat(markdown): add paste detection with PasteChoicePopup for text/markdown/image
Simplify Ctrl+V shortcut to only handle recent internal copies, letting
native paste events flow to setupPaste for external clipboard content.
Extend setupPaste with text/HTML detection and popup callbacks. Wire
PasteChoicePopup in Editor.tsx with markdown (turndown HTML-to-MD),
plain text, and image paste choices.
2026-03-13 11:21:28 +05:30
Hiren Kangad b1163c3232 feat(markdown): integrate undo/redo for edit mode and checkbox toggles 2026-03-13 11:21:28 +05:30
Hiren Kangad 10135463b1 feat(markdown): wire MarkdownOverlay into canvas lifecycle with React portals 2026-03-13 11:21:27 +05:30
Hiren Kangad dd1578110b refboard: support rotated image crop editing 2026-03-13 11:21:27 +05:30
Hiren Kangad 12410c0098 refboard: stabilize image flip and crop transforms 2026-03-13 11:21:27 +05:30
Hiren Kangad f77ac59e6c fix(sticky): make size presets absolute again 2026-03-13 00:50:21 +05:30
Hiren Kangad ce1cc60d67 fix(sticky): restore zoom-aware preset creation and live editing sync 2026-03-13 00:42:32 +05:30
Hiren Kangad 2d05226ddd fix(sticky): fixed-size creation, shared presets module, live resize sync
Fixes three review findings:

1. Sticky creation no longer zoom-adapts — always creates at M preset
   (28px/260w) in world space. Consistent at any zoom level.

2. Preset definitions extracted to stickyPresets.ts (shared domain module).
   TextFormatToolbar and tools.ts both import from there — no more
   canvas→component dependency inversion.

3. TextEditor.onLiveResize callback syncs spatial index and transform
   box as sticky background grows during typing.
2026-03-13 00:31:44 +05:30
Hiren Kangad 706b00cf35 feat(sticky): proportional card width per text size preset
Each S/M/L/XL/XXL preset now sets both fontSize and card width:
S(20px/200w), M(28px/260w), L(36px/320w), XL(48px/400w), XXL(60px/480w).

Creation picks preset based on zoom, toolbar toggle updates both.
Wider size toggle buttons for XL/XXL labels, font name maxWidth 110px.
Backward compatible — existing stickies keep original width.
2026-03-13 00:26:09 +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 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 5158938eb3 refactor(text): update Editor TextFormatToolbar to use TextSprite API 2026-03-12 22:09:12 +05:30
Hiren Kangad ecd4408d8a feat(review): group-aware comment targeting — resolve clicks to stable children
New reviewTargeting.ts provides resolveReviewTargetAtPoint() which:
- descends into groups to find the deepest commentable child under the click
- walks children in z-order (front-to-back) for correct visual stacking
- handles nested groups recursively
- returns null if no valid child exists under the point

Editor.tsx review click handler now delegates to the resolver instead of
raw queryRegion + topmost-z pick. Defensive guard rejects group objectIds
before draft creation.

Existing group-anchored threads continue to render and open normally.
2026-03-12 22:09:12 +05:30
Hiren Kangad 1134ddb5c9 refactor(review): use shared getPointAnchorWorld in jump-to-object 2026-03-12 22:09:12 +05:30
Hiren Kangad 40153751f6 refactor(review): remove panel draft composer, move suppression to Editor 2026-03-12 22:09:12 +05:30
Hiren Kangad 54a7557233 feat(review): wire InlineCommentComposer into Editor with anchored positioning 2026-03-12 22:09:12 +05:30
Hiren Kangad cf879b34d5 feat(review): add draftCommentText state and double-submit guard 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 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 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 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 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 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 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 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 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