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
This commit is contained in:
@@ -0,0 +1,959 @@
|
||||
# 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
|
||||
|
||||
1. **Given** a user is viewing notes on a mobile device (< 768px),
|
||||
2. **When** notes are displayed,
|
||||
3. **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.tsx` component
|
||||
- [ ] Vertical card layout (not masonry)
|
||||
- [ ] Simplified content: title + 2-3 lines preview
|
||||
- [ ] Reduced badges (pin icon, label count only)
|
||||
- [ ] No image thumbnails on mobile
|
||||
- [ ] 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**
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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 component
|
||||
- `keep-notes/components/swipeable-wrapper.tsx` - Swipe gesture wrapper
|
||||
|
||||
### Files to Modify
|
||||
|
||||
- `keep-notes/app/(main)/page.tsx` - Conditional rendering for mobile/desktop
|
||||
- `keep-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
|
||||
|
||||
1. **Given** a user is using the app on a mobile device,
|
||||
2. **When** viewing any page,
|
||||
3. **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-full` on mobile containers
|
||||
- [ ] Reduce padding on mobile (px-4 vs px-6)
|
||||
- [ ] Remove max-width constraints on mobile
|
||||
- [ ] 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-hidden` on body
|
||||
- [ ] Ensure no fixed widths on mobile
|
||||
- [ ] Test on Galaxy S22 Ultra (main target)
|
||||
- [ ] 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
|
||||
|
||||
```css
|
||||
/* 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:**
|
||||
```typescript
|
||||
<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 Button
|
||||
- `keep-notes/hooks/use-media-query.ts` - Hook for responsive queries
|
||||
|
||||
### Files to Modify
|
||||
|
||||
- `keep-notes/components/header.tsx` - Responsive header
|
||||
- `keep-notes/components/note-input.tsx` - Mobile variant
|
||||
- `keep-notes/app/(main)/page.tsx` - Container adjustments
|
||||
- `keep-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
|
||||
|
||||
1. **Given** a user is on a mobile device,
|
||||
2. **When** navigating the app,
|
||||
3. **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.tsx` component
|
||||
- [ ] 3 tabs: Notes, Favorites, Settings
|
||||
- [ ] Icons for each tab
|
||||
- [ ] Active state indicator
|
||||
- [ ] 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:**
|
||||
|
||||
```typescript
|
||||
// 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):
|
||||
|
||||
```css
|
||||
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 layout
|
||||
- `keep-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
|
||||
|
||||
1. **Given** a user is on a mobile device,
|
||||
2. **When** they want to create a note,
|
||||
3. **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.tsx` component
|
||||
- [ ] Fixed position: bottom-right of screen
|
||||
- [ ] Circle button: 56x56px
|
||||
- [ ] Plus icon (+)
|
||||
- [ ] Shadow and elevation
|
||||
- [ ] Ripple effect on tap
|
||||
- [ ] Create full-screen note editor
|
||||
- [ ] Create `MobileNoteEditor.tsx` component
|
||||
- [ ] Full viewport: `h-screen w-screen`
|
||||
- [ ] Title field at top
|
||||
- [ ] Content field takes remaining space
|
||||
- [ - Action buttons at bottom (Save, Cancel)
|
||||
- [ ] 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)
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
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 Button
|
||||
- `keep-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
|
||||
|
||||
1. **Given** a user is viewing notes on a mobile device,
|
||||
2. **When** they swipe on a note card,
|
||||
3. **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-swipeable` or `use-swipeable`
|
||||
- [ ] Configure thresholds and velocities
|
||||
- [ ] Handle touch events properly
|
||||
- [ ] 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```css
|
||||
/* 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
|
||||
|
||||
```typescript
|
||||
// Web Vibration API
|
||||
if ('vibrate' in navigator) {
|
||||
navigator.vibrate(50) // 50ms vibration
|
||||
}
|
||||
```
|
||||
|
||||
### Undo Toast
|
||||
|
||||
```typescript
|
||||
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 wrapper
|
||||
- `keep-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
|
||||
|
||||
1. **Given** a user is viewing the app on a mobile device,
|
||||
2. **When** reading any text,
|
||||
3. **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)
|
||||
|
||||
```css
|
||||
/* 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)
|
||||
|
||||
```css
|
||||
: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 utilities
|
||||
- `keep-notes/components/mobile-note-card.tsx` - Apply mobile typography
|
||||
- `keep-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
|
||||
|
||||
1. **Given** a user is using the app on a mobile device,
|
||||
2. **When** performing any action,
|
||||
3. **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
|
||||
|
||||
```css
|
||||
/* Good: GPU-accelerated */
|
||||
.element {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Bad: Triggers reflow */
|
||||
.element {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
### Skeleton Loading
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// Keep shorter debounce on mobile for responsiveness
|
||||
const debounceTime = isMobile ? 150 : 300
|
||||
|
||||
const debouncedSearch = useDebounce(searchQuery, debounceTime)
|
||||
```
|
||||
|
||||
### Performance Measurement
|
||||
|
||||
```typescript
|
||||
// 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 loader
|
||||
- `keep-notes/hooks/use-visibility.ts` - Intersection Observer hook
|
||||
|
||||
### Files to Modify
|
||||
|
||||
- `keep-notes/components/masonry-grid.tsx` - Performance optimizations
|
||||
- `keep-notes/components/mobile-note-card.tsx` - GPU-accelerated animations
|
||||
- `keep-notes/app/(main)/page.tsx` - Skeleton loading states
|
||||
|
||||
---
|
||||
|
||||
## Epic Summary
|
||||
|
||||
**Stories in Epic 12:**
|
||||
1. 12-1: Mobile Note Cards Simplification
|
||||
2. 12-2: Mobile-First Layout
|
||||
3. 12-3: Mobile Bottom Navigation
|
||||
4. 12-4: Full-Screen Mobile Note Editor
|
||||
5. 12-5: Mobile Quick Actions (Swipe Gestures)
|
||||
6. 12-6: Mobile Typography & Spacing
|
||||
7. 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
|
||||
|
||||
- [x] Created Epic 12 with 7 comprehensive user stories
|
||||
- [x] Documented mobile UX requirements
|
||||
- [x] Detailed each story with tasks and dev notes
|
||||
- [x] 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.tsx`
|
||||
- `keep-notes/components/swipeable-note-card.tsx`
|
||||
- `keep-notes/components/fab-button.tsx`
|
||||
- `keep-notes/components/mobile-bottom-nav.tsx`
|
||||
- `keep-notes/components/mobile-note-editor.tsx`
|
||||
- `keep-notes/components/note-skeleton.tsx`
|
||||
- `keep-notes/hooks/use-media-query.ts`
|
||||
- `keep-notes/hooks/use-swipe-actions.ts`
|
||||
- `keep-notes/hooks/use-visibility.ts`
|
||||
|
||||
**Files to Modify:**
|
||||
- `keep-notes/app/(main)/page.tsx`
|
||||
- `keep-notes/app/layout.tsx`
|
||||
- `keep-notes/components/header.tsx`
|
||||
- `keep-notes/components/note-input.tsx`
|
||||
- `keep-notes/components/masonry-grid.tsx`
|
||||
- `keep-notes/app/globals.css`
|
||||
|
||||
---
|
||||
|
||||
*Created: 2026-01-17*
|
||||
*Based on user feedback from Galaxy S22 Ultra testing*
|
||||
*Desktop Interface: NO CHANGES - Mobile Only*
|
||||
|
||||
Reference in New Issue
Block a user