# Unified URL Variable Structure — music.html & vj.html

## Background: How URL Hash Fragments Work

A URL has this anatomy:

```
https://example.com/vj.html?show=reggae&npub=npub1abc#/playlist/my-episode-42
\_____________________/\________/ \_______________/ \________________________/
        origin          pathname    query string         hash fragment
```

### The `#` Hash Fragment

The **hash fragment** (everything after `#`) has special properties:

1. **Not sent to the server** — When the browser requests a URL, the hash is stripped. The server never sees it. This makes it ideal for client-side-only state.
2. **No page reload** — Changing `window.location.hash` does NOT reload the page. It fires a `hashchange` event that JavaScript can listen to. This is how single-page app routing works without a framework.
3. **Bookmarkable** — The hash IS part of the URL in the address bar, so users can bookmark it, copy/paste it, and share it.
4. **History entries** — Changing the hash pushes a new entry onto the browser history stack, so Back/Forward buttons work naturally.

### Query String vs Hash

| Feature | Query String `?key=value` | Hash Fragment `#/path` |
|---------|--------------------------|----------------------|
| Sent to server | Yes | No |
| Page reload on change | Yes, unless using `replaceState` | No |
| History entry on change | Only with `pushState`/`replaceState` | Automatic |
| Best for | Identity, auth mode, external references | In-page navigation, view state |

Both pages currently use **both** mechanisms — query strings for identity/auth and hash fragments for in-page navigation. This is a good pattern.

---

## Current State Audit

### Query String Parameters Currently Used

| Parameter | Page | Purpose |
|-----------|------|---------|
| `npub` | music.html | Target user pubkey in npub format |
| `pubkey` | music.html, vj.html | Target user pubkey in hex format |
| `auth` | both | Auth mode override: `required`, `optional`, `none` |
| `a` | vj-stream.mjs | Stream coordinate `30311:{pubkey}:{slug}` |
| `naddr` | vj-stream.mjs | Nostr address encoding of stream |
| `show` | vj-playlist.html | Pre-select a show slug |

### Hash Routes Currently Used

Both [`music.html`](www/music.html) and [`vj.html`](www/vj.html) share identical hash routing via [`parseRoute()`](www/vj.html:4695) and [`handleRoute()`](www/vj.html:5195):

| Hash Route | Purpose |
|------------|---------|
| `#/` | Home — empty search view |
| `#/search/{query}` | Search results for query |
| `#/album/{albumId}` | Album detail drill-down |
| `#/artist/{artistId}` | Artist detail drill-down |
| `#/track/{trackId}` | Play a specific track |
| `#/playlist/{identifier}` | Open own playlist by identifier |
| `#/playlist/{pubkey}/{identifier}` | Open friend playlist by pubkey + identifier |

### Problems with Current Structure

1. **vj.html has no show in the URL** — The selected show is only in the `<select>` dropdown state. You cannot link to a specific show.
2. **No episode in the URL** — When an episode is selected, it is not reflected in the URL. You cannot link to a specific episode.
3. **music.html cannot load an episode playlist** — There is no route that says "load this episode from this show by this pubkey."
4. **Inconsistent pubkey handling** — music.html accepts both `npub` and `pubkey` query params; vj.html only accepts `pubkey`.
5. **No cross-page linking** — A link generated in vj.html cannot be opened in music.html to play that episode's playlist.

---

## Proposed Unified URL Structure

### Design Principles

1. **Query string** = identity + context that persists across hash navigation
2. **Hash fragment** = current view/navigation state within the page
3. **Both pages share the same parameter vocabulary** so links are interchangeable
4. **Show and episode are query params** because they represent session context, not navigation state

### Query String Parameters — Unified

```
?npub={npub1...}          # Target user — npub format, preferred
&pubkey={hex}             # Target user — hex format, fallback
&auth={required|optional|none}  # Auth mode
&show={slug}              # Active show slug — NEW for vj.html
&episode={identifier}     # Active episode playlist identifier — NEW
&a={30311:pubkey:slug}    # Stream coordinate — existing in vj-stream.mjs
&naddr={naddr1...}        # Nostr address — existing in vj-stream.mjs
```

