11 KiB
Event Management Page (event-management.html)
Overview
A unified page for managing all of the user's Nostr events across every kind. Three-column drill-down layout: Kinds → Events → Relay Status. Supports deletion (kind 5) with per-relay verification, and blocking (via mute-list.mjs kind 10000) as a fallback when deletion fails.
Existing Infrastructure
| Component | Location | Role |
|---|---|---|
| Mute/block list | mute-list.mjs |
NIP-51 kind:10000 — addMute('e', eventId, isPrivate) blocks an event across all pages |
| Block UI | block.html |
Manages mute list entries (pubkeys, hashtags, words, event IDs) |
| DB browser | db.html |
Reads IndexedDB ndk-shared store, grouped by kind |
| Event editor | event.html |
Loads/edits/reposts individual events by kind |
| Publish flow | init-ndk.mjs publishEvent() → ndk-worker.js handlePublish() |
Returns relayResults.successful[] and relayResults.failed[] |
| Per-relay fetch | init-ndk.mjs fetchEventsFromAllRelays() |
Currently wraps ndkFetchEvents — returns combined, not per-relay |
| NDK subscribe | init-ndk.mjs subscribe() |
Live subscription with ndkEvent window events |
Architecture
Three-Column Layout
graph LR
A[Left Column: Kind List] -->|click kind| B[Center Column: Events for Kind]
B -->|click event| C[Right Column: Relay Detail + Actions]
Left Column — Kind Summary
- On page load, reads IndexedDB cache to build a list of kinds the user has published
- Each row: kind number, human-readable label, event count
- Clicking a kind populates the center column
- Optional "Refresh from Relays" button to hydrate from live relay data
Center Column — Events for Selected Kind
- Lists all events of the selected kind, sorted by
created_atdescending - Each row shows: truncated event ID, d-tag or content preview, timestamp
- For addressable events (kinds 30000+): shows the d-tag as primary identifier
- Clicking an event populates the right column
- Batch actions: "Select All" checkbox for bulk delete
Right Column — Event Detail + Relay Status + Actions
- Shows full event JSON (collapsible, like db.html raw modal)
- Relay presence table: which relays have this event
- Action buttons: Delete, Block, Copy JSON
- After delete: per-relay verification results
Startup Flow
flowchart TD
A[Page Load] --> B[Read IndexedDB ndk-shared]
B --> C[Group cached events by kind]
C --> D[Render Kind List in left column]
D --> E[User clicks a kind]
E --> F[Show cached events for that kind]
F --> G[Optional: Subscribe for live updates]
G --> H[User clicks an event]
H --> I[Show event detail + relay info]
The page starts fast by reading only from IndexedDB. No relay queries on startup. Relay hydration happens lazily:
- When the user clicks a kind, a background subscription fetches fresh data for that kind
- When the user clicks an event and wants relay detail, we query individual relays
Repost / Copy to All Relays Flow
When the relay status table shows an event exists on only some relays, the user can "Repost to All Relays" to spread it to every connected relay. This reuses the existing publishRawEvent worker message (same as event.html's "Repost as-is" feature) — the event is already signed, so it gets published without re-signing.
flowchart TD
A[User clicks Repost to All Relays] --> B[Send already-signed event via publishRawEvent]
B --> C[Show relayResults.successful and relayResults.failed]
C --> D[Update relay status table with new presence]
Delete Flow
flowchart TD
A[User clicks Delete] --> B[Confirm dialog]
B --> C[Publish kind 5 deletion event]
C --> D[Show which relays accepted the delete request]
D --> E[Wait 2-3 seconds]
E --> F[Re-query each relay individually for the event]
F --> G{Event still present?}
G -->|Yes| H[Show relay as: Delete failed - offer Block]
G -->|No| I[Show relay as: Deleted successfully]
Block Flow
When deletion fails on some relays, the user can block the event:
- Calls
addMute('e', eventId, isPrivate)frommute-list.mjs - This adds the event ID to the kind:10000 mute list
- All other pages that call
isEventMuted()will filter it out - The block is immediate and works regardless of relay cooperation
Per-Relay Query Enhancement
The current fetchEventsFromAllRelays() returns combined results, not per-relay. We need a new worker message type to query a specific relay for a specific event:
New worker message: queryRelay
- Input:
{ type: 'queryRelay', relayUrl, filter, requestId } - Opens a temporary connection to the specific relay (or uses existing pool connection)
- Returns events found on that specific relay
- Used for post-delete verification
Implementation Plan
Phase 1: Page Shell + Kind List from Cache
- Create
event-management.htmlwith standard page template (header, footer, sidenav, hamburger) - Three-column responsive CSS layout (collapses to stacked on mobile)
- On init: open IndexedDB
ndk-shared, read all events, group by kind - Render left column with kind list (kind number, label, count)
- Kind label lookup table for common kinds (0=Profile, 1=Note, 3=Contacts, 10002=Relay List, 30023=Article, 31123=Skill, etc.)
Phase 2: Event List for Selected Kind
- Click a kind → populate center column with events of that kind
- Show event ID (truncated), d-tag for addressable kinds, content preview, created_at date
- Sort by created_at descending
- Filter/search input for the event list
- Pagination or virtual scroll for kinds with many events
- Background subscription to hydrate fresh events for the selected kind
Phase 3: Event Detail + Relay Status
- Click an event → populate right column with full detail
- Collapsible raw JSON viewer
- Parsed tag display (key tags shown as labeled fields)
- "Check Relays" button that queries each connected relay for this event
- Relay status table: relay URL, status icon (present/absent/checking)
- "Repost to All Relays" button — republishes the already-signed event to all connected relays (uses
publishRawEventworker message, same as event.html's Repost feature) - After repost: update relay status table showing which relays now have the event
Phase 4: Delete with Per-Relay Verification
- Delete button → confirmation dialog
- Publish kind 5 deletion request via
publishEvent() - Display which relays accepted the deletion request (from
relayResults) - After short delay, re-query each relay to verify event removal
- Update relay status table with verification results (deleted / still present / error)
- Add
queryRelaymessage type tondk-worker.jsfor single-relay queries
Phase 5: Block Integration
- Block button on event detail (always available)
- "Block" button highlighted/suggested when delete verification shows failures
- Calls
addMute('e', eventId, isPrivate)frommute-list.mjs - Private/public toggle for the block (default: private)
- Visual indicator if event is already blocked
- Configure mute list on page init (same pattern as
block.html)
Phase 6: Polish + Batch Operations
- Batch select events in center column
- Batch delete selected events
- Batch block selected events
- Export selected events as JSON
- Kind list refresh button (re-subscribe to all user events)
- Status bar showing operation progress
File Changes
| File | Change |
|---|---|
www/event-management.html |
New — full page |
www/ndk-worker.js |
Add queryRelay message handler for single-relay event queries |
www/js/init-ndk.mjs |
Add queryRelay() export that sends the new message type |
UI Wireframe
┌─────────────────────────────────────────────────────────────────────┐
│ ☰ Event Management [avatar]│
├──────────────┬──────────────────────┬───────────────────────────────┤
│ Kinds │ Events (Kind 1) │ Event Detail │
│ │ │ │
│ [search] │ [search/filter] │ ID: abc123... │
│ │ │ Created: 2025-01-15 │
│ 0 Profile 1│ ☐ abc1.. 2025-01-15 │ Content: Hello world... │
│ 1 Notes 47│ ☑ def2.. 2025-01-14 │ │
│ 3 Contacts │ ☐ ghi3.. 2025-01-13 │ ┌─ Raw JSON ──────────────┐ │
│ 7 Reactions│ ☐ jkl4.. 2025-01-12 │ │ { "kind": 1, ... } │ │
│ 10002 Relays │ │ └─────────────────────────┘ │
│ 30023 Article│ │ │
│ 31123 Skills │ │ Relay Status: │
│ │ │ ✅ wss://relay1 present │
│ │ │ ✅ wss://relay2 present │
│ │ │ ❌ wss://relay3 not found │
│ │ │ │
│ │ [Delete Selected] │ [Repost All] [Delete] [Block] │
│ │ │ [Copy JSON] │
│ [Refresh] │ │ Delete Results: │
│ │ │ ✅ relay1: deleted │
│ │ │ ⚠️ relay2: still present │
│ │ │ → [Block this event] │
├──────────────┴──────────────────────┴───────────────────────────────┤
│ footer: relay status 0 sats │
└─────────────────────────────────────────────────────────────────────┘
Kind Label Reference
| Kind | Label |
|---|---|
| 0 | Profile |
| 1 | Note |
| 3 | Contacts |
| 4 | Encrypted DM |
| 5 | Deletion |
| 6 | Repost |
| 7 | Reaction |
| 10000 | Mute List |
| 10002 | Relay List |
| 10123 | Skill Adoption |
| 30023 | Long-form Article |
| 30024 | Draft Article |
| 30078 | App-specific Data |
| 31123 | Skill (Public) |
| 31124 | Skill (Private) |
Responsive Behavior
- Desktop (>1280px): Three columns side by side
- Tablet (800-1280px): Two columns (kinds + events), detail as overlay/modal
- Mobile (<800px): Single column, drill-down navigation with back buttons