diff --git a/README.md b/README.md index e1ea4ab..9215c62 100644 --- a/README.md +++ b/README.md @@ -1,963 +1,6 @@ -# aitherboard Specification +# aitherboard -A decentralized messageboard built on the Nostr protocol. This document defines the complete specification for implementation. +A decentralized messageboard built on the Nostr protocol. -This is a client from [silberengel@gitcitadel.com](https://jumble.imwald.eu/users/npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z) +This is a client from [silberengel@gitcitadel.com](https://aitherboard.imwald.eu/profile/npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z) -## Table of Contents - -- [Technology Stack](#technology-stack) -- [Project Structure](#project-structure) -- [Event Types](#event-types) -- [Relay Management](#relay-management) -- [Authentication](#authentication) -- [Content Modules](#content-modules) -- [User Interface](#user-interface) -- [Performance Requirements](#performance-requirements) -- [Accessibility Requirements](#accessibility-requirements) -- [Security Requirements](#security-requirements) -- [Configuration](#configuration) -- [Deployment](#deployment) - -## Technology Stack - -**REQUIRED**: -- Frontend: Svelte 5 (with runes: `$state`, `$derived`, `$effect`) + TypeScript + Vite -- Styling: Tailwind CSS (4chan-style minimal design) -- Nostr Library: [`nostr-tools`](https://github.com/nbd-wtf/nostr-tools) -- Markdown: `marked` + `DOMPurify` for sanitization -- Storage: IndexedDB (via `idb` or `localforage`) -- Deployment: Docker + Apache httpd (static serving on port 9876) -- PWA: Progressive Web App support - -## Project Structure - -``` -aitherboard/ -├── src/ -│ ├── lib/ -│ │ ├── services/ -│ │ │ ├── nostr/ -│ │ │ │ ├── nostr-client.ts -│ │ │ │ ├── auth-handler.ts -│ │ │ │ └── config.ts -│ │ │ ├── auth/ -│ │ │ │ ├── nip07-signer.ts -│ │ │ │ ├── nsec-signer.ts -│ │ │ │ ├── bunker-signer.ts -│ │ │ │ ├── anonymous-signer.ts -│ │ │ │ ├── session-manager.ts -│ │ │ │ ├── profile-fetcher.ts -│ │ │ │ ├── relay-list-fetcher.ts -│ │ │ │ ├── user-preferences-fetcher.ts -│ │ │ │ ├── user-status-fetcher.ts -│ │ │ │ └── activity-tracker.ts -│ │ │ ├── cache/ -│ │ │ │ ├── indexeddb-store.ts -│ │ │ │ ├── event-cache.ts -│ │ │ │ ├── profile-cache.ts -│ │ │ │ ├── search-index.ts -│ │ │ │ └── anonymous-key-store.ts -│ │ │ └── security/ -│ │ │ ├── key-management.ts -│ │ │ ├── bech32-utils.ts -│ │ │ ├── event-validator.ts -│ │ │ └── sanitizer.ts -│ │ ├── modules/ -│ │ │ ├── threads/ -│ │ │ ├── comments/ -│ │ │ ├── zaps/ -│ │ │ ├── reactions/ -│ │ │ ├── profiles/ -│ │ │ └── feed/ -│ │ └── components/ -│ │ ├── content/ -│ │ ├── interactions/ -│ │ ├── layout/ -│ │ ├── modals/ -│ │ └── preferences/ -│ └── routes/ -├── public/ -├── scripts/ -├── Dockerfile -├── docker-compose.yml -├── httpd.conf.template -└── docker-entrypoint.sh -``` - -## Event Types - -### Supported Event Kinds - -| Kind | Name | NIP | Purpose | -|------|------|-----|---------| -| 0 | Metadata/Profile | 1 | User profile information | -| 1 | Note/Feed Post | 10 | Twitter-like feed posts | -| 7 | Reaction | 25 | Upvote/downvote/like | -| 11 | Thread | 7D | Discussion threads | -| 1111 | Comment | 22 | Threaded comments | -| 9734 | Zap Request | 57 | Lightning payment request (not published to relays) | -| 9735 | Zap Receipt | 57 | Lightning payment confirmation | -| 10000 | Mute List | 51 | User mute preferences | -| 10002 | Relay List | 65 | User relay preferences (inbox/outbox) | -| 10006 | Blocked Relays | 51 | User blocked relay list | -| 10432 | Local Relays | - | Local relay preferences (used alongside 10002) | -| 10133 | Payment Targets | A3 | Payment address information (RFC-8905) | -| 30315 | General User Status | 38 | General user status (NIP-38) | - -**Note**: Media attachments use tags defined in NIP-92 (imeta), NIP-94 (file attachments), and NIP-23 (image tags). - -### Event Structure Examples - -#### Kind 0 (Metadata/Profile) - -```json -{ - "kind": 0, - "content": "{\"name\":\"Alice\",\"about\":\"Developer\",\"picture\":\"https://example.com/alice.jpg\",\"website\":\"https://alice.example.com\",\"lud16\":\"alice@wallet.example.com\",\"nip05\":\"alice@example.com\"}", - "tags": [ - ["name", "Alice"], - ["about", "Developer"], - ["picture", "https://example.com/alice.jpg"], - ["website", "https://alice.example.com"], - ["lud16", "alice@wallet.example.com"], - ["nip05", "alice@example.com"], - ["nip05", "alice@otherexample.com"] - ], - "pubkey": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", - "created_at": 1679673265, - "id": "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", - "sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d" -} -``` - -**REQUIREMENTS**: -- Parse tags (preferred) OR fallback to stringified JSON content -- Support multiple tags for: `lud16`, `website`, `nip05` -- Extract `lud16` tags for payment address display - -#### Kind 1 (Note/Feed Post) - -```json -{ - "kind": 1, - "content": "Hello nostr!", - "tags": [ - ["e", "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", "wss://relay.example.com", "reply", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"], - ["p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"], - ["root", "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446"], - ["client", "aitherboard"] - ], - "pubkey": "a55c15f5e41d5aebd236eca5e0142789c5385703f1a7485aa4b38d94fd18dcc4", - "created_at": 1679673300, - "id": "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", - "sig": "77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d" -} -``` - -**REQUIREMENTS**: -- Support NIP-10 threading (`e`, `p`, `root`, `reply` tags) -- Render markdown content -- Flat threading display (no indentation) - -#### Kind 7 (Reaction) - -```json -{ - "kind": 7, - "content": "+", - "tags": [ - ["e", "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", "wss://relay.example.com", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"], - ["p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", "wss://relay.example.com"], - ["k", "1"], - ["client", "aitherboard"] - ], - "pubkey": "a55c15f5e41d5aebd236eca5e0142789c5385703f1a7485aa4b38d94fd18dcc4", - "created_at": 1679673300, - "id": "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", - "sig": "77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d" -} -``` - -**REQUIREMENTS**: See [Reaction Module](#reaction-module-srclibmodulesreactions) for full requirements. - -#### Kind 11 (Thread) - -```json -{ - "kind": 11, - "content": "Good morning everyone!", - "tags": [ - ["title", "GM"], - ["t", "tech"], - ["image", "https://example.com/cover.jpg"], - ["imeta", "url https://example.com/image1.jpg", "m image/jpeg", "x 1920", "y 1080"], - ["imeta", "url https://example.com/video1.mp4", "m video/mp4", "x 1920", "y 1080", "dim 1920x1080x30"], - ["file", "https://example.com/document.pdf", "application/pdf", "size 1048576"], - ["client", "aitherboard"] - ], - "pubkey": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", - "created_at": 1679673265, - "id": "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", - "sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d" -} -``` - -**REQUIREMENTS**: -- Max 3 `t` tags (topics) -- Threads without topics appear under "General" -- 30-day timeout (configurable) -- Render markdown content -- **Cover image**: Support `image` tag (NIP-23) for thread cover image -- **Media attachments**: Support NIP-92 (imeta) and NIP-94 (file attachments) tags - -#### Kind 1111 (Comment) - -```json -{ - "kind": 1111, - "content": "Cool beans", - "tags": [ - ["K", "11"], - ["E", "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", "wss://relay.example.com", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"], - ["P", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"], - ["e", "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", "wss://relay.example.com", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"], - ["k", "11"], - ["p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"], - ["client", "aitherboard"] - ], - "pubkey": "a55c15f5e41d5aebd236eca5e0142789c5385703f1a7485aa4b38d94fd18dcc4", - "created_at": 1679673300, - "id": "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", - "sig": "77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d" -} -``` - -**REQUIREMENTS**: -- Parse NIP-22 tags: `K`, `E`, `e`, `k`, `p`, `P` -- Flat list display (no indentation) -- Gray blurb showing parent preview -- Click blurb to highlight/scroll to parent -- Render markdown content - -#### Kind 9734 (Zap Request) - -```json -{ - "kind": 9734, - "content": "Zap!", - "tags": [ - ["relays", "wss://nostr-pub.wellorder.com", "wss://anotherrelay.example.com"], - ["amount", "21000"], - ["lnurl", "lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9uh8wetvdskkkmn0wahz7mrww4excup0dajx2mrv92x9xp"], - ["p", "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9"], - ["e", "9ae37aa68f48645127299e9453eb5d908a0cbb6058ff340d528ed4d37c8994fb"], - ["k", "1"], - ["client", "aitherboard"] - ], - "pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322", - "created_at": 1679673265, - "id": "30efed56a035b2549fcaeec0bf2c1595f9a9b3bb4b1a38abaf8ee9041c4b7d93", - "sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d" -} -``` - -**REQUIREMENTS**: -- **NOT published to relays** - sent directly to wallet lnurl callback -- **Primary method**: Send via `lightning:` URI to external wallet -- **Fallback method**: Fetch invoice, display QR code + copyable text -- User pays with external wallet (no in-app wallet) - -#### Kind 9735 (Zap Receipt) - -```json -{ - "id": "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", - "pubkey": "9630f464cca6a5147aa8a35f0bcdd3ce485324e732fd39e09233b1d848238f31", - "created_at": 1674164545, - "kind": 9735, - "tags": [ - ["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"], - ["P", "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"], - ["e", "3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"], - ["k", "1"], - ["bolt11", "lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0"], - ["description", "{\"pubkey\":\"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322\",\"content\":\"\",\"id\":\"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d\",\"created_at\":1674164539,\"sig\":\"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d\",\"kind\":9734,\"tags\":[[\"e\",\"3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"],[\"relays\",\"wss://relay.damus.io\"]]}"], - ["preimage", "5d006d2cf1e73c7148e7519a4c68adc81642ce0e25a432b2434c99f97344c15f"] - ], - "content": "", - "sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d" -} -``` - -**REQUIREMENTS**: -- Display with ⚡ emoji -- Only display if amount ≥ configured threshold (default: 1 sat) -- Published by wallet, not client - -#### Kind 10002 (Relay List) - -```json -{ - "kind": 10002, - "tags": [ - ["r", "wss://alicerelay.example.com"], - ["r", "wss://brando-relay.com"], - ["r", "wss://expensive-relay.example2.com", "write"], - ["r", "wss://nostr-relay.example.com", "read"] - ], - "content": "", - "pubkey": "854043ae8f1f97430ca8c1f1a090bdde6488bd5115c7a45307a2a212750ae4cb", - "created_at": 1699597889, - "id": "a92a316b75e44cfdc19986c634049158d4206fcc0b7b9c7ccbcdabe28beebcd0", - "sig": "1173822c53261f8cffe7efbf43ba4a97a9198b3e402c2a1df130f42a8985a2d0d3430f4de350db184141e45ca844ab4e5364ea80f11d720e36357e1853dba6ca" -} -``` - -**REQUIREMENTS**: -- Replaceable event (one per user) -- `r` tags with relay URLs -- Optional `read` or `write` marker (if omitted, relay is both) -- **Inbox relays** (marked `read` or unmarked): Added to reading operations -- **Outbox relays** (marked `write` or unmarked): Added to publishing operations -- Used alongside kind 10432 (local relays) - both kinds combined for relay selection - -#### Kind 10432 (Local Relays) - -```json -{ - "kind": 10432, - "tags": [ - ["r", "ws://localhost:7777"], - ["r", "ws://local-relay.example.com", "write"], - ["r", "ws://localhost:8080", "read"] - ], - "content": "", - "pubkey": "854043ae8f1f97430ca8c1f1a090bdde6488bd5115c7a45307a2a212750ae4cb", - "created_at": 1699597889, - "id": "a92a316b75e44cfdc19986c634049158d4206fcc0b7b9c7ccbcdabe28beebcd0", - "sig": "1173822c53261f8cffe7efbf43ba4a97a9198b3e402c2a1df130f42a8985a2d0d3430f4de350db184141e45ca844ab4e5364ea80f11d720e36357e1853dba6ca" -} -``` - -**REQUIREMENTS**: -- Replaceable event (one per user) -- `r` tags with relay URLs (typically local network relays) -- Optional `read` or `write` marker (if omitted, relay is both) -- **Inbox relays** (marked `read` or unmarked): Added to reading operations -- **Outbox relays** (marked `write` or unmarked): Added to publishing operations -- Used alongside kind 10002 - both kinds combined for relay selection -- **NOTE**: Kind number 10432 should be verified against current Nostr specifications - -#### Kind 10000 (Mute List) - -```json -{ - "id": "a92a316b75e44cfdc19986c634049158d4206fcc0b7b9c7ccbcdabe28beebcd0", - "pubkey": "854043ae8f1f97430ca8c1f1a090bdde6488bd5115c7a45307a2a212750ae4cb", - "created_at": 1699597889, - "kind": 10000, - "tags": [ - ["p", "07caba282f76441955b695551c3c5c742e5b9202a3784780f8086fdcdc1da3a9"], - ["p", "a55c15f5e41d5aebd236eca5e0142789c5385703f1a7485aa4b38d94fd18dcc4"] - ], - "content": "", - "sig": "1173822c53261f8cffe7efbf43ba4a97a9198b3e402c2a1df130f42a8985a2d0d3430f4de350db184141e45ca844ab4e5364ea80f11d720e36357e1853dba6ca" -} -``` - -**REQUIREMENTS**: -- Replaceable event (one per user) -- Filter out all events from pubkeys in `p` tags -- Applied automatically when user is logged in - -#### Kind 10006 (Blocked Relays) - -```json -{ - "kind": 10006, - "content": "", - "tags": [ - ["relay", "wss://blocked-relay1.com"], - ["relay", "wss://blocked-relay2.com"] - ], - "pubkey": "854043ae8f1f97430ca8c1f1a090bdde6488bd5115c7a45307a2a212750ae4cb", - "created_at": 1699597889, - "id": "a92a316b75e44cfdc19986c634049158d4206fcc0b7b9c7ccbcdabe28beebcd0", - "sig": "1173822c53261f8cffe7efbf43ba4a97a9198b3e402c2a1df130f42a8985a2d0d3430f4de350db184141e45ca844ab4e5364ea80f11d720e36357e1853dba6ca" -} -``` - -**REQUIREMENTS**: -- Replaceable event (one per user) -- Remove relays in `relay` tags from all operations (read and write) -- Applied automatically when user is logged in -- Filtered before establishing connections - -#### Kind 10133 (Payment Targets, NIP-A3) - -```json -{ - "kind": 10133, - "content": "", - "tags": [ - ["payto", "bitcoin", "bc1qxq66e0t8d7ugdecwnmv58e90tpry23nc84pg9k"], - ["payto", "nano", "nano_1dctqbmqxfppo9pswbm6kg9d4s4mbraqn8i4m7ob9gnzz91aurmuho48jx3c"], - ["payto", "lightning", "user@wallet.example.com"] - ], - "pubkey": "afc93622eb4d79c0fb75e56e0c14553f7214b0a466abeba14cb38968c6755e6a", - "created_at": 1679673265, - "id": "67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446", - "sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d" -} -``` - -**REQUIREMENTS**: -- Replaceable event (one per user) -- Extract `payto` tags for payment address display -- Combine with kind 0 `lud16` tags (deduplicated) -- All addresses copyable -- Lightning addresses zappable -- Recognized types: bitcoin, lightning, ethereum, nano, monero, cashme, revolut, venmo -- Unrecognized types displayed generically - -#### Kind 30315 (General User Status, NIP-38) - -```json -{ - "kind": 30315, - "content": "Working on a new project", - "tags": [ - ["d", "general"], - ["client", "aitherboard"] - ], - "pubkey": "854043ae8f1f97430ca8c1f1a090bdde6488bd5115c7a45307a2a212750ae4cb", - "created_at": 1699597889, - "id": "a92a316b75e44cfdc19986c634049158d4206fcc0b7b9c7ccbcdabe28beebcd0", - "sig": "1173822c53261f8cffe7efbf43ba4a97a9198b3e402c2a1df130f42a8985a2d0d3430f4de350db184141e45ca844ab4e5364ea80f11d720e36357e1853dba6ca" -} -``` - -**REQUIREMENTS**: -- Replaceable event (one per user) -- Display status content on profile page -- Fetch from same relays as kind 0 profiles -- Defined in NIP-38 - -## Relay Management - -### Base Relay Lists - -| Category | Relays | Purpose | -|----------|--------|---------| -| **Default Relays** | `wss://theforest.nostr1.com`
`wss://nostr21.com`
`wss://nostr.land`
`wss://orly-relay.imwald.eu`
`wss://nostr.wine` | Base relays for all operations | -| **Profile Relays** | `wss://relay.damus.io`
`wss://aggr.nostr.land`
`wss://profiles.nostr1.com` | Additional relays for profile/kind 1 content | - -### Relay Selection by Operation - -| Operation | Relays Used | Notes | -|-----------|------------|-------| -| **Reading: Threads (kind 11)** | Default + User inbox (if logged in) | Blocked relays removed | -| **Reading: Comments (kind 1111)** | Default + User inbox (if logged in) | Blocked relays removed | -| **Reading: Kind 1 feed** | Default + User inbox (if logged in) | Blocked relays removed | -| **Reading: Kind 1 responses** | Default + User inbox + `wss://aggr.nostr.land` | For threads beneath kind 1 OP events | -| **Reading: Zap receipts (kind 9735)** | Default + User inbox (if logged in) | Blocked relays removed | -| **Reading: Profiles (kind 0)** | Default + Profile + User inbox (if logged in) | Blocked relays removed | -| **Reading: Payment targets (kind 10133)** | Default + Profile + User inbox (if logged in) | Blocked relays removed | -| **Reading: User status (kind 30315)** | Default + Profile + User inbox (if logged in) | Blocked relays removed | -| **Publishing: Threads (kind 11)** | Default + `wss://thecitadel.nostr1.com` + User outbox | Blocked relays removed | -| **Publishing: Comments (kind 1111)** | Default + User outbox + Reply target's inbox | If replying, include target's inbox | -| **Publishing: Kind 1 posts** | Default + User outbox + Reply target's inbox | If replying, include target's inbox | -| **Publishing: Reactions (kind 7)** | Default + User outbox | Blocked relays removed | -| **Publishing: Zap requests (kind 9734)** | Not published to relays | Sent to wallet lnurl callback | -| **Publishing: Zap receipts (kind 9735)** | Relays from zap request | Published by wallet, not client | - -### Dynamic Relay Management - -| Source | Kind | When Applied | Effect | -|--------|------|--------------|--------| -| **User Inbox Relays** | 10002 + 10432 (marked `read` or unmarked) | When user logs in | Added to reading operations | -| **User Outbox Relays** | 10002 + 10432 (marked `write` or unmarked) | When user logs in | Added to publishing operations | -| **Blocked Relays** | 10006 | When user logs in | Removed from all operations | - -**REQUIREMENTS**: -- Relays combined, normalized, and deduplicated automatically -- Relay list updates dynamically when user logs in/out -- Blocked relays filtered before establishing connections -- All operations respect user's kind 10002, kind 10432, and kind 10006 preferences -- Kind 10002 and kind 10432 relays are combined (deduplicated) for each operation - -## Authentication - -### Authentication Methods - -| Method | Implementation | Key Storage | -|--------|----------------|------------| -| **NIP-07** | Browser extension (Alby, nos2x, etc.) | No storage (extension manages) | -| **Nsec** | Direct bech32 nsec or hex private key, stored in the in-browser cache | **REQUIRED**: NIP-49 encrypted in IndexedDB | -| **NIP-46 Bunker** | Remote signer via `bunker://` URI | No local storage | -| **Anonymous** | Generated on the fly when publishing | **REQUIRED**: NIP-49 encrypted in IndexedDB | - -### Key Storage & Encryption - -**CRITICAL**: NO SECRET KEYS STORED ON THE SERVER -- All keys stored client-side only in IndexedDB -- Server only serves static files -- All key management in browser -- **REQUIRED**: All nsec keys (including anonymous) MUST be encrypted with NIP-49 (password-based) before storage -- Store as ncryptsec format (never plaintext nsec) -- All encrypted keys persist in IndexedDB across sessions -- Users can provide their own anonymous key (must be encrypted) - -### Anonymous User Behavior - -- Pattern-based avatar -- Handle: `Aitherite{random}` -- Display with this format, whenever a kind 0 cannot be found, for the npub. - -## Content Modules - -### Event Publishing - -**REQUIREMENTS** (applies to all published events): -- **NIP-89 client tag**: Add `["client", "aitherboard"]` tag to all published events when checkbox is selected -- Checkbox "Include client tag." displayed in all publish forms -- Checkbox selected by default -- Only include client tag if checkbox is selected (not deselected) -- Applies to: kind 1, kind 7, kind 11, kind 1111 (all user-published events) - -### Thread Module (`src/lib/modules/threads/`) - -**Components**: -- `ThreadList.svelte` - Main thread listing -- `ThreadCard.svelte` - Thread preview card (landing page) -- `ThreadView.svelte` - Full thread display -- `CreateThreadForm.svelte` - Thread creation - -**REQUIREMENTS**: -- Max 3 `t` tags (topics) per thread -- Threads without topics under "General" -- 30-day timeout (configurable, checkbox to show older) -- Sorting: newest, most active, most upvoted -- Landing page: Title, relative times, profile badge, vote stats, first 250 chars plaintext (no markdown/images) -- **Client badge**: Display client name from `client` tag (NIP-89) on thread cards - subtle, muted color -- Render markdown in full thread view -- **Cover image**: Display image from `image` tag (NIP-23) prominently on thread view -- **Media attachments**: Handle NIP-92 (imeta) and NIP-94 (file) tags (see [Media Attachments](#media-attachments)) -- Display all target relays with deselection option -- Publication status modal: Success/failure per relay, error messages (hyperlink URLs), auto-close 30s or manual -- Include NIP-89 client tag (see [Event Publishing](#event-publishing)) - -### Comment Module (`src/lib/modules/comments/`) - -**Components**: -- `Comment.svelte` - Individual comment -- `CommentThread.svelte` - Threaded comment list -- `CommentForm.svelte` - Reply form - -**REQUIREMENTS**: -- Flat list (no indentation) -- Gray blurb showing parent preview -- Click blurb to highlight/scroll to parent -- Parse NIP-22 tags: `K`, `E`, `e`, `k`, `p`, `P` -- Render markdown content -- **Client badge**: Display client name from `client` tag (NIP-89) on comment cards - subtle, muted color -- Include NIP-89 client tag (see [Event Publishing](#event-publishing)) - -### Zap Module (`src/lib/modules/zaps/`) - -**Components**: -- `ZapButton.svelte` - Zap action button -- `ZapReceipt.svelte` - Zap receipt display -- `ZapInvoiceModal.svelte` - Invoice display (fallback) - -**REQUIREMENTS**: -- **Primary**: Send zap request (kind 9734) via `lightning:` URI to external wallet -- **Fallback**: Fetch invoice from lnurl callback, display QR code + copyable text -- No in-app wallet connections -- Display kind 9735 receipts (≥ configured threshold, default: 1 sat) -- Display with ⚡ emoji -- Zap requests NOT published to relays (sent to wallet) - -### Reaction Module (`src/lib/modules/reactions/`) - -**Components**: -- `ReactionButtons.svelte` - For threads/comments (kind 11/1111) -- `FeedReactionButtons.svelte` - For kind 1 feed - -**REQUIREMENTS**: -- **Kind 11/1111**: Only `+` and `-` allowed - - `+` renders as upvote - - `-` renders as downvote - - **Backward compatibility**: `⬆️` (up arrow) counted as upvote, `⬇️` (down arrow) counted as downvote -- **Kind 1**: All reactions allowed - - `+` renders as ❤️ - - Default suggested: `+` - - Other reactions in submenu -- One reaction per user per event -- Clicking same reaction twice deletes it -- Display reaction counts -- Include NIP-89 client tag (see [Event Publishing](#event-publishing)) - -### Profile Module (`src/lib/modules/profiles/`) - -**Components**: -- `ProfilePage.svelte` - User profile display -- `ProfileBadge.svelte` - Clickable profile badge with activity indicator and user status -- `PaymentAddresses.svelte` - Unified payment addresses - -**REQUIREMENTS**: -- **Kind 0**: Parse tags (preferred) OR fallback to JSON - - Support multiple: `lud16`, `website`, `nip05` - - Display: name, about, picture, NIP-05, website, lightning addresses -- **Kind 30315 (NIP-38)**: Fetch and display general user status - - Display status content on profile page - - Display status in profile badges on cards (thread cards, comment cards, etc.) - - Replaceable event (one per user) -- **Payment addresses**: Fetch kind 10133, combine with kind 0 `lud16` - - Deduplicate addresses - - All addresses copyable - - Lightning addresses zappable - - Support recognized types: bitcoin, lightning, ethereum, nano, monero, cashme, revolut, venmo - - Display unrecognized types generically -- **Profile badge activity indicator**: See [Content Rendering](#content-rendering) section -- Show kind 1 feed (see relay selection) -- Show kind 1 responses (see relay selection) -- Allow zapping profile owner -- Allow replying to kind 1 with kind 1 or zap - -### Kind 1 Feed Module (`src/lib/modules/feed/`) - -**Components**: -- `FeedPage.svelte` - Main feed page -- `FeedPost.svelte` - Individual kind 1 post -- `FeedReply.svelte` - Kind 1 reply display -- `ZapReceiptReply.svelte` - Zap receipt as reply (with ⚡) -- `CreateFeedForm.svelte` - Create new kind 1 events -- `ReplyToFeedForm.svelte` - Reply to kind 1 events - -**REQUIREMENTS**: -- "View feed" button on landing page opens `/feed` -- Fetch kind 1 events (see relay selection) -- Create new kind 1 events with markdown editor -- Reply to kind 1 with kind 1 (NIP-10 threading) -- Reply to kind 1 with zap (see zap module) -- Reply to kind 1 replies with kind 1 -- Reply to zap receipts with zap receipts or kind 1111 comments -- Display all reactions (not just upvotes) - - Default suggested: `+` (rendered as ❤️) - - Other reactions in submenu -- **Client badge**: Display client name from `client` tag (NIP-89) on kind 1 post cards - subtle, muted color -- Render all content as markdown -- **Media attachments**: Handle NIP-92 (imeta) and NIP-94 (file) tags (see [Media Attachments](#media-attachments)) -- Flat threading display (no indentation) -- Real-time updates -- Infinite scroll -- Include NIP-89 client tag (see [Event Publishing](#event-publishing)) - -## User Interface - -### User Preferences & Readability - -**REQUIREMENTS**: -- **Text size control**: Simple control (small/medium/large) to adjust base font size - - Small: ~14px base - - Medium: ~16px base (default) - - Large: ~18px base - - Preference stored in localStorage - - Applied via CSS custom properties or data attributes -- **Line spacing**: Adjustable line height for better readability - - Tight: 1.4 - - Normal: 1.6 (default) - - Loose: 1.8 -- **Content width**: Maximum content width control - - Narrow: ~600px - - Medium: ~800px (default) - - Wide: ~1200px -- **Theme**: Light/dark mode toggle - - Support system preference detection (`prefers-color-scheme`) - - Manual toggle to override system preference - - Preference stored in localStorage - - Smooth transition between modes -- All preferences persist in localStorage -- Preferences accessible from main navigation/settings menu -- Clear visual feedback when preferences change - -### Content Rendering - -**REQUIREMENTS**: -- All content supports full markdown rendering -- Videos MUST NOT autoplay (require user interaction) -- Images lazy-loaded for performance -- NIP-21 link parsing: - - `nostr:npub...` → Clickable profile badge (with activity indicator and user status) - - `nostr:nevent/note/naddr...` → Event card (250 char preview) → Event viewer -- **Client badges**: Display client name from `client` tag (NIP-89) on all event cards - - Display on: thread cards, comment cards, kind 1 post cards - - Subtle, muted color styling - - Small badge/chip format -- **Profile badge**: - - **Activity indicator** (custom feature, separate from kind 30315): - - Track last activity from all events in relay pool per pubkey - - **Red**: ≥168 hours (7 days) since last interaction - - **Yellow**: ≥48 hours (2 days) but <168 hours since last interaction - - **Green**: <48 hours since last interaction - - Display indicator on all profile badges - - **User status (kind 30315, NIP-38)**: Display user status text in profile badges on cards -- **Content filtering**: Hide content with sensitive tags or #NSFW hashtag - - Hide events with `content-warning` tag or `sensitive` tag - - Hide events with `#NSFW` hashtag in content or tags - - Content is completely hidden (not displayed with warning) -- **Media handling**: See [Media Attachments](#media-attachments) section - -### Media Attachments - -**REQUIREMENTS**: -- **Support NIP-92 (imeta tags)**: Media metadata for images, videos, audio - - Format: `["imeta", "url ", "m ", "x ", "y ", ...]` - - Additional fields: `dim` (dimensions for video), `blurhash`, `thumb`, etc. -- **Support NIP-94 (file tags)**: File attachments - - Format: `["file", "", "", "size ", ...]` -- **Support NIP-23 image tags**: Cover images for threads/articles - - Format: `["image", ""]` -- **Media URL normalization and deduplication**: - - Extract media URLs from: - - `imeta` tags (url field) - - `file` tags (first element after "file") - - `image` tags (second element) - - Markdown image syntax in content: `![alt](url)` - - HTML ``, `