Commit Graph

4447 Commits

Author SHA1 Message Date
Chad Curtis 9fa5b84b0b Eliminate duplicate profile query by deriving sidebar media from feed data
ProfileRightSidebar was making its own relay query (limit 50) with nearly
identical filters to the profile feed query (limit 60), causing two redundant
REQs on every profile load. Replaced the useProfileMedia hook with a pure
extractMedia function that derives media items from the feed events already
fetched by useProfileFeed. Reposts are filtered out so only the profile
owner's own media appears in the sidebar grid.

Also fixed .sort() mutating the kinds array in-place in useProfileFeed.
2026-02-21 23:22:55 -06:00
Alex Gleason b835b2e084 eoseTimeout: 100 2026-02-21 23:20:47 -06:00
Alex Gleason f6c86f1786 Drop eoseTimeout to 1 2026-02-21 23:12:22 -06:00
Alex Gleason 250543c28c Merge branch 'main' of gitlab.com:soapbox-pub/ditto-mew 2026-02-21 23:03:40 -06:00
Alex Gleason 746b30d759 Scroll to top when clicking logo or Home button while already on home feed 2026-02-21 23:01:10 -06:00
Chad Curtis 89b2955364 Remove 3-second loading delay for sidebar trends 2026-02-21 22:54:36 -06:00
Alex Gleason 51459d492d Defer YouTube iframe until user clicks play for privacy
Replace the eagerly-loaded iframe with a click-to-load facade that
shows the video thumbnail and a play button. No requests are made
to YouTube until the user explicitly activates the player. Once
clicked, the iframe mounts with autoplay=1 for a seamless transition.
2026-02-21 22:53:41 -06:00
Alex Gleason dba0a523c6 Rewrite sparklines to use pre-computed label events instead of raw posts
useTagSparklines was fetching 250 kind 1 posts across all relays to
count hashtag usage client-side. Now fetches 7 kind 1985 label events
(1 per day) from relay.ditto.pub, reading pre-computed usage stats
directly from the tags.
2026-02-21 22:45:18 -06:00
Alex Gleason cbd767d62c Remove redundant zap receipt fetching — NIP-85 provides zap stats
useZaps was fetching all kind 9735 events for every post to count zaps
and sum sats. NIP-85 already provides these stats via useEventStats.
Stripped useZaps down to payment flow only. Deleted unused ZapButton
component.
2026-02-21 22:29:29 -06:00
Alex Gleason 1b96889614 Consolidate duplicate NIP-85 stat fetching into single hook
useEventStats now consumes useNip85EventStats instead of having its
own inline NIP-85 query. This eliminates duplicate kind 30383 queries
for every post on the feed — both hooks now share the same TanStack
Query cache entry.
2026-02-21 22:23:23 -06:00
Alex Gleason f2660e4a88 Use lightweight limit:1 query for notification dot instead of fetching full list 2026-02-21 22:21:45 -06:00
Alex Gleason b9c3679c1a Stop fetching kind 7/9735 events when NIP-85 stats are available
useEventStats was always firing a second query for reactions, zaps,
and quotes even when NIP-85 stats were found. With nip85OnlyMode
enabled this fetched hundreds of events across all relays for no
reason. Now returns NIP-85 data directly without a second query.
2026-02-21 22:15:45 -06:00
Alex Gleason da963519ff Remove bulk profile prefetch that fetched all followed users on every page load
useFollowedProfiles was fetching kind 0 events for all 435 followed
users via useAuthors on every page load, just to enable client-side
search matching. This sent a massive query to all relays unnecessarily.

Instead, useSearchProfiles now uses the already-cached follow list
pubkeys (from useFollowList) to sort relay search results, putting
followed users first without any extra network requests.
2026-02-21 22:09:21 -06:00
Alex Gleason 77f699acc2 Verify events with nostr-wasm 2026-02-21 21:45:39 -06:00
Alex Gleason e0932b38d7 Add NostrBatcher to automatically batch Nostr queries
NostrBatcher is a transparent proxy that wraps NPool and intercepts
.query() calls. When it detects a batchable single-item filter pattern,
the request is held for a microtask. If more queries with the same
pattern arrive in the same frame, they're combined into one REQ.

