Skip to content

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 handles
  • EditButton - Individual edit segments on timeline
  • TimelineRow - Reusable row for cuts/inserts tracks
  • MiniMap - 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:

export const UI = {
  MIN_TOUCH_TARGET_PX: 44,  // Minimum touch target size
};

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

← Architecture State Management →