Skip to content

Desktop QA Testing Guide

Comprehensive manual testing checklist for the Sapari desktop app. Run before every major release.

Tests are organized by workflow order and grouped to avoid redundant retesting. Each section builds on previous ones — complete them in order.


Prerequisites

  • Desktop browser (Chrome or Firefox, latest)
  • Window width > 1024px (desktop breakpoint)
  • Test video file (30s+, with speech and silence)
  • Test assets: one video, one audio, one image
  • Backend running locally or connected to staging

0. Onboarding (First-Time User)

Guided Tour

  • First login → spotlight tour starts automatically (after 500ms)
  • Step 1: Upload zone highlighted with orange pulsing border, tooltip on right
  • Step 2: Smart Cut Engine highlighted, tooltip on left
  • Step 3: Audio & Text highlighted, tooltip on left
  • Step 4: Director's Notes highlighted, tooltip on left
  • Step 5: Analyze button highlighted, tooltip on left
  • Step 6: Assets nav item highlighted, tooltip on right
  • "Next" progresses through steps, "Skip" dismisses at any point
  • Last step shows "Done" instead of "Next"
  • Click backdrop to dismiss
  • Tour doesn't show again on refresh (server-persisted via onboarding_seen, localStorage as cache)
  • "?" icon in header (next to "Core Workflow.") replays tour
  • Dark mode: tooltip card, dots, buttons all correct
  • Tooltip transitions smoothly between positions (300ms)
  • Tooltip never goes off-screen (clamped to viewport)

1. Authentication

Login

  • Enter valid email + password → redirects to project list
  • Enter invalid credentials → error message appears in red
  • Submit with empty fields → validation prevents submit
  • Press Enter in password field → submits form (same as clicking SIGN IN)
  • Button shows "SIGNING IN..." and inputs disable during request
  • Theme toggle (sun/moon) switches light/dark mode
  • Theme preference persists after page reload
  • "Remember me" checkbox visible between password and "Forgot password?"
  • Login without "Remember me" → session cookie max-age ~8h (28800s)
  • Login with "Remember me" → session cookie max-age ~30 days (2592000s)
  • Close browser + reopen with "Remember me" → still logged in
  • Login as unverified user → "Resend verification email" button appears under the error
  • Click "Resend verification email" → status changes to "Verification email sent. Check your inbox."
  • After ~10 fast failed login attempts → response is "Too many failed login attempts. Please try again later." (HTTP 429), not "Incorrect email or password"

Email verification

  • Click verification link in email → "Verified" page with success message
  • Click expired/already-used verification link → "Invalid or expired verification link" message (no false success)
  • Click verification link multiple times in quick succession → second attempt shows the rate-limited message, not "Verified"

Change Password (Account Settings)

  • Change password with correct current password → success message, auto-redirects to login after ~2s
  • Login with new password works
  • Wrong current password → error message
  • Missing current password (non-OAuth) → error message
  • New password too short (<8 chars) → validation error
  • OAuth user → can set password without providing current password
  • After password change → all other sessions terminated

Change Email (Account Settings)

  • Collapsible "Change Email" section in Account tab (collapsed by default)
  • Shows current email
  • Enter new email + current password → success message "Check your new email"
  • Wrong password → error
  • Same email as current → error
  • Visit /confirm-email-change?token=... from verification email → "Email Changed" page
  • Invalid/expired token → error page
  • Old email receives notification about the change request
  • Dark mode: all form elements styled correctly

Change Name (Account Settings)

  • Hover name in profile card → pencil icon appears
  • Click → inline edit input with check/X buttons
  • Type new name → Enter or check → name updates immediately
  • Escape or X → cancels without saving
  • Name persists after page refresh

Delete Account (Account Settings)

  • "Delete Account" link below Sign Out
  • Click → inline confirmation with password field + "Type DELETE" field
  • Both fields required — button disabled until filled
  • Wrong password → "Incorrect password" error
  • Correct password + "DELETE" typed → account deactivated, logged out
  • Cancel → resets all fields, hides confirmation
  • Dark mode correct on confirmation box

2. Project Management

