# 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/&#91;id&#93;] --> Type{Item type}

    Type -->|Person| Person[PersonDetailView]
    Type -->|Movie| Movie[Movie detail<br/>§5B.3]
    Type -->|Series| Ep{?episode= param<br/>present?}

    Ep -->|Yes| Focus[Episode Focus View<br/>§5B.2]
    Ep -->|No| Series[Series detail<br/>§5B.4]

    Focus -->|Back to series| Series
    Series -->|Click episode| Focus
```

An episode is **never** browsed as a bare `Episode` item page. Clicking an
episode anywhere — 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 [specs/favorites-browsing.md](specs/favorites-browsing.md) 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 &#40;/&#41;| All[Scope: All]
    Where -->|Library root &#40;/library&#41;| All
    Where -->|Search tab| All
    Where -->|Inside Music| Music[Scope: Music]
    Where -->|Inside Movies| Movies[Scope: Movies]
    Where -->|Inside TV| TV[Scope: TV]

    All --> Chips[Filter chips shown<br/>All chip selected]
    Music --> Chips2[Filter chips shown<br/>Music chip preselected]
    Movies --> Chips2
    TV --> Chips2

    Chips --> Results[Results, grouped by type]
    Chips2 --> Results

    Results --> Change{User taps a chip}
    Change --> Rescope[Re-run search at new scope<br/>query preserved]
    Rescope --> Results
```

**Rules:**

- **Context sets the *initial* chip, never a locked filter.** Entering search
  from TV preselects the TV chip; the user can tap "All" to widen without
  retyping the query. Scope is a starting point, not a cage.
- **Home, `/library`, and the search tab all start at "All".** These are the
  places a user has expressed no narrower intent.
- **Changing scope preserves the query** and re-runs the search. Changing the
  query preserves the scope.
- **Scope maps to item types**, resolved at the point of search:

  | Chip | `includeItemTypes` |
  |------|--------------------|
  | All | *(unset — every type)* |
  | Music | `MusicAlbum`, `MusicArtist`, `Audio`, `Playlist` |
  | Movies | `Movie` |
  | TV | `Series`, `Episode` |

- **Chips render under the search bar**, on both the dedicated search page and
  the in-library header search. They are horizontally scrollable if they
  overflow, never wrapped onto a second row.

### 6.2 Search page layout

```
┌─────────────────────────────────────────┐
│  [🔍 Search...]                  [✕]    │
│                                         │
│  ( All ) (•Music•) ( Movies ) ( TV )    │  ← scope chips
│                                         │
│  Songs ────────────────────────────     │
│  ♪ Song Title - Artist          3:45    │
│  ♪ Song Title - Artist          4:12    │
│  See all (23)                           │
│                                         │
│  Albums ───────────────────────────     │
│  [Cover] Album Title                    │
│  See all (8)                            │
│                                         │
│  Artists ──────────────────────────     │
│  ( Photo ) Artist Name                  │
│  See all (5)                            │
└─────────────────────────────────────────┘
```

- Results stay **grouped by type** even when a scope is selected — a Music
  search still separates Songs / Albums / Artists.
- Each group shows a bounded preview with a **See all (n)** affordance rather
  than an unbounded list, so no single type can bury the others.
- Live search is **debounced** as the user types; a query that becomes empty
  clears results rather than searching for the empty string.

### 6.3 Result group order is user-configurable

Which *kind* of thing a user is usually searching for is personal: a
music-first user wants Songs at the top, a TV-first user wants Shows. Rather
than guessing, the group order is a setting.

```mermaid
flowchart TB
    Settings[Settings → Search] --> List[Draggable list of result groups]

    List --> Drag[User drags a group up or down]
    Drag --> Persist[Order persisted]

    Persist --> Render[Rendering a result set]
    Scope[Active scope chip §6.1] --> Render

    Render --> Filter[1 - Drop groups outside the active scope]
    Filter --> Sort[2 - Sort remaining groups by user order]
    Sort --> Prune[3 - Omit groups with no results]
    Prune --> Show[Render]
```

