Commit Graph

4447 Commits

Author SHA1 Message Date
Chad Curtis 53cc92d9d0 Make /discover the public square: globe, country pulse, mixed feed
The 'Discover' nav link used to drop visitors on /feed — a plain
kind-1 timeline that didn't connect any of the three things Agora is
actually about. This wires a new /discover page that weaves them
together while the old plain feed stays put at /feed.

Page composition:

  - DiscoverHero — reuses the hand-drawn HeroGlobe but reframes it
    around the world itself, not any one campaign. Three marker
    layers (campaign hearts, community rings, country-pulse dots)
    sit on the same sphere, the HOPE_PALETTE slowly drifts every 9s,
    and a rotating ticker pill surfaces immutable network-wide
    stats: total sats raised on-chain, communities online, countries
    posting today.
  - CountryPulseStrip — horizontal strip of country flag chips
    ordered by trailing-window activity from the trusted kind 30385
    snapshots. Click opens /i/iso3166:XX.
  - 'Help raise hope' — horizontal CampaignCard shelf.
  - 'Find your people' — horizontal CommunityMiniCard shelf.
  - 'Voices from everywhere' — useDiscoverFeed infinite timeline
    mixing new campaigns, country posts, community comments, and
    Agora actions, rendered with the kind-appropriate card.

HeroGlobe gains an optional GlobeMarkerKind on each marker so the
campaigns page keeps its hearts-only behaviour while Discover layers
in rings and warm dots.

New hooks:
  - useDiscoverCommunities — global kind 34550 discovery
  - useDiscoverFeed — paginated mixed feed (30223 + 1111 + 36639)
  - useGlobalDonations — network-wide kind 8333 aggregate for the
    hero ticker
2026-05-17 23:39:23 -05:00
Alex Gleason 5c3dc851bc Merge branch 'main' of gitlab.com:soapbox-pub/agora 2026-05-17 23:05:55 -05:00
Alex Gleason 4e8cf62418 Drop redundant Wallet header from /wallet
The PageHeader served no purpose beyond labeling the page: the wallet
UI sits right under it with the balance front and center, the mobile
bolt now opens this route directly, and the page title still wires
through useSeoMeta for the browser tab. Removing it tightens the
mobile layout and saves vertical space above the balance.
2026-05-17 23:04:37 -05:00
Alex Gleason 883b5b5760 Point bottom-nav bolt at /wallet
The apex bolt button in the mobile bottom nav previously routed to the
user's configured home page (defaulting to /), duplicating the Feed
sidebar entry. With /bitcoin folded into /wallet there's no longer a
prominent path to the wallet from mobile, so wire the bolt directly to
/wallet. Tapping it while already on /wallet scrolls to top; the old
feed-cache invalidation no longer applies.
2026-05-17 23:02:13 -05:00
Alex Gleason ff671bda39 Init bitcoinjs-lib ECC eagerly in main.tsx
nostrPubkeyToBitcoinAddress and the PSBT build helpers call
bitcoin.payments.p2tr / Psbt.sign, which require an ECC library to be
registered via bitcoin.initEccLib() first. The lazy init lived inside
getECPair(), which is only reached on the signing path — so render-time
callers (WalletPage, SendBitcoinDialog) blew up on first paint with
'No ECC Library provided'.

Ditto initializes ECC eagerly in main.tsx; agora's bitcoin.ts came from
that port but the main.tsx side never did. Add it.

Regression-of: 9190f62b
2026-05-17 22:56:21 -05:00
Alex Gleason 9190f62b9e Consolidate /bitcoin into /wallet, drop Lightning custody
Agora previously shipped two parallel wallets: a heavy 6,400-line Breez
SDK Lightning wallet at /wallet and a lightweight on-chain Taproot view
at /bitcoin derived from the user's Nostr pubkey. Maintaining two key
custody models, two send flows, two zap paths (Lightning via Spark,
on-chain via PSBT), and the Spark-specific UI (CreateWallet, mnemonic
backup/restore, lock screen, payment history, etc.) didn't pay for itself
once on-chain Bitcoin signing via NIP-07/NIP-46 became viable.

