Commit Graph

3323 Commits

Author SHA1 Message Date
Alex Gleason 474ec6cc99 Make the Esplora API base URL configurable
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.
2026-05-13 11:23:36 -05:00
Alex Gleason 0022b86299 Truncate the version-update toast excerpt to 60 characters
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
2026-05-11 23:19:33 -05:00
Alex Gleason abc37151ad Fetch BTC price from mempool.space instead of CoinGecko
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.
2026-05-11 23:04:13 -05:00
Alex Gleason ba0a144afd Render the Wikipedia widget's TFA title without underscores and link to /i/
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.
2026-05-11 13:45:15 -07:00
Alex Gleason d044218c6a Use a release-summary paragraph for App Store, Play Store, and the in-app toast
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.
2026-05-11 13:13:33 -07:00
Chad Curtis f525f9c393 Merge branch 'fix-blobbi-widget' into 'main'
Add switch-blobbi button to BlobbiWidget

Closes #277

See merge request soapbox-pub/ditto!220
2026-05-11 12:12:54 +00:00
Chad Curtis 2adc0a763b Merge branch 'fix/prevent-blobbi-1124-event-spam' into 'main'
Bound Blobbi social care interactions and energy flow

Closes #276

See merge request soapbox-pub/ditto!219
2026-05-11 12:12:31 +00:00
Alex Gleason e54d7c8155 Show repost header on reposted reactions, zaps, reposts, and poll votes
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.
2026-05-10 19:21:43 -07:00
Alex Gleason d84f2b790f Link people-list avatars to profiles and prefer naddr for kind 3
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).
2026-05-09 17:06:10 -07:00
Alex Gleason a2dbc169b2 Show kind 8333 on-chain zaps as notifications
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.
2026-05-09 16:53:17 -07:00
Alex Gleason ed6ac39015 Move the Restore button inside the embedded snapshot card
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.
2026-05-09 16:31:55 -07:00
Alex Gleason ba4b95972f Tighten the event recovery dialog layout
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.
2026-05-09 16:24:56 -07:00
Alex Gleason 440e00fb47 Add a Restore button to recover previous versions of replaceable events
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.
2026-05-09 16:19:14 -07:00
Alex Gleason 0a41cee6bf Include an e tag in draft article deletion events
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
2026-05-08 12:09:06 -07:00
Patrick PReis 54bf5efa1f Improve blobbi switcher a11y and constrain to horizontal scroll
- 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
2026-05-07 23:52:02 -03:00
Alex Gleason 9c590f4560 Render kind 8333 like a zap in embeds and the detail page
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.
2026-05-07 15:59:00 -07:00
Alex Gleason 589a5f159e Show kind 8333 zaps in the interactions modal Zaps tab
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.
2026-05-07 15:23:50 -07:00
Alex Gleason ae11c91674 Fill the action-bar zap button after a successful zap
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.
2026-05-07 15:09:32 -07:00
Alex Gleason 1283b56be9 Simplify zap success screen
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
2026-05-07 15:00:01 -07:00
Alex Gleason 5c2c35130f Show a grand success screen after a successful zap
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.
2026-05-07 14:55:57 -07:00
Patrick PReis da9f88d181 Add close (X) button to blobbi switcher popover 2026-05-07 15:36:01 -03:00
filemon 59929e9c4d Fix stale-write in dev editor by fetching fresh 31124 before mutation
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.
2026-05-06 23:12:25 -03:00
Patrick PReis 55f8d946f9 Add switch-blobbi button to BlobbiWidget
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.
2026-05-06 23:04:04 -03:00
filemon e3127e8555 Fix stale 6-hour window in social interaction queries
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.
2026-05-06 22:58:33 -03:00
filemon 478f53177e Bound social care history and simplify Blobbi social consolidation
- 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
2026-05-06 22:27:50 -03:00
Alex Gleason 9090ecfa2b Use APP_RELAYS as nostrconnect fallback
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.
2026-05-05 23:03:32 -05:00
Alex Gleason 3555cbcf99 Pare down Lightning zap buttons and drop sats from the display
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.
2026-05-05 20:07:10 -05:00
Alex Gleason 9a08c6e488 Align Lightning zap dialog with on-chain design
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.
2026-05-05 20:01:05 -05:00
Alex Gleason b1c49c06a3 Surface insufficient-balance state directly on the zap amount + button
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.
2026-05-05 19:42:36 -05:00
Alex Gleason 9b81175e85 Strip sats subtitle, comment, and fee icon from on-chain zap dialog
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.
2026-05-05 19:39:46 -05:00
Alex Gleason 29fa317689 Polish on-chain zap dialog microcopy and layout
- 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.
2026-05-05 19:38:24 -05:00
Alex Gleason 7be92b8eec Simplify on-chain zap dialog
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).
2026-05-05 19:35:52 -05:00
Alex Gleason 3caad76477 Improve icon contrast on highlighted search dropdown items
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.
2026-05-05 19:23:38 -05:00
Alex Gleason a6bfd2cb68 Add Birdex chorus button
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.
2026-05-05 17:24:57 -05:00
Alex Gleason df38cfdbca Remove share button from PostActionBar
Share action is still available from the NoteMoreMenu. The inline button
in the action bar is redundant and adds visual noise to the row.
2026-05-05 17:24:49 -05:00
Alex Gleason 234d3a21a3 Support NIP-84 Highlight events (kind 9802)
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.
2026-05-05 17:24:42 -05:00
Alex Gleason 64a4643503 Merge branch 'main' of gitlab.com:soapbox-pub/ditto 2026-05-05 16:11:00 -05:00
Alex Gleason c7e0234896 Unify /i/ external content page UI
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.
2026-05-05 16:00:41 -05:00
Alex Gleason c69aee40a2 Add bottom border to ComposeBox by default
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.
2026-05-05 15:49:55 -05:00
Alex Gleason 908c5b248c Enable commenting on NIP-73 identifier /i/ pages
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.
2026-05-05 15:46:30 -05:00
Alex Gleason 546b1aff9b Let shield icon inherit hover color from button
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.
2026-05-05 15:01:13 -05:00
Chad Curtis 9aecefff40 Merge branch 'feat/blobbi-1124-interactions' into 'main'
Add Blobbi social interactions (kind 1124)

