Adds an audit trail per board, visible from a new clock-icon button on the
toolbar. Useful for review-style work where someone wants to see who
contributed which references and when.
Logged events (high-signal only — canvas-edit noise intentionally skipped):
- image / video / pdf added (whether dropped, pasted, or pulled from a URL)
- board created / renamed / deleted
- thread started, resolved, reopened
- comment posted on a thread
Backend:
- new activity_logs table (id, board_id, user_id, denormalised actor name +
email, action, target_type/id/label, metadata JSON, created_at) with an
index on (board_id, created_at DESC).
- logActivity helper resolves the user once at log time and stores their
display name + email so entries survive deactivation/rename.
- recordActivity wraps logActivity + a Socket.IO emit to the board's room
so the panel updates live without polling.
- GET /api/boards/:id/activity?limit=&before= for pagination
(collection-membership gated, viewer+).
Frontend:
- ActivityPanel side-drawer: time-grouped feed (Today / Yesterday / older),
per-action icons + tone colours (add/remove/edit/comment), pagination
via "Load older", live append on Socket.IO 'activity:new'.
- Relative timestamps refresh every 30s.
- Wired into Editor + Toolbar.
README updated; roadmap entry checked off.
- 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'