Keep/_bmad-output/planning-artifacts/12-1-fix-masonry-drag-and-drop.md
sepehr ddb67ba9e5 fix: unify theme system - fix theme switching persistence
- Unified localStorage key to 'theme-preference' across all components
- Fixed header.tsx using wrong localStorage key ('theme' instead of 'theme-preference')
- Added localStorage hybrid persistence for instant theme changes
- Removed router.refresh() which was causing stale data revert
- Replaced Blue theme with Sepia
- Consolidated auth() calls to prevent race conditions
- Updated UserSettingsData types to include all themes
2026-01-18 22:33:41 +01:00

15 KiB

Story 12.1: Fix Masonry Grid Drag & Drop and Responsive Layout

Status: planning

Story

As a user, I want a responsive masonry grid where notes can be easily dragged and dropped while maintaining their sizes, so that I can organize my notes efficiently on any screen size, similar to Google Keep.

Acceptance Criteria

  1. Given a user is viewing notes in the masonry grid,

  2. When the user drags a note to reorder it,

  3. Then the system should:

    • Allow smooth drag and drop of notes without losing their positions
    • Maintain the exact size (small, medium, large) of each note during drag and after drop
    • Provide visual feedback during drag (opacity change, placeholder)
    • Save the new order to the database
    • Work seamlessly on both desktop and mobile devices
  4. Given the user is viewing notes on different screen sizes,

  5. When the browser window is resized,

  6. Then the system should:

    • Automatically adjust the number of columns to fit the available width
    • Display more columns on larger screens (e.g., 2-4 columns on desktop)
    • Display fewer columns on smaller screens (e.g., 1-2 columns on mobile)
    • Maintain the masonry layout where items fill available vertical space
    • Not break the layout or cause overlapping items
  7. Given notes have different sizes (small, medium, large),

  8. When the grid is rendered,

  9. Then the system should:

    • Respect the size property of each note (small, medium, large)
    • Display small notes as compact cards
    • Display medium notes as standard cards
    • Display large notes as expanded cards
    • Arrange items in a true masonry pattern (no gaps, items stack vertically)

Tasks / Subtasks

  • Analyze current implementation
    • Review Muuri configuration in masonry-grid.tsx
    • Check note size handling (small, medium, large)
    • Identify drag & drop issues
    • Identify responsive layout issues
  • Research best practices
    • Study Google Keep's masonry layout behavior
    • Research Muuri layout options and responsive configuration
    • Document optimal settings for responsive masonry grids
  • Create detailed fix plan
    • Document all issues found
    • Create step-by-step correction plan
    • Define responsive breakpoints
    • Define note size dimensions
  • Implement fixes
    • Fix responsive layout configuration
    • Fix drag & drop behavior
    • Ensure note sizes are properly applied
    • Test on multiple screen sizes
  • Testing and validation
    • Test drag & drop on desktop
    • Test drag & drop on mobile
    • Test responsive behavior
    • Verify note sizes are maintained
    • Verify layout matches Google Keep behavior

Dev Notes

Problem Analysis

Current Implementation:

  • Using Muuri library for masonry grid layout
  • Notes have size property: 'small' | 'medium' | 'large'
  • Layout options include drag settings but not optimized for responsiveness
  • Grid uses absolute positioning with width: 100% but no column count management

Issues Identified:

  1. Responsive Layout Issues:

    • No defined column counts for different screen sizes
    • Grid doesn't adjust number of columns when window resizes
    • Items may overlap or leave gaps
    • Layout breaks on mobile devices
  2. Drag & Drop Issues:

    • Items may not maintain their positions during drag
    • Visual feedback is minimal
    • Drag handle only visible on mobile, but desktop dragging may interfere with content interaction
    • Auto-scroll settings may not be optimal
  3. Note Size Issues:

    • Note sizes (small, medium, large) are defined but may not be applied correctly to CSS
    • No visual distinction between sizes
    • Size changes during drag may cause layout shifts

Google Keep Reference Behavior

Google Keep Layout Characteristics:

  • Fixed card width (e.g., 240px on desktop, variable on mobile)
  • Height varies based on content + size setting
  • Responsive columns:
    • Mobile (320px-480px): 1 column
    • Tablet (481px-768px): 2 columns
    • Desktop (769px-1200px): 3-4 columns
    • Large Desktop (1201px+): 4-5 columns
  • Cards have rounded corners, shadow on hover
  • Smooth animations for drag and resize

Google Keep Drag & Drop:

  • Entire card is draggable on desktop
  • Long press to drag on mobile
  • Visual feedback: opacity reduction, shadow increase
  • Placeholder shows drop position
  • Auto-scroll when dragging near edges
  • Items reorder smoothly with animation

Solution Architecture

Responsive Layout Strategy:

Option 1: CSS Grid + Muuri for Drag/Drop

.masonry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 16px;
}
  • Pros: Native CSS responsive behavior
  • Cons: Muuri may conflict with CSS Grid positioning