Client code doesn't need to know batching exists -- it calls
nostr.query() as usual. The proxy is provided as the 'nostr' object
in NostrContext, so all existing hooks get automatic batching.

Batchable patterns:
- { kinds: [0], authors: [pubkey] } -> profiles
- { ids: [id] } -> events by ID
- { kinds: [7], authors: [user], '#e': [id] } -> reactions
- { kinds: [k], authors: [a], '#d': [d] } -> NIP-85 stats, etc.
- { kinds: [...], '#e': [id] } -> interaction events (zaps, etc.)
- Multi-filter arrays where all filters reference the same #e/#q ID

Also removes manual pre-caching from useFeed (fetchAndCacheAuthors,
fetchAndCacheEventStats) which is no longer needed, and fixes
useComments queryKey instability.
2026-02-21 20:00:07 -06:00
Alex Gleason 2df5a362f9 Merge branch 'main' of gitlab.com:soapbox-pub/ditto-mew 2026-02-21 18:32:33 -06:00
Alex Gleason 3375e89e68 Fix fucked up author loop 2026-02-21 18:25:04 -06:00
Chad Curtis 54363f2431 Fix mention autocomplete clipped inside compose modal
Portal the dropdown to document.body using createPortal so it escapes
the modal's overflow-hidden and stacking context. Positions are now
calculated in viewport coordinates with position:fixed.
2026-02-21 17:36:46 -06:00
Alex Gleason 1fe52beb23 Merge branch 'main' of gitlab.com:soapbox-pub/ditto-mew 2026-02-21 17:31:07 -06:00
Alex Gleason 40078bb093 Seed zero stats for events without NIP-85 data to prevent lazy re-fetches 2026-02-21 17:29:58 -06:00
Chad Curtis bba57e83e4 Fix compose preview to match real note rendering
Mentions now trigger the Edit/Preview toggle. Compose preview uses
NoteContent directly for link previews and embeds instead of rendering
them separately, matching how real notes display. LinkPreview falls back
to a domain+favicon card when OEmbed data is unavailable. Added
disableEmbeds prop to NoteContent for future use.
2026-02-21 17:27:08 -06:00
Alex Gleason d41a920849 Move NIP-85 stats prefetching into useFeed queryFn, remove useBatchEventStats
The effect-based useBatchEventStats hook was unreliable due to race
conditions with Feed re-renders and abort cycles. Instead, fetch kind
30383 stats directly in useFeed's queryFn alongside author prefetching,
seeding the 'event-stats' cache so useEventStats resolves instantly.
2026-02-21 17:22:40 -06:00
Chad Curtis c28797be97 Add follow-prioritized search autocomplete and @mention support
Search autocomplete now prioritizes followed profiles via client-side
matching merged ahead of relay results. A following badge (UserRoundCheck)
appears on avatars in both the sidebar dropdown and search page accounts
tab. The compose box supports @mentions with an inline autocomplete
dropdown that inserts nostr:npub1 URIs and adds p tags on publish.
2026-02-21 16:58:40 -06:00
Alex Gleason f453df10a0 Replace CORS proxy HTML scraping with configurable OEmbed link preview endpoint
Link previews now fetch from a dedicated link preview service (default:
https://fetch.ditto.pub/link/{url}) that returns OEmbed JSON, instead of
fetching raw HTML through a CORS proxy and parsing OG tags client-side.

- Add linkPreviewUrl config with RFC 6570 URI template support
- Rewrite useLinkPreview to consume OEmbed responses
- Update LinkPreview to render OEmbed fields (thumbnail_url, provider_name, etc.)
- Use ExternalFavicon component for link preview favicons (consistent with rest of app)
- Add linkPreviewUrl setting to Advanced Settings UI
- Generalize faviconUrl.ts into templateUrl() with {url} variable support
2026-02-21 16:28:23 -06:00
Alex Gleason 2da1bc1146 fetch.ditto.pub works now 2026-02-21 16:21:55 -06:00
Alex Gleason 325fd02d3b Switch to my link worker for favicon fetching 2026-02-21 16:07:36 -06:00
Alex Gleason 0c583d19af Fix CI: resolve ESLint errors and Node.js 22 localStorage test failures 2026-02-21 15:25:18 -06:00
Alex Gleason cfb5659020 Replace DomainFavicon with ExternalFavicon using configurable favicon URL template
The old DomainFavicon component had a multi-step fallback chain (HTML scraping,
direct URL guessing, then the configured provider) which meant the configurable
setting was almost never reached. The new ExternalFavicon component uses the
configured favicon URL template directly via RFC 6570 URI templates.

- Rename config field faviconProvider -> faviconUrl
- Add faviconUrl() utility with uri-templates for RFC 6570 support
- Remove local Favicon component from ProfileRightSidebar
- Update all consumers to use ExternalFavicon
2026-02-21 15:14:30 -06:00
Alex Gleason c083c9902f Remove localStorage author cache and redundant batch prefetch from Feed
The mew:authorCache localStorage layer and the useAuthors-based batch
prefetch in Feed.tsx are no longer needed — useFeed now pre-caches all
author profiles (kind 0) directly into the query cache during feed
fetching. This removes ~200 lines of redundant caching infrastructure.
2026-02-21 14:29:02 -06:00
Alex Gleason 13fc22fae4 Seed event cache from feed items so useEvent resolves instantly 2026-02-21 14:22:32 -06:00
Alex Gleason cd2bceb805 Collect pubkeys from e and q tags for author pre-caching 2026-02-21 14:18:11 -06:00
Alex Gleason 2fe9f272e5 Also pre-cache kind 0 for pubkeys mentioned in p tags of feed events 2026-02-21 14:13:17 -06:00
Alex Gleason 632a45c629 Pre-fetch and cache kind 0 author profiles in useFeed for all feed tabs
Adds a fetchAndCacheAuthors helper inside useFeed's queryFn that batch-fetches
kind 0 metadata for all authors in a feed page and seeds the ['author', pubkey]
query cache. This eliminates redundant per-author relay queries when useAuthor
is called to render post cards. All three feed branches (communities, follows,
global) now pre-cache authors. The communities branch reuses its existing NIP-05
metadata fetch to avoid a duplicate relay round-trip.
2026-02-21 14:08:49 -06:00
Alex Gleason 5b49ea8ea4 Scope Suspense to content area with skeleton fallback
Moved the Suspense boundary from App.tsx (which wrapped the entire app)
into MainLayout around just the Outlet and right sidebar. The left
sidebar, mobile nav, and mobile top bar now persist while lazy page
chunks load. A skeleton fallback fills the content area during loading.
2026-02-21 13:37:09 -06:00
Alex Gleason 3daf38c9fd Use persistent layout route to prevent sidebar remounting on navigation
MainLayout was rendered inside each page component, causing the entire
layout (sidebar, mobile nav, drawer) to be destroyed and recreated on
every route change. This made navigations feel like full page refreshes.

Restructured to use a React Router layout route with <Outlet />, so
MainLayout persists across navigations and only the page content swaps.
Pages that need custom layout options (showFAB, rightSidebar, etc.) use
the new useLayoutOptions hook to configure the persistent layout.
2026-02-21 13:35:27 -06:00
Alex Gleason d2ea8afb6b Navigate profile links directly to canonical URL (nip05 or npub)
Use getProfileUrl() for all profile navigation instead of the static
/profile path. Compose box avatar, sidebar Profile link, sidebar account
popover, and mobile drawer all now link directly to the user's profile.
The /profile route becomes a redirect for backward compatibility.
2026-02-21 12:59:43 -06:00
Alex Gleason 7aecb29e10 Merge branch 'main' of gitlab.com:soapbox-pub/ditto-mew 2026-02-21 12:48:29 -06:00
Alex Gleason f8774c7a6b Use standard imeta field names: 'summary' for app name, 'image' for icon 2026-02-21 12:20:21 -06:00
Alex Gleason 25a53e32cd Fix icon upload CSP violation: create File from raw bytes instead of fetching data URI 2026-02-21 12:16:40 -06:00
Alex Gleason 749900f853 Extract webxdc name and icon from .xdc manifest, display in embed card
- Add webxdcMeta.ts utility to extract name from manifest.toml and
  icon (icon.png/icon.jpg) from the .xdc ZIP using fflate
- Upload extracted icon to Blossom during file attach
- Add webxdc_name and webxdc_icon properties to imeta tags
- Parse webxdc_name/webxdc_icon in NoteCard and PostDetailPage
- Show app icon and name in WebxdcEmbed launch card, remove filename
  and 'Stateful · shared session' text
2026-02-21 12:15:26 -06:00
Chad Curtis 7b38637af5 Add 'shared a deck' action header for magic deck events in feed
Shows '<cards icon> <name> shared a deck' above magic deck cards in the
feed, matching the pattern of treasure/stream action headers. Suppressed
when the card is already showing a repost header to avoid double headers.
2026-02-21 05:27:56 -06:00
Chad Curtis 3d1ec85f77 Use Palette icon for visual toggle and render deck content on post detail page
Replace Image icon with Palette for the list/visual toggle button.
Add MagicDeckContent to PostDetailPage kind dispatch so deck events
show their full content when viewed as a post detail (naddr route).
2026-02-21 05:22:46 -06:00
Chad Curtis 3c4d6d0efc Fix scroll and add card lightbox gallery to magic deck content
Replace ScrollArea with overflow-y-auto for reliable scrolling in both
list and visual views. Clicking any card (in either view) opens a
full-screen lightbox showing the card image from Scryfall at large size,
with prev/next navigation via arrows, keyboard, and swipe gestures.
Shows card name and foil indicator in the lightbox header.
2026-02-21 05:19:38 -06:00
Chad Curtis b4e3e63a8f Add visual spoiler toggle for magic deck card lists
Decklist box now has a List/Visual toggle in the header. Visual mode
renders a 4-column grid of card images loaded from Scryfall's API using
set/collector_number for exact printings or card name as fallback.
Includes quantity badges, foil shimmer overlay, and graceful fallback
to card name text when images fail to load.
2026-02-21 05:17:11 -06:00
Chad Curtis d0135d1014 Rework MagicDeckContent: single-line meta row and scrollable card list
Merge format badges, card count, and sideboard count onto one line.
Replace expand/collapse with a ScrollArea box (max 240px) containing
the full decklist with sideboard inline below a separator header.
2026-02-21 05:10:08 -06:00
Chad Curtis b7596c1265 Add CardsIcon SVG and hide t:hidden events for magic decks and geocaches
Replace Layers icon with custom CardsIcon (stacked cards SVG) across all
navigation and settings UIs. Filter out events tagged t:unlisted for kind
37381 (magic decks) and t:hidden for kind 37516 (geocaches) in NoteCard.
2026-02-21 05:07:26 -06:00
Alex Gleason 6a9733452a Remove sandbox attr from webxdc iframe — unique subdomains provide isolation, sandbox breaks CORS 2026-02-21 05:05:23 -06:00
Chad Curtis ffc7672183 Add Magic: The Gathering deck lists as extra kind (NIP-MG, kind 37381)
Support kind 37381 addressable events for MTG deck sharing per NIP-MG spec.
Includes MagicDeckContent component with card list rendering, format/archetype
badges, commander/companion display, sideboard, foil indicators, and
expand/collapse for large decks. Adds /decks route and sidebar navigation.
2026-02-21 05:03:46 -06:00
Alex Gleason 8978aca463 Fix webxdc imeta: append .xdc extension to Blossom URLs so media detection and UUID tagging work 2026-02-21 05:02:12 -06:00
Alex Gleason 13f05936dd Add webxdc support: attach .xdc files as stateful mini apps with kind 4079 state sync
- Create useWebxdc hook bridging the Webxdc API with Nostr kind 4079 state updates
- Create WebxdcEmbed component with launch-on-click UX for sandboxed iframe rendering
- Update ComposeBox to accept .xdc files and generate imeta tags with webxdc UUID
- Update NoteCard and PostDetailPage to detect and render webxdc attachments
- Skip .xdc URLs in NoteContent to avoid rendering them as raw links
2026-02-21 04:57:05 -06:00