Commit Graph

4447 Commits

Author SHA1 Message Date
Mary Kate Fain 173f789242 Extract shared portal dropdown logic into usePortalDropdown hook
Both EmojiShortcodeAutocomplete and MentionAutocomplete had identical
logic for fixed viewport positioning with viewport-flip, scroll/resize
dismissal, and portal rendering. Extract into a shared hook to reduce
duplication and centralize the positioning behavior.
2026-04-10 12:50:05 -05:00
Mary Kate Fain 5c8c33747e Guard against redundant protocol:nostr and document prefix queryKey
- Skip appending protocol:nostr if the resolved filter already contains it
- Add comment explaining why the 2-element prefix key correctly invalidates
  the full 5-element useTabFeed query key via TanStack prefix matching
2026-04-10 12:36:27 -05:00
Mary Kate Fain 07a9b956cb Remove dead WidgetContext: hook was never consumed by any component 2026-04-10 11:18:13 -05:00
Mary Kate Fain 0e7f847de0 Medium-priority fixes: decouple sparkline, Map lookup, memo widgets, fix drag closure
8. Extract TrendSparkline to its own file so TrendingWidget doesn't
   depend on the old RightSidebar (re-export kept for compat)
9. Widget definition lookup uses a pre-built Map instead of linear scan
10. SortableWidget wrapped in React.memo to skip re-renders when only
    sibling state changes (picker open, other widget collapse)
11. handleDragEnd computes indices from the updater's current array
    instead of closing over sortableIds (eliminates stale closure risk
    if a query refetch re-renders mid-drag)
2026-04-10 10:33:27 -05:00
Mary Kate Fain 4998ea8f5d Fix high-priority widget issues: scoped queries, Bluesky isolation, Capacitor compat
5. FeedWidget now scopes queries to followed authors when logged in,
   falls back to global when logged out, and requests exact limit
6. BlueskyWidget uses its own useQuery instead of sharing the infinite
   query with BlueskyPage (separate query key, single page, no memory leak)
7. WikipediaWidget uses openUrl() instead of <a target=_blank> which
   silently fails inside Capacitor WKWebView on iOS
2026-04-10 10:27:31 -05:00
Mary Kate Fain 0cc81cd35f Fix 4 blocker issues: error boundaries, resize perf, chat persistence, error handling
1. Wrap each widget in ErrorBoundary so one crash doesn't kill the sidebar
2. Resize uses local state during drag, commits to config only on pointerup
   (was hammering localStorage at 60fps)
3. AI chat messages persist in module-level cache across collapse/expand
   (collapsing previously destroyed the conversation)
4. StatusWidget catches rejected promises from mutateAsync and shows
   destructive toast instead of silently failing
2026-04-10 10:21:50 -05:00
Mary Kate Fain ed09c8947d Fix preset widgets disappearing on first add by falling back to merged config 2026-04-10 09:34:00 -05:00
Mary Kate Fain 2e79d93806 Match 'Add widget' button background to widget card style for visibility 2026-04-10 09:32:05 -05:00
Mary Kate Fain f05097087b Add customizable widget sidebar for the main feed right column
Replace the empty right sidebar placeholder with a user-configurable widget
system. Users can add, remove, reorder, collapse, and resize widgets via
drag-and-drop and a picker dialog. Config persists in localStorage (same
pattern as sidebarOrder) and syncs via encrypted settings.

v1 widgets: Trending Tags, Blobbi (mini pet), Status (NIP-38), AI Chat,
Wikipedia (featured article), Bluesky (trending posts), and feed widgets
for Photos, Music, Articles, Events, and Books.

Defaults: Trending + Blobbi for fresh installs. Desktop-only (hidden below
xl breakpoint). Profile pages retain their dedicated ProfileRightSidebar.
2026-04-10 09:28:04 -05:00
Chad Curtis 72268dfde6 Merge branch 'feat/feed-blobbi-status-visuals' into 'main'
Reflect companion condition in feed Blobbi cards

See merge request soapbox-pub/ditto!169
2026-04-10 13:05:44 +00:00
Alex Gleason 7b63f6112c Clean up profile header: remove lightning address, NIP-05 check icon, and trailing slash from website URLs 2026-04-09 22:30:38 -05:00
Alex Gleason ce61d8d1a6 Restore right sidebar for profile pages, keep fields mobile-only 2026-04-09 22:00:50 -05:00
filemon c4a10b1303 Merge branch 'main' into feat/feed-blobbi-status-visuals 2026-04-09 15:27:28 -03:00
Chad Curtis 76c6846e91 Render BOLT11 lightning invoices in note content
Detect lnbc/lntb/lnbcrt/lntbs invoices (with optional lightning: prefix)
in note text and render them as interactive cards with a theme-aware QR
code, decoded amount, copy button, and Open in Wallet action.

