# Keep-Alive Page Architecture

## Overview

The keep-alive page is a utility that ensures a user's Nostr events persist across the ecosystem by discovering free relays and republishing events to them. It operates as a manual, step-by-step process with buttons for each phase, allowing the user to observe and control each stage.

## Core Concepts

### Event Priority System

Not all events are equally important. The page prioritizes event kinds for spreading:

| Priority | Kinds | Rationale |
|----------|-------|-----------|
| **Critical** | 7375, 7376, 17375, 37375 (Cashu wallet/token events), 10002 (relay list), 0 (profile metadata) | Loss = lost ecash or identity |
| **High** | 3 (contacts/follows), 10000 (mute list), 30078 (app-specific data), 30023 (long-form content) | Important user data |
| **Medium** | 30311 (live events), 30024 (drafts), 1063 (file metadata), 9735 (zap receipts) | Valuable but recoverable |
| **Low** | 1 (text notes), 6 (reposts), 7 (reactions), 1111 (comments) | High volume, low individual value |

### Relay Registry - kind 30078 with t-tag grouping

Discovered and tested relays are stored as addressable events using a `t` tag for grouping and sequential `d` tags for pagination. This keeps all data in Nostr events (portable across devices and apps) without needing a manifest.

**Event structure** (each chunk):
```json
{
  "kind": 30078,
  "tags": [
    ["d", "relay-list-001"],
    ["t", "relay-list"]
  ],
  "content": "[{\"url\":\"wss://relay1.com\",\"available\":true,...}, ...]"
}
```

Each relay entry in the JSON content array:
```json
{
  "url": "wss://relay.example.com",
  "tested": 1711440000,
  "available": true,
  "nip11": { "name": "...", "description": "...", "supported_nips": [], "limitation": {} },
  "geo": null
}
```

**Fetching**: A single filter retrieves all chunks:
```javascript
{ kinds: [30078], authors: [pubkey], "#t": ["relay-list"] }
```

**Pagination**: Each chunk holds ~100 relays (~50KB). Sequential `d` tags (`relay-list-001`, `002`, etc.) make each chunk independently replaceable.

**Republishing**: When re-testing, rebuild chunks and publish. If fewer chunks are needed than before, publish kind 5 deletion events for the extras.

**Size math**: ~500 bytes per relay with NIP-11 data × 100 relays = ~50KB per chunk. 300 relays = 3 events. 500 relays = 5 events. Well within relay limits.

**Cross-app usage**: Any app can discover the relay registry by querying for kind 30078 with `#t: relay-list` for a given pubkey.

## Architecture

### Data Flow

```mermaid
flowchart TD
    A[Step 1: Find My Events] --> B[Step 2: Find Relays]
    B --> C[Step 3: Test Relays]
    C --> D[Step 4: Store Relay Registry]
    D --> E[Step 5: Spread Events]
    
    A -->|Fetch all user events via NDK| A1[Events grouped by kind + priority]
    B -->|Fetch kind 3 follows list| B1[Get follows pubkeys]
    B1 -->|Fetch kind 10002 for each follow| B2[Extract unique relay URLs]
    C -->|Generate session dummy nsec| C1[Post kind 1 test event to each relay]
    C1 -->|Track success/failure| C2[Filtered list of available relays]
    D -->|Publish kind 30078| D1[Relay registry persisted]
    E -->|For each priority tier| E1[Publish events to available relays]
    E1 -->|Rate limited - one relay at a time| E2[Progress log with success/fail per event]
```

### Page Layout

```mermaid
flowchart TD
    subgraph Header
        H[KEEP ALIVE title]
    end
    
    subgraph Body
        BTN[Step Buttons Row]
        STATS[Stats Summary Panel]
        LOG[Live Activity Log - scrollable]
    end
    
    subgraph Footer
        F[Relay status + balance]
    end
```

### Step Buttons

Each button represents a phase. They are enabled sequentially - Step 2 only enables after Step 1 completes, etc. Each button shows a status indicator:

- ○ Not started
- ◐ In progress  
- ● Complete
- ✕ Error

Buttons:
1. **Find My Events** - Fetches all user events, displays count by kind
2. **Find Relays** - Discovers relays from follows' kind 10002
3. **Test Relays** - Tests each discovered relay with dummy event
4. **Save Registry** - Stores tested relay data as kind 30078
5. **Spread: Critical** - Spreads critical priority events
6. **Spread: High** - Spreads high priority events
7. **Spread: Medium** - Spreads medium priority events
8. **Spread: Low** - Spreads low priority events

### Stats Summary Panel

Shows at-a-glance numbers:
- Total events found: X
- Events by priority: Critical: X | High: X | Medium: X | Low: X
- Relays discovered: X
- Relays available: X / X tested
- Events spread: X / X total

### Live Activity Log

A scrollable log area showing real-time progress messages:
```
[12:34:01] Fetching user events...
[12:34:03] Found 142 events across 8 kinds
[12:34:03]   kind 1: 89 events (low priority)
[12:34:03]   kind 0: 1 event (critical priority)
[12:34:03]   kind 7375: 3 events (critical priority)
...
[12:35:10] Testing wss://relay.damus.io ... ✓ available
[12:35:12] Testing wss://relay.nostr.band ... ✓ available
[12:35:15] Testing wss://broken.relay.xyz ... ✕ timeout
```

