# Option A: Minimal Fork — Replace Auth with Nostr Login

## Goal
Fork Monochrome with minimal changes: replace the Appwrite/Firebase auth system with `nostr_login_lite` while keeping the entire music engine, UI, and feature set intact. Structure changes so upstream updates merge cleanly.

## Architecture Overview

```mermaid
flowchart TB
    subgraph Unchanged - Music Engine
        MA[MusicAPI] --> TIDAL[LosslessAPI - Tidal]
        MA --> QOBUZ[QobuzAPI - Qobuz]
        PLAYER[Player + dash.js]
        DB[IndexedDB - local storage]
    end
    subgraph Changed - Auth Layer
        NL[nostr_login_lite] -->|nlMethodSelected event| NA[NostrAuthAdapter]
        NA -->|same AuthManager interface| PB[PocketBase syncManager]
        PB -->|user.$id = nostr pubkey| PBSERVER[PocketBase Server]
    end
    subgraph Changed - UI
        ACCOUNT[Account Page - nostr login UI]
        HEADER[Header - nostr avatar/name]
    end
    NA --> ACCOUNT
    NA --> HEADER
```

## Files to Modify

| File | Change Type | Description |
|------|------------|-------------|
| `js/accounts/config.js` | **Replace** | Remove Appwrite, add nostr_login_lite initialization |
| `js/accounts/auth.js` | **Replace** | New `AuthManager` backed by nostr events instead of Appwrite |
| `js/accounts/pocketbase.js` | **Modify** | Change `user.$id` references to use nostr pubkey |
| `index.html` | **Modify** | Add nostr script tags, replace account page HTML, remove email auth modal |
| `vite.config.js` | **Modify** | Add nostr_login_lite build files to static assets |
| `package.json` | **Modify** | Remove `appwrite` dependency |

## Files That Stay Untouched
- All music API files: `js/api.js`, `js/qobuz-api.js`, `js/music-api.js`
- Player: `js/player.js`, `js/dash-downloader.js`
- UI: `js/ui.js`, `js/ui-interactions.js`, `js/router.js`
- Storage: `js/storage.js`, `js/db.js`, `js/cache.js`
- Features: `js/lyrics.js`, `js/visualizer.js`, `js/downloads.js`, `js/equalizer.js`
- Scrobbling: `js/lastfm.js`, `js/listenbrainz.js`, `js/multi-scrobbler.js`
- All CSS, all public assets, all visualizers

---

## Detailed Implementation Steps

### Step 1: Create the Nostr Auth Adapter

**File: `js/accounts/config.js`** — Complete rewrite

Remove all Appwrite code. Replace with nostr_login_lite initialization.

```javascript
// js/accounts/config.js
// Nostr authentication configuration

let _nostrInitialized = false;
let _initPromise = null;

export async function initNostrAuth() {
    if (_nostrInitialized) return;
    if (_initPromise) return _initPromise;
    
    _initPromise = window.NOSTR_LOGIN_LITE.init({
        persistence: true,
        isolateSession: false,
        theme: 'dark',
        methods: {
            extension: true,
            local: true,
            seedphrase: true,
            connect: true,
            readonly: false
        },
        floatingTab: {
            enabled: false  // We handle our own UI
        }
    });
    
    await _initPromise;
    _nostrInitialized = true;
}

// Stub exports to maintain interface compatibility
export const saveFirebaseConfig = () => {};
export const clearFirebaseConfig = () => {};
```

### Step 2: Replace AuthManager

**File: `js/accounts/auth.js`** — Complete rewrite

The new `AuthManager` must expose the exact same interface that the 6 consumer files expect:
- `authManager.user` — object with at least `.$id` and `.email` (or equivalent)
- `authManager.signOut()`
- `authManager.updateUI(user)`
- `authManager.onAuthStateChanged(callback)`