**Scope and order compose — they are two independent axes.** The scope chip
decides *which* groups are eligible; the settings list decides *what sequence*
the eligible ones appear in. Order is preserved as a relative ranking, never
renumbered per scope:

- Scope **Music** with order `Movies → Songs → Albums → Artists → TV` renders
  `Songs → Albums → Artists`. Movies and TV are filtered out; the surviving
  groups keep their relative order.
- Scope **All** with the same setting renders all five in exactly that order.
- **Changing scope never rewrites the saved order.** A user who narrows to
  Music and back to All sees their original arrangement intact.

**Rules:**

- **Drag and drop to reorder**, in a settings list showing every result group
  (Songs, Albums, Artists, Movies, TV Shows).
- **The order applies to grouped results everywhere** — the search page and
  the in-library header search alike.
- **Order is presentation-only.** It never changes which results are returned
  or how they are ranked *within* a group, only the sequence groups appear in.
- **Empty groups are skipped, not gapped.** A group with no results is omitted
  entirely; it does not reserve space or leave a stray heading.
- **A sensible default ships** (Songs → Albums → Artists → Movies → TV Shows)
  so the setting is an adjustment, never a prerequisite.
- **Keyboard/accessible reordering must exist** alongside dragging — a
  drag-only control is unusable with a screen reader or without a pointer.

### 6.4 Known deviations

Recorded so the gap between this spec and the build is explicit.

- **Scope is not implemented.** The in-library header search calls the same
  unscoped query as the global search page, so searching inside TV returns
  music. The backend already accepts `includeItemTypes` on both the online and
  offline paths, and the per-page list search already uses it — only the global
  path ignores it. *(UR-049)*
- **Filter chips do not exist** on either search surface. *(UR-049)*
- **Group order is hardcoded** to Music → Movies → TV in the results markup,
  with no setting. *(UR-050)*

---

## 7. Download Flows

### 7.1 Initiating Downloads

```mermaid
flowchart TB
    User[User on Album/Track Page] --> ClickDownload[Click Download Button]

    ClickDownload --> CheckType{Download Type?}

    CheckType -->|Single Track| DownloadTrack[Download Single File]
    CheckType -->|Album| DownloadAlbum[Download All Tracks]
    CheckType -->|Artist| ShowOptions[Show Options Dialog]

    ShowOptions --> UserChoice{User Choice}
    UserChoice -->|Discography| DownloadAll[Download All Albums]
    UserChoice -->|Select Albums| AlbumPicker[Album Selection UI]

    DownloadTrack --> QueueDownload[Queue in Download Manager]
    DownloadAlbum --> QueueMultiple[Queue Multiple Files]

    QueueDownload --> ShowProgress[Show Progress Ring<br/>on Download Button]
    QueueMultiple --> ShowProgress

    ShowProgress --> DownloadActive[Download Active:<br/>Button shows % complete]
```

**Download Button States:**
```
States:
1. [⬇] Available     - Gray outline
2. [○ 45%] Downloading - Blue ring progress
3. [✓] Downloaded    - Green checkmark
4. [!] Failed        - Red with retry option
5. [⏸] Paused        - Yellow pause icon
```

### 7.2 Downloads = a browsable offline library, not a flat list

**The central idea:** "my downloads" is not a list of file-transfer rows — it is
*the library, filtered to what's on the device*. A user who has downloaded three
seasons of a show and two albums thinks in terms of shows and albums, not
seventy-odd individual episode/track transfers. So the primary Downloads surface
**reuses the library browse screens**, scoped to downloaded content, and keeps
the transfer-progress list as a secondary "Transfers" view for the *act* of
downloading.

This splits one overloaded page into two clear jobs:

| Surface | Answers | Reuses |
|---------|---------|--------|
| **Downloaded** (browse) | "What do I have offline, and let me play it" | Library grids, detail pages, cards (§5A) |
| **Transfers** (activity) | "What is downloading right now, and control it" | The existing progress-row list |

