docs: specs, requirements, ux-flows and traceability for new features

Add specs for the account menu, downloads-as-offline-library, offline
downloaded-only filter, and scoped search (+ boundary revision). Add the
new UR/DR entries to requirements.md, update ux-flows, and regenerate the
traceability matrix.

TRACES: UR-049, UR-050, UR-052, UR-053, UR-054, UR-055, UR-056
This commit is contained in:
2026-07-23 20:04:35 +02:00
parent dd9d4191f1
commit 8b028b6b60
12 changed files with 3121 additions and 575 deletions
+538 -89
View File
@@ -36,21 +36,74 @@ On desktop (md breakpoint and above), the header contains:
- Logo (links to `/library`)
- Navigation links: Home, Library, Downloads, Settings
- Search bar (inline)
- User menu: Username, Downloads icon, Logout button
- Account menu (see §1.2)
**Mobile Navigation:**
On mobile, the header contains:
- Logo
- Three-dot overflow menu button (Android-style)
- Overflow menu includes:
- Downloads
- Settings
- Sign out
- 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** → Desktop: nav link + icon; Mobile: overflow menu
- **Settings** → Desktop: nav link; Mobile: overflow menu
- **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.)*
---
@@ -386,9 +439,9 @@ flowchart TB
```mermaid
flowchart TB
AlbumsGrid[Albums Grid<br/>FORCED Grid View] --> UserAction{User Action}
AlbumsGrid[Albums Grid<br/>grid/list per §5A] --> UserAction{User Action}
UserAction -->|Click Album| AlbumDetail[Album Detail Page<br/>/library/[albumId]]
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]
@@ -445,54 +498,352 @@ flowchart TB
---
## 6. Search Flow
## 5A. Library Page Layouts
### 6.1 Search Page Navigation
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
BottomNav[Bottom Nav] --> ClickSearch[Click Search Tab]
Page[Library browse page] --> Kind{Content kind}
ClickSearch --> SearchPage[Search Page<br/>/search]
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]
SearchPage --> EmptyState{Has Query?}
EmptyState -->|No| ShowPrompt[Show Empty State:<br/>Search for music,<br/>movies, shows...]
EmptyState -->|Yes| ShowResults[Show Results Grouped:<br/>- Songs<br/>- Albums<br/>- Artists<br/>- Movies<br/>- Episodes]
ShowPrompt --> UserTypes[User Types in Search]
UserTypes --> LiveSearch[Live Search<br/>Debounced 300ms]
LiveSearch --> ShowResults
ShowResults --> UserClick{User Clicks Result}
UserClick -->|Song| PlaySong[Play Song + Queue Results]
UserClick -->|Album| NavAlbum[Navigate to Album Detail]
UserClick -->|Artist| NavArtist[Navigate to Artist Page]
UserClick -->|Movie| NavMovie[Navigate to Movie Detail]
Grid --> Toggle[View toggle in page header]
Toggle --> Persist[Choice persists globally<br/>across all grid pages]
```
**Search Page Layout:**
- **Grids are the default** for anything with cover art worth scanning.
- **Lists are mandatory, not optional**, where position carries meaning —
a track's number within an album, an episode's number within a season.
A grid destroys that ordering cue, so these pages expose **no toggle**.
- **The toggle is global, not per-page.** A user who prefers dense lists
prefers them everywhere; making them re-set it on each page is friction.
The choice persists across launches.
**Responsive columns** (grid mode), tuned so cards stay large enough to read
cover art on a phone and don't become postage stamps on a desktop:
| Breakpoint | Columns |
|------------|---------|
| base (phone) | 2 |
| sm | 3 |
| md | 4 |
| lg | 5 |
| xl | 6 |
### 5A.3 What a card shows
```
┌─────────────┐
│ │ ← cover art (aspect per §5A.1)
│ artwork │ • progress bar overlay if partially played
│ │ • watched/played check if complete
│ [▶] │ • play affordance on hover/focus
└─────────────┘
Primary line ← title, truncated to one line
Secondary line ← artist / year+rating / SxEy — one line, dimmed
```
- **Two lines of text maximum.** Titles truncate rather than wrap; a card that
grows to fit its title breaks grid alignment and makes scanning harder.
- **Progress and watched state live on the artwork**, not in the text — they
must be readable while scanning, without reading.
- **Hover/focus reveals play**, so a card is both a navigation target and a
playback target without a second control competing for space at rest.
### 5A.4 Known deviations
These are places the implementation currently diverges from the rules above.
They are recorded here so the gap is explicit rather than mistaken for intent.
- **The view toggle is discoverable only on a browse page.** The preference is
already global and persisted, but the only control that sets it is the pair
of icon buttons in a library page header. Settings has no display section, so
there is nowhere to look for it. *(UR-029)*
---
## 5B. Video Detail Page Composition
Movie, Series, and Episode detail pages all live at `/library/[id]`. Which
surface renders is decided by item type plus the `?episode=` query param, and
**section order is part of the spec** — it is what makes "keep watching this
show" the path of least resistance.
### 5B.1 Which surface renders
```mermaid
flowchart TB
Nav[Navigate to /library/&#91;id&#93;] --> Type{Item type}
Type -->|Person| Person[PersonDetailView]
Type -->|Movie| Movie[Movie detail<br/>§5B.3]
Type -->|Series| Ep{?episode= param<br/>present?}
Ep -->|Yes| Focus[Episode Focus View<br/>§5B.2]
Ep -->|No| Series[Series detail<br/>§5B.4]
Focus -->|Back to series| Series
Series -->|Click episode| Focus
```
An episode is **never** browsed as a bare `Episode` item page. Clicking an
episode anywhere navigates to `/library/<seriesId>?episode=<episodeId>`, so the
episode is always shown in the context of its series and the series' full
episode list is already loaded.
### 5B.2 Episode Focus View — section order
**The next episodes appear directly below the current episode, above cast and
similar shows.** Nothing may be inserted between the episode hero and the
episode strip.
```
┌─────────────────────────────────────────────────┐
│ [←] │
│ ┌───────────────────────────────────────────┐ │
│ │ episode backdrop │ │
│ │ Series Name │ │ ← 1. HERO
│ │ Episode Title │ │
│ │ S2E4 • 48m • ★8.1 │ │
│ │ Overview… │ │
│ │ ▓▓▓▓▓░░░░░ 32m left │ │
│ │ [▶ Play] │ │
│ └───────────────────────────────────────────┘ │
│ │
│ More Episodes │ ← 2. EPISODE STRIP
│ ┌──────┐┌──────┐┌──────┐┌──────┐ │ (immediately below hero)
│ │ E3 ││▓E4▓ ││ E5 ││ E6 │ → scroll │
│ │ ││NOW ││ ││ │ │
│ └──────┘└──────┘└──────┘└──────┘ │
│ │
│ Cast │ ← 3. CAST
│ ( ○ )( ○ )( ○ )( ○ ) │
│ │
│ More Like This │ ← 4. SIMILAR
│ ┌────┐┌────┐┌────┐┌────┐ │
└─────────────────────────────────────────────────┘
```
**Rules for the episode strip:**
- **Position is fixed.** Hero → episode strip → cast → similar. The strip sits
between the current episode and every other section; cast and related
content are *below* it, never above.
- **Window, not full list.** The strip shows a window around the current
episode — roughly 3 before and 6 after — so the immediate next episodes are
visible without scrolling, and earlier ones remain reachable by scrolling
left. It is horizontally scrollable, not a wrapped grid.
- **Forward bias.** More episodes are shown *after* the current one than
before it: the dominant intent on this screen is "watch the next one."
- **The current episode is present and marked.** It renders in-strip with a
"NOW" badge and a highlight ring, and is not clickable. It anchors the
user's position in the season rather than being hidden.
- **Cross-season continuity.** The window spans the whole series in episode
order, so the strip runs past a season boundary into the next season's first
episodes rather than dead-ending at the end of a season.
- **Per-episode state.** Each card shows a thumbnail, `SxEy` + title, a resume
progress bar when partially watched, and a watched checkmark when complete.
- **Clicking an episode swaps focus in place** (`?episode=` changes); it does
not start playback. Playback starts only from the hero's Play button.
### 5B.3 Movie detail — section order
```
Hero (poster, title, metadata, Play / Download / Favorite)
→ Crew links (Directed by / Written by / Music by)
→ Genre tags
→ Cast
→ More Like This
```
A movie has no continuation set, so cast follows the hero directly.
### 5B.4 Series detail — section order
```
Hero (poster, title, metadata, Play / Download)
→ Crew links
→ Genre tags
→ Seasons + episodes (per-season sections)
→ Cast
→ More Like This
```
The same principle as §5B.2: **episodes come before cast and similar shows.**
The reason a user opens a series page is to pick an episode; discovery content
is secondary and sits underneath.
---
## 6. Search Flow
Search is **context-scoped**: what you are looking at when you start a search
determines what the search covers. A search begun inside the Music library
searches music. A search begun from Home or the top-level library page searches
everything. The scope is always shown, and always overridable.
### 6.1 Scope is inherited from context
```mermaid
flowchart TB
Start[User starts a search] --> Where{Where from?}
Where -->|Home &#40;/&#41;| All[Scope: All]
Where -->|Library root &#40;/library&#41;| All
Where -->|Search tab| All
Where -->|Inside Music| Music[Scope: Music]
Where -->|Inside Movies| Movies[Scope: Movies]
Where -->|Inside TV| TV[Scope: TV]
All --> Chips[Filter chips shown<br/>All chip selected]
Music --> Chips2[Filter chips shown<br/>Music chip preselected]
Movies --> Chips2
TV --> Chips2
Chips --> Results[Results, grouped by type]
Chips2 --> Results
Results --> Change{User taps a chip}
Change --> Rescope[Re-run search at new scope<br/>query preserved]
Rescope --> Results
```
**Rules:**
- **Context sets the *initial* chip, never a locked filter.** Entering search
from TV preselects the TV chip; the user can tap "All" to widen without
retyping the query. Scope is a starting point, not a cage.
- **Home, `/library`, and the search tab all start at "All".** These are the
places a user has expressed no narrower intent.
- **Changing scope preserves the query** and re-runs the search. Changing the
query preserves the scope.
- **Scope maps to item types**, resolved at the point of search:
| Chip | `includeItemTypes` |
|------|--------------------|
| All | *(unset — every type)* |
| Music | `MusicAlbum`, `MusicArtist`, `Audio`, `Playlist` |
| Movies | `Movie` |
| TV | `Series`, `Episode` |
- **Chips render under the search bar**, on both the dedicated search page and
the in-library header search. They are horizontally scrollable if they
overflow, never wrapped onto a second row.
### 6.2 Search page layout
```
┌─────────────────────────────────────────┐
│ [🔍 Search...] [✕]
│ [🔍 Search...] [✕] │
│ │
│ ( All ) (•Music•) ( Movies ) ( TV ) │ ← scope chips
│ │
│ Songs ──────────────────────────── │
│ ♪ Song Title - Artist 3:45 │
│ ♪ Song Title - Artist 4:12 │
│ See all (23) │
│ ♪ Song Title - Artist 3:45 │
│ ♪ Song Title - Artist 4:12 │
│ See all (23)
│ │
│ Albums ─────────────────────────── │
│ [Album Cover] Album Title │
[Album Cover] Album Title
│ See all (8) │
│ [Cover] Album Title
See all (8)
│ │
│ Artists ────────────────────────── │
[Photo] Artist Name
│ See all (5) │
( 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
@@ -532,68 +883,166 @@ States:
5. [⏸] Paused - Yellow pause icon
```
### 7.2 Managing Downloads Page
### 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
User[User] --> NavChoice{Navigation Path}
Nav[Open Downloads] --> Downloads[/downloads]
NavChoice -->|Desktop| HeaderNav[Header: Click Downloads Link]
NavChoice -->|Mobile| HeaderIcon[Header: Click Downloads Icon]
NavChoice -->|Direct| TypeURL[Type /downloads]
Downloads --> View{View}
View -->|Downloaded &#40;default&#41;| Browse[Offline library browse]
View -->|Transfers| Activity[Transfer activity list]
HeaderNav --> DownloadsPage[Downloads Page<br/>/downloads]
HeaderIcon --> DownloadsPage
TypeURL --> DownloadsPage
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]
DownloadsPage --> ShowTabs[Show Tabs:<br/>Active | Completed]
ShowTabs --> ActiveTab{Active Tab}
ActiveTab -->|Active| ShowActive[Show Active Downloads:<br/>- Download progress bars<br/>- Pause/Resume buttons<br/>- Cancel buttons]
ActiveTab -->|Completed| ShowCompleted[Show Completed:<br/>- Downloaded items list<br/>- Delete buttons<br/>- Play buttons]
ShowActive --> UserAction1{User Action}
UserAction1 -->|Pause| PauseDownload[Pause Download]
UserAction1 -->|Cancel| CancelDialog[Show Confirm Dialog]
ShowCompleted --> UserAction2{User Action}
UserAction2 -->|Play| PlayOffline[Play from Local File]
UserAction2 -->|Delete| DeleteDialog[Show Confirm Dialog]
Activity --> Rows[Per-transfer rows:<br/>downloading / queued / paused / failed /<br/>waiting-for-WiFi]
Rows --> Ctl[Pause / Resume / Cancel / Retry]
```
**Navigation to Downloads:**
- **Desktop:** Click "Downloads" link in header navigation
- **All screen sizes:** Click download icon (⬇) button in header user menu
- **Direct:** Navigate to `/downloads` route
**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 Page Layout:**
```
┌─────────────────────────────────────────┐
[←] Downloads │
[Active (3)] [Completed (12)]
─ Downloading ────────────────────
Album Cover Album Title │
Artist Name
[████████░░] 80%
[⏸ Pause] [✕ Cancel]
│ │
Album Cover Album Title
Artist Name
[██░░░░░░░░] 20%
│ [⏸ Pause] [✕ Cancel] │
│ │
│ ─ Queued ───────────────────────── │
│ │
│ Album Cover Album Title │
│ Artist Name │
│ Waiting... │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────────
│ Downloads
( Downloaded ) ( Transfers ) ← view switch
[~ 3.4 GB on device · 12 items] Manage ▸ ← storage summary
Music ← only libraries that
┌────┐┌────┐┌────┐ │ have downloaded content
│alb ││alb ││art │
└────┘└────┘└────┘
TV
┌────┐┌────┐
│show││show│
└────┘└────┘
└─────────────────────────────────────────────┘
```
**Rules:**
- **Libraries with nothing downloaded are omitted**, not shown empty. If only
music is downloaded, only Music appears.
- **Cards, grids, and detail pages are the library's own** (§5A) — offline
browse is the same components with an offline-scoped data source, never a
parallel re-implementation.
- **A downloaded badge / "on device" affordance** distinguishes fully-downloaded
from partially-downloaded containers (e.g. a season with 6 of 10 episodes).
- **Disk usage is shown where the user already looks**, in familiar units — see
§7.3.1.
- **Play always plays the local file** here; nothing on this surface streams.
- **Remove is available at every level** — item, album/season, series — and
states clearly what it frees. Removing the last downloaded child of a
container removes the container from the browse.
- **This surface works identically online and offline.** It is "what's on the
device," a question whose answer does not depend on connectivity. It must not
wait for, or be emptied by, server reachability.
#### 7.3.1 Disk usage — familiar, in place, not a separate audit
Users want to know what each thing costs on disk, but that information has to
feel like the storage views they already know (phone Settings → Storage, a
file browser), not a developer's byte dump.
- **Size rides along with the item, on the card and the detail page** — a small
secondary label (`1.2 GB`, `340 MB`, `48 MB`), never a separate "storage
report" screen the user has to go find.
- **Containers show their total.** A series shows the sum of its downloaded
episodes; an album the sum of its tracks; a season its own subtotal. The
number a user sees on the "Breaking Bad" card is what removing it frees.
- **Human units, rounded, consistent.** Binary or decimal is a choice — pick one
and use it everywhere. Show 23 significant figures (`1.2 GB`, not
`1,283,048,192 bytes` and not `1.28394 GB`).
- **A single device total sits at the top** of the Downloaded surface
(`3.4 GB on device · 12 items`) so the headline number is answered before the
user scans. It reconciles with the sum of what's listed.
- **Remove restates the reclaim** in the same units at the point of action
("Remove download · frees 1.2 GB"), so the cost of keeping vs. freeing is
legible exactly when the user decides.
- **Sort/filter by size is a reasonable enhancement** ("biggest first" to find
what to clear) but is not required for v1.
The bytes-on-disk per item are a backend fact (the download manager writes the
files and can stat them); this is a display and aggregation task, not new
tracking. See §7.7 deviations for what's missing today.
### 7.4 Transfers (activity) view
The existing progress-row list, unchanged in spirit, demoted to a secondary tab.
It is about *transfers in flight*, so it shows only rows that are doing or
waiting to do something:
- **States:** downloading (with progress), queued, paused, failed,
waiting-for-WiFi (§7.5).
- **Controls:** Pause / Resume / Cancel / Retry per row; the 3-concurrent cap
and auto-pump are backend concerns and are not surfaced as manual controls.
- **Completed transfers fall off this view** once done — the finished item lives
in Downloaded, not here. A transient "just finished" confirmation is fine; a
permanent completed-list is not (that's what Downloaded is for).
- **Empty state** points at the library: "Nothing downloading. Browse your
library and tap download to save media for offline."
### 7.5 Navigation & entry points
- Reached via the account menu (§1.2) and, on desktop, the header Downloads
link/icon → `/downloads`.
- `/downloads` opens on **Downloaded** by default; **Transfers** is one tap away
and should draw attention (badge/count) only while transfers are active.
- Initiating a download is unchanged (§7.1): the download button lives on
item/album/series detail pages. The Downloads page manages and browses; it is
not where you start a download.
### 7.7 Known deviations
Recorded so the gap between this spec and the build is explicit.
- **Downloads is a flat two-tab list today** (Active / Completed), rendering one
row per individual transfer with no browsing, grouping, or reuse of the
library screens. Completed downloads never collapse into their album/series.
*(UR-055)*
- **No offline-scoped browse entry point exists in the client.** All browsing
goes through the hybrid repository, which merges cache **and** server; there is
no way to ask for "downloaded content only" as a browse surface. The offline
repository supports it (§7.2) but is not reachable independently. *(UR-055,
DR-082)*
- **The "on device" storage summary and per-container remove** are absent from
the completed list. *(UR-055, UR-056)*
- **Per-item disk usage is not displayed anywhere.** Cards and detail pages show
no size; there is no device total, no container subtotal, and Remove does not
state what it frees. *(UR-056)*
---
## 8. Settings & Account Flows