Closes #265

See merge request soapbox-pub/ditto!211
2026-05-05 19:11:09 +00:00
Chad Curtis a9ff5c43f0 Merge branch 'fix-blobbi-sleepy-eyes' into 'main'
Fix sleepy eye animation breaking when same Blobbi rendered in multiple places

Closes #272

See merge request soapbox-pub/ditto!217
2026-05-05 19:09:58 +00:00
Alex Gleason d34a155922 Simplify permission manager popover
- 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.
2026-05-05 14:00:28 -05:00
Alex Gleason c2c5b5c3be Drop target-pubkey from nsite encryption/decryption prompts
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.
2026-05-05 13:57:15 -05:00
Alex Gleason 5e729f74cd Grant broad permissions-policy and fix nav-bar safe-area spacing on sandbox frames
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.
2026-05-05 12:52:49 -05:00
Alex Gleason 9e6ed02ce1 Add nsite:// sidebar pinning with auto-launch, favicon, and highlight
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
2026-05-05 12:38:26 -05:00
Alex Gleason 973acd7e9b Inject NIP-07 signer into nsites and drop native sandbox path
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.
2026-05-05 12:15:31 -05:00
Alex Gleason d2cf678491 Fix sing-to-Blobbi mic access on Android and preview playback on iOS
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.
2026-05-05 09:20:01 -05:00
Alex Gleason 0497aa33c9 Show live progress during the nostrconnect login handshake
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.
2026-05-04 21:42:45 -05:00