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

263 lines
9.2 KiB
Markdown

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