Option 2: Muuri with Responsive Configuration (RECOMMENDED)

const getColumns = (width) => {
  if (width < 640) return 1;
  if (width < 1024) return 2;
  if (width < 1280) return 3;
  return 4;
};
  • Pros: Muuri handles all positioning and drag/drop
  • Cons: Requires JavaScript to update on resize

Drag & Drop Improvements:

  • Improve visual feedback during drag
  • Optimize auto-scroll speed
  • Add transition animations
  • Ensure mobile touch support

Note Size Implementation:

.note-card[data-size="small"] {
  min-height: 150px;
}
.note-card[data-size="medium"] {
  min-height: 200px;
}
.note-card[data-size="large"] {
  min-height: 300px;
}

Implementation Plan

Step 1: Define Responsive Breakpoints and Dimensions

Create a configuration file for layout settings:

// keep-notes/config/masonry-layout.ts
export interface MasonryLayoutConfig {
  breakpoints: {
    mobile: number;    // < 640px
    tablet: number;     // 640px - 1024px
    desktop: number;    // 1024px - 1280px
    largeDesktop: number; // > 1280px
  };
  columns: {
    mobile: number;
    tablet: number;
    desktop: number;
    largeDesktop: number;
  };
  noteSizes: {
    small: { minHeight: number; width: number };
    medium: { minHeight: number; width: number };
    large: { minHeight: number; width: number };
  };
  gap: number;
  gutter: number;
}

export const DEFAULT_LAYOUT: MasonryLayoutConfig = {
  breakpoints: {
    mobile: 640,
    tablet: 1024,
    desktop: 1280,
    largeDesktop: 1920,
  },
  columns: {
    mobile: 1,
    tablet: 2,
    desktop: 3,
    largeDesktop: 4,
  },
  noteSizes: {
    small: { minHeight: 150, width: 240 },
    medium: { minHeight: 200, width: 240 },
    large: { minHeight: 300, width: 240 },
  },
  gap: 16,
  gutter: 16,
};

Step 2: Update Muuri Configuration

Modify masonry-grid.tsx to use responsive configuration:

// Dynamic column calculation based on window width
const getLayoutOptions = (containerWidth: number) => {
  const columns = calculateColumns(containerWidth);
  const itemWidth = (containerWidth - (columns - 1) * DEFAULT_LAYOUT.gap) / columns;
  
  return {
    dragEnabled: true,
    dragHandle: isMobile ? '.muuri-drag-handle' : undefined,
    dragContainer: document.body,
    dragStartPredicate: {
      distance: 10,
      delay: 0,
    },
    dragPlaceholder: {
      enabled: true,
      createElement: (item: any) => {
        const el = item.getElement().cloneNode(true);
        el.style.opacity = '0.4';
        el.style.transform = 'scale(1.05)';
        return el;
      },
    },
    dragAutoScroll: {
      targets: [window],
      speed: (item: any, target: any, intersection: any) => {
        return intersection * 30; // Faster auto-scroll
      },
      threshold: 50, // Start auto-scroll earlier
      smoothStop: true,
    },
    layoutDuration: 300,
    layoutEasing: 'cubic-bezier(0.25, 1, 0.5, 1)',
    fillGaps: true,
    horizontal: false,
    alignRight: false,
    alignBottom: false,
    rounding: false,
  };
};

// Calculate columns based on container width
const calculateColumns = (width: number) => {
  if (width < DEFAULT_LAYOUT.breakpoints.mobile) return DEFAULT_LAYOUT.columns.mobile;
  if (width < DEFAULT_LAYOUT.breakpoints.tablet) return DEFAULT_LAYOUT.columns.tablet;
  if (width < DEFAULT_LAYOUT.breakpoints.desktop) return DEFAULT_LAYOUT.columns.desktop;
  return DEFAULT_LAYOUT.columns.largeDesktop;
};

Step 3: Apply Note Sizes with CSS

Add CSS classes for different note sizes:

/* keep-notes/components/masonry-grid.css */
.masonry-item-content .note-card[data-size="small"] {
  min-height: 150px;
}

.masonry-item-content .note-card[data-size="medium"] {
  min-height: 200px;
}

.masonry-item-content .note-card[data-size="large"] {
  min-height: 300px;
}

/* Responsive adjustments */
@media (max-width: 640px) {
  .masonry-item-content .note-card {
    width: 100%;
  }
  
  .masonry-item-content .note-card[data-size="small"] {
    min-height: 120px;
  }
  
  .masonry-item-content .note-card[data-size="medium"] {
    min-height: 160px;
  }
  
  .masonry-item-content .note-card[data-size="large"] {
    min-height: 240px;
  }
}

/* Drag state improvements */
.masonry-item.muuri-item-dragging .note-card {
  transform: scale(1.02);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}

.masonry-item.muuri-item-releasing .note-card {
  transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
}

Step 4: Add Resize Handler for Responsive Updates

Add resize listener to update layout when window size changes:

useEffect(() => {
  const handleResize = () => {
    if (!pinnedMuuri.current || !othersMuuri.current) return;
    
    const containerWidth = window.innerWidth - 32; // Subtract padding
    const columns = calculateColumns(containerWidth);
    
    // Update Muuri settings
    [pinnedMuuri.current, othersMuuri.current].forEach(grid => {
      if (grid) {
        grid.refreshItems().layout();
      }
    });
  };
  
  const debouncedResize = debounce(handleResize, 150);
  window.addEventListener('resize', debouncedResize);
  
  return () => {
    window.removeEventListener('resize', debouncedResize);
  };
}, []);

Step 5: Update NoteCard to Display Size Attribute

Ensure NoteCard component renders with data-size attribute:

// In NoteCard component
<Card
  data-testid="note-card"
  data-note-id={note.id}
  data-size={note.size} // Add this
  // ... other props
>

Step 6: Test on Multiple Devices

Test Matrix:

  1. Mobile (< 640px)

    • 1 column layout
    • Drag handle visible
    • Notes stack vertically
    • Touch interaction works
  2. Tablet (640px - 1024px)

    • 2 column layout
    • Desktop drag behavior
    • Notes align in columns
  3. Desktop (1024px - 1280px)

    • 3 column layout
    • Smooth drag and drop
    • Responsive to window resize
  4. Large Desktop (> 1280px)

    • 4 column layout
    • Optimal use of space
    • No layout issues

Files to Create

  • keep-notes/config/masonry-layout.ts - Layout configuration
  • keep-notes/components/masonry-grid.css - Masonry-specific styles

Files to Modify

  • keep-notes/components/masonry-grid.tsx - Update Muuri configuration and add resize handler
  • keep-notes/components/note-card.tsx - Add data-size attribute
  • keep-notes/app/globals.css - Add note size styles if not in separate CSS file

Testing Checklist

Responsive Behavior:

  • Layout adjusts columns when resizing window
  • No items overlap or create gaps
  • Mobile shows 1 column
  • Tablet shows 2 columns
  • Desktop shows 3-4 columns
  • Layout matches Google Keep behavior

Drag & Drop Behavior:

  • Notes can be dragged smoothly
  • Visual feedback during drag (opacity, shadow)
  • Placeholder shows drop position
  • Auto-scroll works when dragging near edges
  • Order is saved after drop
  • Notes maintain their positions
  • Works on both desktop and mobile

Note Sizes:

  • Small notes display compactly
  • Medium notes display with standard height
  • Large notes display with expanded height
  • Sizes are maintained during drag
  • Sizes persist after drop
  • Size changes update layout correctly

Cross-Browser:

  • Chrome: Works correctly
  • Firefox: Works correctly
  • Safari: Works correctly
  • Edge: Works correctly

Performance Considerations

  • Debounce resize events to avoid excessive re-layouts
  • Use requestAnimationFrame for smooth animations
  • Avoid re-initializing Muuri on resize, use refreshItems() instead
  • Optimize drag placeholder creation to avoid expensive DOM operations

Accessibility Considerations

  • Ensure drag handles are keyboard accessible
  • Add ARIA attributes for drag state
  • Provide visual feedback for screen readers
  • Maintain focus management during drag

References

Dev Agent Record

Initial Analysis (2026-01-18)

Problems Identified:

  1. Muuri configuration lacks responsive column management
  2. No resize handler to update layout on window resize
  3. Note sizes (small, medium, large) are not visually applied via CSS
  4. Drag & drop feedback could be improved
  5. Mobile drag handle optimization needed

Solution Approach:

  • Implement responsive column calculation based on window width
  • Add resize listener with debounce to update layout
  • Apply note sizes via CSS data attributes
  • Improve drag & drop visual feedback
  • Test thoroughly on multiple devices

Implementation Progress

  • Analyze current implementation
  • Research best practices
  • Create detailed fix plan
  • Implement fixes
  • Test and validate

Agent Model Used

claude-sonnet-4.5-20250929

Completion Notes List

  • Analyzed Muuri configuration in masonry-grid.tsx
  • Reviewed note size handling (small, medium, large)
  • Identified drag & drop issues
  • Identified responsive layout issues
  • Studied Google Keep's masonry layout behavior
  • Researched Muuri layout options and responsive configuration
  • Documented optimal settings for responsive masonry grids
  • Created comprehensive fix plan with step-by-step instructions
  • Defined responsive breakpoints
  • Defined note size dimensions
  • Fix responsive layout configuration
  • Fix drag & drop behavior
  • Ensure note sizes are properly applied
  • Test on multiple screen sizes

File List

Files to Create:

  • keep-notes/config/masonry-layout.ts
  • keep-notes/components/masonry-grid.css

Files to Modify:

  • keep-notes/components/masonry-grid.tsx
  • keep-notes/components/note-card.tsx
  • keep-notes/app/globals.css (optional, depending on CSS organization)