```mermaid
flowchart TB
    Nav[Open Downloads] --> Downloads[/downloads]

    Downloads --> View{View}
    View -->|Downloaded &#40;default&#41;| Browse[Offline library browse]
    View -->|Transfers| Activity[Transfer activity list]

    Browse --> Libs[Libraries — only those with<br/>downloaded content]
    Libs --> Grid[Library grid, offline-scoped<br/>same cards/layout as online §5A]
    Grid --> Detail[Detail page<br/>same as online]
    Detail --> Play[Play from local file]
    Detail --> Remove[Remove download<br/>frees space, keeps browsable? — see rules]

    Activity --> Rows[Per-transfer rows:<br/>downloading / queued / paused / failed /<br/>waiting-for-WiFi]
    Rows --> Ctl[Pause / Resume / Cancel / Retry]
```

**Why reuse the library screens (not a bespoke list):**

- **One mental model.** Browsing offline should feel identical to browsing
  online — same grids, same card shapes, same detail pages, same play action.
  The only difference is *what's present*, not *how it looks*.
- **It already works in the backend.** The offline repository's `get_items`
  already returns downloaded items **plus** their containers (an album with any
  downloaded track, a series/season with any downloaded episode). That is a
  browsable tree today — see §7.4.
- **It scales.** A flat completed-list becomes unusable at a few dozen items; a
  browsable library does not.

### 7.3 The Downloaded browse surface

```
┌─────────────────────────────────────────────┐
│  Downloads                                  │
│  ( Downloaded )  ( Transfers )     ← view switch
│                                             │
│  [~ 3.4 GB on device · 12 items]  Manage ▸  │  ← storage summary
│                                             │
│  Music                                      │  ← only libraries that
│  ┌────┐┌────┐┌────┐                          │     have downloaded content
│  │alb ││alb ││art │                          │
│  └────┘└────┘└────┘                          │
│                                             │
│  TV                                         │
│  ┌────┐┌────┐                                │
│  │show││show│                                │
│  └────┘└────┘                                │
└─────────────────────────────────────────────┘
```

**Rules:**

- **Libraries with nothing downloaded are omitted**, not shown empty. If only
  music is downloaded, only Music appears.
- **Cards, grids, and detail pages are the library's own** (§5A) — offline
  browse is the same components with an offline-scoped data source, never a
  parallel re-implementation.
- **A downloaded badge / "on device" affordance** distinguishes fully-downloaded
  from partially-downloaded containers (e.g. a season with 6 of 10 episodes).
- **Disk usage is shown where the user already looks**, in familiar units — see
  §7.3.1.
- **Play always plays the local file** here; nothing on this surface streams.
- **Remove is available at every level** — item, album/season, series — and
  states clearly what it frees. Removing the last downloaded child of a
  container removes the container from the browse.
- **This surface works identically online and offline.** It is "what's on the
  device," a question whose answer does not depend on connectivity. It must not
  wait for, or be emptied by, server reachability.

#### 7.3.1 Disk usage — familiar, in place, not a separate audit

Users want to know what each thing costs on disk, but that information has to
feel like the storage views they already know (phone Settings → Storage, a
file browser), not a developer's byte dump.

- **Size rides along with the item, on the card and the detail page** — a small
  secondary label (`1.2 GB`, `340 MB`, `48 MB`), never a separate "storage
  report" screen the user has to go find.
- **Containers show their total.** A series shows the sum of its downloaded
  episodes; an album the sum of its tracks; a season its own subtotal. The
  number a user sees on the "Breaking Bad" card is what removing it frees.