Create & List

  • Click NEW PROJECT → new project appears in grid
  • Empty state shows "No Projects Yet" message with NEW PROJECT button
  • Project count label updates ("X PROJECTS SAVED")
  • Projects sorted newest-first
  • Project cards show video frame thumbnail after first clip is processed
  • Projects without clips show first-letter placeholder

Edit & Delete

  • Click pencil icon → inline name editor appears
  • Type new name, press Enter → name saved
  • Click X → cancels rename, reverts to original
  • Click trash icon → confirmation dialog appears
  • Confirm delete → project removed from list
  • Cancel delete → project stays

Resume

  • Click project card → opens project in editor
  • Status badges show correctly: COMPLETE (green), PROCESSING (yellow)

3. File Upload & Import

Upload

  • Drag and drop video file onto upload area → file accepted, progress bar shows
  • Click file picker → select file → same behavior
  • Upload completes → green checkmark, file listed
  • Upload fails → red X with error message

Clip Reorder

  • Upload 2+ clips → drag handles visible on hover
  • Drag clip to new position → clips visually reorder
  • Refresh page → order persists (saved to localStorage + backend)

YouTube Import (Viral Plan Only)

  • Non-viral user → YouTube import button shows locked state with "VIRAL PLAN" label
  • Non-viral user → "+ YOUTUBE" link hidden in sequence editor
  • Viral user → paste YouTube URL, press Enter → import starts
  • Progress indicator shows during import
  • Successful import → clip appears in timeline
  • Invalid URL → error message

4. Analysis Settings

Silence & False Starts

  • Pacing slider: drag from 0 (OFF) to 100 (AGGRESSIVE) → label updates
  • False start slider: same behavior, purple accent
  • Setting to 0 disables that detection type

Audio & Captions

  • Clean Sweep toggle: on/off visually distinct
  • Caption language dropdown: select each option → selection persists
  • DISABLED → no captions generated

Presets (Configure Step — Analysis Settings)

Saves pacing, false-starts, language, censorship, and director notes — the settings visible on the configure page before analysis runs. Backed by /users/me/presets. - [ ] No presets by default -- only "Custom" shown initially - [ ] Save current settings as preset → enter name, save → appears in preset list - [ ] Click saved preset → sliders and settings auto-adjust - [ ] Delete preset → removed from list

Cost Estimate & Analysis Mode

  • Nothing toggled → shows "MANUAL - FREE" with tooltip (?)
  • Set only language → shows "CAPTIONS - ~X AI MIN" (0.5x rate)
  • Enable pacing or false starts → shows "AI EDIT - ~X AI MIN" (1.0x rate)
  • Switching settings updates cost estimate in real time
  • Tooltip explains mode derivation on hover
  • Insufficient credits → text turns red

Run Analysis

  • Click ANALYZE button → processing begins
  • Progress log shows real-time steps
  • Analysis completes → edits appear on timeline
  • Run picker hidden (only 1 run)

Re-analyze & Run Switching

  • RE-ANALYZE button → confirmation says "Your current analysis will be preserved"
  • Re-analyze with different settings → old edits preserved, new run active
  • Run picker appears in sidebar (between drafts and re-analyze) when >1 run
  • Active run has orange border + "ACTIVE" badge
  • Click inactive run → loading spinner, edits/captions switch
  • Manual cuts belong to the run they were created in
  • Switch back to old run → old edits + transcript restored, manual cuts from that run shown
  • Export uses active run's edits
  • Run labels show "N cuts · M inserts"
  • Dark mode: run picker cards styled correctly

5. Video Player (Collapsed Timeline)

Playback

  • Click Play → video plays, button changes to Pause
  • Click Pause → video pauses, button changes to Play
  • Current time display updates during playback
  • Playhead moves along timeline during playback

Seeking

  • Click on timeline waveform → playhead jumps to click position
  • Arrow Left → seek back 1s
  • Arrow Right → seek forward 1s
  • Shift + Arrow Left → seek back 5s
  • Shift + Arrow Right → seek forward 5s
  • Press 0 → reset zoom

