Files
jellytau/docs/ux-flows.md
T
dtourolle 9b1c9b3c91 feat(settings): rework settings page; remove unused SkeletonLoader/StorageManagement
Settings page refactor plus supporting docs (requirements, ux-flows,
traceability) and the frontend-domain-model spec with implementation-status
banner. Removes SkeletonLoader and StorageManagement components (no remaining
references).
2026-07-23 22:18:37 +02:00

1399 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JellyTau UX Flows & Screen Transitions
This document describes the expected user experience flows, screen transitions, and navigation patterns in JellyTau.
---
## 1. Core Navigation Structure
### 1.1 Navigation System
JellyTau uses a unified navigation system with a bottom navigation bar visible on all platforms (mobile and desktop) and additional header navigation for desktop.
**Bottom Navigation Bar (All Platforms - DR-045, UR-039):**
The bottom navigation bar is the primary navigation and is **always visible** on all platforms (mobile and desktop) except when:
- Full-screen video player is active
- User is on the login screen
**Bottom Nav Structure:**
```
┌─────────────────────────────────────────┐
│ [Home] [Library] [Search] │
└─────────────────────────────────────────┘
```
**Routes:**
- **Home** → `/` (home page with carousels and featured content)
- **Library** → `/library` (library selector showing all libraries)
- **Search** → `/search` (dedicated search page)
**Note:** Available on both mobile and desktop for consistent navigation access.
**Header Navigation (Desktop):**
On desktop (md breakpoint and above), the header contains:
- Logo (links to `/library`)
- Navigation links: Home, Library, Downloads, Settings
- Search bar (inline)
- Account menu (see §1.2)
**Mobile Navigation:**
On mobile, the header contains:
- Logo
- Account menu button (see §1.2)
### 1.2 Account Menu
Account-level destinations — the ones that are *about the user* rather than
about media — live behind a single **account menu**, anchored to the user's
name/avatar at the right of the header.
**Contents, in order:**
```
┌──────────────────────────┐
│ Signed in as <name> │ ← identity, not a menu item
│ <server host> │
├──────────────────────────┤
│ ⬇ Downloads │
│ ⚙ Settings │
│ ▦ Display │ ← grid/list preference (§5A.2)
├──────────────────────────┤
│ ⇥ Sign out │
└──────────────────────────┘
```
**Rules:**
- **One menu, both platforms.** Desktop and mobile show the same items in the
same order. A user who learns where Settings lives on one form factor finds
it in the same place on the other.
- **Anchored to identity.** The trigger is the username/avatar, because that is
where users look for account actions. A bare three-dot icon does not signal
"your account".
- **Sign out is separated** by a divider and placed last — it is destructive and
must not sit adjacent to routine navigation.
- **The menu is reachable from every authenticated screen**, not only from
library routes. See §1.3.
**Access Points Summary:**
- **Downloads** → header icon (desktop) + account menu (both)
- **Settings** → header nav link (desktop) + account menu (both)
- **Sign out** → account menu only
### 1.3 Chrome availability
The header is shared across chrome-bearing routes. Routes fall into three groups:
| Route group | Header | Bottom nav | Account menu reachable? |
|-------------|--------|------------|-------------------------|
| `/library/*` | Yes (own layout, shared `AppHeader`) | Yes | Yes |
| `/`, `/search`, `/downloads` | Yes (root-owned `AppHeader`) | Yes | Yes |
| `/settings` | Own layout | No | n/a — already there |
| `/player/*`, `/login` | No | No | No (by design) |
The rule the app honours: every authenticated, non-immersive screen exposes the
account menu. Only the full-screen player and the login screen are chrome-free.
### 1.4 Known deviations
*(None — the account-menu and chrome-availability defects tracked here under
UR-054 were resolved. Settings, Downloads, Display, and Sign out are now reachable
from every authenticated non-immersive screen via the shared `AccountMenu`, the
username/avatar is the menu trigger, desktop and mobile share one menu, and the
Display preference has a Settings entry — UR-029, §5A.4.)*
---
## 2. Initial App Launch Flow
### 2.1 First-Time Launch
```mermaid
flowchart TB
Launch[App Launch] --> CheckAuth{Stored<br/>Credentials?}
CheckAuth -->|No| LoginScreen[Login Screen<br/>/login]
CheckAuth -->|Yes| AutoLogin[Auto-login]
LoginScreen --> EnterURL[Enter Server URL]
EnterURL --> EnterCreds[Enter Username/Password]
EnterCreds --> LoginSuccess{Success?}
LoginSuccess -->|No| LoginError[Show Error]
LoginError --> EnterCreds
LoginSuccess -->|Yes| StoreToken[Store Token in Keyring]
AutoLogin --> TokenValid{Token Valid?}
TokenValid -->|No| LoginScreen
TokenValid -->|Yes| HomePage
StoreToken --> HomePage[Home Page<br/>/]
```
**Screens:**
1. **Login Screen** (`/login`)
- Server URL input
- Username input
- Password input
- "Remember me" checkbox (default: on)
- Login button
- No header, no bottom nav
2. **Home Page** (`/`)
- Default landing page after successful login
- Shows featured content, carousels, continue watching
- No MiniPlayer visible (nothing playing yet)
- Bottom nav: Home tab active
- Header with navigation links
### 2.2 Subsequent Launches
```mermaid
flowchart TB
Launch[App Launch] --> LoadAuth[Load Stored Token]
LoadAuth --> Validate{Token Valid?}
Validate -->|Yes| RestoreState[Restore Last Screen]
Validate -->|No| LoginScreen[Login Screen<br/>/login]
RestoreState --> CheckPlayer{Was Player<br/>Active?}
CheckPlayer -->|Yes| ShowMiniPlayer[Show MiniPlayer<br/>at bottom]
CheckPlayer -->|No| HideMiniPlayer[No MiniPlayer]
ShowMiniPlayer --> LastScreen[Last Active Screen<br/>with MiniPlayer]
HideMiniPlayer --> HomePage[Home Page<br/>/]
```
**State Restoration:**
- Last viewed screen (route) is restored (defaults to `/` if none)
- If audio was playing, MiniPlayer appears at bottom
- Playback state is NOT automatically resumed (user must press play)
- Queue is restored if it existed
---
## 3. Audio Playback Flows
### 3.1 Starting Audio Playback
```mermaid
flowchart TB
Start[User Action] --> Action{Action Type?}
Action -->|Click Track| TrackList[TrackList Component]
Action -->|Click Album| AlbumDetail[Album Detail Page]
Action -->|Click Play on Album| AlbumPlay[Play Album Button]
TrackList --> PlayTrack[Play Single Track]
PlayTrack --> QueueAll[Queue All Filtered Tracks]
AlbumPlay --> PlayAlbum[Play All Album Tracks]
PlayAlbum --> QueueAlbum[Queue Album Tracks]
QueueAll --> InvokePlay[invoke player_play_queue]
QueueAlbum --> InvokePlay
InvokePlay --> PlayerStarts[Player State: Playing]
PlayerStarts --> MiniAppears[MiniPlayer Slides Up<br/>from Bottom]
MiniAppears --> StayOnPage[User Stays on<br/>Current Screen]
```
**Entry Points for Audio Playback:**
1. **TrackList** (`/library/music/tracks`, `/library/music/albums/[id]`)
- Click track number → Play track + queue all visible tracks
- Clicking track #3 in an album → Play track 3, queue tracks 1-10
2. **Album Card** (grid views)
- Click album → Navigate to album detail
- Play button on card → Play album immediately
3. **Search Results**
- Click track → Play track + queue search results
- Click album → Navigate to album detail
**MiniPlayer Behavior:**
- Slides up from bottom with animation (300ms)
- Height: 64px on mobile, 80px on desktop
- Shows: artwork, title, artist, play/pause, next, favorite
- Stays visible on ALL screens (except video player)
- Click anywhere on MiniPlayer → Navigate to full player
**Track Highlighting:**
When audio is playing, the currently playing track is visually highlighted in track lists and album pages:
- Subtle blue background tint
- Left border accent in Jellyfin blue
- Title text colored in Jellyfin blue
- Desktop: Animated pulsing dots indicator next to title
- Mobile: Play arrow (▶) inline with title
- Highlight updates automatically when skipping to next/previous track
### 3.2 MiniPlayer → Full Player Transition
```mermaid
flowchart TB
Mini[MiniPlayer Visible] --> UserClick{User Action}
UserClick -->|Click MiniPlayer| NavFullPlayer[Navigate to<br/>/player/[id]]
UserClick -->|Swipe Up| SwipeGesture[Swipe Gesture<br/>Planned]
NavFullPlayer --> FullPlayer[Full Audio Player Screen]
SwipeGesture --> FullPlayer
FullPlayer --> ShowControls[Show Full Controls:<br/>- Large artwork<br/>- Progress bar<br/>- Volume slider<br/>- Queue button<br/>- Shuffle/Repeat<br/>- Favorite button]
ShowControls --> MiniHidden[MiniPlayer Hidden]
```
**Full Player Screen** (`/player/[id]`)
- **Header:** Song title, artist (clickable links to artist/album pages)
- **Artwork:** Large album art (centered, dominant)
- **Progress:** Seek bar with current time / total duration
- **Controls:** Previous, Play/Pause, Next (large touch targets)
- **Secondary Controls:** Shuffle, Repeat mode, Queue, Favorite
- **Volume:** Volume slider
- **Bottom Nav:** Still visible (can navigate away while playing)
- **Back button:** Returns to previous screen, MiniPlayer reappears
### 3.3 Full Player → Back to Browsing
```mermaid
flowchart TB
FullPlayer[Full Player Screen] --> UserAction{User Action}
UserAction -->|Back Button / Close| HistoryBack[window.history.back]
UserAction -->|Bottom Nav Click| NavOther[Navigate to<br/>Other Screen]
HistoryBack --> PrevScreen[Return to Previous Screen<br/>in Browser History]
NavOther --> NewScreen[Navigate to New Screen]
PrevScreen --> MiniReappears[MiniPlayer Slides Up<br/>from Bottom]
NewScreen --> MiniReappears
MiniReappears --> PlaybackContinues[Playback Continues<br/>in Background]
```
**Navigation Behavior:**
- **Back Button:** Uses browser history (`window.history.back()`) to return to the previous page
- **Expected behavior:** Returns user to the screen they were on before opening full player
- **Example:** User browsing album → clicks track → full player opens → clicks back → returns to album
**Key UX Principles:**
- **Playback Never Stops:** Navigating away from player does NOT stop playback
- **MiniPlayer Persistence:** MiniPlayer visible on ALL screens (except video/login)
- **Queue Preserved:** Current queue remains intact
- **State Restoration:** Returning to full player shows same state (position, volume, etc.)
- **Natural Navigation:** Back button behaves as expected (returns to previous page, not just closes modal)
---
## 4. Video Playback Flows
### 4.1 Starting Video Playback
```mermaid
flowchart TB
Start[User Action] --> Action{Action Type?}
Action -->|Click Movie| MovieDetail[Movie Detail Page]
Action -->|Click Episode| EpisodeClick[Episode Click]
Action -->|Click Play Button| PlayButton[Play Button]
MovieDetail --> PlayMovie[Play Movie Button]
EpisodeClick --> PlayEpisode[Play Episode]
PlayMovie --> CheckResume{Resume<br/>Position?}
PlayEpisode --> CheckResume
CheckResume -->|Yes, >30s| ShowDialog[Resume Dialog]
CheckResume -->|No| DirectPlay[Start from Beginning]
ShowDialog --> UserChoice{User Choice}
UserChoice -->|Resume| ResumePlay[Start at Saved Position]
UserChoice -->|Start Over| DirectPlay
ResumePlay --> FullscreenVideo[Fullscreen Video Player<br/>/player/[id]]
DirectPlay --> FullscreenVideo
FullscreenVideo --> HideUI[Hide All UI:<br/>- No Bottom Nav<br/>- No MiniPlayer<br/>- Fullscreen only]
```
**Resume Dialog:**
```
┌─────────────────────────────────────────┐
│ Continue Watching? │
│ │
│ [Movie Title] │
│ Resume from 12:34 / 1:45:00 │
│ │
│ [Start from Beginning] [Resume] │
└─────────────────────────────────────────┘
```
### 4.2 Video Player Screen (IR-003, IR-004, UR-003)
**Initial State (First 3 seconds):**
- Controls visible overlay
- Top bar: Back button, title
- Bottom bar: Play/Pause, seek bar, time, settings (subtitles, audio track)
- Center: Large play/pause button
**After 3 Seconds (Idle):**
- All controls fade out (500ms animation)
- Fullscreen video only
- System UI hidden (status bar, nav bar)
**User Interaction:**
- **Tap screen:** Controls reappear for 3 seconds
- **Double tap left side:** Rewind 10 seconds (shows animated feedback with "-10" indicator)
- **Double tap right side:** Forward 10 seconds (shows animated feedback with "+10" indicator)
- **Swipe up/down on left side:** Adjust brightness (0.3-1.7x, shows brightness indicator with progress bar)
- **Swipe up/down on right side:** Adjust volume (0-100%, shows volume indicator with progress bar)
- **Keyboard arrows:** ← rewind 10s, → forward 10s (desktop/external keyboard)
- **Keyboard space/K:** Toggle play/pause
- **Keyboard F:** Toggle fullscreen
- **Pinch:** Zoom (planned)
### 4.3 Exiting Video Player
```mermaid
flowchart TB
VideoPlaying[Video Playing] --> UserAction{User Action}
UserAction -->|Back Button| StopVideo[Stop Playback]
UserAction -->|Home Button| Background[App to Background]
UserAction -->|Video Ends| VideoEnd[Playback Ended]
StopVideo --> SaveProgress[Save Progress<br/>to Local DB + Server]
VideoEnd --> SaveComplete[Mark as Watched<br/>Save Progress]
Background --> PauseVideo[Pause Video]
SaveProgress --> ExitFullscreen[Exit Fullscreen]
SaveComplete --> AutoNext{Next Episode<br/>Available?}
AutoNext -->|Yes| ShowCountdown[Show Countdown<br/>Next in 5s...]
AutoNext -->|No| ExitFullscreen
ShowCountdown --> UserCancel{User Cancels?}
UserCancel -->|Yes| ExitFullscreen
UserCancel -->|No, timeout| PlayNext[Play Next Episode]
ExitFullscreen --> RestoreUI[Restore UI:<br/>- Bottom Nav<br/>- Previous Screen]
PlayNext --> VideoPlaying
PauseVideo --> ShowNotification[Show Notification:<br/>Tap to Resume]
```
**Auto-Next Overlay:**
```
┌─────────────────────────────────────────┐
│ │
│ [Episode Thumbnail] │
│ │
│ Next: S01E02 - Episode Title │
│ Starting in 5 seconds... │
│ │
│ [Cancel] [Play Now] │
└─────────────────────────────────────────┘
```
---
## 5. Music Library Navigation Flows
### 5.1 Music Category Landing Page
```mermaid
flowchart TB
LibraryHome[Library Home<br/>/library] --> ClickMusic[Click Music Library]
ClickMusic --> MusicLanding[Music Landing Page<br/>/library/music]
MusicLanding --> ShowCategories[Show Category Cards:<br/>- Tracks<br/>- Artists<br/>- Albums<br/>- Playlists<br/>- Genres]
ShowCategories --> UserClick{User Clicks Category}
UserClick -->|Tracks| TracksPage[All Tracks Page<br/>/library/music/tracks]
UserClick -->|Artists| ArtistsPage[Artists Grid<br/>/library/music/artists]
UserClick -->|Albums| AlbumsPage[Albums Grid<br/>/library/music/albums]
UserClick -->|Playlists| PlaylistsPage[Playlists Grid<br/>/library/music/playlists]
UserClick -->|Genres| GenresPage[Genres Browser<br/>/library/music/genres]
```
**Category Cards:**
```
┌─────────────────────────────────────────┐
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ 🎵 │ │ 👤 │ │ 💿 │ │
│ │Track│ │Artist│ │Album│ │
│ └──────┘ └──────┘ └──────┘ │
│ ┌──────┐ ┌──────┐ │
│ │ 📝 │ │ 🎭 │ │
│ │List │ │Genre│ │
│ └──────┘ └──────┘ │
└─────────────────────────────────────────┘
```
### 5.2 Albums View Flow
```mermaid
flowchart TB
AlbumsGrid[Albums Grid<br/>grid/list per §5A] --> UserAction{User Action}
UserAction -->|Click Album| AlbumDetail[Album Detail Page<br/>/library/[id]]
UserAction -->|Click Play on Card| PlayAlbum[Play Album Immediately]
AlbumDetail --> ShowAlbum[Show Album:<br/>- Album Art<br/>- Title, Artist<br/>- Track List<br/>- Download Button<br/>- Favorite Button]
ShowAlbum --> TrackAction{User Action}
TrackAction -->|Click Track| PlayTrack[Play Track + Queue Album]
TrackAction -->|Click Artist| NavArtist[Navigate to Artist Page]
TrackAction -->|Download Album| DownloadFlow[Download Flow]
TrackAction -->|Back Button| BackToGrid[Return to Albums Grid]
```
**Album Detail Layout:**
```
┌─────────────────────────────────────────┐
│ [←] [♡] [⬇] │
│ │
│ ┌────────────────────┐ │
│ │ │ │
│ │ Album Artwork │ │
│ │ │ │
│ └────────────────────┘ │
│ │
│ Album Title │
│ Artist Name (clickable) │
│ 2024 • 12 tracks • 45:23 │
│ │
│ [▶ Play] [🔀 Shuffle] │
│ │
│ ───────────────────────────────────── │
│ 1 Track Title 3:45 │
│ 2 Track Title 4:12 │
│ 3 Track Title 3:28 │
│ ... │
└─────────────────────────────────────────┘
```
### 5.3 Artist Navigation
```mermaid
flowchart TB
ArtistsGrid[Artists Grid] --> ClickArtist[Click Artist]
ClickArtist --> ArtistPage[Artist Detail Page<br/>/library/artist/[id]]
ArtistPage --> ShowContent[Show Artist Content:<br/>- Artist Photo<br/>- Biography<br/>- Albums Grid<br/>- Top Tracks<br/>- Similar Artists]
ShowContent --> UserAction{User Action}
UserAction -->|Click Album| AlbumDetail[Album Detail Page]
UserAction -->|Play Top Tracks| PlayArtist[Play Artist Radio]
UserAction -->|Click Similar Artist| OtherArtist[Other Artist Page]
```
---
## 5A. Library Page Layouts
Every browse page is one of two shapes: a **card grid** or a **row list**. This
section is the rule for which shape a page takes, what a card looks like, and
what the user is allowed to change.
### 5A.1 Card shape follows the media, not the page
Card aspect ratio is a property of *what the item is*, and is never overridden
per-page. This is the single most important layout rule: a user scanning a grid
recognises content type by silhouette before reading a word.
| Item type | Aspect | Rationale |
|-----------|--------|-----------|
| Album, Artist, Track, Playlist | **1:1 square** | Matches album art; the universal music convention (Spotify) |
| Movie, Series, Season | **2:3 poster** | Matches printed poster art; the universal video convention (Netflix) |
| Episode | **16:9 thumbnail** | A frame from the episode, not cover art — signals "a thing you watch next" |
| Library / collection folder | **16:9** | Reads as a container, distinct from the items inside it |
Artist cards are square but rendered **circular-masked**, so artists are
distinguishable from albums at a glance within the same music grid.
### 5A.2 Grid vs. list
```mermaid
flowchart TB
Page[Library browse page] --> Kind{Content kind}
Kind -->|Visual-first<br/>albums, artists, movies,<br/>shows, playlists| Grid[Card grid<br/>user may switch to list]
Kind -->|Ordinal<br/>tracks in an album,<br/>episodes in a season| List[Row list<br/>always; no toggle]
Grid --> Toggle[View toggle in page header]
Toggle --> Persist[Choice persists globally<br/>across all grid pages]
```
- **Grids are the default** for anything with cover art worth scanning.
- **Lists are mandatory, not optional**, where position carries meaning —
a track's number within an album, an episode's number within a season.
A grid destroys that ordering cue, so these pages expose **no toggle**.
- **The toggle is global, not per-page.** A user who prefers dense lists
prefers them everywhere; making them re-set it on each page is friction.
The choice persists across launches.
**Responsive columns** (grid mode), tuned so cards stay large enough to read
cover art on a phone and don't become postage stamps on a desktop:
| Breakpoint | Columns |
|------------|---------|
| base (phone) | 2 |
| sm | 3 |
| md | 4 |
| lg | 5 |
| xl | 6 |
### 5A.3 What a card shows
```
┌─────────────┐
│ │ ← cover art (aspect per §5A.1)
│ artwork │ • progress bar overlay if partially played
│ │ • watched/played check if complete
│ [▶] │ • play affordance on hover/focus
└─────────────┘
Primary line ← title, truncated to one line
Secondary line ← artist / year+rating / SxEy — one line, dimmed
```
- **Two lines of text maximum.** Titles truncate rather than wrap; a card that
grows to fit its title breaks grid alignment and makes scanning harder.
- **Progress and watched state live on the artwork**, not in the text — they
must be readable while scanning, without reading.
- **Hover/focus reveals play**, so a card is both a navigation target and a
playback target without a second control competing for space at rest.
### 5A.4 Known deviations
These are places the implementation currently diverges from the rules above.
They are recorded here so the gap is explicit rather than mistaken for intent.
- **The view toggle is discoverable only on a browse page.** The preference is
already global and persisted, but the only control that sets it is the pair
of icon buttons in a library page header. Settings has no display section, so
there is nowhere to look for it. *(UR-029)*
---
## 5B. Video Detail Page Composition
Movie, Series, and Episode detail pages all live at `/library/[id]`. Which
surface renders is decided by item type plus the `?episode=` query param, and
**section order is part of the spec** — it is what makes "keep watching this
show" the path of least resistance.
### 5B.1 Which surface renders
```mermaid
flowchart TB
Nav[Navigate to /library/&#91;id&#93;] --> Type{Item type}
Type -->|Person| Person[PersonDetailView]
Type -->|Movie| Movie[Movie detail<br/>§5B.3]
Type -->|Series| Ep{?episode= param<br/>present?}
Ep -->|Yes| Focus[Episode Focus View<br/>§5B.2]
Ep -->|No| Series[Series detail<br/>§5B.4]
Focus -->|Back to series| Series
Series -->|Click episode| Focus
```
An episode is **never** browsed as a bare `Episode` item page. Clicking an
episode anywhere navigates to `/library/<seriesId>?episode=<episodeId>`, so the
episode is always shown in the context of its series and the series' full
episode list is already loaded.
### 5B.2 Episode Focus View — section order
**The next episodes appear directly below the current episode, above cast and
similar shows.** Nothing may be inserted between the episode hero and the
episode strip.
```
┌─────────────────────────────────────────────────┐
│ [←] │
│ ┌───────────────────────────────────────────┐ │
│ │ episode backdrop │ │
│ │ Series Name │ │ ← 1. HERO
│ │ Episode Title │ │
│ │ S2E4 • 48m • ★8.1 │ │
│ │ Overview… │ │
│ │ ▓▓▓▓▓░░░░░ 32m left │ │
│ │ [▶ Play] │ │
│ └───────────────────────────────────────────┘ │
│ │
│ More Episodes │ ← 2. EPISODE STRIP
│ ┌──────┐┌──────┐┌──────┐┌──────┐ │ (immediately below hero)
│ │ E3 ││▓E4▓ ││ E5 ││ E6 │ → scroll │
│ │ ││NOW ││ ││ │ │
│ └──────┘└──────┘└──────┘└──────┘ │
│ │
│ Cast │ ← 3. CAST
│ ( ○ )( ○ )( ○ )( ○ ) │
│ │
│ More Like This │ ← 4. SIMILAR
│ ┌────┐┌────┐┌────┐┌────┐ │
└─────────────────────────────────────────────────┘
```
**Rules for the episode strip:**
- **Position is fixed.** Hero → episode strip → cast → similar. The strip sits
between the current episode and every other section; cast and related
content are *below* it, never above.
- **Window, not full list.** The strip shows a window around the current
episode — roughly 3 before and 6 after — so the immediate next episodes are
visible without scrolling, and earlier ones remain reachable by scrolling
left. It is horizontally scrollable, not a wrapped grid.
- **Forward bias.** More episodes are shown *after* the current one than
before it: the dominant intent on this screen is "watch the next one."
- **The current episode is present and marked.** It renders in-strip with a
"NOW" badge and a highlight ring, and is not clickable. It anchors the
user's position in the season rather than being hidden.
- **Cross-season continuity.** The window spans the whole series in episode
order, so the strip runs past a season boundary into the next season's first
episodes rather than dead-ending at the end of a season.
- **Per-episode state.** Each card shows a thumbnail, `SxEy` + title, a resume
progress bar when partially watched, and a watched checkmark when complete.
- **Clicking an episode swaps focus in place** (`?episode=` changes); it does
not start playback. Playback starts only from the hero's Play button.
### 5B.3 Movie detail — section order
```
Hero (poster, title, metadata, Play / Download / Favorite)
→ Crew links (Directed by / Written by / Music by)
→ Genre tags
→ Cast
→ More Like This
```
A movie has no continuation set, so cast follows the hero directly.
### 5B.4 Series detail — section order
```
Hero (poster, title, metadata, Play / Download)
→ Crew links
→ Genre tags
→ Seasons + episodes (per-season sections)
→ Cast
→ More Like This
```
The same principle as §5B.2: **episodes come before cast and similar shows.**
The reason a user opens a series page is to pick an episode; discovery content
is secondary and sits underneath.
---
## 6. Search Flow
Search is **context-scoped**: what you are looking at when you start a search
determines what the search covers. A search begun inside the Music library
searches music. A search begun from Home or the top-level library page searches
everything. The scope is always shown, and always overridable.
### 6.1 Scope is inherited from context
```mermaid
flowchart TB
Start[User starts a search] --> Where{Where from?}
Where -->|Home &#40;/&#41;| All[Scope: All]
Where -->|Library root &#40;/library&#41;| All
Where -->|Search tab| All
Where -->|Inside Music| Music[Scope: Music]
Where -->|Inside Movies| Movies[Scope: Movies]
Where -->|Inside TV| TV[Scope: TV]
All --> Chips[Filter chips shown<br/>All chip selected]
Music --> Chips2[Filter chips shown<br/>Music chip preselected]
Movies --> Chips2
TV --> Chips2
Chips --> Results[Results, grouped by type]
Chips2 --> Results
Results --> Change{User taps a chip}
Change --> Rescope[Re-run search at new scope<br/>query preserved]
Rescope --> Results
```
**Rules:**
- **Context sets the *initial* chip, never a locked filter.** Entering search
from TV preselects the TV chip; the user can tap "All" to widen without
retyping the query. Scope is a starting point, not a cage.
- **Home, `/library`, and the search tab all start at "All".** These are the
places a user has expressed no narrower intent.
- **Changing scope preserves the query** and re-runs the search. Changing the
query preserves the scope.
- **Scope maps to item types**, resolved at the point of search:
| Chip | `includeItemTypes` |
|------|--------------------|
| All | *(unset — every type)* |
| Music | `MusicAlbum`, `MusicArtist`, `Audio`, `Playlist` |
| Movies | `Movie` |
| TV | `Series`, `Episode` |
- **Chips render under the search bar**, on both the dedicated search page and
the in-library header search. They are horizontally scrollable if they
overflow, never wrapped onto a second row.
### 6.2 Search page layout
```
┌─────────────────────────────────────────┐
│ [🔍 Search...] [✕] │
│ │
│ ( All ) (•Music•) ( Movies ) ( TV ) │ ← scope chips
│ │
│ Songs ──────────────────────────── │
│ ♪ Song Title - Artist 3:45 │
│ ♪ Song Title - Artist 4:12 │
│ See all (23) │
│ │
│ Albums ─────────────────────────── │
│ [Cover] Album Title │
│ See all (8) │
│ │
│ Artists ────────────────────────── │
│ ( Photo ) Artist Name │
│ See all (5) │
└─────────────────────────────────────────┘
```
- Results stay **grouped by type** even when a scope is selected — a Music
search still separates Songs / Albums / Artists.
- Each group shows a bounded preview with a **See all (n)** affordance rather
than an unbounded list, so no single type can bury the others.
- Live search is **debounced** as the user types; a query that becomes empty
clears results rather than searching for the empty string.
### 6.3 Result group order is user-configurable
Which *kind* of thing a user is usually searching for is personal: a
music-first user wants Songs at the top, a TV-first user wants Shows. Rather
than guessing, the group order is a setting.
```mermaid
flowchart TB
Settings[Settings → Search] --> List[Draggable list of result groups]
List --> Drag[User drags a group up or down]
Drag --> Persist[Order persisted]
Persist --> Render[Rendering a result set]
Scope[Active scope chip §6.1] --> Render
Render --> Filter[1 - Drop groups outside the active scope]
Filter --> Sort[2 - Sort remaining groups by user order]
Sort --> Prune[3 - Omit groups with no results]
Prune --> Show[Render]
```
**Scope and order compose — they are two independent axes.** The scope chip
decides *which* groups are eligible; the settings list decides *what sequence*
the eligible ones appear in. Order is preserved as a relative ranking, never
renumbered per scope:
- Scope **Music** with order `Movies → Songs → Albums → Artists → TV` renders
`Songs → Albums → Artists`. Movies and TV are filtered out; the surviving
groups keep their relative order.
- Scope **All** with the same setting renders all five in exactly that order.
- **Changing scope never rewrites the saved order.** A user who narrows to
Music and back to All sees their original arrangement intact.
**Rules:**
- **Drag and drop to reorder**, in a settings list showing every result group
(Songs, Albums, Artists, Movies, TV Shows).
- **The order applies to grouped results everywhere** — the search page and
the in-library header search alike.
- **Order is presentation-only.** It never changes which results are returned
or how they are ranked *within* a group, only the sequence groups appear in.
- **Empty groups are skipped, not gapped.** A group with no results is omitted
entirely; it does not reserve space or leave a stray heading.
- **A sensible default ships** (Songs → Albums → Artists → Movies → TV Shows)
so the setting is an adjustment, never a prerequisite.
- **Keyboard/accessible reordering must exist** alongside dragging — a
drag-only control is unusable with a screen reader or without a pointer.
### 6.4 Known deviations
Recorded so the gap between this spec and the build is explicit.
- **Scope is not implemented.** The in-library header search calls the same
unscoped query as the global search page, so searching inside TV returns
music. The backend already accepts `includeItemTypes` on both the online and
offline paths, and the per-page list search already uses it — only the global
path ignores it. *(UR-049)*
- **Filter chips do not exist** on either search surface. *(UR-049)*
- **Group order is hardcoded** to Music → Movies → TV in the results markup,
with no setting. *(UR-050)*
---
## 7. Download Flows
### 7.1 Initiating Downloads
```mermaid
flowchart TB
User[User on Album/Track Page] --> ClickDownload[Click Download Button]
ClickDownload --> CheckType{Download Type?}
CheckType -->|Single Track| DownloadTrack[Download Single File]
CheckType -->|Album| DownloadAlbum[Download All Tracks]
CheckType -->|Artist| ShowOptions[Show Options Dialog]
ShowOptions --> UserChoice{User Choice}
UserChoice -->|Discography| DownloadAll[Download All Albums]
UserChoice -->|Select Albums| AlbumPicker[Album Selection UI]
DownloadTrack --> QueueDownload[Queue in Download Manager]
DownloadAlbum --> QueueMultiple[Queue Multiple Files]
QueueDownload --> ShowProgress[Show Progress Ring<br/>on Download Button]
QueueMultiple --> ShowProgress
ShowProgress --> DownloadActive[Download Active:<br/>Button shows % complete]
```
**Download Button States:**
```
States:
1. [⬇] Available - Gray outline
2. [○ 45%] Downloading - Blue ring progress
3. [✓] Downloaded - Green checkmark
4. [!] Failed - Red with retry option
5. [⏸] Paused - Yellow pause icon
```
### 7.2 Downloads = a browsable offline library, not a flat list
**The central idea:** "my downloads" is not a list of file-transfer rows — it is
*the library, filtered to what's on the device*. A user who has downloaded three
seasons of a show and two albums thinks in terms of shows and albums, not
seventy-odd individual episode/track transfers. So the primary Downloads surface
**reuses the library browse screens**, scoped to downloaded content, and keeps
the transfer-progress list as a secondary "Transfers" view for the *act* of
downloading.
This splits one overloaded page into two clear jobs:
| Surface | Answers | Reuses |
|---------|---------|--------|
| **Downloaded** (browse) | "What do I have offline, and let me play it" | Library grids, detail pages, cards (§5A) |
| **Transfers** (activity) | "What is downloading right now, and control it" | The existing progress-row list |
```mermaid
flowchart TB
Nav[Open Downloads] --> Downloads[/downloads]
Downloads --> View{View}
View -->|Downloaded &#40;default&#41;| Browse[Offline library browse]
View -->|Transfers| Activity[Transfer activity list]
Browse --> Libs[Libraries — only those with<br/>downloaded content]
Libs --> Grid[Library grid, offline-scoped<br/>same cards/layout as online §5A]
Grid --> Detail[Detail page<br/>same as online]
Detail --> Play[Play from local file]
Detail --> Remove[Remove download<br/>frees space, keeps browsable? — see rules]
Activity --> Rows[Per-transfer rows:<br/>downloading / queued / paused / failed /<br/>waiting-for-WiFi]
Rows --> Ctl[Pause / Resume / Cancel / Retry]
```
**Why reuse the library screens (not a bespoke list):**
- **One mental model.** Browsing offline should feel identical to browsing
online — same grids, same card shapes, same detail pages, same play action.
The only difference is *what's present*, not *how it looks*.
- **It already works in the backend.** The offline repository's `get_items`
already returns downloaded items **plus** their containers (an album with any
downloaded track, a series/season with any downloaded episode). That is a
browsable tree today — see §7.4.
- **It scales.** A flat completed-list becomes unusable at a few dozen items; a
browsable library does not.
### 7.3 The Downloaded browse surface
```
┌─────────────────────────────────────────────┐
│ Downloads │
│ ( Downloaded ) ( Transfers ) ← view switch
│ │
│ [~ 3.4 GB on device · 12 items] Manage ▸ │ ← storage summary
│ │
│ Music │ ← only libraries that
│ ┌────┐┌────┐┌────┐ │ have downloaded content
│ │alb ││alb ││art │ │
│ └────┘└────┘└────┘ │
│ │
│ TV │
│ ┌────┐┌────┐ │
│ │show││show│ │
│ └────┘└────┘ │
└─────────────────────────────────────────────┘
```
**Rules:**
- **Libraries with nothing downloaded are omitted**, not shown empty. If only
music is downloaded, only Music appears.
- **Cards, grids, and detail pages are the library's own** (§5A) — offline
browse is the same components with an offline-scoped data source, never a
parallel re-implementation.
- **A downloaded badge / "on device" affordance** distinguishes fully-downloaded
from partially-downloaded containers (e.g. a season with 6 of 10 episodes).
- **Disk usage is shown where the user already looks**, in familiar units — see
§7.3.1.
- **Play always plays the local file** here; nothing on this surface streams.
- **Remove is available at every level** — item, album/season, series — and
states clearly what it frees. Removing the last downloaded child of a
container removes the container from the browse.
- **This surface works identically online and offline.** It is "what's on the
device," a question whose answer does not depend on connectivity. It must not
wait for, or be emptied by, server reachability.
#### 7.3.1 Disk usage — familiar, in place, not a separate audit
Users want to know what each thing costs on disk, but that information has to
feel like the storage views they already know (phone Settings → Storage, a
file browser), not a developer's byte dump.
- **Size rides along with the item, on the card and the detail page** — a small
secondary label (`1.2 GB`, `340 MB`, `48 MB`), never a separate "storage
report" screen the user has to go find.
- **Containers show their total.** A series shows the sum of its downloaded
episodes; an album the sum of its tracks; a season its own subtotal. The
number a user sees on the "Breaking Bad" card is what removing it frees.
- **Human units, rounded, consistent.** Binary or decimal is a choice — pick one
and use it everywhere. Show 23 significant figures (`1.2 GB`, not
`1,283,048,192 bytes` and not `1.28394 GB`).
- **A single device total sits at the top** of the Downloaded surface
(`3.4 GB on device · 12 items`) so the headline number is answered before the
user scans. It reconciles with the sum of what's listed.
- **Remove restates the reclaim** in the same units at the point of action
("Remove download · frees 1.2 GB"), so the cost of keeping vs. freeing is
legible exactly when the user decides.
- **Sort/filter by size is a reasonable enhancement** ("biggest first" to find
what to clear) but is not required for v1.
The bytes-on-disk per item are a backend fact (the download manager writes the
files and can stat them); this is a display and aggregation task, not new
tracking. See §7.7 deviations for what's missing today.
### 7.4 Transfers (activity) view
The existing progress-row list, unchanged in spirit, demoted to a secondary tab.
It is about *transfers in flight*, so it shows only rows that are doing or
waiting to do something:
- **States:** downloading (with progress), queued, paused, failed,
waiting-for-WiFi (§7.5).
- **Controls:** Pause / Resume / Cancel / Retry per row; the 3-concurrent cap
and auto-pump are backend concerns and are not surfaced as manual controls.
- **Completed transfers fall off this view** once done — the finished item lives
in Downloaded, not here. A transient "just finished" confirmation is fine; a
permanent completed-list is not (that's what Downloaded is for).
- **Empty state** points at the library: "Nothing downloading. Browse your
library and tap download to save media for offline."
### 7.5 Navigation & entry points
- Reached via the account menu (§1.2) and, on desktop, the header Downloads
link/icon → `/downloads`.
- `/downloads` opens on **Downloaded** by default; **Transfers** is one tap away
and should draw attention (badge/count) only while transfers are active.
- Initiating a download is unchanged (§7.1): the download button lives on
item/album/series detail pages. The Downloads page manages and browses; it is
not where you start a download.
### 7.7 Known deviations
Recorded so the gap between this spec and the build is explicit.
- **Downloads is a flat two-tab list today** (Active / Completed), rendering one
row per individual transfer with no browsing, grouping, or reuse of the
library screens. Completed downloads never collapse into their album/series.
*(UR-055)*
- **No offline-scoped browse entry point exists in the client.** All browsing
goes through the hybrid repository, which merges cache **and** server; there is
no way to ask for "downloaded content only" as a browse surface. The offline
repository supports it (§7.2) but is not reachable independently. *(UR-055,
DR-082)*
- **The "on device" storage summary and per-container remove** are absent from
the completed list. *(UR-055, UR-056)*
- **Per-item disk usage is not displayed anywhere.** Cards and detail pages show
no size; there is no device total, no container subtotal, and Remove does not
state what it frees. *(UR-056)*
---
## 8. Settings & Account Flows
### 8.1 Settings Navigation
```mermaid
flowchart TB
User[User] --> NavChoice{Navigation Path}
NavChoice -->|Desktop| HeaderSettings[Header: Click Settings Link]
NavChoice -->|Mobile| OverflowMenu[Click Overflow Menu<br/>→ Settings]
NavChoice -->|Direct| TypeURL[Navigate to /settings]
HeaderSettings --> SettingsPage[Settings Page<br/>/settings]
OverflowMenu --> SettingsPage
TypeURL --> SettingsPage
SettingsPage --> ShowSections[Show Sections:<br/>- Account<br/>- Playback<br/>- Downloads<br/>- Appearance<br/>- About]
ShowSections --> UserClick{User Clicks Section}
UserClick -->|Account| AccountSettings[Account Settings:<br/>- Server URL<br/>- Username<br/>- Logout button]
UserClick -->|Playback| PlaybackSettings[Playback Settings:<br/>- Gapless playback<br/>- Volume normalization<br/>- Crossfade duration]
UserClick -->|Downloads| DownloadSettings[Download Settings:<br/>- Max concurrent<br/>- WiFi only<br/>- Storage location<br/>- Auto-cache next tracks]
UserClick -->|Appearance| AppearanceSettings[Appearance Settings:<br/>- Dark mode<br/>- Accent color]
```
**Navigation to Settings:**
- **Desktop:** Click "Settings" link in header navigation
- **Mobile:** Click three-dot overflow menu → Select "Settings"
- **Direct:** Navigate to `/settings` route
**Settings apply instantly.** Every control on the Settings page persists the
moment the user changes it — toggling a switch, picking a level, or releasing a
slider writes that setting immediately. There is **no "Save" button** and no
save/dirty state to reason about; leaving the page never risks losing a change.
Sliders update their live readout while dragging but only persist on release
(`change`, not each `input` tick) to avoid flooding the backend.
### 8.2 Logout Flow
```mermaid
flowchart TB
AnyScreen[Any Screen] --> ClickLogout[Click Logout Button<br/>in Header]
ClickLogout --> ConfirmDialog[Show Confirmation:<br/>"Log out of [Server]?"]
ConfirmDialog --> UserConfirm{User Confirms?}
UserConfirm -->|No| CancelLogout[Cancel - Stay on Current Screen]
UserConfirm -->|Yes| StopPlayer[Stop Playback]
StopPlayer --> ClearToken[Delete Token from Keyring]
ClearToken --> ClearState[Clear App State:<br/>- Player state<br/>- Queue<br/>- Current screen]
ClearState --> NavLogin[Navigate to Login Screen<br/>/login]
NavLogin --> ShowLogin[Show Login Screen:<br/>- No Header<br/>- No Bottom Nav<br/>- No MiniPlayer]
```
**Logout Button Location:**
- Always visible in header user menu (logout icon)
- Accessible from any authenticated screen
---
## 9. Background & Lock Screen Behavior
### 9.1 Audio Playback in Background (Android)
```mermaid
flowchart TB
Playing[Audio Playing] --> Background{User Action}
Background -->|Home Button| AppBackground[App to Background]
Background -->|Screen Lock| ScreenLock[Screen Locked]
AppBackground --> ContinuePlay[Playback Continues]
ScreenLock --> ContinuePlay
ContinuePlay --> ShowNotification[Show Media Notification:<br/>- Artwork<br/>- Title/Artist<br/>- Play/Pause<br/>- Next/Previous]
ShowNotification --> LockScreen[Lock Screen Controls:<br/>Media Session Integration]
LockScreen --> UserInteract{User Interaction}
UserInteract -->|Tap Notification| OpenApp[Open App to Last Screen<br/>with MiniPlayer]
UserInteract -->|Lock Screen Controls| SendCommand[Send Command to Player]
UserInteract -->|BLE Headset Button| HeadsetControl[AVRCP Command]
```
**Notification Layout (Android):**
```
┌─────────────────────────────────────────┐
│ [Artwork] Song Title │
│ Artist Name │
│ Album Name │
│ │
│ [⏮] [⏸] [⏭] [✕] │
└─────────────────────────────────────────┘
```
### 9.2 Video Playback in Background (Android — PiP & Background Audio)
Leaving the app while a **local video** is playing does not simply pause it.
What happens depends on which background behaviour is active. The two are
**mutually exclusive**, and both apply **only to locally-rendering video**
audio-only playback, library/menu browsing, and remote/cast sessions never
trigger PiP (see decision gate below).
```mermaid
flowchart TB
Leave[User leaves app<br/>Home / gesture / screen lock] --> Gate{Local video surface<br/>actively rendering?<br/>canEnterPip}
Gate -->|No — audio, browsing,<br/>or remote/cast| Normal[App backgrounds normally<br/>audio, if any, continues via<br/>media notification &#40;§9.1&#41;]
Gate -->|Yes| Mode{Background mode armed?}
Mode -->|Background-audio toggle ON<br/>UR-040| Handoff[Hand off to native audio service<br/>WebView &lt;video&gt; torn down,<br/>video decode stops, audio continues]
Mode -->|Default<br/>UR-041| PiP[Auto-enter Picture-in-Picture<br/>on onUserLeaveHint]
PiP --> PiPWindow[Floating PiP window:<br/>- Video keeps rendering into surface<br/>- WebView hidden<br/>- Play/Pause RemoteAction<br/> &#40;reflects live player state&#41;]
PiPWindow --> PiPReturn{User action}
PiPReturn -->|Tap window| Restore[Return to full player<br/>WebView restored, surface re-fit]
PiPReturn -->|Close window| Stop[Playback stops]
Handoff --> Foreground[On return to foreground:<br/>resume WebView video at position]
```
**Key rules:**
- **Video-only gate.** Auto-PiP is guarded by the native `canEnterPip` check
(local video surface actively rendering). Audio playback and menu/library
browsing background normally; remote/cast sessions render nothing locally, so
a PiP window would be an empty box and is refused. *(UR-041, IR-026)*
- **Only one background behaviour at a time.** The background-audio toggle
(UR-040) disarms auto-PiP while it is on, so a video is either handed to the
audio service *or* floated in PiP, never both.
- **PiP controls track the player.** The play/pause RemoteAction in the PiP
window reflects the live player state and updates on every playback-state
change, not only when the button is pressed. *(DR-053)*
- **Non-disruptive transition.** ExoPlayer keeps rendering into the same
surface across enter/exit, so entering or leaving PiP never interrupts the
video; on exit the surface is re-fit to full-screen bounds. *(DR-053)*
**PiP window (Android):**
```
┌───────────────────┐
│ │
│ ▶ video frame │
│ │
│ [⏸] │ ← play/pause RemoteAction
└───────────────────┘
sized to the video's aspect ratio
```
---
## 10. Error States & Edge Cases
### 10.1 Network Loss During Streaming
```mermaid
flowchart TB
Streaming[Streaming Audio/Video] --> LoseNetwork[Network Connection Lost]
LoseNetwork --> CheckLocal{Local Copy<br/>Available?}
CheckLocal -->|Yes| SwitchLocal[Switch to Local Playback<br/>Seamlessly]
CheckLocal -->|No| ShowBuffer[Show Buffering Spinner]
ShowBuffer --> WaitReconnect[Wait for Reconnection<br/>30 second timeout]
WaitReconnect --> Reconnect{Reconnected?}
Reconnect -->|Yes| Resume[Resume Streaming]
Reconnect -->|No| ShowError[Show Error Toast:<br/>"Unable to stream.<br/>Check connection."]
ShowError --> OfferRetry[Offer Retry Button]
ShowError --> OfferDownload[Offer "Download for Offline"]
```
### 10.2 Server Unreachable
```mermaid
flowchart TB
Action[User Action Requires Server] --> TryConnect[Attempt Connection]
TryConnect --> Timeout{Connection<br/>Timeout?}
Timeout -->|Yes| ShowError[Show Error:<br/>"Server unreachable"]
Timeout -->|No| Success[Action Succeeds]
ShowError --> OfferOptions[Offer Options:<br/>- Retry<br/>- Switch to Offline Mode<br/>- Change Server]
```
### 10.3 Download Failed
```mermaid
flowchart TB
Downloading[Download in Progress] --> Failure{Failure Type?}
Failure -->|Network Error| Retry[Auto-retry<br/>with Backoff]
Failure -->|Disk Full| ShowDiskError[Show Error:<br/>"Not enough storage"]
Failure -->|Server Error| ShowServerError[Show Error:<br/>"Server error"]
Retry --> RetryCount{Retry Count<br/>< 3?}
RetryCount -->|Yes| Downloading
RetryCount -->|No| Failed[Mark as Failed]
ShowDiskError --> Failed
ShowServerError --> Failed
Failed --> UserAction[Show in Downloads:<br/>with Retry Button]
```
---
## 11. Platform-Specific UX Patterns
### 11.1 Android-Specific
**Hardware Back Button:**
- **In Full Player:** Return to previous screen, show MiniPlayer
- **In Video Player:** Stop playback, exit fullscreen
- **In Album Detail:** Return to library grid
- **At Library Home:** Exit app (show confirmation)
**System Volume Buttons:**
- **While playing audio:** Adjust playback volume
- **While controlling remote session:** Adjust remote session volume (shows session name in volume panel)
- **In menus:** Adjust system volume (default behavior)
**Share Integration:**
- Long-press album/song → Share menu
- Options: Share with other apps, Copy link
### 11.2 Linux Desktop-Specific
**Keyboard Shortcuts:**
- `Space`: Play/Pause
- `→`: Next track
- `←`: Previous track
- `/`: Focus search
- `Ctrl+Q`: Quit
**Window Behavior:**
- Minimize to tray (playback continues)
- Close window (show confirmation if playing)
- MPRIS integration for desktop media controls
**Mouse Interactions:**
- Hover over MiniPlayer: Show additional controls (volume, queue peek)
- Right-click: Context menu (Add to playlist, Go to artist, Download)
---
## 12. UX Principles Summary
### 12.1 Core Principles
1. **Playback Persistence:**
- Audio playback never stops unless user explicitly stops it
- MiniPlayer visible on all screens (except video/login)
- Queue and position preserved across navigation
2. **Non-Blocking UI:**
- Downloads happen in background
- Sync operations never block user interaction
- Optimistic updates (favorite, progress) with background sync
3. **Offline-First:**
- Downloaded content works offline
- Seamless switch between online/offline
- Progress and preferences saved locally
4. **Progressive Disclosure:**
- Simple defaults, advanced options hidden
- Context menus for secondary actions
- Settings organized by category
5. **Responsive Design:**
- Mobile-first UI
- Desktop enhancements (hover states, keyboard shortcuts)
- Tablet: Grid layouts with more columns
### 12.2 Animation & Transitions
| Transition | Duration | Easing |
|------------|----------|--------|
| MiniPlayer slide up/down | 300ms | ease-out |
| Screen navigation | 200ms | ease-in-out |
| Video controls fade | 500ms | ease-out |
| Download button state change | 150ms | ease-in-out |
| Modal appear | 200ms | ease-out |
| Toast notification | 250ms | ease-in-out |
### 12.3 Touch Targets (Mobile)
| Element | Minimum Size |
|---------|--------------|
| Bottom nav buttons | 48x48 dp |
| List item (track, album) | Full width x 56 dp |
| Player controls | 56x56 dp |
| MiniPlayer | Full width x 64 dp |
| Download button | 40x40 dp |
| Favorite button | 40x40 dp |
---
## 13. Future UX Enhancements
### 13.1 Planned Features
1. **Gesture Navigation:**
- Swipe up on MiniPlayer → Full player
- Swipe down on full player → Back to previous screen
- Swipe between tracks in full player
2. **Queue Management UI (DR-020):**
- Drag to reorder
- Swipe to remove
- Add to queue vs. Play next
3. **Sleep Timer (UR-026):**
- Accessible from full player menu
- Presets: 15min, 30min, 1hr, End of track, End of album
- Countdown visible in MiniPlayer
4. **Home Screen (UR-034):**
- Hero banner carousel
- Continue watching/listening
- Recently added
- Personalized recommendations
5. **Cast/Remote Control Enhancements:**
- Picture-in-picture for remote sessions
- Multi-room audio (play on multiple devices)
- Handoff (transfer playback to phone from TV)
### 13.2 Accessibility Enhancements
- Screen reader optimization
- High contrast mode
- Larger text option
- Voice control integration
- Haptic feedback for controls
---
This UX flow documentation should be updated as new features are implemented and user feedback is incorporated.