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).
56 KiB
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
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:
-
Login Screen (
/login)- Server URL input
- Username input
- Password input
- "Remember me" checkbox (default: on)
- Login button
- No header, no bottom nav
-
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
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
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:
-
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
-
Album Card (grid views)
- Click album → Navigate to album detail
- Play button on card → Play album immediately
-
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
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
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
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
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
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
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
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
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
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 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
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 includeItemTypesAll (unset — every type) Music MusicAlbum,MusicArtist,Audio,PlaylistMovies MovieTV 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.
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 → TVrendersSongs → 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
includeItemTypeson 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
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 |
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_itemsalready 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, not1,283,048,192 bytesand not1.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. /downloadsopens 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
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
/settingsroute
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
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)
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).
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
canEnterPipcheck (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
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
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
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 searchCtrl+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
-
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
-
Non-Blocking UI:
- Downloads happen in background
- Sync operations never block user interaction
- Optimistic updates (favorite, progress) with background sync
-
Offline-First:
- Downloaded content works offline
- Seamless switch between online/offline
- Progress and preferences saved locally
-
Progressive Disclosure:
- Simple defaults, advanced options hidden
- Context menus for secondary actions
- Settings organized by category
-
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
-
Gesture Navigation:
- Swipe up on MiniPlayer → Full player
- Swipe down on full player → Back to previous screen
- Swipe between tracks in full player
-
Queue Management UI (DR-020):
- Drag to reorder
- Swipe to remove
- Add to queue vs. Play next
-
Sleep Timer (UR-026):
- Accessible from full player menu
- Presets: 15min, 30min, 1hr, End of track, End of album
- Countdown visible in MiniPlayer
-
Home Screen (UR-034):
- Hero banner carousel
- Continue watching/listening
- Recently added
- Personalized recommendations
-
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.