## Implementation Details

### Phase 1: Page Skeleton

- Modify `www/keep-alive.html` from template
- Title: "KEEP ALIVE"
- CSS for button row, stats panel, log area
- Import required NDK functions: `initNDKPage`, `getPubkey`, `subscribe`, `publishEvent`, `ndkFetchEvents`, `fetchEvents`, `getVersion`, etc.
- All page logic inline in the HTML script module, following existing page patterns

### Phase 2: Find My Events

```javascript
// Fetch all events authored by the user
const filters = { authors: [currentPubkey] };
const events = await ndkFetchEvents(filters);

// Group by kind and assign priority
const eventsByKind = new Map();
for (const event of events) {
    if (!eventsByKind.has(event.kind)) eventsByKind.set(event.kind, []);
    eventsByKind.get(event.kind).push(event);
}
```

Priority assignment uses the table above. Events are stored in memory for the spreading phase.

### Phase 3: Find Relays

1. Fetch user's kind 3 event to get follows list
2. Extract pubkeys from `p` tags
3. Fetch kind 10002 events for all follows: `{ kinds: [10002], authors: [followPubkeys...] }`
4. Parse `r` tags from each 10002 event to extract relay URLs
5. Deduplicate and normalize URLs
6. Also check if we already have a stored relay registry (kind 30078 with `#t: relay-list`) and merge

### Phase 4: Test Relays

Uses the user's own kind 0 (profile metadata) event as the test payload. This is ideal because:
- It's already signed by the user — no dummy keypair needed
- Every relay accepts kind 0 events
- It serves double duty: testing the relay AND spreading the user's profile

For each discovered relay:
1. Use the user's kind 0 event fetched in Phase 2 (already signed)
2. Open a direct WebSocket connection to the relay (bypassing NDK pool since these are not user relays)
3. Send the kind 0 event as the test
4. Wait for OK response or timeout (5 seconds)
5. Also fetch NIP-11 info document via HTTP: `fetch(relayUrl.replace('wss://', 'https://'), { headers: { Accept: 'application/nostr+json' } })`
6. Record result: available/unavailable, NIP-11 data, response time

**Important**: This uses raw WebSocket connections, not the NDK worker, because we don't want to pollute the user's relay pool with hundreds of test relays.

### Phase 5: Store Relay Registry

1. Build relay metadata array from test results
2. Chunk into events of ~100 relays each with `d: "relay-list-001"`, `d: "relay-list-002"`, etc.
3. Each event tagged with `["t", "relay-list"]`
4. Publish via `publishEvent()` (uses user's connected relays)
5. If fewer chunks needed than previously stored, publish kind 5 deletion events for extras

### Phase 6: Spread Events

For each priority tier, when the user clicks the corresponding spread button:

1. Iterate through available relays
2. For each relay, open a WebSocket connection
3. Send events one at a time with a delay between each (rate limiting)
4. Use `publishRawEvent`-style approach: events are already signed by the user
5. Track success/failure per event per relay
6. Log progress in real-time
7. Rate limiting: ~500ms between events to the same relay, process one relay at a time

**Relay kindness rules**:
- Respect NIP-11 `limitation.max_event_tags`, `limitation.max_content_length`
- Don't send events that exceed relay's stated limits
- If relay returns `blocked:` or `rate-limited:` in OK response, back off and skip
- Close WebSocket cleanly after batch is done

### Worker Considerations

The keep-alive page will need to do direct WebSocket operations for:
- Testing relays with dummy events (Phase 4)
- Spreading events to non-pool relays (Phase 6)

This will be done **client-side in the page script**, not through the NDK worker, because:
- We don't want to add hundreds of relays to the NDK pool
- We need fine-grained control over individual relay connections
- The worker's publish function only publishes to pool relays

The NDK worker is still used for:
- Authentication and user pubkey
- Fetching user events (Phase 2)
- Fetching follows and kind 10002 events (Phase 3)
- Publishing the relay registry kind 30078 (Phase 5)

## File Structure

All implementation is in a single file following existing patterns:
- `www/keep-alive.html` - Complete page with inline styles and script module

No new JS modules needed for Phase 1. If the WebSocket relay testing/spreading logic grows complex, it could be extracted to `www/js/keep-alive.mjs` later.

## Future Enhancements

- **Geo-location**: Resolve relay hostnames to IP addresses, use a geo-IP service, store lat/lng in relay registry metadata. Display on a world map showing where user data exists.
- **Broader discovery**: Fetch kind 10002 from popular relays for random pubkeys to discover more relays beyond the user's social graph.
- **Automation**: Add a "Run All" button that executes all steps sequentially.
- **Scheduling**: Option to run keep-alive periodically (e.g., weekly) using a service worker or reminder.
- **Diff mode**: Show which events are already on which relays before spreading, to avoid redundant publishes.
