Previously handle_identity_publish_profile defaulted to a single
hard-coded relay (ws://localhost:18081) so the user's kind:0 profile
event only ever landed on the local relay — hence "Manage Relays
shows N connected, but profile edits don't propagate" from testing.
Fix — two-layer change:
- identity_manager::publish_profile now takes `&[String]` relays
instead of one URL. Adds each relay to the nostr-sdk client,
gives 15s for handshakes, publishes, then surfaces per-relay
accept/reject in a new ProfilePublishOutcome struct so the UI
can show WHICH relays accepted vs. rejected and WHY.
- RPC handle_identity_publish_profile no longer defaults to the
local relay: pulls the ENABLED list from nostr_relays::list_relays
(the same table that powers Manage Relays) and publishes to every
entry. Accepts an optional `relays: [...]` override for tests.
- At-least-one-accept guarantee: if every relay rejects, the call
errors instead of silently reporting published=true. User gets a
real error message listing the failures.
- Response shape: `{event_id, accepted: [urls], rejected: [[url,
reason]], relays_attempted: N, published: bool}` so the UI can
show a useful status block after clicking Publish.
relay_url_matches is tolerant of trailing-slash / case differences
since nostr-sdk canonicalises URLs internally.
Covers the publishing half of task #29; avatar/banner upload UI is
still open.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>