# 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
``` 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 `