- 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
25 KiB
Epic 12: Mobile Experience Overhaul
Status: ready-for-dev
Epic Overview
Epic Goal: Transform Keep's interface into a truly mobile-first experience while keeping the desktop interface unchanged.
User Pain Points:
- Interface overflows device screen (Galaxy S22 Ultra)
- Note cards too complex and large for mobile
- Masonry grid layout not suitable for small screens
- Too much visual information on mobile
- No mobile-specific UX patterns
Success Criteria:
- ✅ No horizontal/vertical overflow on any mobile device
- ✅ Simplified note cards optimized for mobile viewing
- ✅ Mobile-first layouts that adapt to screen size
- ✅ Smooth 60fps animations on mobile
- ✅ Touch-friendly interactions (44x44px min targets)
- ✅ Desktop interface completely unchanged
- ✅ Tested on Galaxy S22 Ultra and various mobile devices
Story 12.1: Mobile Note Cards Simplification
Status: ready-for-dev
Story
As a mobile user, I want simple, compact note cards, so that I can see more notes and scan the interface quickly.
Acceptance Criteria
- Given a user is viewing notes on a mobile device (< 768px),
- When notes are displayed,
- Then the system should:
- Display notes in a vertical list (NOT masonry grid)
- Show simple card with title + 2-3 lines of preview only
- Minimize badges and indicators (pin, labels, notebook)
- Hide image thumbnails on mobile
- Ensure touch targets are minimum 44x44px
- Implement swipe-to-delete or quick actions
Tasks / Subtasks
- Create mobile variant of NoteCard component
- Create
MobileNoteCard.tsxcomponent - Vertical card layout (not masonry)
- Simplified content: title + 2-3 lines preview
- Reduced badges (pin icon, label count only)
- No image thumbnails on mobile
- Create
- Implement mobile list layout
- Replace masonry grid with simple list on mobile
- 100% width cards on mobile
- Adequate spacing between cards
- Add mobile touch interactions
- Tap to open note (full screen)
- Long-press for actions menu
- Swipe gestures (left/right actions)
- Ensure responsive design
- Mobile cards: < 768px
- Desktop cards: >= 768px (UNCHANGED)
- Smooth transition between breakpoints
- Test on mobile devices
- Galaxy S22 Ultra (main target)
- iPhone SE (small screen)
- Android various sizes
- Portrait and landscape
Dev Notes
Mobile Card Design Requirements
Layout:
┌─────────────────────────────┐
│ [PIN] Title │ <- Title row with pin icon
│ Preview text... │ <- 2-3 lines max
│ [📎] [🏷️] • 2d ago │ <- Footer: indicators + time
└─────────────────────────────┘
Typography (Mobile):
- Title: 16-18px, semibold, 1 line clamp
- Preview: 14px, regular, 2-3 lines clamp
- Footer text: 12px, lighter color
Spacing (Mobile):
- Card padding: 12-16px
- Gap between cards: 8-12px
- Touch targets: 44x44px minimum
Color & Contrast:
- Light background on cards
- Good contrast for readability
- Subtle hover state
Swipe Gestures Implementation
Swipe Left → Archive
// Use react-swipeable or similar
<Swipeable
onSwipeLeft={() => handleArchive(note)}
onSwipeRight={() => handlePin(note)}
threshold={50}
>
<MobileNoteCard note={note} />
</Swipeable>
Swipe Right → Pin Long Press → Action Menu
Responsive Logic
// In page.tsx
const isMobile = useMediaQuery('(max-width: 768px)')
{isMobile ? (
<div className="flex flex-col gap-3">
{notes.map(note => <MobileNoteCard key={note.id} note={note} />)}
</div>
) : (
<MasonryGrid notes={notes} /> // Existing desktop behavior
)}
Files to Create
keep-notes/components/mobile-note-card.tsx- New mobile-specific componentkeep-notes/components/swipeable-wrapper.tsx- Swipe gesture wrapper
Files to Modify
keep-notes/app/(main)/page.tsx- Conditional rendering for mobile/desktopkeep-notes/components/note-card.tsx- No changes (keep desktop version intact)
Story 12.2: Mobile-First Layout
Status: ready-for-dev
Story
As a mobile user, I want an interface optimized for my small screen, so that everything is accessible without zooming or horizontal scrolling.
Acceptance Criteria
- Given a user is using the app on a mobile device,
- When viewing any page,
- Then the system should:
- Use 100% width containers on mobile
- Reduce margins/padding on mobile
- Use compact header on mobile (60-80px vs 80px)
- Simplified note input on mobile
- Eliminate ALL horizontal overflow
- Prevent double scroll (menu + page)
- Maintain existing desktop layout unchanged
Tasks / Subtasks
- Create responsive container layout
- Use
w-fullon mobile containers - Reduce padding on mobile (px-4 vs px-6)
- Remove max-width constraints on mobile
- Use
- Optimize header for mobile
- Reduce header height on mobile (60px vs 80px)
- Compact search bar on mobile
- Hide non-essential controls on mobile
- Simplify note input on mobile
- Use minimal input on mobile
- Placeholder text: "Add a note..."
- Full FAB button for creating notes
- Fix horizontal overflow issues
- Use
overflow-x-hiddenon body - Ensure no fixed widths on mobile
- Test on Galaxy S22 Ultra (main target)
- Use
- Test on various screen sizes
- Small phones: 320-375px
- Medium phones: 375-428px
- Large phones: 428px+ (Galaxy S22 Ultra)
- Tablets: 768-1024px
Dev Notes
Breakpoint Strategy
/* Mobile First Approach */
/* Mobile: 0-767px */
.container {
width: 100%;
padding: 0.5rem 1rem;
}
/* Tablet: 768px+ */
@media (min-width: 768px) {
.container {
max-width: 1280px;
padding: 2rem 3rem;
}
}
Header Optimization
Desktop (current):
- Height: 80px
- Padding: px-6 lg:px-12
- Search: max-w-2xl
Mobile (new):
- Height: 60px
- Padding: px-4
- Search: flex-1, shorter
Note Input Simplification
Desktop: Full card with title, content, options
Mobile:
<div className="fixed bottom-20 right-4 z-40">
<FabButton onClick={openMobileNoteEditor}>
<Plus className="h-6 w-6" />
</FabButton>
</div>
Files to Create
keep-notes/components/fab-button.tsx- Floating Action Buttonkeep-notes/hooks/use-media-query.ts- Hook for responsive queries
Files to Modify
keep-notes/components/header.tsx- Responsive headerkeep-notes/components/note-input.tsx- Mobile variantkeep-notes/app/(main)/page.tsx- Container adjustmentskeep-notes/app/globals.css- Responsive utilities
Story 12.3: Mobile Bottom Navigation
Status: ready-for-dev
Story
As a mobile user, I want easy-to-access navigation tabs, so that I can quickly switch between views.
Acceptance Criteria
- Given a user is on a mobile device,
- When navigating the app,
- Then the system should:
- Display horizontal tabs at bottom of screen (Bottom Navigation)
- Show 3-4 tabs max (Notes, Favorites, Settings)
- Clearly indicate active tab
- Animate transitions between tabs
- NOT affect desktop interface (unchanged)
Tasks / Subtasks
- Create Bottom Navigation component
- Create
MobileBottomNav.tsxcomponent - 3 tabs: Notes, Favorites, Settings
- Icons for each tab
- Active state indicator
- Create
- Implement tab navigation logic
- Switch between views (Notes, Favorites, Settings)
- Maintain state on tab switch
- Animate transitions
- Style for mobile UX
- Fixed position at bottom
- Height: 56-64px (standard mobile nav)
- Safe area padding for iPhone notch
- Material Design / iOS Human Guidelines compliant
- Test on mobile devices
- Android (including Galaxy S22 Ultra)
- iOS (iPhone SE, 14 Pro)
- Different screen orientations
- Ensure desktop unchanged
- Only show on mobile (< 768px)
- No CSS conflicts with desktop layout
Dev Notes
Bottom Navigation Design
Layout:
┌─────────────────────────────────┐
│ [📝 Notes] [⭐ Favs] [⚙️] │
└─────────────────────────────────┘
^ Active (with underline/indicator)
Material Design Spec:
- Height: 56px minimum
- Icons: 24x24px
- Labels: 12-14px (can be hidden on very small screens)
- Active indicator: 4px height bar below icon
Implementation:
// keep-notes/components/MobileBottomNav.tsx
'use client'
import { Home, Star, Settings } from 'lucide-react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
export function MobileBottomNav() {
const pathname = usePathname()
const tabs = [
{ icon: Home, label: 'Notes', href: '/' },
{ icon: Star, label: 'Favorites', href: '/favorites' },
{ icon: Settings, label: 'Settings', href: '/settings' },
]
return (
<nav className="fixed bottom-0 left-0 right-0 bg-white dark:bg-slate-900 border-t lg:hidden">
<div className="flex justify-around items-center h-[56px]">
{tabs.map(tab => (
<Link
key={tab.href}
href={tab.href}
className={cn(
"flex flex-col items-center justify-center gap-1",
pathname === tab.href ? "text-blue-500" : "text-gray-500"
)}
>
<tab.icon className="h-6 w-6" />
<span className="text-xs">{tab.label}</span>
</Link>
))}
</div>
</nav>
)
}
Safe Area Padding
For iPhone notch (notch devices):
padding-bottom: env(safe-area-inset-bottom, 0);
Files to Create
keep-notes/components/mobile-bottom-nav.tsx- Bottom navigation component
Files to Modify
keep-notes/app/layout.tsx- Add bottom nav to layoutkeep-notes/app/(main)/page.tsx- Adjust layout spacing
Story 12.4: Full-Screen Mobile Note Editor
Status: ready-for-dev
Story
As a mobile user, I want to create notes in full-screen mode, so that I can focus on content without distractions.
Acceptance Criteria
- Given a user is on a mobile device,
- When they want to create a note,
- Then the system should:
- Show a Floating Action Button (FAB) to create note
- Open full-screen note editor when tapped
- Display title and content fields optimized for mobile
- Place action buttons at bottom of screen
- Animate smoothly back to list view
- NOT affect desktop experience
Tasks / Subtasks
- Create Floating Action Button (FAB)
- Create
fab-button.tsxcomponent - Fixed position: bottom-right of screen
- Circle button: 56x56px
- Plus icon (+)
- Shadow and elevation
- Ripple effect on tap
- Create
- Create full-screen note editor
- Create
MobileNoteEditor.tsxcomponent - Full viewport:
h-screen w-screen - Title field at top
- Content field takes remaining space
- [ - Action buttons at bottom (Save, Cancel)
- Create
- Optimize mobile keyboard handling
- Auto-focus on title when opened
- Keyboard-avoiding behavior
- Smooth keyboard transitions
- Implement save & close flow
- Save note on close
- Animated transition back to list
- Auto-scroll to new note in list
- Test on mobile devices
- Galaxy S22 Ultra
- iPhone
- Android various sizes
- Portrait and landscape
Dev Notes
FAB Design (Material Design)
// keep-notes/components/fab-button.tsx
'use client'
import { Plus } from 'lucide-react'
interface FabButtonProps {
onClick: () => void
}
export function FabButton({ onClick }: FabButtonProps) {
return (
<button
onClick={onClick}
className="fixed bottom-20 right-4 w-14 h-14 rounded-full bg-blue-500 text-white shadow-lg hover:shadow-xl transition-shadow z-50 lg:hidden"
aria-label="Create note"
style={{
width: '56px',
height: '56px',
}}
>
<Plus className="h-6 w-6" />
</button>
)
}
Specs:
- Size: 56x56px (standard FAB)
- Elevation: 6px (shadow-lg)
- Animation: 300ms
- Ripple effect on tap
Full-Screen Editor Layout
┌─────────────────────────────┐
│ [X] │ <- Top bar: Close button
│ Title │ <- Title input
├─────────────────────────────┤
│ │
│ Content area │ <- Takes remaining space
│ (auto-expands) │
│ │
├─────────────────────────────┤
│ [Cancel] [Save] │ <- Bottom bar: Actions
└─────────────────────────────┘
Keyboard Avoidance
import { KeyboardAvoidingView } from 'react-native' // or web equivalent
// On web, use CSS:
.keyboard-avoiding {
padding-bottom: 200px; // Estimated keyboard height
transition: padding-bottom 0.3s;
}
.keyboard-visible {
padding-bottom: 0;
}
Files to Create
keep-notes/components/fab-button.tsx- Floating Action Buttonkeep-notes/components/mobile-note-editor.tsx- Full-screen editor
Files to Modify
keep-notes/app/(main)/page.tsx- Add FAB to mobile layout
Story 12.5: Mobile Quick Actions (Swipe Gestures)
Status: ready-for-dev
Story
As a mobile user, I want quick swipe actions on notes, so that I can manage notes efficiently.
Acceptance Criteria
- Given a user is viewing notes on a mobile device,
- When they swipe on a note card,
- Then the system should:
- Swipe left: Archive the note
- Swipe right: Pin the note
- Long press: Show action menu
- Provide haptic feedback on swipe
- Show undo toast after action
- NOT affect desktop (no swipe on desktop)
Tasks / Subtasks
- Implement swipe gesture library
- Integrate
react-swipeableoruse-swipeable - Configure thresholds and velocities
- Handle touch events properly
- Integrate
- Add swipe actions
- Swipe left → Archive
- Swipe right → Pin/Unpin
- Long press → Action menu
- Add visual feedback
- Swipe indicator (icon appears)
- [ - Color change during swipe
- [ - Smooth animation
- [ - Snap back if not swiped enough
- Implement haptic feedback
- Vibrate on swipe (50-100ms)
- Vibrate on action complete
- Respect device haptic settings
- Add undo functionality
- Show toast after action
- Undo button in toast
- [ - Revert action on undo tap
- Test on mobile devices
- Android (various sensitivity)
- iOS (smooth swipes)
- [ - Different screen sizes
Dev Notes
Swipe Implementation
// Using use-swipeable
import { useSwipeable } from 'react-swipeable'
export function SwipeableNoteCard({ note }: { note: Note }) {
const handlers = useSwipeable({
onSwipedLeft: () => handleArchive(note),
onSwipedRight: () => handlePin(note),
preventDefaultTouchmoveEvent: true,
trackMouse: false, // Touch only on mobile
})
return (
<div {...handlers}>
<MobileNoteCard note={note} />
</div>
)
}
Visual Feedback During Swipe
/* Swipe left (archive) */
.swipe-left {
background: linear-gradient(90deg, #f59e0b 0%, transparent 100%);
}
/* Swipe right (pin) */
.swipe-right {
background: linear-gradient(-90deg, #fbbf24 0%, transparent 100%);
}
Haptic Feedback
// Web Vibration API
if ('vibrate' in navigator) {
navigator.vibrate(50) // 50ms vibration
}
Undo Toast
import { toast } from 'sonner'
const handleArchive = async (note: Note) => {
await toggleArchive(note.id)
toast.success('Note archived', {
action: {
label: 'Undo',
onClick: () => toggleArchive(note.id)
}
})
}
Files to Create
keep-notes/components/swipeable-note-card.tsx- Swipe wrapperkeep-notes/hooks/use-swipe-actions.ts- Swipe logic hook
Files to Modify
keep-notes/components/mobile-note-card.tsx- Wrap in swipeable
Story 12.6: Mobile Typography & Spacing
Status: ready-for-dev
Story
As a mobile user, I want readable text and comfortable spacing, so that the interface is pleasant to use.
Acceptance Criteria
- Given a user is viewing the app on a mobile device,
- When reading any text,
- Then the system should:
- Use mobile-optimized font sizes (min 16px)
- Use generous line heights (1.5-1.6)
- Have comfortable padding for touch
- Maintain good contrast ratios
- NOT affect desktop typography
Tasks / Subtasks
- Define mobile typography system
- Base font size: 16px (prevents iOS zoom)
- Headings: 18-24px
- Body text: 16px
- Small text: 14px
- Line heights: 1.5-1.6
- Optimize spacing for mobile
- Card padding: 12-16px
- Gap between elements: 8-12px
- [ - Touch targets: 44x44px minimum
- Ensure contrast compliance
- WCAG AA: 4.5:1 ratio
- Dark mode contrast
- [ - Test on mobile screens
- Create utility classes
text-mobile-base: 16px- [ -
text-mobile-sm: 14px - [ -
text-mobile-lg: 18px
- Test on mobile devices
- Various screen sizes
- Different orientations
- [ - Accessibility check
Dev Notes
Typography Scale (Mobile)
/* Mobile Typography */
:root {
--mobile-font-base: 16px;
--mobile-font-sm: 14px;
--mobile-font-lg: 18px;
--mobile-font-xl: 24px;
--line-height-relaxed: 1.6;
--line-height-normal: 1.5;
}
.text-mobile-base { font-size: var(--mobile-font-base); }
.text-mobile-sm { font-size: var(--mobile-font-sm); }
.text-mobile-lg { font-size: var(--mobile-font-lg); }
.text-mobile-xl { font-size: var(--mobile-font-xl); }
.leading-mobile { line-height: var(--line-height-relaxed); }
Why 16px Minimum?
iOS Safari automatically zooms if font-size < 16px on input fields. Setting base font to 16px prevents this.
Contrast Ratios (WCAG AA)
- Normal text: 4.5:1
- Large text (18pt+): 3:1
- UI components: 3:1
Spacing System (Mobile)
:root {
--spacing-mobile-xs: 4px;
--spacing-mobile-sm: 8px;
--spacing-mobile-md: 12px;
--spacing-mobile-lg: 16px;
--spacing-mobile-xl: 20px;
}
Files to Modify
keep-notes/app/globals.css- Typography and spacing utilitieskeep-notes/components/mobile-note-card.tsx- Apply mobile typographykeep-notes/components/mobile-bottom-nav.tsx- Apply mobile spacing
Story 12.7: Mobile Performance Optimization
Status: ready-for-dev
Story
As a mobile user, I want fluid animations and fast performance, so that the app is responsive and smooth.
Acceptance Criteria
- Given a user is using the app on a mobile device,
- When performing any action,
- Then the system should:
- Animate at 60fps consistently
- Have no layout shifts
- Show loading skeletons on mobile
- Lazy load images
- Use optimized debounce for mobile
- Test and verify on Galaxy S22 Ultra
Tasks / Subtasks
- Optimize animations for mobile
- Use CSS transforms (GPU-accelerated)
- Limit animation duration to 300ms max
- Respect
prefers-reduced-motion
- Eliminate layout shifts
- Use skeleton loaders instead of empty states
- [ - Reserve space for content
- Use loading states
- Implement lazy loading
- Lazy load images
- Intersection Observer for off-screen content
- [ - Code splitting for mobile components
- Optimize event handlers
- Debounce search on mobile (150-200ms)
- [ - Passive event listeners where possible
- [ - Throttle scroll events
- Test on real devices
- Galaxy S22 Ultra (main target)
- iPhone SE, 14 Pro
- Android various models
- Measure FPS and performance
- Performance monitoring
- Add performance marks
- [ - Monitor Core Web Vitals
- [ - Log slow interactions
Dev Notes
GPU-Accelerated Animations
/* Good: GPU-accelerated */
.element {
transform: translateX(0);
opacity: 1;
}
/* Bad: Triggers reflow */
.element {
left: 0;
width: 100%;
}
Skeleton Loading
// keep-notes/components/note-skeleton.tsx
export function NoteSkeleton() {
return (
<div className="animate-pulse bg-gray-200 rounded-lg p-4">
<div className="h-4 bg-gray-300 rounded mb-2 w-3/4" />
<div className="h-3 bg-gray-300 rounded mb-1" />
<div className="h-3 bg-gray-300 rounded w-1/2" />
</div>
)
}
Lazy Loading Images
// Using Intersection Observer
const [isVisible, setIsVisible] = useState(false)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true)
}
})
if (ref.current) {
observer.observe(ref.current)
}
return () => observer.disconnect()
}, [])
<div ref={ref}>
{isVisible && <img src={...} />}
</div>
Debounce Optimization
// Keep shorter debounce on mobile for responsiveness
const debounceTime = isMobile ? 150 : 300
const debouncedSearch = useDebounce(searchQuery, debounceTime)
Performance Measurement
// Performance API
performance.mark('render-start')
// ... component renders
performance.mark('render-end')
performance.measure('render', 'render-start', 'render-end')
// Log slow renders (> 16ms = < 60fps)
const measure = performance.getEntriesByName('render')[0]
if (measure.duration > 16) {
console.warn('Slow render:', measure.duration, 'ms')
}
Files to Create
keep-notes/components/note-skeleton.tsx- Skeleton loaderkeep-notes/hooks/use-visibility.ts- Intersection Observer hook
Files to Modify
keep-notes/components/masonry-grid.tsx- Performance optimizationskeep-notes/components/mobile-note-card.tsx- GPU-accelerated animationskeep-notes/app/(main)/page.tsx- Skeleton loading states
Epic Summary
Stories in Epic 12:
- 12-1: Mobile Note Cards Simplification
- 12-2: Mobile-First Layout
- 12-3: Mobile Bottom Navigation
- 12-4: Full-Screen Mobile Note Editor
- 12-5: Mobile Quick Actions (Swipe Gestures)
- 12-6: Mobile Typography & Spacing
- 12-7: Mobile Performance Optimization
Total Stories: 7 Estimated Complexity: High (comprehensive mobile overhaul) Priority: High (critical UX issue on mobile)
Dependencies:
- Story 12-1 should be done first (foundational)
- Story 12-2 depends on 12-1
- Story 12-3, 12-4, 12-5 depend on 12-1
- Story 12-6 depends on 12-1
- Story 12-7 can be done in parallel
Testing Requirements:
- ✅ Test on Galaxy S22 Ultra (main target from user feedback)
- ✅ Test on iPhone SE (small screen)
- ✅ Test on iPhone 14 Pro (large screen)
- ✅ Test on Android various sizes
- ✅ Test in portrait and landscape
- ✅ Verify desktop unchanged (0 regression)
Success Metrics:
- Zero horizontal/vertical overflow on mobile
- 60fps animations on mobile devices
- Touch targets meet minimum 44x44px
- Desktop functionality 100% unchanged
- User satisfaction on mobile UX
Dev Agent Record
Agent Model Used
claude-sonnet-4-5-20250929
Completion Notes List
- Created Epic 12 with 7 comprehensive user stories
- Documented mobile UX requirements
- Detailed each story with tasks and dev notes
- Created file list for implementation
- Epic pending implementation
File List
Epic Files:
_bmad-output/implementation-artifacts/12-mobile-experience-overhaul.md(this file)
Files to Create (across all stories):
keep-notes/components/mobile-note-card.tsxkeep-notes/components/swipeable-note-card.tsxkeep-notes/components/fab-button.tsxkeep-notes/components/mobile-bottom-nav.tsxkeep-notes/components/mobile-note-editor.tsxkeep-notes/components/note-skeleton.tsxkeep-notes/hooks/use-media-query.tskeep-notes/hooks/use-swipe-actions.tskeep-notes/hooks/use-visibility.ts
Files to Modify:
keep-notes/app/(main)/page.tsxkeep-notes/app/layout.tsxkeep-notes/components/header.tsxkeep-notes/components/note-input.tsxkeep-notes/components/masonry-grid.tsxkeep-notes/app/globals.css
Created: 2026-01-17 Based on user feedback from Galaxy S22 Ultra testing Desktop Interface: NO CHANGES - Mobile Only