Skip to content

Conversation

@ffischbach-ino
Copy link
Member

@ffischbach-ino ffischbach-ino commented Sep 15, 2025

Description

This feature implements real-time card locking to prevent multiple users from simultaneously dragging the same card, eliminating conflicting drag operations. When a user starts dragging a card, other users see visual indicators showing who is currently moving it and are prevented from interfering with the drag operation.

Changelog

Added real-time collaborative drag locking system that displays user avatars and names on cards being moved by other participants, preventing drag conflicts and improving the collaborative editing experience. The system includes WebSocket-based event broadcasting, Redux state management, and comprehensive visual feedback with proper error handling for network failures

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • The light- and dark-theme are both supported and tested
  • The design was implemented and is responsive for all devices and screen sizes
  • The application was tested in the most commonly used browsers (e.g. Chrome, Firefox, Safari)

(Optional) Visual Changes

@ffischbach-ino ffischbach-ino force-pushed the ff/disable-note-moving-for-others-while-dragging-#2891 branch from 0bb8014 to c2942e0 Compare September 15, 2025 11:13
@Schwehn42 Schwehn42 added the Changes Requested Changes requested by the reviewer label Sep 16, 2025
Copy link
Member

@Schwehn42 Schwehn42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locks aren't sent to users who join after it was created:

  1. User A: start dragging a note (can be done on mobile to keep the lock)
  2. User B: reload page
  3. result: lock is not displayed for User B

@Schwehn42 Schwehn42 removed the Changes Requested Changes requested by the reviewer label Oct 9, 2025
Copy link
Member

@Schwehn42 Schwehn42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me all in all!

only comment I have is, currently the websocket handler for incoming messages is part of draglocks itself. would it make sense to abstract/generalize in order to make it usable for other use cases as well?

@Planlos5000
Copy link
Collaborator

