# Greyscale Feature Expansion Plan

## Overview

Expand Greyscale from a minimal "search & play" PoC into a feature-rich music client by implementing: Albums, Artists, Track Metadata & Recommendations, Caching, Downloads, Downloads Manager, and Lyrics.

## Current State

Greyscale currently has 4 files and 4 API methods:
- [`searchTracks()`](greyscale-app/js/api.js:125) — search by query
- [`getTrackStream()`](greyscale-app/js/api.js:148) — resolve stream URL
- [`getCoverUrl()`](greyscale-app/js/api.js:180) — cover art URL
- [`initInstances()`](greyscale-app/js/api.js:64) — load API instances

The UI is a single search box → track list → player bar.

## Architecture After Expansion

```mermaid
flowchart TB
    subgraph greyscale-app
        HTML[index.html - SPA with router]
        CSS[style.css - expanded theme]
        APP[js/app.js - router + page rendering]
        API[js/api.js - all API methods + cache]
        CACHE[js/cache.js - TTL cache layer]
        PLAYER[js/player.js - audio + dash.js]
        DL[js/downloads.js - download manager]
        LYRICS[js/lyrics.js - synced lyrics]
        UTILS[js/utils.js - shared helpers]
    end

    subgraph External APIs
        INST[Uptime Workers]
        TIDAL[Tidal API Instances]
        TIDAL_CDN[Tidal Resources CDN]
        LRCLIB[lrclib.net - lyrics]
        GENIUS[Genius API - annotations]
    end

    APP --> API
    APP --> PLAYER
    APP --> DL
    APP --> LYRICS
    API --> CACHE
    API --> INST
    API --> TIDAL
    API --> TIDAL_CDN
    LYRICS --> LRCLIB
    LYRICS --> GENIUS
    DL --> API
```

## File Structure After Expansion

```
greyscale-app/
├── index.html          # SPA shell with detail-page container + modals
├── style.css           # Expanded with album/artist/lyrics/download styles
└── js/
    ├── app.js          # Router, page rendering, event wiring
    ├── api.js          # All API methods (expanded from 4 to ~20)
    ├── cache.js        # NEW: In-memory TTL cache
    ├── player.js       # Audio playback (minor additions)
    ├── downloads.js    # NEW: Single + bulk download manager
    ├── lyrics.js       # NEW: Lyrics fetching + display
    └── utils.js        # NEW: Shared helpers (escapeHtml, formatTime, etc.)
```

---

## Phase 1: Foundation — Caching + Utils + Router

### 1.1 Create `js/cache.js` — In-Memory TTL Cache

Reference: [`monochrome/js/cache.js`](monochrome/js/cache.js:1)

A simple Map-based cache with TTL expiration. Every API method will check cache before fetching.

**API surface:**
```javascript
export class APICache {
    constructor(options = { maxSize: 200, ttl: 30 * 60 * 1000 })
    async get(namespace, key)        // returns cached value or null
    async set(namespace, key, value) // stores with timestamp
    clearExpired()                   // prune stale entries
    clear()                          // wipe everything
    getCacheStats()                  // { size, namespaces }
}
```

### 1.2 Create `js/utils.js` — Shared Helpers

Extract from [`app.js`](greyscale-app/js/app.js:4) and add new helpers:

```javascript
export function formatDuration(seconds)    // already in app.js, move here
export function escapeHtml(value)          // already in app.js, move here
export function formatDate(dateStr)        // NEW: format release dates
export function getTrackArtists(track)     // NEW: extract artist name(s)
export function getTrackTitle(track)       // NEW: clean title
export function buildTrackFilename(track)  // NEW: for downloads
export function sanitizeForFilename(str)   // NEW: remove invalid chars
```

### 1.3 Add SPA Router to `app.js`

Transform the single-page search into a routable SPA. Reference: [`monochrome/js/router.js`](monochrome/js/router.js:1)

**Routes:**
| Path | Page | Description |
|------|------|-------------|
| `/` or `/home` | Search page | Current search UI (default) |
| `/album/{id}` | Album detail | Album header + track list |
| `/artist/{id}` | Artist detail | Artist info + albums + top tracks |
| `/track/{id}` | Track detail | Track info + recommendations |

**Implementation:** Hash-based routing (`#/album/123`) to avoid needing server-side routing changes. The `results-section` div becomes a dynamic content area.

### 1.4 Update `index.html`

Add a `<div id="page-content">` container that the router swaps content into. Add a back button. Add a download notification area.

---

## Phase 2: Albums

### 2.1 API Methods in `api.js`

Reference: [`monochrome/js/api.js`](monochrome/js/api.js:372) lines 372-544

**Add to `GreyscaleAPI`:**

```javascript
async searchAlbums(query)
// Endpoint: GET /search/?al={query}
// Returns: { items: Album[], totalNumberOfItems }
// Uses cache namespace: 'search_albums'

async getAlbum(id)
// Endpoint: GET /album/?id={id}
// Returns: { album: AlbumMeta, tracks: Track[] }
// Handles pagination for albums with 100+ tracks
// Uses cache namespace: 'album'
```