Timeline scrub preview (sprite tile)

  • Mouse-down + drag the playhead → a thumbnail tile floats above the timeline tracking the cursor
  • Thumbnail updates as the cursor moves across the timeline
  • Thumbnail stays clamped inside the timeline's horizontal bounds near either edge (does not overflow off-screen)
  • DevTools Network tab: zero proxy-URL range requests fire during the drag; exactly one fires on mouse-release
  • Release the playhead → video seeks to the released position; thumbnail disappears
  • Clip without a sprite yet (e.g., still processing) → falls back to today's per-tick seek behavior (no thumbnail)

Keyboard Shortcuts

  • Space → toggle play/pause (only when not in text input)
  • Cmd/Ctrl + Z → undo last edit action
  • Cmd/Ctrl + Shift + Z → redo
  • Cmd/Ctrl + Y → redo (alternative)
  • Delete/Backspace → toggle selected edit active/inactive
  • + or = → zoom in
  • - → zoom out

Volume

  • Main audio volume slider: drag → volume changes
  • Range 0-100%
  • Volume percentage displays above slider

Edit Segments on Collapsed Timeline

Edit segment colors on collapsed timeline

  • Silence edits show in orange
  • False start edits show in purple
  • Manual edits show in cyan
  • Asset edits show in blue
  • Keep edits show as dashed yellow border (transparent fill)
  • Click edit → selects it (highlight visible)
  • Click elsewhere → deselects

Overlap Cycling (Collapsed)

  • When edits overlap: badge shows "½" (or similar count)
  • Click badge → cycles to next overlapping edit
  • Non-visible edit shows at reduced opacity
  • In non-overlapping region, can still click the edit underneath

Cut/Keep Mode Toggle

  • Toolbar shows cut/keep mode toggle (scissors vs check icon)
  • Default mode is "cut" -- clicking timeline creates a CUT edit
  • Switching to "keep" mode -- clicking timeline creates a KEEP edit
  • Keep edits render as dashed yellow border with transparent fill
  • Keep regions dim areas outside them (dark overlay on non-keep areas)
  • Dim overlays appear on all tracks (waveform, video inserts, audio inserts)
  • Edits inside keep regions are fully interactive (clickable, draggable)
  • Edits outside keep regions show EyeOff warning badge
  • Edit popover shows "Outside keep region" warning for affected edits
  • Keep filter button in toolbar toggles keep visibility
  • Keep card in edit list shows yellow border with Check icon
  • Keep segments have lower z-index so other edits render on top
  • Multiple keep regions: only their union is preserved, rest is dimmed
  • Playback skips regions outside active keeps (mirrors export behavior)

5b. Keyboard Shortcuts & Playback Speed

JKL Shuttle Control

  • L key: plays forward. Press again for 2x, 4x, 8x
  • J key: rewinds. Press again for 2x, 4x, 8x
  • K key: stops, resets speed to 1x
  • K + L (hold K, tap L): step one frame forward
  • K + J (hold K, tap J): step one frame backward
  • Shortcuts don't fire in inputs (Director's Notes, project name)

Speed Control UI

  • Bottom-left of video: speed button beside volume button
  • Click speed button to open vertical slider (0.25x to 4x)
  • Click elsewhere to close slider
  • Click rate label to reset to 1x
  • Top-right badge shows current speed when not 1x
  • Speed persists across clip boundaries (multi-clip)

Shortcut Help Overlay

  • Press ? to open, Escape or backdrop to close
  • Lists all shortcuts grouped by Playback, Navigation, Editing, View
  • Dark mode correct

5c. Playback URL Refresh (Worker-Fronted Clips)

Clip playback URLs route through a Cloudflare Worker at /media/v1/<jwt> with a short TTL (300 seconds). useClipProxyUrls refreshes each URL ~30 seconds before expiry so playback never hits a 401. useProxyUrlSwap preserves <video> playback state across the src swap. When refresh hard-fails, ClipPlaybackErrorBanner surfaces above the video with Retry and Dismiss affordances.

These tests verify the retry-as-contract layer end-to-end. Some require shortening the server-side TTL to make the refresh observable within a test session.

