Files
client/plans/post-html-url-params.md
2026-04-17 16:52:51 -04:00

9.2 KiB

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

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:

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:

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:

<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

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

Step 5: Modify Subscription Logic

Replace the hardcoded currentPubkey subscription with dynamic logic:

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:

// 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:

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:

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