**Helper methods:**
```javascript
prepareAlbum(album)        // normalize artist field
deduplicateAlbums(albums)  // remove duplicate versions
```

### 2.2 Album Detail Page in `app.js`

When user navigates to `#/album/{id}`:

```
┌─────────────────────────────────────────┐
│  ← Back                                │
│                                         │
│  [Album Cover 320px]  Album Title       │
│                       Artist Name →     │
│                       2024 • 12 tracks  │
│                       [▶ Play All] [⬇]  │
│                                         │
├─────────────────────────────────────────┤
│  1. Track Title              3:42  [⬇]  │
│  2. Track Title              4:15  [⬇]  │
│  3. Track Title              3:58  [⬇]  │
│  ...                                    │
└─────────────────────────────────────────┘
```

**Features:**
- Click album cover/title from search results → navigate to album page
- "Play All" loads entire album into player queue
- Click individual track → play from that position in album
- Artist name is clickable → navigates to artist page
- Download button per track + "Download All" for album

### 2.3 Album Results in Search

Modify search to show album results alongside track results. Add a tab bar or section header:

```
[Tracks] [Albums]
```

When "Albums" tab is active, show album cards with cover, title, artist, year.

---

## Phase 3: Artists

### 3.1 API Methods in `api.js`

Reference: [`monochrome/js/api.js`](monochrome/js/api.js:739) lines 739-836

**Add to `GreyscaleAPI`:**

```javascript
async searchArtists(query)
// Endpoint: GET /search/?a={query}
// Returns: { items: Artist[], totalNumberOfItems }

async getArtist(artistId)
// Endpoint: GET /artist/?id={id} (primary info)
//         + GET /artist/?f={id}&skip_tracks=true (content/discography)
// Returns: { name, picture, albums, eps, tracks }
// Scans nested response to extract albums and top tracks
// Uses searchAlbums() to find additional releases

async getArtistPictureUrl(pictureId, size)
// Constructs Tidal CDN URL for artist images
```

**Optional (nice-to-have):**
```javascript
async getSimilarArtists(artistId)
// Endpoint: GET /artist/similar/?id={id} (v2.3+ instances)

async getArtistBiography(artistId)
// Endpoint: Tidal v1 API direct call
```

### 3.2 Artist Detail Page in `app.js`

When user navigates to `#/artist/{id}`:

```
┌─────────────────────────────────────────┐
│  ← Back                                │
│                                         │
│  [Artist Picture]  Artist Name          │
│                                         │
├─────────────────────────────────────────┤
│  Top Tracks                             │
│  1. Track Title — Album      3:42  [⬇]  │
│  2. Track Title — Album      4:15  [⬇]  │
│  ...                                    │
├─────────────────────────────────────────┤
│  Albums                                 │
│  [Cover] Title   [Cover] Title          │
│  2024            2022                   │
│                                         │
│  Singles & EPs                           │
│  [Cover] Title   [Cover] Title          │
└─────────────────────────────────────────┘
```

**Features:**
- Click artist name anywhere → navigate to artist page
- Top tracks section: click to play, download button
- Albums/EPs shown as a grid of cards → click to navigate to album page
- "Play All Top Tracks" button

### 3.3 Artist Results in Search

Add `[Artists]` tab to search results. Show artist cards with picture and name.

---

## Phase 4: Track Metadata & Recommendations

### 4.1 API Methods in `api.js`

Reference: [`monochrome/js/api.js`](monochrome/js/api.js:1030) lines 1030-1073

**Add to `GreyscaleAPI`:**

```javascript
async getTrackMetadata(id)
// Endpoint: GET /info/?id={id}
// Returns full track object with album, artist, duration, quality info
// Uses cache namespace: 'track_meta'

async getTrackRecommendations(id)
// Endpoint: GET /recommendations/?id={id} (v2.4+ instances)
// Returns: Track[]
// Uses cache namespace: 'recommendations'
```

### 4.2 Track Detail Page in `app.js`

When user navigates to `#/track/{id}`:

```
┌─────────────────────────────────────────┐
│  ← Back                                │
│                                         │
│  [Album Cover]  Track Title             │
│                 Artist Name →           │
│                 Album Title →           │
│                 3:42 • FLAC • 24-bit    │
│                 [▶ Play] [⬇] [Lyrics]   │
│                                         │
├─────────────────────────────────────────┤
│  Recommended Tracks                     │
│  1. Similar Track — Artist     4:01     │
│  2. Similar Track — Artist     3:33     │
│  ...                                    │
└─────────────────────────────────────────┘
```

### 4.3 Enrich Existing Track Display

- In search results and album track lists, clicking the track title navigates to track detail
- Show quality badges (HI_RES, LOSSLESS, etc.) where available