```javascript
// js/accounts/auth.js — Nostr-backed AuthManager
import { initNostrAuth } from './config.js';

export class AuthManager {
    constructor() {
        this.user = null;
        this.authListeners = [];
        this.init();
    }

    async init() {
        await initNostrAuth();
        
        // Check for existing auth state
        const authState = window.NOSTR_LOGIN_LITE.getAuthState();
        if (authState && authState.pubkey) {
            this._setUser(authState);
        }
        
        // Listen for login events
        window.addEventListener('nlMethodSelected', (event) => {
            const { pubkey, method } = event.detail;
            if (pubkey) {
                this._setUser({ pubkey, method });
            }
        });
        
        // Listen for logout events
        window.addEventListener('nlLogout', () => {
            this.user = null;
            this.updateUI(null);
            this.authListeners.forEach(cb => cb(null));
        });
    }

    _setUser(authState) {
        // Create a user object compatible with existing code
        // Existing code uses user.$id and user.email
        this.user = {
            $id: authState.pubkey,
            email: null,  // Nostr doesn't have email
            pubkey: authState.pubkey,
            method: authState.method,
            // npub for display
            displayId: authState.pubkey.slice(0, 8) + '...' + authState.pubkey.slice(-4)
        };
        this.updateUI(this.user);
        this.authListeners.forEach(cb => cb(this.user));
    }

    onAuthStateChanged(callback) {
        this.authListeners.push(callback);
        if (this.user !== null) {
            callback(this.user);
        }
    }

    async signOut() {
        window.NOSTR_LOGIN_LITE.logout();
        this.user = null;
        this.updateUI(null);
        this.authListeners.forEach(cb => cb(null));
    }

    // Launch the nostr login modal
    launchLogin() {
        window.NOSTR_LOGIN_LITE.launch();
    }

    updateUI(user) {
        const connectBtn = document.getElementById('firebase-connect-btn');
        const clearDataBtn = document.getElementById('firebase-clear-cloud-btn');
        const statusText = document.getElementById('firebase-status');
        const emailContainer = document.getElementById('email-auth-container');
        const emailToggleBtn = document.getElementById('toggle-email-auth-btn');

        if (!connectBtn) return;

        if (user) {
            connectBtn.textContent = 'Sign Out';
            connectBtn.classList.add('danger');
            connectBtn.onclick = () => this.signOut();
            if (clearDataBtn) clearDataBtn.style.display = 'block';
            if (emailContainer) emailContainer.style.display = 'none';
            if (emailToggleBtn) emailToggleBtn.style.display = 'none';
            if (statusText) statusText.textContent = `Signed in as ${user.displayId}`;
        } else {
            connectBtn.textContent = 'Sign in with Nostr';
            connectBtn.classList.remove('danger');
            connectBtn.onclick = () => this.launchLogin();
            if (clearDataBtn) clearDataBtn.style.display = 'none';
            if (emailToggleBtn) emailToggleBtn.style.display = 'none';
            if (statusText) statusText.textContent = 'Sync your library across devices';
        }
    }
}

export const authManager = new AuthManager();
```

### Step 3: Adapt PocketBase to Use Nostr Pubkey

**File: `js/accounts/pocketbase.js`** — Minimal modifications

The key change: everywhere `user.$id` is used as the user identifier, it now contains the nostr pubkey (a 64-char hex string). This is already handled by Step 2 since we set `user.$id = authState.pubkey`.

Specific changes needed:
1. The `firebase_id` field in PocketBase queries should work as-is since it is just a string identifier
2. No structural changes needed — the pubkey is just a different string format for the same field

**Verify these lines work with a 64-char hex pubkey instead of an Appwrite ID:**
- Line 28: `firebase_id="${uid}"` — Works, just a string filter
- Line 35: `firebase_id: uid` — Works, just a string field
- Line 96: `{ f_id: uid }` — Works, just a query param
- Line 394: `authManager.user?.$id` — Works, returns pubkey

**One potential issue:** PocketBase field `firebase_id` has a misleading name. Consider renaming to `user_id` in the PocketBase schema, or just leave it as-is for compatibility.

### Step 4: Update index.html

**Changes to `index.html`:**

1. **Add nostr script tags before app.js** (around line 5803):
```html
<!-- Nostr Login -->
<script src="/nostr_login_lite/build/nostr.bundle.js"></script>
<script src="/nostr_login_lite/build/nostr-lite.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pocketbase@0.21.3/dist/pocketbase.umd.js"></script>
<script type="module" src="/js/app.js"></script>
```

2. **Replace account page content** (lines 5356-5398):
Replace "Connect with Google" and "Connect with Email" buttons with a single "Sign in with Nostr" button. Remove the email auth modal (lines 1030-1063). Remove the Firebase config textarea (lines 1244-1268).

3. **Update account page description text** to reference Nostr instead of Google/Email.

4. **Remove the inline script** that checks `window.authManager.user.email` (lines 5384-5389).

### Step 5: Update Header Account Dropdown

**File: `js/app.js`** — Modify the `updateAccountDropdown` function (around line 2723)

The existing code at line 2732 calls `authManager.signInWithGoogle()` — replace with `authManager.launchLogin()`. The existing code at line 2733 opens the email auth modal — replace with `authManager.launchLogin()`.

Specific changes:
- Line 2732: `authManager.signInWithGoogle()` → `authManager.launchLogin()`
- Line 2733-2738: Remove email auth modal trigger, replace with nostr login
- Lines 2726-2731: Update the "not logged in" dropdown HTML to show "Sign in with Nostr" instead of Google/Email buttons

