Completes the local-relay integration so it actually works end-to-end and feels
first-class.
Native (Android):
- android-templates/network_security_config.xml — permits cleartext for
LOOPBACK ONLY (127.0.0.1 / localhost / ::1); all real traffic stays TLS. This
is what lets ws://127.0.0.1 (Citrine) connect on API 28+, which otherwise
silently blocks cleartext.
- scripts/inject-network-security.mjs — idempotently copies the XML into
res/xml/ and adds android:networkSecurityConfig to <application>, mirroring
the existing inject-amber-queries pattern so it survives `cap sync`.
- Wired into android:init / android:sync / android:build.
Reads AND writes now reach Citrine app-wide:
- NostrProvider's NPool routers (reqRouter/eventRouter) fold in the active
Citrine URL via a ref, so every default-pool query hits it first (instant
local reads) and every useNostrPublish write also lands on-device (backup +
offline capture). Previously only explicit useSkateRelays paths benefited.
- A dedicated effect refreshes ['nostr'] queries when the local relay comes/goes
so reads switch over immediately. Fully no-op off-Android (url stays null).
UI/UX (Relays settings CitrineCard):
- Four crisp states — off / searching / live / waiting — with a breathing then
glowing status pip, benefit chips (Instant reads · Works offline · On-device
backup) that pop in on connect, a spinning Recheck, an actionable install CTA,
and a persistent encryption-reassurance note.
- Graceful first-run: shows "searching" until the first probe settles (via new
useCitrine.settled), never flashing the orange "not detected" state.
Tailwind: added status-breathe / status-live / pop-in keyframes+animations.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>