Relay Manager (relays)
Back up, sync, and migrate your Nostr events. The first iteration provides a real local account/relay editor, archive grouping, and placeholders for future event operations.
The app icon is stored in icon.svg, referenced by appweaver.icon in package metadata.
Approved next revision
The Fetch / Coverage / Sync design records the
agreed Q&A decisions: NIP-77 first with ordinary paging fallback, incremental
fetching with ten-second overlap, per-fetch range Coverage with ⋮ Refetch/Resume,
non-expiring evidence, compare-before-sync, and Accepted/Verified delivery results.
Fallback page size will default to 100 and be configurable per run. Specialized
relay handling is postponed. These changes are planned, not yet implemented;
the sections below describe the current working version.
Progress and exit criteria: implementation plan.
Timeline UI
Run /relays list. The command output contains the manager; it is not registered
in the Widgets dock. Add an npub/hex public key, add relay URLs, and toggle read/write
roles. + Account lets you select the authenticated browser account or a saved
bunker connection (by its account owner's public key), or enter an npub manually.
The account dropdown and its add button share a row, as do manual npub entry and
its add button. + Account toggles the add-account section open/closed.
Saved connections load automatically when the picker opens. Adding only copies the public key;
it does not switch accounts, request event signing, or store bunker credentials.
Changes save locally immediately. Event counts reflect the local archive;
there is no sample-data or design-preview mode.
Account removal sits beside + Account and retains archived events. Account
names use fetched kind-0 display_name, then name, then shortened npub.
The identity line shows the npub without a fixed watch-only classification.
Account form rows vertically center inputs and buttons, keeping the Open button
at its normal height rather than stretching it to the dropdown height.
Opening a selected account automatically starts background profile/NIP-65
discovery and relay connection checks. Results are cached for five minutes;
changing relay/source URLs starts a fresh refresh. Concurrent views share one
in-flight refresh. The widget polls every two seconds only while refresh is pending.
DM/CLI list remains a local read without automatic networking.
Discovery queries configured relays, extra recovery sources, and BOT_RELAYS
(up to 12 unique sources, five-second collection window). Fetching preserves local
settings; Reset to published relays replaces the local read/write list with
the fetched NIP-65 snapshot. Reset does not publish; Edit JSON remains an
account-level control.
Publish NIP-65 opens AppWeaver's normal signer picker restricted to the selected
account. Use its authenticated signer or a saved bunker with the same account
public key. The server verifies the signature, author, kind, and exact current relay
settings before sending to configured relays, previous published relays, and
BOT_RELAYS. Operations reports acceptance/rejection for each destination.
Acceptance is not a guarantee of indefinite storage. Older discovery responses
cannot overwrite a newer published snapshot.
Fetch / coverage / sync flow
The timeline is organized as Relays → Extra fetch sources → Fetch → Coverage → Sync → Operations. Relay settings remain independent of event operations:
- Relays configures the local NIP-65 read/write list and shows connection status.
- Extra fetch sources manages fetch-only recovery URLs via + Recovery source.
- Fetch browses unique archived events by kind/year/month. Its source scope is configured NIP-65 write relays plus extra sources, deduplicated by URL. Fetch / Back up starts a background archive operation, retaining each original signed event and recording presence only on relays it was actually fetched from.
- Coverage shows present/missing/unknown archived-event counts per configured write relay, with the latest observation timestamp. Read-only relays and extras are not sync destinations.
- Sync groups confirmed missing copies as relay → kind → year → month. The same event missing on two write relays contributes two deliveries to the total.
- Operations shows backup and NIP-65 publication progress/results.
Backup excludes direct-message/seal/gift-wrap kinds 4, 13, 14, 1059, 1060 and ephemeral kinds 20000–29999. It validates event signatures and author identity, deduplicates by event ID, and preserves replaceable/deletion/expired events in the archive (future replay must apply their semantics). Attached media URLs are stored inside the event; media files are not downloaded.
Each relay is queried in bounded 500-event time windows, splitting saturated windows without dropping same-timestamp boundary events. Only an actual EOSE completes a query. Timeouts/closures retain verified events collected so far and leave a partial operation. Each run allows 100 pages per source; checkpoints are stored after each page. Click Fetch / Back up again to resume a partial or interrupted operation with unchanged source URLs. Completed backups start a fresh snapshot on the next click. A saturated single-second window remains explicitly partial; relay limits/retention can prevent complete lifetime recovery.
Presence older than one hour, absent observations, and query failures are unknown, never assumed missing. A successful per-ID query is required before recording missing. The future sync runner must refresh unknown/stale coverage and verify sent events afterward. Connection status is separate from event presence.
The local relay configuration can differ from its published NIP-65 snapshot while preparing a migration. Preserve old relays as extra sources, archive their history, sync to configured new write relays, verify, then publish the new NIP-65 list.
Connection checks measure WebSocket establishment in milliseconds (five-second timeout, batches of five). Connected does not imply relay read/write permission. Last check timestamps and results persist. Extra sources are recovery locations, not advertised read/write relays.
Commands
/relays list [--account <npub>]— timeline manager; text account listing in DM/CLI./relays add <npub>— add an account by public key./relays update <npub> --url <wss-url> --role <both|read|write|source>— add/change a relay./relays update <npub> --url <wss-url> --remove— remove a NIP-65 relay./relays update <npub> --url <wss-url> --role source --remove— remove an extra fetch source./relays update <npub> --data '<json>'— upsert validated account configuration./relays update <npub> --published— apply the last fetched NIP-65 snapshot locally./relays fetch <npub>— fetch profile and advertised relay snapshot./relays check <npub>— measure configured relay/source connection times./relays backup <npub>— start/resume a background public-event backup./relays publish <npub> '<signed-event-json>'— publish a signed NIP-65 list matching current local settings; use the widget to sign it./relays delete <npub>— remove the account from the manager, retaining archived events.
Configuration JSON:
{
"relays": [{ "url": "wss://relay.example.com", "read": true, "write": true }],
"extraSources": ["wss://old-relay.example.com"]
}
These local configuration mutations execute immediately without drafts. They do not delete Nostr events. No signer credentials are stored in this iteration.
Storage and remaining work
db.sqlite holds independent account JSON documents, profile/snapshot metadata,
relay connection observations, and presentation-independent archive/operation
tables. Legacy scaffold rows are retained untouched but are not treated as accounts.
Timestamped per-event/per-relay presence is stored separately from relay settings
and connection checks. Existing positive relay_events observations are migrated
additively; existing account/archive data is retained. Changing a source URL does
not remove that URL from NIP-65 settings, or vice versa.
Network existence checks, individual event browsing/selection, export, sync, relay authentication, and deletion requests remain pending. Their controls are disabled. Backup and NIP-65 publishing are working actions. Coverage and missing trees render persisted facts only; there is no fabricated sample data.
See architecture and the durable implementation plan for the next iterations. Shared contracts: plugin author guide.