- Add lightning-invoice token type to NoteContent tokenizer
- Create LightningInvoiceCard with tap-to-expand square QR, cqw-scaled
  amount text, and responsive layout
- Extract shared theme-aware QR color logic into src/lib/qrColors.ts
  (deduplicate from FollowQRDialog)
2026-04-09 08:02:26 -05:00
Alex Gleason 437b8de652 Remove right sidebar content and show profile fields inline 2026-04-08 23:34:03 -05:00
Alex Gleason adadb6ed53 Fix native file downloads: save directly to Documents on iOS/Android 2026-04-08 22:54:46 -05:00
Alex Gleason f7c90a4a23 Remove trending hashtags section from logged-out homepage 2026-04-08 22:28:22 -05:00
Alex Gleason 82632bb76c Store nostr:login in secure storage on native platforms
Use capacitor-secure-storage-plugin to persist login credentials
(nsec keys) in iOS Keychain / Android KeyStore instead of plaintext
localStorage. Web behavior is unchanged. Existing native users are
auto-migrated on first launch: if secure storage is empty but
localStorage has data, it is moved over and the plaintext copy is
removed.

Also ignore ios/ directory in ESLint (Capacitor-generated files).
2026-04-08 22:20:48 -05:00
Alex Gleason 221d3f4aff Merge branch 'mobile-search' 2026-04-08 22:11:38 -05:00
Alex Gleason 6a1a462ab0 Upgrade @nostrify/react to ^0.5.0 (async storage support)
Upgrade to the new version that includes the NLoginStorage interface
and storage/fallback props on NostrLoginProvider for pluggable async
storage backends (e.g. Capacitor Secure Storage).

- Add resolve.dedupe for react/react-dom to prevent dual-React issues
- Update NoteContent tests to use async findBy* queries since the
  provider now always awaits storage initialization
2026-04-08 22:08:56 -05:00
Alex Gleason 5ee8bc1cc0 Improve mobile search UX: lock scroll, hide bottom nav, dismiss accessory bar, and fix close behavior 2026-04-08 22:04:26 -05:00
Alex Gleason 76d53859cf Simplify webxdc to always open in fullscreen panel 2026-04-08 20:47:46 -05:00
Alex Gleason e482afbd3f Fix sandbox origin isolation and Android build issues 2026-04-08 20:47:42 -05:00
Alex Gleason 11ff27efe2 Enable iOS swipe-back navigation and fix bottom nav layout 2026-04-08 20:47:37 -05:00
Alex Gleason 8f6f678132 Add safe area padding and fix fullscreen sandbox on iOS 2026-04-08 20:47:32 -05:00
Alex Gleason f25139103c Add native SandboxPlugin for iOS and Android 2026-04-08 20:47:28 -05:00
Alex Gleason 0028b506e7 Fix webxdc bridge: serve script via resolveFile instead of injectedScripts
SandboxFrame's virtual script serving intercepted /webxdc.js and served
the empty placeholder content before resolveFile was ever called. The
dynamically generated bridge script (which embeds selfAddr etc.) was
never reaching the iframe.

Move bridge serving and HTML injection into resolveFileWithBridge so
the content is served from bridgeScriptRef after onReady populates it.
2026-04-08 16:55:01 -05:00
Alex Gleason 926c27d51c Fix webxdc race condition: await onReady before sending init
The sandbox frame was sending init immediately and calling onReady
concurrently, so fetch requests arrived before the archive was
downloaded and unzipped. Now onReady is awaited before init is sent,
matching the original Webxdc behavior.
2026-04-08 16:50:44 -05:00
Alex Gleason c4454ee2a1 Refactor iframe.diy usage into unified SandboxFrame component
Extract duplicated sandbox protocol logic from NsitePreviewDialog and
Webxdc into a single SandboxFrame component. Shared utilities (MIME
types, base64, HTML injection, JSON-RPC types) move to src/lib/sandbox/.

Add configurable sandboxDomain to AppConfig so the iframe.diy domain
can be overridden via ditto.json, preparing for native Capacitor
implementations.

Strip unused console/navigation/error RPC from previewInjectedScript,
leaving only the /index.html path normalization.
2026-04-08 16:41:23 -05:00
Chad Curtis e56737f776 Fix blobbi discovery: query by author instead of relying on profile.has[]
The Blobbi collection was previously discovered via the profile's has[] tag
list, meaning any blobbi whose d-tag was missing from that secondary index
would be invisible to the user despite existing on the relay.

Now useBlobbisCollection() without args queries all kind 31124 events by
author + ecosystem namespace tag — the user authored these events, so that
is the source of truth. The profile.has[] list is still used for selection
ordering preference, but no longer gates discovery.