This consolidation aligns Agora with Ditto's wallet model:

  - The on-chain Taproot view from /bitcoin becomes the only /wallet UI.
  - /bitcoin redirects to /wallet for back-compat; sidebar and TopNav
    drop the duplicate Bitcoin entry.
  - The Breez/Spark wallet stack is removed: SparkWalletProvider,
    SparkWalletContext, all of src/components/SparkWallet/*, useSparkWallet,
    useCommunityBatchZaps, usePaymentContext, WalletSettingsContent, and
    LightningEffect are deleted (~6,400 lines).
  - Ditto's mature bitcoin/zap stack is ported: useOnchainZap (single-event
    on-chain zaps + kind 8333 receipts), OnchainZapContent, ZapDialog with
    Bitcoin/Lightning tabs, ZapSuccessScreen, BitcoinContentHeader, and the
    larger SendBitcoinDialog. useZaps loses its breezService branch and
    falls back to NWC → WebLN → manual QR.
  - bitcoin.ts now threads esploraBaseUrl through every call, matching
    AppConfig and allowing future relay/Esplora customization.
  - CommunityZapDialog is bitcoin-only; CommunityDetailPage drops the
    sibling Lightning trigger.

Lightning recovery remains intentional. A small "Looking for your old
wallet?" link on /wallet routes to /wallet/recovery, which lazy-loads
@breeztech/breez-sdk-spark (now in its own 67 KB chunk plus the WASM)
only when a user needs to evacuate funds. The recovery page:

  - Auto-detects the NIP-78 kind-30078 d="spark-wallet-backup" relay
    backup and offers one-click NIP-44 decrypt via the user's signer.
  - Accepts a manual 12-word mnemonic as fallback.
  - Connects Breez in-memory, sweeps the entire on-chain balance to the
    user's Nostr-derived Taproot address, then disconnects. Nothing is
    persisted; the old wallet is never "restored" — only evacuated.

Other small carry-overs from Ditto needed by the ported code:
useFormatMoney + AppConfig.currencyDisplay ("usd" | "sats"), and the
nostrId helper (HexId branded type + isNostrId validator).

48 files changed, 2,464 insertions(+), 9,743 deletions(-).
2026-05-17 22:53:20 -05:00
Chad Curtis 707b24f41e Raise hero globe minimum size so it doesn't shrink as much 2026-05-17 22:46:50 -05:00
Chad Curtis 7e93dcba6c Stop hero globe from snapping back on spotlight change
The rotation rAF loop was being keyed by `useEffect([markers,
ringSizes, selectedKey])`. Each time `HeroCampaignSpotlight` cycled
to the next campaign, `selectedKey` changed, the effect tore down,
the new effect re-initialized `start = null`, and the elapsed-time
calculation snapped rotation back to 0°.

Hold `markers` and `selectedKey` in refs that the rAF loop reads
on each frame, and drop them from the effect's dep list so the loop
runs uninterrupted for the lifetime of the component.

Also dresses the hero's primary CTA up as an Apple-style liquid glass
pill: faint warm-tinted translucent body (white→amber→rose at low
opacity), heavy backdrop blur, hair-thin inner edge, soft warm-tinted
drop shadow. Hover lifts the tint and shadow a hair without changing
the pill's character — no specular streaks, no halo, no shadow
bloom. Slightly taller than the default `size=lg` (h-12, px-7,
text-base) so it reads as primary without feeling chunky.
2026-05-17 21:53:28 -05:00
Chad Curtis 42abac7527 Polish hero into hopeful beacon with per-campaign atmosphere
- Anchor the globe's center to the right edge of the `max-w-7xl` content
  container (matching the TopNav account switcher), nudged inward via a
  percentage translate so a substantial slice of the sphere always reads
  inside the hero regardless of viewport width.
- Drop the per-breakpoint width classes in favor of a fluid
  `clamp(360px, 46dvw, 820px)` so the globe scales smoothly with the
  viewport instead of in three discrete jumps. HeroGlobe accepts a
  `style` prop so the page can pass the clamp() inline.

Make the globe feel like a beacon of hope:

- New outer halo div behind the SVG with a wide hue-tinted radial glow,
  heavy blur, and a slow opacity-only breathing animation
  (`hero-globe-halo-breath`, 6.5s) so the layout never shifts.
- Sphere base gradient warmed from cream/cool-earth to dawn-gold/honey
  — the disc reads as 'lit from within' instead of dirt-colored.
- Outer dark rim swapped for a soft back-lit limb light tinted with the
  active hope hue. Narrow band, low opacity — suggests atmosphere
  rather than a neon ring.

Tie the globe to the surrounding atmosphere:

- New `src/lib/hopePalette.ts` exports a curated set of warm sunrise /
  dawn hues (`scrim`, `glow`, `rim` per entry) plus
  `hopeHueFor(seed)` that deterministically hashes a string (e.g. the
  campaign aTag) to a stable palette entry.
- New `HeroAtmosphere` mounts a fresh layer of tinted gradients each
  time the active seed changes and crossfades over 1.5s to match
  `CampaignHeroBackground`. Uses `mix-blend-mode: screen` so it warms
  the photo instead of flattening it.
- `HeroGlobe` takes the active `HopeHue` so the halo and limb tint
  agree with the rest of the hero.

Layer order is now: photo BG → atmosphere → globe → readability scrim →
content. The scrim sits *above* the globe so it can darken whatever
slice of the sphere ends up behind the headline, and is hidden at lg+
where the globe is already pushed outside the headline column.
2026-05-17 21:53:28 -05:00
mkfain 937da49cd7 Drop templated reply on /claim, copy npub only
Per design feedback, the npub card on /claim's empty state no longer
offers a templated reply message — just a single 'Copy my npub'
action backed by the same click-to-copy npub block above it.
2026-05-17 21:34:31 -05:00
mkfain a26269ebbe Show npub copy card on /claim empty state
Freshly-signed-up invitees who reach /claim with no campaigns yet now
see a primary card with their own npub formatted for copy. Two copy
actions:

- 'Copy reply message' (templated: "I finished setting up my Agora
  account! My npub is: npub1… — you can add me as a beneficiary now.")
- 'Copy npub only' for pasting into an existing thread.

The old 'No campaigns found yet' card stays as a secondary, demoted
'Expecting a campaign already?' message below the npub card.

Pure UX — no URL params, no inviter awareness, no analytics ping back.
The invitee still sends the message manually through whatever channel
the invite originally came from.
2026-05-17 21:32:57 -05:00
Chad Curtis 0fcce88409 Nudge the hero globe ~10% left at each breakpoint 2026-05-17 21:15:05 -05:00
Chad Curtis 0ea1e55ee4 Slow the hero globe to ~140s per revolution 2026-05-17 21:15:05 -05:00
Chad Curtis 2c8cd11153 Rebuild campaigns hero around photo BG + globe + spotlight
The hero is now layered like Treasures' HeroGallery:

- CampaignHeroBackground (new) — full-bleed banner image from the
  currently-spotlit campaign, crossfading over ~1.5 s and panning left.
  Warm tint + film grain overlay so foreground text stays legible.
- HeroGlobe — pushed to the right edge with a larger radius, slightly
  translucent so the photo bleeds through. Hearts replace the old dots
  for marker symbols; clicking one selects that campaign.
- HeroCampaignSpotlight (new) — minimal text overlay anchored to the
  bottom-left of the hero container (title, summary, avatar + author,
  location, progress bar with goal, 'View' link). No card chrome.

Land polygons are now the full Natural Earth 110m fidelity (~10.5k
vertices) instead of being heavily Douglas-Peucker'd, so coastlines
look organic rather than chunky. Back-hemisphere rings are now
properly hidden by walking each edge and either dropping back-side
vertices outright or interpolating to the sphere limb where a ring
crosses it — fixes the 'phantom continents through the front' bug.
Rings additionally fade in/out over a narrow z-band near the limb
instead of popping at z = 0.

Markers also have proper z-fade and pull off-canvas when on the back
so they can't intercept clicks they aren't visible for. Selected
markers scale 1.35x with a stronger glow so the user can tell which
campaign the spotlight refers to.

Other cleanup:

- formatCampaignAmount + formatSatsShort move out of CampaignCard.tsx
  into src/lib/formatCampaignAmount.ts so CampaignCard stops failing
  the react-refresh/only-export-components lint.
- Hero CTAs drop the 'Unstoppable fundraising on Nostr' pill and the
  em dash from the supporting copy.
- New keyframes (heroPanLeft / heroPanRight) for the slow Ken-Burns
  pan on the background photos, with prefers-reduced-motion respected.
2026-05-17 21:15:05 -05:00
Chad Curtis 2a69747744 Add slow-spinning globe behind campaigns hero
Drops the 'Unstoppable fundraising on Nostr' pill and the em dash from the
hero copy, and adds an ambient SVG globe sitting behind the headline.

The globe is a pure-SVG orthographic projection (no WebGL, no canvas). It
renders Natural Earth 110m country boundaries pre-simplified down to ~1.5k
vertices (17 KB inline). Coloring is intentionally warm — cream sphere with
sandy-amber land — to avoid the satellite/HUD aesthetic. Campaigns whose
location string resolves to an ISO 3166-1 country appear as small glowing
markers, deduped by country.

Rotation is driven by requestAnimationFrame and applied imperatively via
refs (no React re-renders during animation), and respects
prefers-reduced-motion by holding at a static angle.
2026-05-17 21:15:05 -05:00
mkfain 48881677b5 Add /receive and /claim landing pages + invite/notify shortcuts
When creating a campaign, organizers can now invite or notify
beneficiaries directly from the recipient picker:

- 'Recipient not here yet? Invite them' button below the search box
  copies a templated message that links to /receive — a signup-focused
  landing page that pitches Agora to people who don't have a Nostr
  account yet, then redirects them to /claim after onboarding.

- Each selected recipient row gains a 'Send {name} a message about
  this campaign' button that copies a templated message linking to
  /claim — a sign-in landing page that shows the user every campaign
  whose 'p' tag includes their pubkey (using a new recipientPubkeys
  option on useCampaigns that adds an #p filter).

Both landing pages live outside FundraiserLayout so they read as
standalone marketing/landing screens, matching the FollowPage pattern.
Copy templates match the spec wording from the request.
2026-05-17 21:13:08 -05:00
mkfain c9f3a304e6 Frame logged-out donate flow as a chooser, log-in path first
The previous behavior sent logged-out single-recipient donors straight
into the external-pay (BIP-21 QR) view, which is the lossy path —
externally-paid donations never publish a kind 8333 receipt, so they
don't count toward the campaign goal or show up in the donor list.

Now the dialog opens on a LoggedOutChooserView that presents the
ideal path first:

1. **Log in & donate** (recommended, highlighted card). Opens the
   standard LoginDialog inline; on success the outer DonateDialog
   re-renders into the normal donate form.
2. **Donate to {Recipient} directly** — secondary option, only shown
   for single-recipient campaigns. Copy makes the tradeoff explicit:
   the recipient still receives the funds, but the donation won't
   count toward the campaign goal.

Multi-recipient campaigns hide the secondary option (the split
fundamentally needs the donor's signed PSBT) and explain why.

ExternalPayView gains an optional onBack so users can return to the
chooser without closing the dialog.
2026-05-17 20:50:03 -05:00
mkfain ad364e4b19 Let logged-out donors pay single-recipient campaigns externally
The split-PSBT flow legitimately needs a Nostr signature, but a campaign
with one recipient is just a regular Bitcoin payment — no reason to gate
that on a Nostr login. For single-recipient campaigns, the DonateDialog
now opens an ExternalPayView for logged-out users (and for logged-in
users whose signer can't build PSBTs) with:

- the recipient's Taproot address (Nostr-pubkey-derived) with copy
- a QR code embedding a BIP-21 `bitcoin:<addr>?amount=<btc>` URI
- optional USD amount input that drops into the URI as BTC
- copy URI / open-in-wallet actions
- a heads-up that externally-paid donations won't appear in Agora's
  donor list or progress bar, since no kind 8333 receipt is published

Multi-recipient campaigns still require login (the split needs the
donor's signed PSBT to construct one tx with N outputs).
2026-05-17 20:37:23 -05:00
mkfain f413d29fa1 Remove duplicate close X in mobile hamburger menu
The mobile nav drawer in TopNav has its own X button inside the panel
header, but SheetContent was also rendering the shadcn primitive close
button just outside the panel — two X buttons for the same sheet.

Add an opt-in `hideClose` prop to SheetContent and set it on the
TopNav drawer. Other Sheet consumers (MobileDrawer, etc.) keep the
default built-in close.
2026-05-17 20:30:45 -05:00
mkfain 323c613222 Add archive flow for campaigns
Authors can soft-close a campaign by republishing it with a
`["status", "archived"]` tag. Archived campaigns are hidden from the
main fundraisers feed and the donate button is disabled, but the detail
page still loads by direct link so existing donors can find it and
past donations remain attached. The author sees Archive / Reopen
buttons on the detail page and an Archived badge on cards.

useCampaigns gains an `includeArchived` option (default false) so a
future profile view can opt in. NIP.md documents the new status tag.
2026-05-17 20:20:44 -05:00
mkfain babfbc5b10 Round campaign USD amounts to whole dollars
Cents are visual noise on zap goal progress displays. Add satsToUSDWhole
helper and use it in CampaignCard, CampaignDetailPage, and the goal
preview in CreateCampaignPage. Wallet and send-bitcoin flows continue to
use satsToUSD with cents.
2026-05-17 20:11:14 -05:00
lemon 640a8328cf Polish campaign editing form 2026-05-17 18:03:46 -07:00
lemon e56523b819 Add campaign editing flow 2026-05-17 18:03:46 -07:00
lemon 177caded5c Refine campaign creation form 2026-05-17 18:03:46 -07:00
Chad Curtis d9d99d6b0b Point Discover nav link to /feed 2026-05-17 19:55:26 -05:00
lemon 7dbfc31f04 Render zaps with comment card layout 2026-05-17 17:40:21 -07:00
lemon fb6f157c42 Align zap cards with comments 2026-05-17 17:40:21 -07:00
lemon 3504a24be5 Realign zap card with the regular comment layout
The zap card was an ActivityCard with a chunky amber circle in
the avatar slot and a compact ActorRow up top. That visual
language reads as 'an activity log entry' which clashes with the
NIP-22 comments alongside it on the campaign page. Rebuild it
to mirror NoteCard's normal layout exactly:

- Donor avatar takes the standard size-11 (size-10 in threaded
  mode) slot, with a small amber zap badge anchored bottom-right
  to keep the kind signal.
- Author block uses the same font-bold name + nip05 + timeAgo
  stack as a regular note, with the verb ('donated' / 'sent')
  and amount inlined on the name row so the card reads as one
  sentence.
- For campaign targets the amount is followed by 'to <Campaign
  Title>' where the title is a clickable Link to the naddr —
  same routing the regular CommentContext header would use.
  Resolved via a single useAddrEvent call gated on the receipt's
  a-tag so non-campaign zaps incur no fetch.
- The donor comment renders below the author row as muted italic
  text — same spot a normal note's body sits — instead of being
  tucked under the actor row.
- Action bar uses the shared {actionButtons} JSX with the exact
  same spacing as a comment, so reply / repost / react / zap /
  share / more line up vertically across cards in the thread.

The amber Zap import and ProfileHoverCard / Avatar imports were
already in scope, so no new imports beyond useAddrEvent.
2026-05-17 17:40:21 -07:00
lemon f49c20787e Polish zap cards and clamp the campaign story
Four related changes:

1. Campaign story now clips to three lines (~4.5rem) behind a
   soft fade overlay, with a Read more / Show less toggle.
   When there's no story yet the empty placeholder renders
   unclipped as before.

2. Zap cards in NoteCard gained the same reply/repost/react/
   share action bar as a regular note. Each kind 9735 / 8333
   event is a valid Nostr event in its own right, so NIP-22
   replies target it directly via its event id and reactions
   bind to it the same way.

3. The 'zapped' label is now 'donated' when the receipt's a-tag
   points at a kind 30223 campaign, and 'sent' otherwise. The
   target kind is read from the addressable coordinate; pure
   e-tag Lightning zaps fall back to 'sent' without a fetch.

4. Zap amounts render in USD when a BTC→USD price is cached,
   with the raw sats string moved to the title tooltip. Falls
   back to sats when the price hasn't loaded. A new useBtcPrice
   hook shares the existing 'btc-price' cache key so all
   call sites (NoteCard, CampaignCard, useBitcoinWallet) dedupe
   to one in-flight request.
2026-05-17 17:40:21 -07:00
lemon 39fed90296 Trim campaign donate card
- Drop the extra divider above the action bar; PostActionBar
  already carries its own border-t/b.
- Remove hex pubkey lines under organizer and beneficiary names;
  show the NIP-05 instead when available, otherwise just the name.
- Drop the Donors section entirely. Donations now appear inline
  in the comments thread, so a separate list is redundant.
- Relocate the organizer attribution from a dedicated card section
  to a small 'by {name}' link next to the title in the hero
  overlay. Same subtle styling, just a less intrusive spot.
2026-05-17 17:40:21 -07:00
lemon 760e11138d Show donation receipts in campaign comments
Three changes work together so campaign pages reflect how
campaigns actually receive support — via on-chain donations,
not Lightning zaps:

1. NoteCard's zap-receipt layout now renders kind 8333 in
   addition to kind 9735. The helpers (getZapAmountSats,
   getZapSenderPubkey) already branched correctly; only the
   isZap gate and the amount/message extraction were 9735-only.

2. PostActionBar gained a hideZap prop. Campaigns set it so the
   action bar shows only reply / repost / react / share — a
   generic Lightning zap is the wrong CTA when the campaign
   has its own donation flow.

3. CampaignDetailPage interleaves kind 8333 donation receipts
   into the comments thread, sorted by created_at alongside
   kind 1111 comments. Each donation produces one receipt per
   beneficiary, so we dedupe by (txid, donor) and rewrite the
   canonical receipt's amount tag to the summed total so the
   card shows the full donation rather than one share.
2026-05-17 17:39:39 -07:00
lemon 534b8f0102 Add comments and reactions to campaign pages
Campaigns (kind 30223) now expose the standard PostActionBar
(reply/repost/react/zap/share/more) plus a NIP-22 threaded
comments list, mirroring how PostDetailPage handles other
addressable kinds. A stats row above the action bar opens the
existing InteractionsModal.
2026-05-17 17:39:39 -07:00
lemon 5cb4c9f950 Align campaign support section 2026-05-17 17:39:18 -07:00
lemon e37552c8ce Refine campaign support card 2026-05-17 17:39:18 -07:00
lemon 3af32e167c Simplify campaign support card 2026-05-17 17:39:18 -07:00
lemon 3927a50633 Combine campaign support sections 2026-05-17 17:39:18 -07:00
lemon 0712034720 Clarify campaign organizer and recipients 2026-05-17 17:39:18 -07:00
lemon c0a23061ee Move beneficiaries above campaign story 2026-05-17 17:38:27 -07:00
lemon 44be9e6e35 Promote campaign donation panel 2026-05-17 17:38:27 -07:00
lemon fa813ed084 Restyle campaign category meta 2026-05-17 17:38:27 -07:00
lemon 2c58a7b0fd Tune campaign hero actions 2026-05-17 17:38:27 -07:00
lemon 532ff57c29 Overlay campaign hero details 2026-05-17 17:38:27 -07:00
lemon f99c1d0b17 Avoid duplicate campaign story image 2026-05-17 17:38:27 -07:00
lemon 703bb6d3ab Use people search for campaign beneficiaries 2026-05-17 17:38:27 -07:00
lemon 162d4eee43 Make campaigns USD-first 2026-05-17 17:38:27 -07:00
Alex Gleason 810cbfba00 Set default featured campaigns 2026-05-17 19:34:51 -05:00
Chad Curtis 3e5af1922d Build campaign creator link with useProfileUrl
The 'organized by' link on the campaign detail page was using a raw
hex pubkey URL (/<hex>). Switch to useProfileUrl so the link prefers
the creator's verified NIP-05 identifier when one is available and
falls back to the npub otherwise — same pattern the rest of the app
uses for profile navigation.
2026-05-17 19:00:25 -05:00
Chad Curtis 3a540ffaa1 Point TopNav mobile drawer Profile link at the user's npub
The mobile drawer's Profile link was hardcoded to /profile, which has
no route and fell through to the catch-all NIP19Page. nip19.decode
threw on 'profile' and the profile page rendered 'Please log in to
view your profile' even when the user was logged in.

Encode the current user's pubkey as an npub for the link target,
matching how every other profile link in the app is built.

Regression-of: 704cb42e
2026-05-17 19:00:20 -05:00
Chad Curtis 2b9ea24238 Resolve hex pubkey URLs on the profile page
NIP19Page renders <ProfilePage /> for raw 64-char hex identifiers that
relays resolve to a kind-0 author, but ProfilePage only knew how to
decode NIP-19 and NIP-05. The hex param fell through nip19.decode (which
throws), returned undefined, and the page rendered 'Please log in to
view your profile' — even for logged-in users visiting somebody else's
hex URL.

Accept raw hex pubkeys in the pubkey resolver, and replace the
misleading log-in copy with 'User not found' (the no-param case can't
reach this branch from the router anyway).

Regression-of: d58f4bb6
2026-05-17 19:00:14 -05:00
Chad Curtis 77eee4f872 Switch credential domain and Android deep links from ditto.pub to agora.spot
The iOS Associated Domains entitlement, Android intent filters, AASA
file, and assetlinks.json already reference agora.spot. Three call
sites still hard-coded ditto.pub:

- CREDENTIAL_DOMAIN in src/lib/credentialManager.ts, which keys iCloud
  Keychain Shared Web Credentials by domain. Saved nsecs were being
  filed under ditto.pub and so could never be matched against the
  agora.spot AASA file.
- MainActivity.handleNotificationIntent host check, which only routed
  the WebView when the tapped notification's URI host equaled
  ditto.pub.
- NostrPoller.showNotification, which built notification PendingIntents
  pointing at https://ditto.pub/notifications.
2026-05-17 18:42:46 -05:00