### Step 6: Update Settings Page Auth Section

**File: `js/settings.js`** — Remove email auth UI logic

- Lines 62-137: Remove all email auth modal logic (toggle, cancel, sign in, sign up, reset password). Replace with a simple nostr login trigger.
- Line 59: `authManager.updateUI(authManager.user)` — Keep as-is, works with new adapter.

### Step 7: Update vite.config.js

Add the nostr_login_lite build directory to Vite's public assets:

```javascript
// In vite.config.js, ensure nostr_login_lite/build/ files are served
// They're already in the project root, so they should be accessible
// May need to add to the public directory or configure an alias
```

### Step 8: Update package.json

Remove the `appwrite` dependency:
```json
// Remove from dependencies:
// "appwrite": "^23.0.0"
```

### Step 9: Handle Profile Display with Nostr

**File: `js/app.js`** — Modify auth state change handler (line 2765)

When a nostr user logs in, fetch their kind:0 profile from relays to get avatar and display name:

```javascript
authManager.onAuthStateChanged(async (user) => {
    if (user && user.pubkey) {
        // Try to get nostr profile (kind:0)
        try {
            const pool = new window.NostrTools.SimplePool();
            const relays = ['wss://relay.damus.io', 'wss://nos.lol'];
            const events = await pool.querySync(relays, {
                kinds: [0],
                authors: [user.pubkey],
                limit: 1
            });
            pool.close(relays);
            
            if (events.length > 0) {
                const profile = JSON.parse(events[0].content);
                if (profile.picture) {
                    headerAccountImg.src = profile.picture;
                    headerAccountImg.style.display = 'block';
                    headerAccountIcon.style.display = 'none';
                    return;
                }
            }
        } catch (e) {
            console.warn('Failed to fetch nostr profile:', e);
        }
        
        // Fallback: check PocketBase profile
        const data = await syncManager.getUserData();
        if (data?.profile?.avatar_url) {
            headerAccountImg.src = data.profile.avatar_url;
            headerAccountImg.style.display = 'block';
            headerAccountIcon.style.display = 'none';
            return;
        }
    }
    headerAccountImg.style.display = 'none';
    headerAccountIcon.style.display = 'block';
});
```

### Step 10: Update Theme Store Auth

**File: `js/themeStore.js`** — Minimal changes

The theme store uses `authManager.user` and `authManager.onAuthStateChanged()` — both work with the new adapter. The `authManager.user.$id` calls will return the nostr pubkey, which is fine for PocketBase lookups.

No changes needed if PocketBase `firebase_id` field accepts the nostr pubkey string.

---

## Git Strategy for Upstream Merges

### Branch Structure
```
upstream/main ──────────────────────────────────►
                    \                    \
                     merge                merge
                      \                    \
nostr/main ────────────●────────────────────●──►
```

### Files with Conflict Risk
Only these files differ from upstream:
1. `js/accounts/auth.js` — **Full replacement** (use `ours` on merge)
2. `js/accounts/config.js` — **Full replacement** (use `ours` on merge)
3. `index.html` — **Partial changes** (account section, script tags)
4. `js/app.js` — **Small changes** (header dropdown, auth state handler)
5. `js/settings.js` — **Small changes** (remove email auth UI)
6. `package.json` — **Remove appwrite** (minor)

### .gitattributes for Merge Safety
```
js/accounts/auth.js merge=ours
js/accounts/config.js merge=ours
```

### Merge Procedure
1. `git fetch upstream`
2. `git merge upstream/main`
3. Resolve conflicts in `index.html`, `js/app.js`, `js/settings.js` manually (usually trivial)
4. `js/accounts/` auto-resolves to `ours`

---

## Testing Checklist

- [ ] App loads without errors (no Appwrite import failures)
- [ ] Nostr login modal opens when clicking "Sign in with Nostr"
- [ ] Extension login works (Alby, nos2x, etc.)
- [ ] Local key login works
- [ ] NIP-46 connect works
- [ ] After login, `authManager.user` is populated with pubkey
- [ ] PocketBase sync triggers on login (library, history, playlists)
- [ ] Header avatar shows nostr profile picture (kind:0)
- [ ] Sign out clears state and resets UI
- [ ] Music playback works without login (no regressions)
- [ ] Search, album pages, artist pages all work
- [ ] Theme store works with nostr auth
- [ ] Profile page works with nostr pubkey
- [ ] Upstream merge produces no conflicts in music engine files

---

## Summary

**Total files changed: 6** (auth.js, config.js, pocketbase.js, index.html, app.js, settings.js)
**Total files unchanged: ~40+**
**Risk level: Low** — auth is cleanly separated from music engine
**Upstream merge difficulty: Low** — only auth files and minor UI changes