The dList parameter remains available for targeted fetches (e.g. the
companion layer only needs one specific blobbi).
2026-04-08 11:02:03 -05:00
Chad Curtis feb6c1a9f6 Add drop shadow and solid gradient to overflow tab arrows 2026-04-08 10:27:17 -05:00
Chad Curtis 6f8d225597 Increase overflow tab arrow stroke to 4 and boost contrast 2026-04-08 10:22:04 -05:00
Chad Curtis 9ecd99a6a1 Add 'Write a letter' option to profile more menu
Adds a Mail-icon menu item in the profile more menu for other users'
profiles. Navigates to /letters/compose?to={npub} so the recipient is
pre-filled, matching the same flow used by the notification reply button.
2026-04-08 04:01:11 -05:00
Chad Curtis 287097627d Hide delivery method when push disabled; fix persistent description
Only show the delivery method radio group when push notifications are
enabled. Update the persistent option description to explain it is for
devices that don't support push notifications (e.g. GrapheneOS).
2026-04-08 00:20:20 -05:00
Alex Gleason 5cb731e557 Merge remote-tracking branch 'origin/main' into wallet 2026-04-07 23:20:35 -05:00
Chad Curtis 3ee491a63b Add push vs persistent notification delivery option for Android
Default to push mode (no foreground service). Persistent mode with
the always-on background polling service is opt-in via the new
Delivery Method section in notification settings.

- Add notificationStyle ('push' | 'persistent') to EncryptedSettings
- Show radio group in NotificationSettings on native platforms
- Pass notificationStyle through Capacitor plugin to SharedPreferences
- DittoNotificationPlugin starts/stops foreground service on style change
- MainActivity only starts service on launch when style is persistent
- Re-enable unread polling on native when push mode is active
2026-04-07 10:54:30 -05:00
Chad Curtis 7944f73da3 fix: use fetchFreshEvent and preserve non-p-tags in Follow All handlers
FollowPackDetailContent, TeamSoapboxCard, and InitialSyncGate all had
handleFollowAll implementations that queried kind 3 directly (bypassing
fetchFreshEvent) and rebuilt the tag array with only p-tags, silently
dropping all non-p-tags (relay hints, petnames, etc.). They also did
not pass prev for published_at preservation.

Align all three with the safe pattern already used in FollowPage and
useFollowActions.
2026-04-07 09:03:07 -05:00
Chad Curtis 17c1936817 Support follow pack/set naddr identifiers on /follow URL
The /follow route now accepts naddr1 identifiers for follow packs
(kind 39089) and follow sets (kind 30000) in addition to npub/nprofile.

Renders an immersive fullscreen layout with pack info hero, avatar
stack, big Follow All CTA with status indicator, and Feed/Members
tabs using the standard SubHeaderBar arc.

Follow All uses the safe fetch-fresh -> modify -> publish pattern
with prev for published_at preservation.

Shared components (PackFeedTab, MemberCard, MemberCardSkeleton) and
parsePackEvent are reused from FollowPackDetailContent and packUtils.

Also fixes SubHeaderBar tab indicator positioning when innerClassName
centers the tab container (adds containerOffset + ResizeObserver for
layout-dependent recalculation).
2026-04-07 08:55:27 -05:00
Chad Curtis c570f4689d Merge branch 'curated-ditto-feed' into 'main'
Curate Ditto feed by curator follow list with photos, divines, videos, and music

See merge request soapbox-pub/ditto!164
2026-04-07 12:52:23 +00:00
Chad Curtis 064ab1e101 Address MR review: extract feed hook, fix cache key, add error handling, make curator configurable
- Remove unused 'authors' parameter from useInfiniteHotFeed
- Extract inline query from Feed.tsx into useCuratedDittoFeed hook
- Use content-based fingerprint for query key instead of list length
- Add error state handling so curator fetch failure shows empty state
  instead of infinite skeletons for first-time visitors
- Move hardcoded curator pubkey to AppConfig (curatorPubkey) so it
  can be overridden via ditto.json without a code change
- Remove LANDING_KINDS/LANDING_WEBXDC_FILTER from Feed.tsx (now in hook)
2026-04-07 07:48:23 -05:00
Alex Gleason 5660a1cb1b Add multi-signer PSBT signing for Bitcoin transactions
Split createBitcoinTransaction into buildUnsignedPsbt, signPsbtLocal,
and finalizePsbt so the signing step can be delegated to any signer.

Introduce BtcSigner interface and three extended signer classes:
- NSecSignerBtc: local Taproot signing with the raw private key
- NBrowserSignerBtc: delegates to window.nostr.signPsbt() (NIP-07)
- NConnectSignerBtc: sends sign_psbt RPC over NIP-46 relay channel