### Hash Routes — Unified

Both pages support the same routes. The hash controls what is displayed in the search/results area:

```
#/                                    # Home — empty search
#/search/{query}                      # Search results
#/album/{albumId}                     # Album detail
#/artist/{artistId}                   # Artist detail
#/track/{trackId}                     # Play specific track
#/playlist/{identifier}               # Own playlist/episode
#/playlist/{pubkey}/{identifier}      # Friend/external playlist/episode
```

### Full URL Examples

#### VJ working on a show
```
vj.html?show=saturday-reggae&episode=ep-1744382400
```
The VJ is working on the "Saturday Reggae" show, episode "ep-1744382400". The show dropdown auto-selects, the episode loads into the setlist.

#### Sharing a VJ episode link for music.html playback
```
music.html?npub=npub1abc123...&show=saturday-reggae#/playlist/ep-1744382400
```
music.html loads, fetches the playlist event for that episode from the given npub, and plays it.

#### Sharing a specific episode with full context
```
vj.html?npub=npub1abc123...&show=saturday-reggae&episode=ep-1744382400
```
Opens vj.html viewing someone else's show and episode in read-only mode.

#### Direct track link — works on either page
```
music.html#/track/12345678
vj.html#/track/12345678
```

#### Cross-page playlist link
```
music.html#/playlist/npub1abc123.../ep-1744382400
```
music.html loads the episode playlist from that pubkey and plays it.

---

## URL Flow Diagram

```mermaid
flowchart TD
    A[Page Load] --> B{Parse query string}
    B --> C[Extract npub/pubkey -> targetPubkey]
    B --> D[Extract auth -> authMode]
    B --> E[Extract show -> selectedShow]
    B --> F[Extract episode -> selectedEpisode]
    
    C --> G[Initialize authentication]
    D --> G
    G --> H{Parse hash fragment}
    
    E --> I[Auto-select show in dropdown]
    F --> J[Auto-load episode into setlist/queue]
    
    H --> K{Route type?}
    K -->|#/search/...| L[Run search]
    K -->|#/album/...| M[Show album detail]
    K -->|#/artist/...| N[Show artist detail]
    K -->|#/track/...| O[Play track]
    K -->|#/playlist/...| P[Load playlist/episode]
    K -->|#/ or empty| Q[Show home]
    
    I --> R[Filter episodes by show]
    J --> R
    R --> S[Render setlist/queue]
    
    P --> T{Has pubkey in path?}
    T -->|Yes| U[Fetch from external pubkey]
    T -->|No| V[Load from own playlists]
```

---

## Implementation Plan

### Phase 1: Add `show` and `episode` query params to vj.html

**File: [`www/vj.html`](www/vj.html)**

#### 1a. Read `show` from URL on page load
- In the initialization flow after authentication, read `?show=` from the query string
- If present, auto-select that show in `#selectStreamShow`
- This replaces the current behavior where the show is only selected manually

#### 1b. Read `episode` from URL on page load
- Read `?episode=` from the query string
- If present, find the matching playlist by identifier and load it into the setlist
- Call [`openMyPlaylistByIdentifier()`](www/vj.html:5172) or equivalent

#### 1c. Update URL when show/episode changes
- When the user selects a show from the dropdown, update `?show=` via `history.replaceState`
- When an episode is selected, update `?episode=` via `history.replaceState`
- Use `replaceState` not `pushState` to avoid polluting history with every selection change

#### 1d. Generate shareable links
- Add a "Copy Link" button or integrate into existing share flow
- Build URL: `vj.html?show={slug}&episode={identifier}` for own content
- Build URL: `vj.html?npub={npub}&show={slug}&episode={identifier}` for sharing to others

### Phase 2: Unify pubkey handling

