Mobile Interface¶
Sapari's mobile interface is optimized for touch interactions on phones and tablets. It provides a streamlined editing workflow with gesture-based controls.
Architecture¶
The mobile experience lives in features/mobile/ and adapts the desktop editing workflow for touch:
features/mobile/
├── components/
│ ├── MobileDashboard.tsx # Main mobile container
│ ├── MobileWizard.tsx # Onboarding flow
│ ├── MobileUploadStep.tsx # File/YouTube upload
│ ├── MobileConfigureStep.tsx # Analysis settings
│ ├── MobileProcessing.tsx # Progress indicator
│ ├── SwipeReview.tsx # Card-based edit review
│ ├── FocusMode.tsx # Precision timeline editing
│ ├── MobileExport.tsx # Export panel
│ ├── MobileAssetsView.tsx # Asset library
│ ├── MobileAssetEditor.tsx # Trim/cut assets
│ ├── PreviewSettingsPanel.tsx # Format/caption settings
│ ├── BottomNav.tsx # Tab navigation
│ ├── MiniTimeline.tsx # Compact timeline view
│ ├── TimestampAdjuster.tsx # Precise time input
│ └── swipe-review/ # SwipeReview sub-components
│ ├── EditsPanel.tsx
│ ├── CaptionsPanel.tsx
│ ├── StylePanel.tsx
│ ├── FormatPanel.tsx
│ └── types.ts
└── index.ts
Key Components¶
MobileDashboard¶
The root container that manages mobile navigation state and coordinates between views:
- Upload wizard flow
- Processing status
- SwipeReview (edit review)
- FocusMode (precision editing)
- Export panel
- Asset library
Uses BottomNav for tab-based navigation between main sections.
SwipeReview¶
A Tinder-style card interface for reviewing AI-suggested edits. Users swipe:
- Right to keep an edit (silence removal, false start cut)
- Left to reject an edit
Features: - Video preview synced to current edit - Caption preview with style settings - Gesture-based card navigation - Bulk accept/reject actions - Category filtering (silences, false starts, all)
Configuration in shared/config/constants.ts:
export const SWIPE_REVIEW = {
SWIPE_THRESHOLD_PX: 100, // Min swipe distance to trigger
SWIPE_EXIT_OFFSET_PX: 400, // Card exit animation distance
SWIPE_ANIMATION_DELAY_MS: 200, // Delay before next card
CARD_ROTATION_FACTOR: 0.05, // Tilt during swipe
VIDEO_HEIGHT: '35vh', // Preview video height
};
FocusMode¶
Precision timeline editing for fine-tuning edit boundaries. Designed for users who need frame-accurate control on mobile.
Features¶
- Zoomable Timeline: 1x-50x zoom for precision editing
- Drag Handles: Touch-friendly handles to adjust edit start/end times
- Dual-Row View: Separate rows for cuts (silences, false starts) and inserts (assets)
- Mini Map: Overview of full timeline with viewport indicator (draggable)
- Action Bar: Quick actions for +CUT, +INSERT, delete, undo/redo
- Category Tabs: Filter view to ALL, CUTS only, or INSERTS only
Gestures¶
| Gesture | Action |
|---|---|
| Tap edit | Select edit for adjustment |
| Drag handle | Adjust edit start/end boundary |
| Pinch | Zoom timeline in/out |
| Two-finger pan | Scroll zoomed timeline |
| Drag mini map viewport | Jump to different timeline position |
Configuration¶
export const FOCUS_MODE = {
MIN_ZOOM: 1,
MAX_ZOOM: 50, // 50x for precision editing
DEFAULT_ZOOM: 4, // Initial zoom level
ZOOM_STEP_MULTIPLIER: 1.15, // Zoom per button tap
ZOOM_HOLD_INTERVAL_MS: 80, // Continuous zoom interval
ZOOM_HOLD_DELAY_MS: 300, // Delay before hold zoom
MIN_EDIT_WIDTH_FOR_LABEL_PCT: 12,
PAN_THRESHOLD_PX: 8,
VIDEO_HEIGHT: '30vh',
MINIMAP_ZOOM_THRESHOLD: 1.2, // Show minimap above this zoom
};
Component Structure¶
FocusMode extracts several sub-components for reusability:
DragHandle- Touch-friendly resize handlesEditButton- Individual edit segments on timelineTimelineRow- Reusable row for cuts/inserts tracksMiniMap- Overview with draggable viewport indicator
MobileExport¶
Streamlined export interface with: - Format selection (16:9, 9:16, 1:1, original) - Caption toggle and settings - Letterbox color picker - Export progress tracking
MobileAssetEditor¶
Touch-optimized asset trimming with: - Visual waveform display - Draggable trim handles - Preview playback - Duration indicator
Touch Interaction Patterns¶
Hit Areas¶
All interactive elements meet WCAG 2.5.5 touch target requirements:
Gesture Handling¶
Mobile components use refs for touch state to avoid stale closures:
const touchStateRef = useRef({
startX: 0,
startY: 0,
mode: 'idle' as TouchMode,
});
const handleTouchStart = (e: React.TouchEvent) => {
const touch = e.touches[0];
touchStateRef.current = {
startX: touch.clientX,
startY: touch.clientY,
mode: 'pending',
};
};
Memory Management¶
Components properly clean up timeouts, intervals, and event listeners:
useEffect(() => {
const interval = setInterval(updatePlayhead, 50);
return () => clearInterval(interval);
}, []);
useEffect(() => {
return () => {
if (holdTimerRef.current) clearTimeout(holdTimerRef.current);
if (zoomIntervalRef.current) clearInterval(zoomIntervalRef.current);
};
}, []);
Responsive Detection¶
Mobile layout requires both touch support AND a small viewport:
export const UI = {
MOBILE_BREAKPOINT: 768, // < 768px = mobile
TABLET_BREAKPOINT: 1024, // 768-1024px = tablet
};
useIsMobile() checks isTouchDevice && Math.min(innerWidth, innerHeight) < MOBILE_BREAKPOINT. The touch check prevents desktop browsers resized narrow from switching to mobile UI (where swipe gestures wouldn't work). The min-dimension check ensures phones in landscape still count as mobile.
Dashboard.tsx uses useIsMobile() to conditionally render MobileDashboard vs desktop layout.
State Management¶
Mobile components share state with desktop via React Query. The same hooks work on both:
// Works identically on mobile and desktop
const { data: edits } = useEdits(projectUuid);
const { mutate: toggleEdit } = useToggleEdit();
This ensures edits made on mobile sync seamlessly when switching to desktop.
Key Files¶
| Component | Location |
|---|---|
| Mobile dashboard | features/mobile/components/MobileDashboard.tsx |
| SwipeReview | features/mobile/components/SwipeReview.tsx |
| FocusMode | features/mobile/components/FocusMode.tsx |
| Mobile export | features/mobile/components/MobileExport.tsx |
| Asset editor | features/mobile/components/MobileAssetEditor.tsx |
| Bottom nav | features/mobile/components/BottomNav.tsx |
| Configuration | shared/config/constants.ts |