A spec was a promise; sixteen of them had become descriptions of code that already shipped, sitting beside four that describe work still outstanding, with nothing in the file telling the two apart. Half the statuses were also wrong — audio-equalizer read "Accepted" with the EQ live on both platforms, the native video spec said the flag stays off after the default was flipped on. The shipped designs move into docs/architecture, which is the maintained description of the build, and the spec files go. Git history keeps the originals; what a future change still needs is carried across: - 01-rust-backend: favourites rewritten (the old section named a file that no longer exists and called shipped buttons "planned"), domain vocabulary owned by Rust (SearchScope, exclusions, the bitrate ladder), background workers - 02-svelte-frontend: app shell and chrome, library mosaic, series/episode navigation, downloaded browse, safe-area insets, native-video store, logging - 03-data-flow: locally-indexed search - 05-platform-backends: audio settings on ExoPlayer, the equalizer's band vocabulary, native video compositing, the background-audio handoff - 06-downloads-and-offline: one storage model, offline catalog visibility - 09-security: path confinement and input binding docs/specs/README.md now says what the directory is for and where each shipped design went. Deferred work the specs recorded is kept beside the code it concerns rather than lost: season-bounded autoplay, the two dead search commands, why indexing is a full crawl. requirements.md had fourteen stale statuses — Android audio parity still read "Linux only", DR-150 still said the native-video default was off, DR-190 was Proposed after DR-196 implemented it, and five tooling requirements were Proposed after landing. Three unbuilt specs suggested requirement ids that have since been allocated to other work; each now carries a warning.
1583 lines
66 KiB
Markdown
1583 lines
66 KiB
Markdown
# 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 30 seconds (shows animated feedback with "+30" indicator)
|
||
- **Single tap play/pause is deferred** by the 300 ms double-tap window, so a double tap
|
||
skips without also toggling pause (UR-061)
|
||
- **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 30s (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/[id]] --> 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 — a series' season list, a Home carousel (§5B.5), etc. —
|
||
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. Should an episode ever arrive without a `seriesId` (deep link, stale
|
||
cache), the bare Episode page renders as a fallback and links back to its parent
|
||
series and season by title so the user is never stranded.
|
||
|
||
### 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, Resume SxEy / Download / Favorite / Clear history)
|
||
→ Crew links
|
||
→ Genre tags
|
||
→ Seasons (collapsible; only the current season expanded)
|
||
→ 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.
|
||
|
||
**Rules for the seasons block** *(UR-062, UR-064)*:
|
||
|
||
- **The page opens where the viewer is.** The backend resolves the current
|
||
episode — in progress, else Next Up, else first unwatched, else the premiere —
|
||
and the page scrolls it into view with an `Up next` badge and a highlight ring.
|
||
Never season 1 by default, unless season 1 *is* where the viewer is.
|
||
- **Seasons collapse; only the current one is expanded.** A ten-season show
|
||
otherwise renders hundreds of rows and buries the episode the viewer came for.
|
||
A collapsed season still names its episode count and watched count, so
|
||
progress is readable without expanding it.
|
||
- **The hero button opens, it does not play.** It reads `Resume S2E4` /
|
||
`Play S1E1` — naming its target — and navigates to that episode's Focus View,
|
||
where Play commits. Play on a *container* is navigation (§5B.5); Play on a
|
||
*leaf* is the commitment.
|
||
- **A season is never its own page.** `/library/<seasonId>` redirects to
|
||
`/library/<seriesId>#season-N`. Every affordance that names a season — the
|
||
episode breadcrumb, a season card in a grid, a Downloads drill-in — lands on
|
||
the series with that season in view, so the episodes of all seasons stay one
|
||
browsable list.
|
||
- **Watch history is erasable** per series (hero) and per season (season
|
||
header). It confirms first, cannot be undone, and needs the server. Clearing a
|
||
whole series returns it to S1E1 by the same path a never-watched show takes.
|
||
|
||
### 5B.5 Home-card interaction — tap opens, long-press plays
|
||
|
||
Cards on the Home screen carousels (Next Movie, Next Episode, Continue
|
||
Watching, Recently Added, …) **do not play on tap.** A plain tap opens the
|
||
item; playback is the deliberate, second gesture.
|
||
|
||
| Card kind | Tap (short) | Long-press (~500 ms hold) |
|
||
|-----------|-------------|---------------------------|
|
||
| Movie | Movie detail page (`/library/<id>`) | Confirm → play now (`/player/<id>`) |
|
||
| Episode | Series Episode Focus View (`/library/<seriesId>?episode=<id>`, per §5B.1) | Confirm → play now (`/player/<id>`) |
|
||
| Series / Season / Album / Artist / Playlist / Folder | Detail page (`/library/<id>`) | Same as tap (no single "play now" target) |
|
||
| Channel / live leaf | Player (`/player/<id>`) — no detail page exists | Confirm → play now |
|
||
|
||
Rationale and rules:
|
||
|
||
- **Tap is navigation, not commitment.** Previously a tap on a movie/episode
|
||
jumped straight into the player, which made it easy to lose your place in a
|
||
half-watched item or start a stream you only meant to inspect. Tap now lands
|
||
on the detail/focus page, where Play is an explicit button.
|
||
- **Long-press is the shortcut for "just play it."** It surfaces a native
|
||
confirm (`Play "<name>" now?`) before starting playback, so an accidental
|
||
hold never blows away a resume position silently.
|
||
- **The long-press must not fight the carousel.** Detection cancels if the
|
||
pointer moves more than ~10 px (a horizontal scroll of the row), so holding
|
||
to scroll never triggers play.
|
||
- **Episodes still obey §5B.1** — a home tap on an episode opens the series
|
||
Focus View, never a bare Episode page, so the series context loads.
|
||
|
||
This behavior lives in `MediaCard` (`onLongPress` prop + pointer-based
|
||
detection) so any surface can opt in; today the Home carousels are the only
|
||
opt-in. Grids and other surfaces keep tap-to-open with no long-press.
|
||
|
||
---
|
||
|
||
## 5C. Favourites
|
||
|
||
Favouriting is a two-sided promise: the heart takes the input, and the app must
|
||
be able to give it back. This section covers both sides — where you can mark a
|
||
favourite, and where marked favourites resurface.
|
||
|
||
See [architecture/01-rust-backend.md](architecture/01-rust-backend.md#favorites-system) for the layer
|
||
assignment and wire shapes.
|
||
|
||
### 5C.1 The heart appears wherever an item does
|
||
|
||
A favourite is a property of an *item*, so the affordance follows the item
|
||
rather than living on one privileged screen. Any surface that shows a whole
|
||
item shows its heart.
|
||
|
||
| Surface | Heart position | Notes |
|
||
|---------|----------------|-------|
|
||
| Movie / Series detail hero | In the button row, after Play and Download | §5B.3, §5B.4 |
|
||
| Episode Focus View hero | Same row as Play / Download | §5B.2 |
|
||
| Album, Artist, Playlist detail | In the header button row | §5.2 |
|
||
| Media card (any grid or carousel) | Top-right overlay on the artwork | Hidden on server-only (greyed) cards |
|
||
| Mini player | Right of the track metadata | Existing behaviour, unchanged |
|
||
| Full player | Secondary controls row | §3.2 — **not yet built**, see §5C.5 |
|
||
|
||
Rules:
|
||
|
||
- **The heart never competes with the card.** On a media card it is its own
|
||
button and swallows the tap, so hearting an item never also opens or plays
|
||
it, and never triggers the §5B.5 long-press.
|
||
- **State is shown, not guessed.** A filled heart means the *server* considers
|
||
the item a favourite (or you just tapped it). An item favourited in Jellyfin
|
||
Web, on another device, or by another client renders filled here without
|
||
being touched in JellyTau.
|
||
- **Feedback is immediate.** The heart fills on tap and a toast confirms;
|
||
neither waits for the server round-trip.
|
||
|
||
### 5C.2 Three ways back to what you favourited
|
||
|
||
Favourites are not one destination — they are a lens, and the right surface
|
||
depends on whether the user is *browsing*, *deciding*, or *hunting*.
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
User[User wants their favourites] --> How{Intent}
|
||
|
||
How -->|Passive: show me something| Home[Home carousels<br/>Favourite Movies / Shows / Music]
|
||
How -->|Deliberate: my whole collection| Page[Favourites page<br/>/library/favorites]
|
||
How -->|Narrowing: within this library| Filter[Favourites filter<br/>on a library page]
|
||
|
||
Home -->|See all| Page
|
||
Page --> Detail[Item detail page]
|
||
Filter --> Detail
|
||
```
|
||
|
||
**Home carousels.** Rows for favourite movies, shows and music sit below
|
||
*Recently Added*. A row with nothing in it **does not render** — a fresh install
|
||
shows no empty favourite rows. Each row ends with *See all*, landing on the
|
||
matching tab of the Favourites page.
|
||
|
||
**The Favourites page** (`/library/favorites`) is the complete collection,
|
||
scoped by tabs:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ [←] Favourites │
|
||
│ ┌─────┬────────┬───────┬───────┐ │
|
||
│ │ All │ Movies │ Shows │ Music │ ← scope tabs │
|
||
│ └─────┴────────┴───────┴───────┘ │
|
||
│ │
|
||
│ ┌────┐┌────┐┌────┐┌────┐┌────┐ │
|
||
│ │ ♥ ││ ♥ ││ ♥ ││ ♥ ││ ♥ │ grid/list │
|
||
│ └────┘└────┘└────┘└────┘└────┘ per §5A │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
- Cards obey §5A in full — shape follows the media, so a mixed *All* tab reads
|
||
as posters, squares and thumbnails side by side rather than one forced shape.
|
||
- Reached from a card on the library overview (`/library`) and from *See all*
|
||
on any home favourites row.
|
||
- Sorted by name. Jellyfin does not record *when* an item was favourited, so
|
||
"recently favourited" is not offerable — see §5C.5.
|
||
- Empty state, per tab: *"Nothing favourited yet — tap the heart on anything
|
||
you like."*
|
||
|
||
**The in-library filter** is for narrowing where the user already is: a
|
||
favourites toggle in the header of the Movies, TV and Music browse pages,
|
||
filtering the current list in place. It is **session-scoped and not persisted** —
|
||
a sticky filter that silently hides most of a library reads as data loss on the
|
||
next launch.
|
||
|
||
### 5C.3 Removing a favourite removes it everywhere, at once
|
||
|
||
Un-hearting an item on the Favourites page removes its card from the grid
|
||
immediately; the same item disappears from the home rows and shows an empty
|
||
heart on its detail page without a manual refresh. The reverse holds for
|
||
favouriting. There is no confirmation prompt — the action is one tap to undo.
|
||
|
||
### 5C.4 Offline
|
||
|
||
- **Marking works offline.** The heart fills, the toast confirms, and the change
|
||
is held locally.
|
||
- **It reaches the server on reconnect**, without the user returning to the
|
||
screen where they made it.
|
||
- **Browsing offline shows favourites among media on the device**, subject to
|
||
the same "Show all server media" gate as every other browse surface (§7.2) —
|
||
with the gate off, an empty Favourites tab means *nothing favourited is
|
||
downloaded*, and the page does not quietly fall back to the server catalog.
|
||
|
||
### 5C.5 Known deviations
|
||
|
||
- **The full player has no heart.** §3.2 and §3.3 list a Favorite button among
|
||
the full player's secondary controls; it was never built, and this pass does
|
||
not add it. The mini player heart above it is the only in-player affordance.
|
||
*(UR-067)*
|
||
- **No "recently favourited" sort.** Jellyfin's API does not expose a favourite
|
||
timestamp, so favourites can only be ordered by name. Recording the
|
||
timestamp locally at toggle time would order *this device's* favourites only,
|
||
which is worse than a consistent name sort.
|
||
- **Music is one tab, not three.** The Music scope mixes albums, artists and
|
||
tracks in a single grid rather than offering sub-tabs. Acceptable while
|
||
favourite counts are small; revisit if the tab becomes unscannable.
|
||
|
||
---
|
||
|
||
## 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 (/)| All[Scope: All]
|
||
Where -->|Library root (/library)| 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 (default)| 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 2–3 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 (§9.1)]
|
||
|
||
Gate -->|Yes| Mode{Background mode armed?}
|
||
|
||
Mode -->|Background-audio toggle ON<br/>UR-040| Handoff[Hand off to native audio service<br/>WebView <video> 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/> (reflects live player state)]
|
||
|
||
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.
|