useCurrentUser now constructs Btc-extended signers instead of base
ones. signerWithNudge forwards signPsbt when present on the wrapped
signer. SendBitcoinDialog uses the new useBitcoinSigner hook instead
of useNsecAccess, enabling sending from all login types.
2026-04-07 00:16:36 -05:00
Alex Gleason c49afc7add Add Bitcoin send functionality with 3-step confirmation flow
Implement sending Bitcoin transactions from the wallet page. The send
flow uses a 3-step dialog: form entry, confirmation review, and success
result. Only available for nsec logins since extension/bunker signers
don't expose the raw private key needed for Taproot signing.

Fixes over the reference implementation:
- Send Max correctly subtracts estimated fees
- Address validation via bitcoinjs-lib (checksum + format)
- Fee estimation accounts for actual output count (1 vs 2)
- Confirmation step before broadcast (irreversible action)
- All API calls use mempool.space (consistent with existing code)
- Success links to in-app NIP-73 tx detail page

New files:
- src/hooks/useNsecAccess.ts: extract private key from nsec login
- src/components/SendBitcoinDialog.tsx: 3-step send dialog

New functions in src/lib/bitcoin.ts:
- fetchUTXOs, getFeeRates, broadcastTransaction
- validateBitcoinAddress, estimateFee, maxSendable
- createBitcoinTransaction (PSBT construction + Taproot signing)
- npubToBitcoinAddress, btcToSats
2026-04-06 21:35:13 -05:00
Alex Gleason 64bac10758 Fix baseline alignment of Bitcoin txid/address in comment context rows
The link was using inline-flex items-center with child spans at different
font sizes (text-sm 'transaction' + text-xs monospace hash). Flexbox
center-aligns by box center, not text baseline, causing the smaller text
to appear shifted up. Changed to plain inline text flow so the browser's
natural baseline alignment handles mixed font sizes correctly.
2026-04-06 21:14:37 -05:00
Alex Gleason e74cd1efbb Add NIP-73 Bitcoin transaction and address detail pages
Integrate Bitcoin content into the /i/* external content system using NIP-73
identifiers (bitcoin:tx:{txid} and bitcoin:address:{address}).

- Add bitcoin-tx and bitcoin-address types to ExternalContent parser
- Create BitcoinTxHeader with mempool.space-style inputs/outputs flow view
- Create BitcoinAddressHeader with balance, stats, and recent transactions
- Add useBitcoinTx and useBitcoinAddress hooks (mempool.space Esplora API)
- Switch all Bitcoin API calls from blockstream.info to mempool.space
- Update WalletPage to link transactions to /i/bitcoin:tx:{txid} pages
- Remove unused blockExplorerAddress/blockExplorerTx config fields
- Add compact Bitcoin previews for embedded note contexts
2026-04-06 20:58:17 -05:00
Alex Gleason 773592f9dd Make block explorer URLs configurable via AppConfig URI templates
Add blockExplorerAddress and blockExplorerTx fields as RFC 6570 URI
templates with {address} and {txid} variables respectively. Default to
mempool.space instead of blockstream.info. Wallet page uses UriTemplate
to fill the configured templates.
2026-04-06 19:57:28 -05:00
Alex Gleason 995088842a Hide transactions button and list when there are no transactions 2026-04-06 19:49:52 -05:00
Alex Gleason 4abc45a849 Replace explorer link with collapsible Transactions toggle
Transactions button with chevron replaces the 'View on explorer' link.
Clicking toggles the tx list open/closed with a smooth accordion slide
using CSS grid-template-rows animation. Chevron rotates on open.
2026-04-06 19:43:56 -05:00
Alex Gleason 5ce2d3d8b4 Add transaction history to wallet page
Fetch transactions from Blockstream Esplora API, compute net amount per
tx relative to the user's address, and display as a list below the QR
code. Each row shows receive/send direction, relative date, USD amount
(with BTC underneath), and links to the block explorer. Includes loading
skeletons and empty state.
2026-04-06 19:34:16 -05:00
Alex Gleason 4391743695 Show wallet balance in USD with BTC underneath
Fetch BTC/USD price from CoinGecko (refreshes every 60s). Display USD
as the hero balance, BTC amount as the secondary line. Remove sats
display entirely. Pending amounts also shown in USD.
2026-04-06 19:28:21 -05:00
Alex Gleason a145f92bcb Simplify wallet page to modern crypto wallet UX
Remove outer Balance card wrapper, stats grid, and How It Works section.
Balance is now the hero element, centered with QR code below and a
compact pill-shaped address with inline copy. Clean, minimal layout.
2026-04-06 19:20:05 -05:00