# Plan: post.html URL-Driven Optionality

## Overview

Transform `post.html` into a generic, URL-parameter-driven page that can serve as:
- **My Posts** page (current default behavior)
- **User Profile** page (any user's profile + their posts)
- **User Posts** page (any user's posts without profile header)
- **Home Feed** page (posts from users you follow)

All controlled via URL query parameters, with full combinability.

---

## URL Parameters

| Parameter | Values | Default | Description |
|-----------|--------|---------|-------------|
| `npub` | hex pubkey or bech32 npub | logged-in user | Whose posts to display |
| `profile` | `true` / `false` | `false` | Show read-only profile card above posts |
| `post` | `true` / `false` | `true` | Show the post composition box |
| `follows` | `true` / `false` | `false` | Show posts from the target user's follows instead of just their posts |

### Example URLs

```
post.html                                          → Own posts + post box (current behavior)
post.html?post=false                                → Own posts, no post box
post.html?npub=npub1rmz9...                         → That user's posts, with post box
post.html?npub=abc123hex&profile=true               → Profile card + that user's posts
post.html?npub=npub1rmz9...&profile=true&post=false → Read-only profile page for a user
post.html?follows=true                              → Home feed (posts from people you follow)
post.html?follows=true&post=true                    → Home feed with post box
```

---

## Architecture

### Parameter Parsing Flow

```mermaid
flowchart TD
    A[Page Load] --> B[Parse URL params]
    B --> C{npub param?}
    C -->|Yes| D[Convert to hex if bech32]
    C -->|No| E[Use logged-in user pubkey]
    D --> F[targetPubkey = parsed hex]
    E --> F
    F --> G{profile=true?}
    G -->|Yes| H[Fetch kind 0 for targetPubkey]
    G -->|No| I[Skip profile section]
    H --> J[Render read-only profile card]
    I --> K{post param}
    J --> K
    K -->|true / default| L[Show divPost contenteditable]
    K -->|false| M[Hide divPost]
    L --> N{follows=true?}
    M --> N
    N -->|Yes| O[Fetch kind 3 contact list for targetPubkey]
    N -->|No| P[Subscribe to kind 1 for targetPubkey only]
    O --> Q[Subscribe to kind 1 for all followed pubkeys]
    P --> R[Render feed]
    Q --> R
```

### Key Design Decisions

1. **npub conversion**: Use `NostrTools.nip19.decode()` from the already-loaded `nostr.bundle.js` to handle bech32→hex. If the value doesn't start with `npub`, treat it as hex directly.

2. **Profile card**: A new `divProfile` section inserted into `divBody` above `divPost`. It's a read-only card showing: banner, avatar, display name, name, about, website, nip-05, LN address. Styled consistently with existing `.divPostItem` aesthetic.

3. **Post box visibility**: The existing `divPost` element is shown/hidden based on the `post` parameter. When hidden, the keydown listener is not attached.

4. **Feed subscription logic**: The `ndkEvent` listener's filter changes based on mode:
   - **Single user mode** (`follows=false`): Filter `evt.pubkey === targetPubkey` (current behavior, just with configurable target)
   - **Follows mode** (`follows=true`): Fetch kind 3 for targetPubkey, extract followed pubkeys, subscribe to kind 1 for all of them, filter events from any of those pubkeys

5. **Page title**: Dynamically set based on mode — e.g., "POST", "FEED", or the user's display name.

---

## Implementation Steps

### Step 1: Add URL Parameter Parsing

At the top of the `main()` function in `post.html`, parse URL parameters:

```javascript
const urlParams = new URLSearchParams(window.location.search);
const npubParam = urlParams.get('npub');        // hex or bech32
const profileParam = urlParams.get('profile');  // 'true' or 'false'
const postParam = urlParams.get('post');        // 'true' or 'false'  
const followsParam = urlParams.get('follows');  // 'true' or 'false'

const showProfile = profileParam === 'true';
const showPostBox = postParam !== 'false';      // default true
const showFollows = followsParam === 'true';
```

### Step 2: Add npub→hex Conversion

After NDK initialization, resolve the target pubkey:

```javascript
let targetPubkey = currentPubkey; // default: logged-in user

if (npubParam) {
  if (npubParam.startsWith('npub')) {
    // Wait for NostrTools to be available from nostr.bundle.js
    const decoded = window.NostrTools.nip19.decode(npubParam);
    targetPubkey = decoded.data;
  } else {
    targetPubkey = npubParam; // assume hex
  }
}
```

### Step 3: Add Profile Card Section

Add a new `divProfile` element to the HTML body, positioned above `divPost` in `divBody`:

```html
<div id="divProfile" style="display: none;"></div>
<div id="divPost" contenteditable="true"></div>
```

When `showProfile` is true, fetch kind 0 for `targetPubkey` and render a read-only card:

- Banner image (full width)
- Avatar (circular/rounded)
- Display name / name
- About text
- Website link
- NIP-05
- LN address

Reuse the existing `fetchProfile()` from `post-interactions.mjs` for the kind 0 fetch, or use `fetchEventsFromAllRelays` directly for the full event data (since `fetchProfile` only caches a subset of fields).

### Step 4: Conditionally Show/Hide Post Box

```javascript
if (!showPostBox) {
  divPost.style.display = 'none';
} else {
  divPost.addEventListener('keydown', handleKeyDown);
}
```

### Step 5: Modify Subscription Logic

Replace the hardcoded `currentPubkey` subscription with dynamic logic:

```javascript
if (showFollows) {
  // Fetch kind 3 contact list for targetPubkey
  const contactEvents = await fetchEventsFromAllRelays(
    { kinds: [3], authors: [targetPubkey], limit: 1 }
  );
  // Extract followed pubkeys from tags
  const followedPubkeys = extractFollowedPubkeys(contactEvents);
  
  // Subscribe to posts from all followed users
  subscribe(
    { kinds: [1], authors: followedPubkeys, limit: 50 },
    { closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
  );
} else {
  // Subscribe to posts from targetPubkey only
  subscribe(
    { kinds: [1], authors: [targetPubkey], limit: 50 },
    { closeOnEose: false, cacheUsage: 'CACHE_FIRST' }
  );
}
```

### Step 6: Update Event Listener Filter

The `ndkEvent` listener currently checks `evt.pubkey === currentPubkey`. This needs to change:

```javascript
// Build the set of pubkeys we're interested in
let feedPubkeys = new Set();
if (showFollows) {
  followedPubkeys.forEach(pk => feedPubkeys.add(pk));
} else {
  feedPubkeys.add(targetPubkey);
}

// In the event listener:
if (evt.kind === 1 && feedPubkeys.has(evt.pubkey)) {
  // ... existing post handling logic
}
```

### Step 7: Update Page Title and Header

Dynamically set the header text based on the mode:

```javascript
const headerText = document.querySelector('.divHeaderText');
if (showFollows) {
  headerText.textContent = 'FEED';
  document.title = 'FEED';
} else if (npubParam && targetPubkey !== currentPubkey) {
  // Viewing another user - will update with their name when profile loads
  headerText.textContent = 'USER';
  document.title = 'USER';
} else {
  headerText.textContent = 'POST';
  document.title = 'POST';
}
```

### Step 8: Add Profile Card CSS

Add styles for the read-only profile card in the `<style>` block:

```css
#divProfile {
  width: 60%;
  min-width: 300px;
  max-width: 600px;
  margin-bottom: 20px;
  border: 2px solid var(--primary-color);
  border-radius: 10px;
  background: var(--secondary-color);
  overflow: hidden;
}
```

### Step 9: Update See More Button for Follows Mode

The "See More" button's fetch-older logic needs to use the correct author list (followed pubkeys or target pubkey) when requesting older posts.

### Step 10: Handle Post Publishing in Non-Own-User Mode

When viewing another user's posts, the post box (if shown) should still publish as the logged-in user. The published post won't appear in the feed unless the logged-in user is the target user. Consider adding a note or handling this gracefully.

---

## Files Modified

| File | Changes |
|------|---------|
| `www/post.html` | URL param parsing, conditional rendering, profile card HTML/CSS, dynamic subscriptions, updated event filtering, dynamic page title |
| `www/js/post-interactions.mjs` | Possibly export `fetchProfile` or add a `fetchFullProfile` that returns all kind 0 fields (not just the cached subset) |

## Files NOT Modified

- `www/js/init-ndk.mjs` — No changes needed; `subscribe`, `fetchEventsFromAllRelays`, `getPubkey` already provide everything we need
- `www/profile.html` — Remains as the editable profile page
- `www/npub.html` — Remains as the QR code display page

---

## Edge Cases

1. **Invalid npub/hex**: If the npub parameter can't be decoded, fall back to logged-in user with a console warning
2. **No follows**: If `follows=true` but the user has no kind 3 event or no followed pubkeys, show an empty feed with a message
3. **Large follow lists**: Kind 3 can have hundreds of pubkeys. The subscription should batch or limit to avoid overwhelming relays. Consider subscribing in chunks of 50-100 authors.
4. **Post box + other user**: When `post=true` and viewing another user, the post box publishes as the logged-in user. The post won't appear in the feed. Could either hide the post box automatically when `targetPubkey !== currentPubkey`, or let the user decide via the `post` param.
