Hardcoded MEMPOOL_API constant in src/lib/bitcoin.ts becomes a baseUrl
parameter on every fetch helper, sourced from a new `esploraBaseUrl`
field on AppConfig (default `https://mempool.space/api`). The wallet,
zap dialogs, on-chain zap verification, and NIP-73 Bitcoin tx/address
pages now read the URL from useAppContext and pass it through, so
self-hosted Esplora deployments (or Blockstream's) work without code
changes. The mempool.space-specific `/v1/prices` extension is still
appended by fetchBtcPrice.
The release-summary paragraph (max 500 chars by convention) skipped the
truncation that the legacy first-bullet fallback applied, so toasts could
render an entire paragraph. Truncate both branches uniformly on a word
boundary with an ellipsis, matching the prior 60-character cap.
Regression-of: d044218c
Consolidates Bitcoin-related HTTP onto a single host — the rest of the
wallet already uses mempool.space for addresses, txs, UTXOs, fees, and
broadcast — so dropping the CoinGecko dependency removes one external
service from the connect-src surface and simplifies CSP / privacy review.
mempool.space's /api/v1/prices returns USD (and several other fiat
currencies) at the same shape we need, so fetchBtcPrice keeps its
`Promise<number>` signature and every caller continues to read from
the same React Query cache key.
The featured-article card was showing the raw `title` field
(e.g. "Japan_Cup") and opening Wikipedia in a new tab. Use the
API's `normalizedtitle` for display and route the click through
/i/ so users land on the in-app article view.
Each CHANGELOG.md release section now begins with a single plaintext
paragraph (max ~500 chars) before any `### Category` heading. That
paragraph drives the release blurb in three storefronts and the
in-app version-update toast, so we no longer ship a marketing-grade
description in one place and a raw bullet list in another.
scripts/extract-release-notes.mjs is the single source of truth for
extraction. It emits the full section (summary + lists) by default
and only the summary paragraph with --summary, with a
`Ditto vX.Y.Z` fallback for legacy entries that have no summary.
CI changes:
- New `release-notes` job (build stage, default node:22 image)
produces `artifacts/release-notes.md` and
`artifacts/release-notes-summary.txt` once per pipeline.
- `release` job pulls release-notes.md as the GitLab Release
description (replaces the old inline awk extraction). It now uses
`needs:` with `artifacts: false` for build-apk/build-ipa to
avoid re-downloading the .apk/.aab/.ipa it doesn't open.
- `publish-app-store` copies release-notes-summary.txt to
`ios/fastlane/metadata/en-US/release_notes.txt` (replaces its
own awk extraction).
- `publish-google-play` drops `--skip_upload_changelogs`, writes
the summary to
`android/fastlane/metadata/android/en-US/changelogs/<versionCode>.txt`
and points fastlane supply at `--metadata_path`. This is the
first time we upload a What's New text to the Play Store from CI.
App-side changes:
- `src/lib/changelog.ts` parser captures the leading non-blank
paragraph (before any bullet or category heading) into
`entry.summary`.
- `VersionCheck.tsx` toast uses `entry.summary` when present,
falling back to the legacy 60-char first-bullet excerpt for
backward compatibility.
- `ChangelogPage` renders the summary as a lede paragraph above
the bullet list in both LatestRelease and ChangelogEntryCard.
Changelog content:
- Added summary paragraphs to v2.14.3, v2.14.2, v2.14.1.
Skill + AGENTS.md updates:
- `release` skill documents the summary paragraph format, the
500-char convention, and the seven-job pipeline.
- `ci-cd-publishing` skill gains a 'Release notes pipeline' section
mapping each storefront to its source artifact.
- AGENTS.md pipeline summary mentions release-notes and the summary
flow into both store "What's new" fields.
NoteCard's reaction/repost/zap/poll-vote branches return early with their
own ActivityCard layouts, skipping the inline 'X reposted' header that
the normal note layout renders below. As a result, when one of these
events appeared in a feed via a kind 6/16 repost, the reposter
attribution was silently dropped.
Add an optional `header` slot to ActivityCard and pass the repost header
into all four early-return branches when `repostedBy` is set.
Stacked avatars in PeopleAvatarStack are now clickable, navigating to
the user's npub profile so readers can jump straight to a member from a
follow list, follow set, or follow pack — not only from the surrounding
post. Each avatar is wrapped in a Link with a stopPropagation handler so
the click doesn't bubble up to the card-level navigation, and the focus
ring is now visible on keyboard focus.
Kind 3 follow-list events are legacy replaceable kinds (NIP-01) but
fell outside the 10000–19999 range that NoteCard, EmbeddedPeopleListCard,
PostDetailPage, and NoteMoreMenu all special-cased — so clicking a
follow list in a feed went to a per-event nevent that pinned to a stale
revision instead of the stable naddr. The four call sites are now
unified behind a new lib/encodeEvent.ts helper that treats kinds 0, 3,
and 41 as replaceable, alongside 10000–19999 and 30000–39999. The same
helper exposes encodeEventNevent for callers that intentionally want to
reference a historical version (e.g. the profile-recovery dialog).
Lightning (kind 9735) and on-chain (kind 8333) zaps now share a single
"zap" group bucket in the notifications page and render with the same
header, sats label, and Zap icon. The Zaps preference toggles both kinds
together; native and push notification queries pick up 8333 automatically
through the shared kind list.
Position the Restore button (or Current badge) absolutely in the
top-right corner of the embedded post, replacing the row beneath it.
Each snapshot now occupies just one container's worth of vertical
space, and the action sits next to the content it acts on instead of
detached below it. The overlay stops click and keyboard propagation so
clicking Restore doesn't also navigate away to the embedded card's
link target.
The double-container effect — outer card frame around an embedded post
that already had its own border — wasted vertical space and made each
snapshot read like two stacked boxes. Drop the outer frame so the
embedded card is the only container, with a primary-colored ring on the
current version. The redundant date row also goes (the embedded card
displays its own timestamp), leaving just a right-aligned Restore button
or 'Current' badge below each snapshot.
The note 3-dots menu now exposes a 'Restore previous version' option for
replaceable and addressable events the current user owns, sitting next
to Delete. It opens a generic EventRecoveryDialog modeled after the
existing profile/mute-list/badge recovery dialogs — querying past
versions with nostr.req() (to bypass NPool's NSet deduplication) using
the same (kind, authors[, #d]) filter shape, and rendering each
historical version through EmbeddedPost so any kind displays correctly.
Restoring republishes the chosen snapshot's content and tags via
useNostrPublish with the snapshot passed as 'prev' so published_at is
preserved. Inline isAddressableKind helpers in useDeleteEvent and
useNostrPublish are now sourced from a shared src/lib/eventKinds.ts.
The deletion event for a NIP-37 draft wrap (kind 31234) only carried
the addressable `a` coordinate. Per NIP-09, a deletion should also
reference the specific event by id when available, so relays and
clients that key their deletion logic on `e` tags don't miss it.
Look up the draft's event id from the TanStack Query cache (the drafts
list already stores it as `eventId` when parsing the wrap) and append
an `e` tag alongside the existing `a` tag. Falls back gracefully to
`a` only if the event id can't be resolved.
Regression-of: e93c6651
- Add aria-label to close button and companion selection buttons
- Replace flex-wrap with horizontal scroll (max-w-[18rem]) so only ~5
blobbis are visible at once; overflow scrolls horizontally
- Add visible thin scrollbar (.scrollbar-thin utility) overriding global
scrollbar-hiding, plus a right-edge fade gradient to hint at overflow
- Add flex-shrink-0 to prevent items from collapsing
- Break BlobbiWidgetContent destructuring across multiple lines for
readability
The on-chain zap kind used to fall through the "unknown kind" path in
every surface except InteractionsModal: a bare NIP-31 alt-tag tombstone
on PostDetailPage, a generic embedded preview for nostr: quote URIs,
and a plain "This event kind is not supported" string inside the reply
composer's parent preview. Visually it was nothing like a zap.
Route kind 8333 through dedicated cards that mirror the 9735 Lightning
treatment pixel-for-pixel: amber bolt bubble, sender avatar, "zapped"
verb, amber sats amount, italic comment. Per NIP.md we verify the
claimed amount against mempool.space before displaying it, so the new
`useVerifiedOnchainZap` hook short-circuits to the single-event path of
`verifyOnchainZap`. Until verification resolves (or if it fails) the
card shows a muted "verifying…" / "unverified" hint next to the amount
so we don't silently lie.
Covers three surfaces in one pass:
- Detail page (nevent URL): new isOnchainZap branch in PostDetailPage
- Embedded quotes: new EmbeddedOnchainZapCard in EmbeddedNote
- Reply composer parent preview: uses EmbeddedPost -> EmbeddedNote,
so it inherits the fix for free.
The Zaps tab only rendered NIP-57 receipts (kind 9735), so a post that
had been zapped only on-chain appeared to have no zappers at all. Merge
the two rails into a single unified view-model and render them with
identical rows — same avatar, same name line, same amber amount badge,
same chevron link. The dedup and on-chain verification are already
handled by useOnchainZaps upstream; this change is just plumbing.
The modal now takes the full target event instead of a bare eventId so
the on-chain query can compute the `a` coordinate for addressable kinds.
Updated all call sites (PostDetailPage, PodcastDetailContent,
MusicDetailContent) accordingly.
Previously the bolt icon next to reply/repost/react was stateless: an
outlined zap icon whether you'd zapped the post or not. This matched
neither the repost button (flips to the accent color when reposted) nor
the reaction button (fills when you've reacted), and the gap was most
noticeable with on-chain zaps where users expected the same visual
confirmation they get for Lightning.
Add a useUserZap hook that consults both rails in one REQ: kind 8333
filtered by authors+#e (our on-chain zap, self-authored), and kind 9735
filtered by #e with a client-side extractZapSender match (NIP-57
receipts are authored by the LNURL server, not the zapper). The send
hooks (useOnchainZap, useZaps) optimistically set the cache to true on
success so the icon fills immediately, without waiting for the relay to
echo the event back.
Wired into every action-bar renderer that carries the zap button:
NoteCard, PostActionBar, PhotoBottomBar, VinesFeedPage, BookFeedItem.
Remove the auto-close progress bar, the "Sent via Bitcoin/Lightning" rail
indicator, and the sats subtext under the USD amount. The screen now
dismisses only via the Done button, so the rail-specific plumbing
(autoCloseMs, kind) is gone from the component API as well.
Regression-of: 5c2c3513
Previously, a successful send from the Zap dialog auto-closed and surfaced a
toast. That undersold what just happened — the user sent Bitcoin. Now both
rails (on-chain + Lightning) flip the dialog over to a dedicated success
screen with an animated check, amount, recipient card, rail indicator, and
(for on-chain) a "View transaction" link to mempool.space. The dialog
auto-dismisses after six seconds if the user walks away.
The dev editor read tags and content from the TanStack Query cache
(companion.allTags / companion.event.content) and published without
prev, risking overwrite of concurrent changes (e.g. social
consolidation advancing the checkpoint on another device).
Apply the standard read-modify-write pattern: fetchFreshEvent before
merge, use prev.tags/content as the base, and pass prev to
publishEvent so published_at is preserved.
Users with multiple Blobbis can now change which one is displayed in the
widget without navigating to the full Blobbi page. A new ArrowLeftRight
icon appears below the companion (Footprints) button and opens a popover
with all available Blobbis for quick selection.
Move effectiveSince computation from useMemo into queryFn so
Date.now() is evaluated fresh on each TanStack Query refetch.
Previously, long-lived pages froze the window floor at mount time,
causing interactions from friends to go undetected after hours.
Also document the intentional boost→feed reaction animation reuse.
- Gate social actions by projected stat thresholds (< 70) so visitors
can only help stats in visual distress
- Add energy category with Energy Drink and Power Nap Pillow items
- Apply 6-hour recency window to interaction queries (limit 30)
- Fix BlobbiActionsProvider tree placement so BlobbiPage shares context
with the companion layer
- Preserve event content in dev editor (don't overwrite checkpoint JSON)
- Show Needs Now summary in activity tab with priority badges
- Remove unused need-driven consolidation infrastructure
Regression-of: 9aecefff
When the user has no NIP-65 write relays configured, the nostrconnect://
URI was built with a single relay (wss://relay.ditto.pub). Fall back to
the full APP_RELAYS write list instead so the remote signer has more
connection options during the handshake.
The Send and Pay-with-WebLN buttons no longer carry a lightning-bolt icon; the on-chain tab's primary button is text-only too, so this brings the flow in line. Sats are no longer shown anywhere on the Lightning screens — the USD amount is the whole story as far as the user is concerned, and the sats figure is just an implementation detail the LNURL flow handles.
Presets drop from $1/$5/$10/$25/$100 to $0.10/$0.50/$1/$2/$5 and the default amount moves from $5 to $0.50. Lightning zaps are tip-jar-shaped, not dinner-shaped — the on-chain tab can stay where it is because a fixed-fee on-chain send doesn't make sense below a few dollars.
Both tabs now lead with a big clickable USD amount and the same //// preset row, with the sats figure relegated to a small secondary line. Dropped the Lightning-only comment textarea and emoji picker — the on-chain flow never had them and keeping two different preambles made the tabs feel like different products. Preset buttons are shorter (h-8) in both tabs.
The invoice screen now shows USD primary / sats secondary in the header and renders the QR through QRCodeCanvas instead of a data-URL img, matching the on-chain fallback's QR styling. Large-amount two-tap confirmation (>= $100) now applies to Lightning too.
When the requested amount plus fee exceeds the user's balance, the
big dollar readout and send button both go destructive-red, the
button text becomes 'Not enough Bitcoin' and disables, and the
'Balance: $X' footnote in the fee row is hidden. The zero-balance
case ("you don't have any Bitcoin yet") still shows the balance line
so users understand why send is disabled.
The dialog collapses to: amount, presets, Send button, fee. Removes
the sats-per-amount subtitle under the big number, the collapsible
'Add a comment' accordion, and the gauge icon next to the fee line.
Comments ride along as empty strings on the zap payload, so the
backing 8333 publish still works, it just doesn't carry user text.
- Center the 'Add a comment' accordion toggle and make its textarea
span the full width when opened.
- Tighten the 'How does sending Bitcoin work?' FAQ: leads with the
Nostr-key-as-wallet framing, drops the extra paragraph about
balances, and keeps the fee and public/irreversible points concise.
- Move the fee/balance line below the Send button so it reads like a
footnote under the primary action instead of competing with it.
Redesign the on-chain flow around a sleek 'Send $X' experience: click
the big number to edit, presets sit underneath, comment collapses behind
a chevron, recipient address and OR divider are gone, the send button
just says 'Send $5.20' with the fee included, and fee speeds are
deduplicated so duplicate sat/vB tiers don't repeat.
The fee speed now auto-adjusts when the amount changes to keep the fee
below 40% of the send amount — once the user manually picks a speed,
auto-adjustment is disabled for the session.
Dialog framing switches from 'Send a Zap' to 'Send Bitcoin', the
redundant description line is dropped, and the (?) popover routes to
one of two new tab-specific FAQ entries (send-bitcoin-onchain or
send-bitcoin-lightning).
Nav items and the 'Search for...' entry in the autocomplete dropdown had
primary-colored icons on a primary-tinted circle, which lost contrast
against the primary-tinted row background when highlighted. Swap both to
accent-foreground tones when the row is selected.
New BirdexChorusButton plays every species' Wikipedia recording at once
— a dawn chorus for the whole life list. Hides itself when no species
has usable audio, and the feed-card variant swallows clicks so toggling
playback doesn't navigate away from the NoteCard.
Render highlight excerpts as pull-quotes with source attribution across
feed cards, detail pages, and quote embeds. Without this, kind 9802
events fell through to UnknownKindContent in cards and — worse — had
their quoted prose fed through the kind-1 tokenizer in embeds,
auto-linkifying URLs and hashtags that were part of the original source,
not the highlight author's post.
Integration points:
- HighlightContent renders the excerpt with an accent blockquote, wraps
the highlighted span in <mark> when a context tag is present, and
attributes the source via EmbeddedNaddr (a-tag), EmbeddedNote
(e-tag), or a sanitized URL chip (r-tag).
- EmbeddedHighlightCard gives quoted highlights a dedicated compact
card instead of the generic-embed fallback.
- Added 9802 to NoteCard + PostDetailPage dispatch, KIND_HEADER_MAP,
CommentContext labels/icons, NOTIFICATION_KIND_NOUNS, and the
EmbeddedNote dispatcher.
- Registered an EXTRA_KINDS entry with feedIncludeHighlights (off by
default) and showHighlights (on), plus a /highlights route backed by
KindFeedPage.
- Added a highlights notification type with its own subscription
template, preference toggle, grouped notification row, and
author-ownership filter so users are only notified when their own
content is highlighted.
- feedUtils hides empty highlights with no source reference.
Three inconsistencies between /i/ pages cleaned up:
* Action bar now always renders three universal interaction buttons
(Comment, React, Share) with the Comment button opening the
compose modal and showing the top-level comment count. ISBN keeps
its Write Review (star) button as a fourth. Previously Bitcoin
and other non-book identifiers showed only React + Share.
* Book content tabs (Comments / Reviews) switched from the flat
shadcn Tabs primitive to SubHeaderBar + TabButton so the curved
arc styling matches Feed, Profile, Search, and every other tabbed
page in the app.
* Extracted ExternalCommentsSection — the inline ComposeBox +
threaded list + loading/empty states are now shared between the
default single-list layout and the ISBN Comments tab instead of
duplicated.
Every context except the Feed composer follows the compose box with
content that needs visual separation (comment lists, wall posts,
threaded replies), so add a default border-b and let Feed opt out with
a new hideBorder prop — it sits directly above SubHeaderBar's arc
background, which already provides its own separator.
Modal usage (forceExpanded) stays borderless since it lives inside
a dialog container.
ExternalContentPage already supported fetching comments for non-URL
external roots (bitcoin:tx:..., isbn:..., iso3166:..., etc.), but the
inline ComposeBox and the FAB's ReplyComposeModal were both gated on
the URL-only commentRootUrl — leaving those pages with no way to post.
Widen ComposeBox's replyTo and ReplyComposeModal's event to accept
`#${string}` NIP-73 identifiers alongside URLs and events, route them
through the existing NIP-22 publish path (usePostComment already
handled string roots), and wire the page up to the combined
commentRoot.
text-primary was applied directly to the Shield icon, overriding the
ghost button's hover:text-accent-foreground. On hover the background
turned into accent while the icon stayed primary, producing a
low-contrast pairing. Move text-primary up to the Button so the
ghost variant's hover rule can take over, and let the icon inherit
currentColor.
- Remove the app-name subtitle under "Permissions"; the site is already
identified by the nav bar directly above the popover.
- Drop the trash icon from the "Revoke all" button so the destructive
action reads as plain text like the row items.
- Remove the per-row allow/deny toggle. Stored permissions are always
"allowed" in practice (a denied prompt doesn't surface a row the user
would want to keep around), so the toggle added noise without a
realistic use case. Users who change their mind can remove the row and
re-prompt.
- Drop the status check/X icon on the left of each row now that the
toggle is gone and every listed permission is implicitly allowed.
- Show the remove button always (was opacity-0 until hover) and switch
it from a trash icon to an X, matching the close-affordance idiom used
elsewhere in the app.
- Drop the siteName prop from NsitePermissionManager; nothing uses it
anymore.
The target pubkey was rendered as a truncated hex string in the prompt,
which is noise to the user: they can't verify it, it doesn't scope the
stored permission (which is global-to-the-app), and showing a pubkey
next to "Allow" misleadingly suggests the decision applies only to that
peer. Drop the field from NsitePromptState and stop threading it through
the four encrypt/decrypt RPC branches.
Sandbox iframes live on a cross-origin subdomain, so most capability APIs
are blocked unless the parent delegates them with allow=. Add a permissive
policy covering media (camera, microphone, display-capture, encrypted-media,
picture-in-picture, autoplay, speaker-selection), sensors (accelerometer,
gyroscope, magnetometer, ambient-light-sensor, compute-pressure, battery),
input (gamepad, midi, keyboard-map, xr-spatial-tracking), and UX features
(fullscreen, geolocation, idle-detection, screen-wake-lock, clipboard-write,
web-share, window-management, storage-access) so nsites and webxdc apps can
use anything a regular web app would.
Deliberately omitted for security: payment, publickey-credentials-*,
otp-credentials, identity-credentials-get (all phishing/account-takeover
vectors), local-fonts (fingerprinting), bluetooth/hid/serial/usb/
clipboard-read (raw device access left off for now).
Drop WebxdcIframe's narrow allow= override so webxdc apps get the same
broad policy instead of downgrading to just autoplay/fullscreen/gamepad.
Also split safe-area-top from content padding in the NsitePreviewDialog
and WebxdcEmbed nav bars: the outer element reserves space for the
notch inset, the inner row keeps a fixed px-3 py-2 flex layout so
content stays vertically centered inside the intended 44px bar height.
Introduces a new sidebar item type for nsites that auto-opens the nsite
preview when clicked, using React Router state to prevent external URLs
from triggering auto-launch.
- Add isNsiteUri/nsiteUriToSubdomain helpers and parseNsiteSubdomain
- Create NsiteSidebarItem with site favicon and link preview title label
- Wire nsite:// dispatch in SidebarNavList, useFeedSettings, SidebarMoreMenu
- NoteMoreMenu pins named nsite events as nsite:// URIs instead of nostr:
- NsiteCard gains a Pin/Unpin button and an autoPlayKey prop that re-opens
the player each time the sidebar item is clicked
- NsitePlayerContext tracks the active subdomain for sidebar highlighting,
provided in MainLayout so sidebar and pages share state
- PostDetailContent consumes nsiteAutoPlay router state and clears it
after consumption so a page refresh doesn't re-trigger auto-play
When a logged-in user opens an nsite preview, a window.nostr provider is
injected into the sandboxed iframe. The provider proxies signEvent, nip04,
and nip44 calls to the parent signer over the existing JSON-RPC bridge.
A permission system gates each operation:
- getPublicKey is auto-allowed (clicking Run implies consent)
- signEvent prompts are granular per event kind (like Amber)
- encrypt/decrypt prompts are per operation type
- Users can check 'Remember for this site' to persist decisions
- Permissions are scoped to (userPubkey, siteId) in localStorage
The nsite preview nav bar gains a shield icon that opens a popover for
managing stored permissions.
Kind labels for the signer nudge, the permission prompt, and the post-
detail loading title now route through a central KIND_LABELS registry
(src/lib/kindLabels.ts) instead of three divergent inline maps.
The native SandboxPlugin (iOS WKWebView / Android WebView overlay) is
removed; SandboxFrame now always uses iframe.diy, so native behavior
matches web. This drops ~1100 lines of native code, the Android-only
blob prefetch workaround in NsitePreviewDialog, and the createPluginCall
registration in MainActivity and capacitor.config.json.
The sing action uses getUserMedia + MediaRecorder, which in a browser is
gated only by the standard web mic prompt. In Capacitor's Android
WebView it additionally requires the RECORD_AUDIO permission to be
declared in AndroidManifest.xml; without it the WebView rejects with
NotAllowedError and no system prompt is ever shown, so tapping record
silently fails on the Android app while working fine in the browser.
Also add MODIFY_AUDIO_SETTINGS, which some devices require for the
echoCancellation / noiseSuppression / autoGainControl constraints that
InlineSingCard passes to getUserMedia.
Separately, reorder AUDIO_MIME_CANDIDATES to prefer audio/mp4/aac over
audio/webm;codecs=opus. iOS WKWebView cannot decode WebM/Opus in an
<audio> element, so the recorded Blob's preview URL failed to load on
iOS. Android WebView and desktop Chromium both support mp4/aac, so
preferring it first is safe cross-platform. This mirrors the ordering
already used by useVoiceRecorder.ts.
When a user tapped "Open Signer App", the dialog previously stayed
frozen on the same screen — same button, same copy-URI fallback, no
feedback — until the login either succeeded (and the dialog dismissed)
or timed out after two minutes. With slow or flaky signers (Amber's
current listening-REQ bug being the immediate trigger, but any NIP-46
signer that takes more than a second or two to respond hits the same
hole) this looked indistinguishable from a hang. Users retapped the
button, closed the dialog, gave up.
Now the dialog swaps the QR / Open Signer App area for a centered
spinner with a live status line as the handshake advances:
- "Waiting for signer connection…" while the signer app has the user
and we're listening on kind 24133 for the connect-ack.
- "Getting public key…" once the connect-ack arrives and we're
issuing the NIP-46 get_public_key RPC.
On mobile the swap happens synchronously when the user taps "Open
Signer App" so they see the progress state the moment they return
from the signer — this is the most important window, since that's
exactly when the original UI left them staring at a button they
were worried they needed to re-tap. On desktop the QR stays visible
through the awaiting-connect phase (they may still be scanning with
a different device) and only swaps in once the signer has
acknowledged.
The progress view includes a Cancel link (primary color, matches the
"Create account" affordance) that aborts the in-flight subscription
and regenerates fresh connect params — equivalent to the existing
Retry path, but reachable while the handshake is live instead of
only after a failure.
The handshake phases are surfaced via the new `onStatus` callback
on `NLogin.fromNostrConnect` in @nostrify/react 0.6.0. Bumps
@nostrify/react to ^0.6.0 and @nostrify/nostrify / @nostrify/types
to their matching versions (^0.52.0 / ^0.37.0) to avoid duplicate
nested package copies that would otherwise split type identity.
Incidental cleanup while editing the dialog: the Copy URI button and
the "Tap to open your signer app" / "Scan with your signer app"
status lines are removed. The primary Open Signer App button is
self-explanatory on mobile, and the QR on desktop doesn't need a
caption.