- **Human units, rounded, consistent.** Binary or decimal is a choice — pick one
  and use it everywhere. Show 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 &#40;§9.1&#41;]

    Gate -->|Yes| Mode{Background mode armed?}

    Mode -->|Background-audio toggle ON<br/>UR-040| Handoff[Hand off to native audio service<br/>WebView &lt;video&gt; torn down,<br/>video decode stops, audio continues]
    Mode -->|Default<br/>UR-041| PiP[Auto-enter Picture-in-Picture<br/>on onUserLeaveHint]

    PiP --> PiPWindow[Floating PiP window:<br/>- Video keeps rendering into surface<br/>- WebView hidden<br/>- Play/Pause RemoteAction<br/>  &#40;reflects live player state&#41;]

    PiPWindow --> PiPReturn{User action}
    PiPReturn -->|Tap window| Restore[Return to full player<br/>WebView restored, surface re-fit]
    PiPReturn -->|Close window| Stop[Playback stops]

    Handoff --> Foreground[On return to foreground:<br/>resume WebView video at position]
```

**Key rules:**

- **Video-only gate.** Auto-PiP is guarded by the native `canEnterPip` check
  (local video surface actively rendering). Audio playback and menu/library
  browsing background normally; remote/cast sessions render nothing locally, so
  a PiP window would be an empty box and is refused. *(UR-041, IR-026)*
- **Only one background behaviour at a time.** The background-audio toggle
  (UR-040) disarms auto-PiP while it is on, so a video is either handed to the
  audio service *or* floated in PiP, never both.
- **PiP controls track the player.** The play/pause RemoteAction in the PiP
  window reflects the live player state and updates on every playback-state
  change, not only when the button is pressed. *(DR-053)*
- **Non-disruptive transition.** ExoPlayer keeps rendering into the same
  surface across enter/exit, so entering or leaving PiP never interrupts the
  video; on exit the surface is re-fit to full-screen bounds. *(DR-053)*

**PiP window (Android):**
```
        ┌───────────────────┐
        │                   │
        │   ▶ video frame   │
        │                   │
        │       [⏸]         │  ← play/pause RemoteAction
        └───────────────────┘
   sized to the video's aspect ratio
```

---

## 10. Error States & Edge Cases

### 10.1 Network Loss During Streaming

```mermaid
flowchart TB
    Streaming[Streaming Audio/Video] --> LoseNetwork[Network Connection Lost]

    LoseNetwork --> CheckLocal{Local Copy<br/>Available?}

    CheckLocal -->|Yes| SwitchLocal[Switch to Local Playback<br/>Seamlessly]
    CheckLocal -->|No| ShowBuffer[Show Buffering Spinner]

    ShowBuffer --> WaitReconnect[Wait for Reconnection<br/>30 second timeout]

    WaitReconnect --> Reconnect{Reconnected?}

    Reconnect -->|Yes| Resume[Resume Streaming]
    Reconnect -->|No| ShowError[Show Error Toast:<br/>"Unable to stream.<br/>Check connection."]

    ShowError --> OfferRetry[Offer Retry Button]
    ShowError --> OfferDownload[Offer "Download for Offline"]
```

### 10.2 Server Unreachable

```mermaid
flowchart TB
    Action[User Action Requires Server] --> TryConnect[Attempt Connection]

    TryConnect --> Timeout{Connection<br/>Timeout?}

    Timeout -->|Yes| ShowError[Show Error:<br/>"Server unreachable"]
    Timeout -->|No| Success[Action Succeeds]

    ShowError --> OfferOptions[Offer Options:<br/>- Retry<br/>- Switch to Offline Mode<br/>- Change Server]
```

### 10.3 Download Failed

```mermaid
flowchart TB
    Downloading[Download in Progress] --> Failure{Failure Type?}

    Failure -->|Network Error| Retry[Auto-retry<br/>with Backoff]
    Failure -->|Disk Full| ShowDiskError[Show Error:<br/>"Not enough storage"]
    Failure -->|Server Error| ShowServerError[Show Error:<br/>"Server error"]

    Retry --> RetryCount{Retry Count<br/>< 3?}
    RetryCount -->|Yes| Downloading
    RetryCount -->|No| Failed[Mark as Failed]

    ShowDiskError --> Failed
    ShowServerError --> Failed

    Failed --> UserAction[Show in Downloads:<br/>with Retry Button]
```

---

## 11. Platform-Specific UX Patterns

### 11.1 Android-Specific

**Hardware Back Button:**
- **In Full Player:** Return to previous screen, show MiniPlayer
- **In Video Player:** Stop playback, exit fullscreen
- **In Album Detail:** Return to library grid
- **At Library Home:** Exit app (show confirmation)

**System Volume Buttons:**
- **While playing audio:** Adjust playback volume
- **While controlling remote session:** Adjust remote session volume (shows session name in volume panel)
- **In menus:** Adjust system volume (default behavior)

**Share Integration:**
- Long-press album/song → Share menu
- Options: Share with other apps, Copy link

### 11.2 Linux Desktop-Specific

**Keyboard Shortcuts:**
- `Space`: Play/Pause
- `→`: Next track
- `←`: Previous track
- `/`: Focus search
- `Ctrl+Q`: Quit

**Window Behavior:**
- Minimize to tray (playback continues)
- Close window (show confirmation if playing)
- MPRIS integration for desktop media controls

**Mouse Interactions:**
- Hover over MiniPlayer: Show additional controls (volume, queue peek)
- Right-click: Context menu (Add to playlist, Go to artist, Download)

---

## 12. UX Principles Summary

### 12.1 Core Principles

1. **Playback Persistence:**
   - Audio playback never stops unless user explicitly stops it
   - MiniPlayer visible on all screens (except video/login)
   - Queue and position preserved across navigation

2. **Non-Blocking UI:**
   - Downloads happen in background
   - Sync operations never block user interaction
   - Optimistic updates (favorite, progress) with background sync

3. **Offline-First:**
   - Downloaded content works offline
   - Seamless switch between online/offline
   - Progress and preferences saved locally

4. **Progressive Disclosure:**
   - Simple defaults, advanced options hidden
   - Context menus for secondary actions
   - Settings organized by category

5. **Responsive Design:**
   - Mobile-first UI
   - Desktop enhancements (hover states, keyboard shortcuts)
   - Tablet: Grid layouts with more columns

### 12.2 Animation & Transitions

| Transition | Duration | Easing |
|------------|----------|--------|
| MiniPlayer slide up/down | 300ms | ease-out |
| Screen navigation | 200ms | ease-in-out |
| Video controls fade | 500ms | ease-out |
| Download button state change | 150ms | ease-in-out |
| Modal appear | 200ms | ease-out |
| Toast notification | 250ms | ease-in-out |

### 12.3 Touch Targets (Mobile)

| Element | Minimum Size |
|---------|--------------|
| Bottom nav buttons | 48x48 dp |
| List item (track, album) | Full width x 56 dp |
| Player controls | 56x56 dp |
| MiniPlayer | Full width x 64 dp |
| Download button | 40x40 dp |
| Favorite button | 40x40 dp |

---

## 13. Future UX Enhancements

### 13.1 Planned Features

1. **Gesture Navigation:**
   - Swipe up on MiniPlayer → Full player
   - Swipe down on full player → Back to previous screen
   - Swipe between tracks in full player

2. **Queue Management UI (DR-020):**
   - Drag to reorder
   - Swipe to remove
   - Add to queue vs. Play next

3. **Sleep Timer (UR-026):**
   - Accessible from full player menu
   - Presets: 15min, 30min, 1hr, End of track, End of album
   - Countdown visible in MiniPlayer

4. **Home Screen (UR-034):**
   - Hero banner carousel
   - Continue watching/listening
   - Recently added
   - Personalized recommendations

5. **Cast/Remote Control Enhancements:**
   - Picture-in-picture for remote sessions
   - Multi-room audio (play on multiple devices)
   - Handoff (transfer playback to phone from TV)

### 13.2 Accessibility Enhancements

- Screen reader optimization
- High contrast mode
- Larger text option
- Voice control integration
- Haptic feedback for controls

---

This UX flow documentation should be updated as new features are implemented and user feedback is incorporated.