**Files: [`www/vj.html`](www/vj.html), [`www/music.html`](www/music.html)**

#### 2a. Accept both `npub` and `pubkey` in vj.html
- Update [`getTargetPubkeyFromUrl()`](www/vj.html:2493) to check `npub` first, then `pubkey` — matching music.html's pattern

#### 2b. Prefer `npub` in generated URLs
- When building shareable URLs, always use `npub=` format since it is the Nostr standard for sharing

### Phase 3: Enable music.html to load episode playlists

**File: [`www/music.html`](www/music.html)**

#### 3a. Add `show` query param support
- Read `?show=` on page load — used as context/filter when loading playlists from a target pubkey

#### 3b. Add `episode` query param support
- Read `?episode=` on page load
- If present along with a target pubkey, subscribe to that user's kind 30078/30004 events and find the matching episode
- Load the episode's tracks into the queue and start playback

#### 3c. Handle `#/playlist/{pubkey}/{identifier}` for episodes
- The existing [`handleRoute()`](www/music.html:4168) already handles `#/playlist/{pubkey}/{identifier}` via [`openFriendPlaylistByKey()`](www/music.html:4216)
- Ensure this path works for episode playlists — it should subscribe to the pubkey's events and find the playlist by identifier
- If the playlist is not yet in the local `friendPlaylists` map, fetch it from relays

### Phase 4: Create a shared URL utility module

**New file: `www/js/url-params.mjs`**

Extract the duplicated URL parsing logic into a shared module:

```javascript
// Shared URL parameter parsing for all pages
export function getTargetPubkeyFromUrl() { ... }
export function resolveAuthModeFromUrl() { ... }
export function getShowFromUrl() { ... }
export function getEpisodeFromUrl() { ... }
export function updateUrlParam(key, value) { ... }  // replaceState helper
export function removeUrlParam(key) { ... }
export function buildShareUrl(page, params) { ... }
```

### Phase 5: Sync URL state on user actions

**Files: [`www/vj.html`](www/vj.html), [`www/music.html`](www/music.html)**

#### 5a. vj.html — show selection updates URL
- `selectStreamShow` change handler calls `updateUrlParam('show', slug)`

#### 5b. vj.html — episode selection updates URL
- When an episode is clicked in the sidebar, call `updateUrlParam('episode', identifier)`

#### 5c. vj.html — episode creation updates URL
- After creating a new episode, update both `show` and `episode` in the URL

#### 5d. music.html — playlist selection updates URL
- When a playlist is selected, update the hash to `#/playlist/{identifier}` — this already happens

---

## Summary of URL Parameter Vocabulary

| Parameter | Location | Type | Used By | Description |
|-----------|----------|------|---------|-------------|
| `npub` | query | string | both | Target user npub — preferred format |
| `pubkey` | query | hex | both | Target user hex pubkey — fallback |
| `auth` | query | enum | both | Auth mode: required, optional, none |
| `show` | query | slug | both | Active show slug |
| `episode` | query | string | both | Active episode playlist identifier |
| `a` | query | coord | vj.html | Stream coordinate 30311:pubkey:slug |
| `naddr` | query | naddr | vj.html | Nostr address of stream |
| `#/search/{q}` | hash | path | both | Search query |
| `#/album/{id}` | hash | path | both | Album detail view |
| `#/artist/{id}` | hash | path | both | Artist detail view |
| `#/track/{id}` | hash | path | both | Play specific track |
| `#/playlist/{id}` | hash | path | both | Own playlist/episode |
| `#/playlist/{pk}/{id}` | hash | path | both | External playlist/episode |

## Files to Modify

| File | Changes |
|------|---------|
| `www/vj.html` | Read show/episode from URL, update URL on selection, accept npub param |
| `www/music.html` | Read show/episode from URL, load episode playlists from external pubkeys |
| `www/js/url-params.mjs` | NEW — shared URL parsing utilities |
| `www/js/vj-stream.mjs` | No changes needed — already reads `a` and `naddr` |