I would like to revist the backend implementation for this feature.
I currently found out that nats can provide a key value store with a ttl (https://docs.nats.io/nats-concepts/jetstream/key-value-store) and so can redis. Since we already use nats or redis for messaging, this could be a better fit for this feature to store the lock.

@Planlos5000 Planlos5000 force-pushed the ff/disable-note-moving-for-others-while-dragging-#2891 branch 6 times, most recently from 9e4670c to 0a86128 Compare November 3, 2025 12:10
@Planlos5000 Planlos5000 force-pushed the ff/disable-note-moving-for-others-while-dragging-#2891 branch from e12cb40 to 02c1af4 Compare November 10, 2025 10:43
@Planlos5000 Planlos5000 force-pushed the ff/disable-note-moving-for-others-while-dragging-#2891 branch 2 times, most recently from e42bb46 to 5e48481 Compare December 3, 2025 08:55
@Planlos5000 Planlos5000 added the feat This pull request or issue adds a new feature label Dec 3, 2025
@Planlos5000 Planlos5000 linked an issue Dec 3, 2025 that may be closed by this pull request
@Planlos5000 Planlos5000 force-pushed the ff/disable-note-moving-for-others-while-dragging-#2891 branch from 5e48481 to ead38bd Compare December 3, 2025 20:57
Copy link
Member

@Schwehn42 Schwehn42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dragging a note results in error {"level":"error","ts":1765883370.748571,"caller":"cache/nats.go:109","msg":"key does not exist","requestID":"ffaa04ed1986/SDosvy3TId-000057","key":"a4dcee40-05bc-4352-88ee-3b2cf5a11c85","error":"nats: key not found"}.

one time it even crashed the server, saying {"level":"error","ts":1765883359.650216,"caller":"notes/service.go:444","msg":"failed to get lock","requestID":"ffaa04ed1986/SDosvy3TId-000052","err":"nats: key not found"}, though I couldn't consistently reproduce that.

Prevent multiple users from simultaneously dragging the same card by implementing
a real-time locking system with visual feedback.

- Add NOTE_DRAG_START and NOTE_DRAG_END WebSocket events
- Create dragLocks Redux slice for state management
- Add API endpoints for broadcasting drag events
- Implement visual indicators showing which user is dragging
- Display user avatar and name on locked cards
- Disable drag interactions on locked cards
- Add proper styling with DragLockIndicator.scss

This prevents conflicting drag operations and provides clear visual feedback
about who is currently moving each card.
Extract remaining inline styles from Sortable.tsx to Sortable.scss for better
maintainability and separation of concerns. All styling is now properly
organized in CSS files with BEM methodology.
Revert shouldCombine class name to maintain compatibility with existing
Note.scss styling that depends on .shouldCombine selector for visual
feedback during note stacking operations.
Remove console.log and console.warn statements from drag lock
implementation to resolve ESLint warnings while maintaining
silent error handling for better user experience.
- Implement in-memory drag lock management with automatic cleanup
- Add configurable timeout and cleanup intervals with sensible defaults
- Support concurrent access with RWMutex for thread safety
- Include background cleanup goroutine for expired locks
- Provide dependency injection interface for testability
- Create drag_locks table with note_id as primary key
- Add foreign key constraints to notes, users, and boards
- Include indexes for performance optimization
- Support atomic lock operations across multiple pods
ffischbach-ino and others added 27 commits January 8, 2026 15:44
- Add nil dragLocks parameter to New() function call in board templates test
- Fixes test compilation error due to updated router signature
- Required for API integration tests to pass
Update sendWebSocketMessage parameter type from 'any' to 'string' to improve type safety and align with WebSocket API expectations.
Add DragLockMessage interface and ClientMessage union type to support
type-safe client-to-server WebSocket messaging for drag lock operations.
Update sendWebSocketMessage function to accept ClientMessage type
instead of generic string parameter, ensuring type safety for
WebSocket client messages.
- Add DragOverlay.scss for subtle column color tinting of dragged notes
- Update CustomDndContext to pass colorClassName to dragged note
- Apply background color and border styling based on original column
- Include dark theme support with appropriate color variants
- Move indicator to bottom-right outside the note (-12px positioning)
- Increase z-index for better visibility
- Simplify styles by removing custom pill styling
- Add enhanced shadow for prominence in both light and dark themes
- Replace custom drag indicator with note-author__container--self styling
- Import and reuse NoteAuthorList.scss for consistent design
- Switch from Avatar to UserAvatar component for proper prop support
- Maintain semantic structure with figure and aria attributes
- Apply column color theming through existing CSS variables
- Rename shadowed variable 'note' to 'targetNote'
- Replace unsafe non-null assertion with safe fallback for user ID
- Create dedicated DragIndicatorPill component for drag indicators
- Replace complex inline note-author structure in Sortable component
- Simplify DragLockIndicator.scss to only handle positioning
- Add proper TypeScript types with AvataaarProps
- Enable automatic column color theming through CSS variables
- Improve code maintainability and component reusability
- Import AvataaarProps from 'types/avatar' instead of 'avataaars'
- Align with codebase conventions used in other components
- Add flexbox centering properties to avatar container
- Ensure avatar is perfectly centered horizontally and vertically
- Improve visual alignment of user avatars in drag indicators
- Change avatar dimensions from spacing-md to icon-medium
- Use semantic icon sizing constants for better consistency
- Maintain visual proportions while following design system conventions
- Add 'isMovingThis' translation key for English and German
- Prepare translation infrastructure for drag indicator messages
- Translation values need completion in follow-up
- Reduce opacity of locked notes from 0.7 to 0.4
- Make it more obvious when notes are being dragged by other users
- Enhance user experience by clearly indicating non-interactive state
@Planlos5000 Planlos5000 force-pushed the ff/disable-note-moving-for-others-while-dragging-#2891 branch from 7e518a3 to 46ebefe Compare January 8, 2026 14:55
@github-actions
Copy link

github-actions bot commented Jan 8, 2026

The deployment to the dev cluster was successful. You can find the deployment here: https://5410.development.scrumlr.fra.ics.inovex.io
This deployment is only for testing purposes and will be deleted after 1 week.
To redeploy rerun the workflow.
DO NOT STORE IMPORTANT DATA ON THIS DEPLOYMENT

Deployed Images
  • ghcr.io/inovex/scrumlr.io/scrumlr-frontend:sha-66afb59

  • ghcr.io/inovex/scrumlr.io/scrumlr-server:sha-66afb59

@Planlos5000
Copy link
Collaborator

Currenly we sometimes get the following error when draging notes

{"level":"debug","ts":1767889890.2045777,"msg":"received message","requestID":"florian-inovex/mcy26hRnNi-000015","message":"eyJ0eXBlIjoiRFJBR19MT0NLX01FU1NBR0UiLCJkYXRhIjp7ImFjdGlvbiI6IkFDUVVJUkUiLCJub3RlSWQiOiJmOTY3NzU2Yy0zNDc3LTQyMDItYTBmOS04NmM3NTQ2MTE2OTcifX0="}
{"level":"debug","ts":1767889890.2171407,"msg":"broadcasting to board","requestID":"florian-inovex/mcy26hRnNi-000015","board":"6cf3b717-02fd-4ce0-a97b-e2a2dc4084a6","msg":"NOTE_DRAG_START"}
{"level":"debug","ts":1767889890.2183917,"msg":"message received","message":{"type":"NOTE_DRAG_START","data":{"noteId":"f967756c-3477-4202-a0f9-86c754611697","userId":"3b029f15-33a3-4037-9cd5-abcfd7422de2"}}}
{"level":"debug","ts":1767889890.6444461,"msg":"received message","requestID":"florian-inovex/mcy26hRnNi-000015","message":"eyJ0eXBlIjoiRFJBR19MT0NLX01FU1NBR0UiLCJkYXRhIjp7ImFjdGlvbiI6IlJFTEVBU0UiLCJub3RlSWQiOiJmOTY3NzU2Yy0zNDc3LTQyMDItYTBmOS04NmM3NTQ2MTE2OTcifX0="}
{"level":"debug","ts":1767889890.6471694,"msg":"broadcasting to board","requestID":"florian-inovex/mcy26hRnNi-000015","board":"6cf3b717-02fd-4ce0-a97b-e2a2dc4084a6","msg":"NOTE_DRAG_END"}
{"level":"debug","ts":1767889890.6473963,"msg":"received message","requestID":"florian-inovex/mcy26hRnNi-000015","message":"eyJ0eXBlIjoiRFJBR19MT0NLX01FU1NBR0UiLCJkYXRhIjp7ImFjdGlvbiI6IlJFTEVBU0UiLCJub3RlSWQiOiJmOTY3NzU2Yy0zNDc3LTQyMDItYTBmOS04NmM3NTQ2MTE2OTcifX0="}
{"level":"debug","ts":1767889890.6478271,"msg":"message received","message":{"type":"NOTE_DRAG_END","data":{"noteId":"f967756c-3477-4202-a0f9-86c754611697","userId":"3b029f15-33a3-4037-9cd5-abcfd7422de2"}}}
{"level":"debug","ts":1767889890.6498935,"msg":"broadcasting to board","requestID":"florian-inovex/mcy26hRnNi-000015","board":"6cf3b717-02fd-4ce0-a97b-e2a2dc4084a6","msg":"NOTE_DRAG_END"}
{"level":"debug","ts":1767889890.6506217,"msg":"message received","message":{"type":"NOTE_DRAG_END","data":{"noteId":"f967756c-3477-4202-a0f9-86c754611697","userId":"3b029f15-33a3-4037-9cd5-abcfd7422de2"}}}
{"level":"debug","ts":1767889901.3898916,"msg":"received message","requestID":"florian-inovex/mcy26hRnNi-000015","message":"eyJ0eXBlIjoiRFJBR19MT0NLX01FU1NBR0UiLCJkYXRhIjp7ImFjdGlvbiI6IkFDUVVJUkUiLCJub3RlSWQiOiI2MDUxYWUzOC0yNTRkLTRkNjUtOWE5Mi1lMTUwOGQwMDJjNWEifX0="}
panic: concurrent write to websocket connection

goroutine 160 [running]:
github.com/gorilla/websocket.(*messageWriter).flushFrame(0xc000bf43c0, 0x1, {0x0, 0x0, 0x0})
	/home/florian/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:617 +0x985
github.com/gorilla/websocket.(*messageWriter).Close(0xc000bf43c0)
	/home/florian/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:731 +0x5e
github.com/gorilla/websocket.(*Conn).beginMessage(0xc0004ee000, 0xc00074e390, 0x1)
	/home/florian/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:480 +0x68
github.com/gorilla/websocket.(*Conn).NextWriter(0xc0004ee000, 0x1)
	/home/florian/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:520 +0xa5
github.com/gorilla/websocket.(*Conn).WriteJSON(0xc0004ee000, {0x19f90e0, 0xc000a0c340})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat This pull request or issue adds a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hidden note while moving

4 participants