---

## Phase 5: Downloads

### 5.1 Single Track Download in `api.js`

Reference: [`monochrome/js/api.js`](monochrome/js/api.js:1111) lines 1111-1306

**Add to `GreyscaleAPI`:**

```javascript
async downloadTrackBlob(id, quality = 'LOSSLESS')
// Fetches the stream URL, downloads the audio as a Blob
// Handles both direct URLs and DASH manifests
// Returns: { blob, filename, track }
```

### 5.2 Create `js/downloads.js` — Download Manager

Reference: [`monochrome/js/downloads.js`](monochrome/js/downloads.js:1)

```javascript
export class DownloadManager {
    constructor(api)

    // Single track download
    async downloadTrack(track, quality, options = {})
    // - Resolves stream URL
    // - Downloads with progress tracking
    // - Triggers browser download

    // Bulk download (album/playlist)
    async downloadAlbum(albumId, quality, options = {})
    // - Fetches album tracks
    // - Downloads each sequentially with progress
    // - Option: ZIP all files together using client-zip CDN

    // Progress tracking
    onProgress(callback)  // { trackId, stage, percent, message }

    // Queue management
    getActiveDownloads()
    cancelDownload(taskId)
}
```

### 5.3 Download UI

**Per-track download button:** Small `⬇` icon next to each track in results, album pages, artist pages.

**Download notification area:** Fixed position toast/panel showing active downloads:

```
┌──────────────────────────────┐
│ Downloads                  ✕ │
│ ┌──────────────────────────┐ │
│ │ Track Name     ████░ 72% │ │
│ │ Track Name     Queued... │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
```

### 5.4 Download Quality Selection

Simple dropdown or modal when clicking download:
- HI_RES_LOSSLESS (24-bit FLAC)
- LOSSLESS (16-bit FLAC)
- HIGH (AAC 320)
- LOW (AAC 96)

---

## Phase 6: Lyrics

### 6.1 Create `js/lyrics.js` — Lyrics Engine

Reference: [`monochrome/js/lyrics.js`](monochrome/js/lyrics.js:1)

**Lyrics sources (in priority order):**
1. **LRCLIB** (`https://lrclib.net/api/`) — free, no auth, synced + plain lyrics
2. **Genius** (optional) — annotations and song info

```javascript
export class LyricsManager {
    constructor()

    async fetchLyrics(track)
    // 1. Try LRCLIB synced lyrics: GET /api/get?artist_name=X&track_name=Y
    // 2. Fallback to LRCLIB search: GET /api/search?q=X
    // 3. Returns: { synced: boolean, lines: [{time, text}], plain: string }

    parseLRC(lrcText)
    // Parse LRC format into [{time: seconds, text: string}]

    getCurrentLine(currentTime, lines)
    // Binary search for active lyric line at current playback time
}
```

### 6.2 Lyrics Display

**Side panel or overlay** that slides in when "Lyrics" button is clicked:

```
┌─────────────────────────────────────────┐
│  Lyrics                            ✕    │
│                                         │
│  (dimmed) Previous line                 │
│  ► Current line highlighted             │
│  (dimmed) Next line                     │
│  (dimmed) Next line                     │
│                                         │
└─────────────────────────────────────────┘
```

**Features:**
- Auto-scroll synced lyrics with playback
- Click a lyric line to seek to that timestamp
- Highlight current line with accent color
- Fallback to plain text if no synced lyrics available
- "Lyrics" button in player bar (next to controls)
- Cache lyrics per track ID

---

## Implementation Order & Dependencies

```mermaid
flowchart LR
    P1[Phase 1: Foundation] --> P2[Phase 2: Albums]
    P1 --> P3[Phase 3: Artists]
    P1 --> P4[Phase 4: Track Meta]
    P2 --> P5[Phase 5: Downloads]
    P3 --> P5
    P4 --> P5
    P1 --> P6[Phase 6: Lyrics]

    P1 -.-> |cache.js, utils.js, router| P1
    P5 -.-> |needs track/album data| P5
    P6 -.-> |needs player time events| P6
```

**Recommended implementation sequence:**
1. **Phase 1** — Foundation (cache, utils, router) — everything depends on this
2. **Phase 2** — Albums — most impactful browsing feature
3. **Phase 3** — Artists — builds on album infrastructure
4. **Phase 4** — Track Metadata & Recommendations — enriches existing track display
5. **Phase 5** — Downloads — needs track/album/artist data to be useful
6. **Phase 6** — Lyrics — independent of other features, can be done in parallel after Phase 1

---

## Key Design Principles

1. **Stay vanilla JS** — no build tools, no npm, no frameworks
2. **Reference monochrome, dont import it** — re-implement only what we need
3. **Progressive enhancement** — each phase produces a working app
4. **Cache aggressively** — reduce API load, improve responsiveness
5. **CDN dependencies only** — dash.js (already used), client-zip (for bulk downloads)