Preemptive refresh (happy path)

Setup — one-time, shortens TTL so refresh fires within ~30s instead of every 5 min: 1. SSH to staging: ssh deploy@100.110.63.6 2. Edit /home/deploy/sapari/.env: change MEDIA_TOKEN_TTL_SECONDS from 300 to 60. Do NOT drop below REFRESH_BUFFER_SECONDS + ~5s. With TTL=30 and buffer=30, refresh fires at mint-landing and the new token would immediately be in the refresh window too — tight loop. TTL=60 gives a ~30s playback-then-refresh cycle. 3. Restart backend: cd /home/deploy/sapari && docker compose restart backend. Wait ~20s for health check.

Test (desktop): - [ ] Load a project with a clip. Open browser devtools → Network tab. Play the clip. - [ ] Expect at ~30s after initial playback: a second /api/v1/projects/.../clips/.../proxy request fires. - [ ] Expect immediately after: a second /media/v1/<different-jwt> fetch fires with a new JWT (compare the last few chars of the token in the URL — they should differ). - [ ] Expect: playback continues without a visible stall. A brief buffering overlay (<200ms) is acceptable while the new source loads — shows the sprite-tile poster if the clip has a sprite, otherwise a "Buffering..." text overlay. A long stall is a regression. - [ ] Position is preserved within ±100ms across the swap. - [ ] Expect wrangler tail --env staging to show the second /media/v1/* returning 200 (or 206 for range requests).

Teardown: - [ ] Revert MEDIA_TOKEN_TTL_SECONDS=300 and docker compose restart backend.

Backend-down failure (banner + retry)

Setup: same TTL=60 from above, or leave at 300 if you're patient.

Test: - [ ] Play a clip. Once it's playing, stop the backend: docker compose stop backend. - [ ] Wait for the refresh window (~30s with TTL=60, ~4.5 min with TTL=300). - [ ] Expect: after useClipProxyUrls' 2s retry also fails, ClipPlaybackErrorBanner appears at the top of the video area (above the <video> element). - [ ] Expect banner copy: "Playback interrupted — Server problem refreshing playback. Try again, or reload the page." - [ ] Click Retry in the banner. Nothing happens yet (backend still down). - [ ] Click Dismiss (X icon). Banner disappears. - [ ] Restart backend: docker compose start backend. Wait ~20s. - [ ] Expect the next refetchInterval tick (or user can click Retry again): playback resumes, refreshError clears on the clip, banner stays dismissed.

Session-expired smoke (401 on mint)

Test: - [ ] Load a project with a clip playing normally. - [ ] In devtools → Application → Cookies, delete session_id. - [ ] Wait for next refresh (~30s if TTL=60). - [ ] Expect /api/v1/projects/.../clips/.../proxy returns 401. - [ ] Expect frontend ApiClient.onUnauthorized fires → AuthContext cleared → redirected to login. - [ ] No banner, no retry storm, no surprise UI.

Error copy differentiation

With the backend returning a 500 (stop backend scenario), banner shows "Server problem..." copy. With a network-simulated failure (devtools → Network → Offline), banner shows "Connection issue..." copy. - [ ] Offline mode: banner copy matches "Connection issue refreshing playback..." - [ ] Backend stopped: banner copy matches "Server problem refreshing playback..."

Retry button — iterate across errored clips

Only meaningful when you can force multiple clips to error simultaneously (e.g. all using a broken proxy). In practice, single-clip retry covers this — the Retry button calls refetchClip(uuid) once per errored clip. - [ ] Multi-clip project, backend stopped, wait for refresh failures on multiple clips. Banner appears once regardless of how many clips errored. - [ ] Click Retry. Devtools → Network: one /proxy request per errored clip fires.

Dismiss + re-error cycle

  • Trigger a refresh failure, banner appears.
  • Dismiss. Banner hides.
  • Fix the failure (restart backend, restore connectivity), wait for successful refresh, refreshError clears.
  • Trigger a new failure (stop backend again, wait for refresh).
  • Expect: banner re-appears. Dismiss is session-scoped but auto-resets when errors clear.

6. Expanded Timeline Editor

Layout

Expanded timeline with two tracks and edit popover

  • Two tracks visible: Cuts track (top) and Inserts track (bottom)
  • Waveform renders on both tracks
  • Playhead synced across tracks and video

Edit Selection & Popover

  • Click edit segment → popover appears above or below
  • Popover shows: type badge with color, time range, duration, confidence (if applicable)
  • Popover positioned within viewport bounds (doesn't clip edges)
  • Click outside popover → closes (timeline and video remain interactive while popover open)
  • Popover actions:
  • DISMISS (for active edits) → marks edit inactive
  • ACCEPT (for inactive edits) → marks edit active
  • Trash icon → deletes edit permanently

Drag & Resize

  • Click and hold edit body (>4px movement) → enters drag mode
  • Drag left/right → edit moves, duration preserved
  • Release → position committed
  • Click without dragging (<4px) → treated as click (popover opens)
  • Hover left edge → cursor changes to col-resize
  • Drag left handle → start time changes, end time fixed
  • Drag right handle → end time changes, start time fixed
  • Cannot resize below minimum duration (segment stays visible)
  • During drag: segment shows slight transparency

Undo/Redo

  • Drag edit → Cmd+Z → returns to previous position
  • Cmd+Shift+Z → re-applies the drag
  • Delete edit → Cmd+Z → edit fully restored (all fields intact)
  • Drag edit twice rapidly → Cmd+Z → goes back exactly one drag (not two)

Zoom & Viewport

  • Scroll wheel up → zoom in (point under cursor stays stationary)
  • Scroll wheel down → zoom out (same focal behavior)
  • + button → zoom in (center-focused)
  • - button → zoom out
  • Reset button → return to 1x zoom
  • Zoom range: 1x to 50x
  • Mini-map appears when zoomed >1x

Mini-map with viewport rectangle at high zoom

  • Drag mini-map viewport rectangle → timeline pans
  • Click on mini-map → viewport jumps to that position
  • Playhead stays visible (auto-scroll follows during playback)

Loop & A/B

  • LOOP toggle → playback loops within current viewport
  • SHOW ORIGINAL toggle → plays original unedited video
  • Toggle off → returns to edited version

Overlap Cycling (Expanded)

  • Overlapping edits show badge with count
  • Click badge → cycles visible edit, auto-selects newly visible one
  • Hidden edit has pointer-events disabled (can't accidentally interact)

7. Insert Focus Mode

Enter/Exit

  • Click focus mode button → switches to multi-lane view
  • Back button → returns to normal expanded view

Layout

Insert focus mode with VIDEO and AUDIO lane sections

  • VIDEO section: timed inserts in lanes, strips below
  • AUDIO section: timed audio assets in lanes, strips below
  • Assets packed into minimum lanes (no overlap within lane)
  • Section headers show section type

Interactions

  • Same drag/resize/select behavior as normal expanded mode
  • Popover works identically
  • Zoom and mini-map work identically
  • Overlap cycling badges visible and functional

7b. In-App Notifications

Bell Icon

  • Bell icon visible in content header (next to "?" tour button)
  • Red dot appears when unread count > 0
  • Red dot disappears when all marked read

Notification Panel (Dropdown)

  • Click bell opens compact dropdown card (not full-screen)
  • Click outside or X closes panel
  • Panel shows all unread + last 5 read notifications
  • "Read all" button visible when unread exist, marks all as read
  • Brutalist shadow styling, dark header

Notification Items

  • Type-colored dot: green (success), yellow (warning), red (error), orange (info)
  • Read notifications have gray dot
  • Time-ago display (now, 5m, 2h, 1d)
  • Check button marks individual as read (optimistic update)
  • X button archives/dismisses (optimistic, item removed immediately)

Real-Time (SSE)

  • Start analysis, wait for completion -- notification appears without refresh
  • Start export, wait for completion -- notification appears without refresh
  • Browser tab title shows "(N) Sapari" with unread count
  • Tab title resets to "Sapari" when all read

Dark Mode

  • Panel colors correct in dark mode
  • Orange border, dark header, correct text contrast

8. Asset Library

Asset Groups

  • Create group: click +, enter name → group appears
  • Rename group: click group name → inline edit → Enter saves, Escape cancels ("All Assets" is not renameable)
  • Delete group: click trash → confirmation → removed
  • Expand/collapse group: click card → toggles
  • Pin group: click pin → stays at top

Upload Assets

  • Drag file onto group → upload starts with progress bar
  • Upload completes → green checkmark, asset card appears
  • Upload fails → red X, error shown
  • Multiple files: all upload simultaneously

Asset Cards & Thumbnails

  • Hover shows action buttons (rename, edit, delete)
  • Video assets show frame thumbnail (center-cropped 240x240)
  • Image assets show scaled thumbnail
  • Audio assets show Mic icon (no thumbnail)
  • Broken/expired thumbnail URL falls back gracefully (no broken image icon)
  • Type label visible (VIDEO, AUDIO, IMAGE)

Asset Actions

  • Rename: pencil icon → inline edit → Enter saves
  • Delete: trash icon → confirmation → removed
  • Edit (scissors icon): opens Asset Editor

Search & Filter

  • Search box filters by name (real-time, no submit needed)
  • Type filter: All / Image / Video / Audio
  • Sort: Name A-Z, Name Z-A, Newest, Oldest

Asset Editor

  • Opens with video/audio preview
  • Trim sliders adjust playback region
  • Manual cuts: click to add cut points
  • Extract audio checkbox: saves audio as new asset
  • Save → processes and creates edited copy
  • Cancel → closes without changes

9. Asset Behavior Configuration

Fixed Positions

  • Set asset as Intro → always plays at video start
  • Set as Outro → always plays at video end
  • Set as Watermark → overlaid continuously (locked, can't drag)
  • Set as Background Audio → mixed with main audio, no visual

Insertion Modes

  • AI-Directed → AI places automatically during analysis
  • Manual → only placed when user explicitly inserts
  • Unconfigured → no auto-placement

10. Asset Placement & Visual Modes

Manual Insertion

  • Click INSERT ASSET in timeline controls → asset picker opens
  • Select asset → edit placed at playhead on Inserts Track
  • Cannot insert inside an existing INSERT region (snaps to nearest valid position)
  • Switching visual mode resets audio defaults (replace: original/0%, overlay: mix/50%/duck, insert: mix/100%)

Visual Mode: Replace

Replace mode — asset fullscreen, main video hidden

  • Asset plays fullscreen, replacing main video
  • Main video hidden during asset duration
  • Popover shows REPLACE mode indicator

Visual Mode: Overlay

Overlay mode — PIP on top of main video

  • Asset renders on top of main video (PIP)
  • Main video visible underneath
  • Position grid in popover: 9 positions + custom
  • Size slider: 10-200%
  • Opacity slider: 0-100%
  • Flip H / Flip V / Rotate buttons work

Visual Mode: Insert

Insert mode — asset spliced into timeline

  • Asset spliced into timeline, expanding total duration
  • Main video splits around insert
  • INSERT region visible on timeline
  • Image assets display as static image during insert duration
  • Video assets play during insert duration
  • Playback continues seamlessly through insert regions (no manual re-play needed)
  • Audio controls in popover: MIX / ASSET ONLY / MUTE toggle, volume slider, duck checkbox

Visual Mode: None (Audio Only)

  • No visual output
  • Audio plays per audio mode setting

Audio Modes

  • Original Only → only main audio plays
  • Asset Only → only asset audio, main muted
  • Mix → both play, volume slider controls asset level (0-100%)
  • Ducking toggle → main audio reduces during asset playback

11. WYSIWYG Overlay Editor

Drag

Selected overlay with resize handles and floating toolbar

  • Click overlay on video preview → selects it (border + handles appear)
  • Drag overlay → position updates in real-time
  • Snap to grid: overlay snaps when near grid lines (visual indicators appear)
  • Release → new X/Y position saved

Resize

  • Corner handles: drag to resize (maintains aspect ratio)
  • Edge handles: drag to resize one dimension
  • Minimum size enforced

Toolbar

  • Floating toolbar appears above selected overlay (when paused)
  • Size ± buttons (2% increments)
  • Opacity ± buttons (5% increments)
  • Flip H, Flip V, Rotate buttons
  • Toolbar repositions if near viewport edge
  • Playing video → toolbar hides
  • Deselect overlay → toolbar hides

Custom Position

  • Drag overlay to custom position → popover shows X% / Y% readout
  • RESET TO GRID button in popover → snaps back to grid position

12. Insert Overlap Modes (Merge/Split)

Detection

  • Place non-insert asset spanning an INSERT → popover shows "Overlaps" section
  • Works for: audio-only, overlay, and replace assets

Merge (Default)

Merge mode — continuous segment with dashed border

  • Toggle shows MERGE → asset plays through insert uninterrupted
  • Merge indicator (dashed border) visible on timeline segment

Split

Split mode — segment split with gap at insert

  • Click toggle → switches to SPLIT
  • Asset visually splits into separate pieces on timeline (gap at insert)
  • Asset audio pauses during insert, resumes after
  • For overlay: overlay disappears during insert
  • For replace: replace pauses, insert video appears, replace resumes

Multiple Overlaps

  • Asset overlapping two INSERTs → each has independent MERGE/SPLIT toggle
  • Can mix modes (merge with one insert, split with another)

13. Captions

Display

  • Captions render as text overlay on video during playback
  • Caption timing synced with playhead
  • Position: configurable (top/bottom)

CC Toggle (Video Preview)

  • CC button visible bottom-left of video preview (next to speed button)
  • Click CC → captions hidden in preview, button dims to white
  • Click CC again → captions shown, button turns orange
  • Toggle state syncs with export panel's captions enabled setting
  • Captions on by default when project has caption lines

Editor

  • Click to expand caption editor panel
  • Caption list shows all lines with time ranges
  • Active caption highlighted during playback
  • Auto-scroll follows playhead

Editing

  • Click caption text → inline editor appears
  • Edit text, press Enter → saves
  • Press Escape → cancels edit
  • Click reset icon → restores original text
  • Click trash → deletes caption line

Styling (in Export Settings)

  • Font size slider adjusts caption size
  • Position toggle: Top / Bottom
  • Font family: Sans / Serif / Mono
  • Text color picker
  • Background toggle + color + opacity

13c. Vertical Crop

CROP/BARS Toggle (SettingsSidebar FORMAT section)

  • Select non-native aspect ratio (e.g. 9:16) → CROP/BARS toggle appears
  • Click CROP → video auto-zooms to fill, enters adjusting mode
  • Click BARS → video letterboxed with background color
  • Same aspect ratio as source → no toggle shown

Zoom + Pan (adjusting mode)

  • Scroll wheel zooms in/out
  • Zoom snaps at fill level (no bars)
  • Zoom slider works (1x to 10x)
  • Drag to pan — smooth, axis-locked (mostly-horizontal drag locks vertical)
  • Pan snaps to center when close
  • Play button hidden during adjusting (drag works)
  • Speed badge doesn't scale with zoom

Accept Crop

  • Click DONE → crop locked, video plays normally
  • Click ADJUST CROP → re-enters adjusting mode
  • Export with crop → FFmpeg crop matches preview (no bars if filled)
  • Export without crop (BARS mode) → letterboxed as before

Overlays Stay Visible Under Crop (regression for #119)

  • Crop enabled + caption_position=top → captions visible at top of cropped frame (not pushed off-screen)
  • Crop enabled + caption_position=bottom → captions visible at bottom of cropped frame
  • Crop enabled + asset insert with overlay visual mode → overlay still visible at its configured position, not cropped out
  • Crop enabled + trial/free user watermark → watermark still visible at bottom-right corner of cropped frame
  • Changing zoom/pan doesn't visually scale or shift any overlay (only the video moves)
  • Preview matches the exported MP4 — captions, inserts, and watermark land in the same places

13d. Sidebar Presets (Editor — Format + Caption Style)

Saves the sidebar's visible state: aspect ratio, letterbox background, caption style/font/size/position/color/length, caption background (enable/color/opacity), and video flip H/V. Distinct from the configure-step Analysis Presets — this one is backed by /users/me/preview-presets.

  • No presets by default -- only "Custom" shown initially
  • Save current sidebar state as preset → enter name, save → appears in dropdown
  • Click saved preset → aspect ratio, caption style, caption font, caption size, caption position, caption color, caption length, caption BG enable/color/opacity, video flip H/V all update
  • After picking a preset, change any setting → dropdown resets to "Custom"
  • Re-pick the same preset → values re-apply (drift detection allows re-selection after edits)
  • Delete preset → removed from dropdown; if it was selected, dropdown switches to "Custom"
  • Saving preset A, then changing settings and saving preset B, then clicking A restores A's format+caption state

14. Export

Create Export

  • Click export button → export panel opens
  • Fill name, select platform (YouTube/TikTok/Instagram/Custom)
  • Select resolution (720p/1080p) -- 2K/4K post-launch (Phase 12J, Viral tier)
  • Toggle captions on/off
  • Set main audio volume (0-100%)
  • Click CREATE EXPORT → export added to list as pending

Export Progress

  • Rendering export shows spinner + progress percentage
  • Progress updates in real-time (SSE)

Download & Manage

  • Completed export shows green checkmark + download button
  • Click download → browser download starts (MP4)
  • File size and duration displayed
  • Failed export shows red X
  • Delete export: trash icon → confirmation → removed

15. Browser Tab Context

  • No project → tab shows "Sapari"
  • Open project → tab shows "ProjectName - Sapari"
  • Analyze → tab shows "Analyzing..." (with notification prefix if unread)
  • Analysis complete → tab returns to "ProjectName - Sapari"
  • Notification arrives → tab shows "(N) ProjectName - Sapari"
  • Mark all read → prefix disappears

15b. Welcome Modal (Trial Users)

  • First login as trial → welcome modal appears before onboarding
  • Shows credit balance, tier name, pricing (AI Edit 1x, Captions 0.5x)
  • Mentions watermark, credit conversion on subscribe, lock-out after trial
  • Click "GET STARTED" → modal dismisses, onboarding starts
  • Refresh → modal does not reappear
  • Non-trial user → modal never shows

15c. Shortcut Discovery

  • Settings > Editor tab > Help section shows "Keyboard Shortcuts" button
  • Click → ShortcutOverlay opens
  • ESC or backdrop click dismisses

15d. Error Handling

  • Trigger a 422 (e.g. bad request) → generic message + support ID shown
  • No internal field names or stack traces visible to user
  • Support ID is 8 characters, different each time

16. Edge Cases & Regression

Empty States

  • No projects → empty state with NEW PROJECT button
  • No edits detected → timeline empty, can still add manual edits
  • No assets → asset library shows empty state
  • No captions (language disabled) → caption panel hidden or shows empty

Boundary Conditions

  • Very short video (<5s) → analysis and editing still work
  • Very long video (>30min) → no performance degradation in timeline
  • Same asset placed twice → both instances play independently
  • Maximum zoom (50x) → timeline remains usable
  • Popover at viewport edge → repositions to stay visible (horizontally and vertically)

Undo/Redo Integrity

  • Undo after delete → all asset fields restored (visual mode, position, audio settings)
  • Undo drag of edit into insert region → anchor position correctly restored
  • Undo drag of edit out of insert region → anchor position correctly cleared
  • Undo after merge→split toggle → positioning updates correctly
  • Rapid play/pause (20x) → audio still works (no stuck states)

Timeline Consistency

  • Video overlay preview matches timeline position for all edit types
  • Edit at time 0 → renders correctly (no off-by-one)
  • Edit at video end → renders correctly
  • Resize edit to minimum → segment visible, handles accessible

Performance

  • 50+ edits on timeline → no lag during scroll/zoom
  • Drag edit → smooth 60fps movement (no jank)
  • Wheel zoom → smooth transition, no flickering
  • Mini-map updates in real-time during pan