+
)
diff --git a/src/components/ZapStreamEmbeddedPlayer/index.tsx b/src/components/ZapStreamEmbeddedPlayer/index.tsx
index b16a67fa..ac9de1ab 100644
--- a/src/components/ZapStreamEmbeddedPlayer/index.tsx
+++ b/src/components/ZapStreamEmbeddedPlayer/index.tsx
@@ -44,7 +44,7 @@ export default function ZapStreamEmbeddedPlayer({
title="zap.stream"
src={embedSrc}
className={cn(
- 'rounded-lg border w-full max-w-[400px] aspect-video max-h-[min(70vh,28rem)]',
+ 'not-prose rounded-lg border w-full max-w-[400px] aspect-video max-h-[min(70vh,28rem)]',
className
)}
allow="autoplay; encrypted-media; fullscreen; clipboard-write"
diff --git a/src/hooks/useFetchProfile.tsx b/src/hooks/useFetchProfile.tsx
index 67b50c14..0cc37162 100644
--- a/src/hooks/useFetchProfile.tsx
+++ b/src/hooks/useFetchProfile.tsx
@@ -1,5 +1,6 @@
import { PROFILE_FETCH_PROMISE_TIMEOUT_MS } from '@/constants'
import { getProfileFromEvent } from '@/lib/event-metadata'
+import { getSeededProfileForNavigation } from '@/lib/profile-navigation-seed'
import { userIdToPubkey } from '@/lib/pubkey'
import { useNostrOptional } from '@/providers/nostr-context'
import { useNoteFeedProfileContext } from '@/providers/NoteFeedProfileContext'
@@ -312,6 +313,21 @@ export function useFetchProfile(id?: string, skipCache = false) {
return
}
}
+
+ // Userbadge → profile panel: feed row already had this profile, but secondary stack is outside NoteFeedProfileContext.
+ if (extractedPubkey && !skipCache) {
+ const fromNavigation = getSeededProfileForNavigation(extractedPubkey)
+ if (fromNavigation) {
+ setProfile(fromNavigation)
+ setPubkey(extractedPubkey)
+ setIsFetching(false)
+ setError(null)
+ processingPubkeyRef.current = extractedPubkey
+ initializedPubkeysRef.current.add(extractedPubkey)
+ effectRunCountRef.current.delete(extractedPubkey)
+ return
+ }
+ }
// CRITICAL: Early exit if already processing this exact pubkey - prevents infinite loops
// This check must happen FIRST, before any other logic
diff --git a/src/lib/nostr-parser.tsx b/src/lib/nostr-parser.tsx
index 218c9c27..3c269d25 100644
--- a/src/lib/nostr-parser.tsx
+++ b/src/lib/nostr-parser.tsx
@@ -539,15 +539,17 @@ function NostrInlineVideo({ mediaUrl, fallbackText }: { mediaUrl: string; fallba
)
}
return (
-
+
+
+
)
}
diff --git a/src/lib/profile-navigation-seed.ts b/src/lib/profile-navigation-seed.ts
new file mode 100644
index 00000000..2bcf5cab
--- /dev/null
+++ b/src/lib/profile-navigation-seed.ts
@@ -0,0 +1,25 @@
+import type { TProfile } from '@/types'
+
+const seeds = new Map
()
+
+function normPubkey(pubkey: string): string {
+ return pubkey.toLowerCase()
+}
+
+/**
+ * Call before navigating to `/users/…` from a userbadge (or anywhere we already have a {@link TProfile}).
+ * The secondary profile panel mounts outside {@link NoteFeedProfileContext}, so `useFetchProfile` would
+ * otherwise start a cold relay/IDB path even though the feed row already resolved this profile.
+ */
+export function seedProfileForNavigation(profile: TProfile): void {
+ if (!profile?.pubkey) return
+ seeds.set(normPubkey(profile.pubkey), profile)
+}
+
+/** Instant paint for `useFetchProfile` when opening the profile route from a seeded navigation. */
+export function getSeededProfileForNavigation(pubkey: string): TProfile | undefined {
+ const pk = normPubkey(pubkey)
+ const p = seeds.get(pk)
+ if (p && normPubkey(p.pubkey) === pk) return p
+ return undefined
+}
diff --git a/src/pages/secondary/ProfileEditorPage/index.tsx b/src/pages/secondary/ProfileEditorPage/index.tsx
index b45343c8..865011bb 100644
--- a/src/pages/secondary/ProfileEditorPage/index.tsx
+++ b/src/pages/secondary/ProfileEditorPage/index.tsx
@@ -472,21 +472,43 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
return (
{/* Banner & avatar uploaders */}
-
- {/* Avatar first in DOM + higher fetch priority so it loads before the banner (same as profile view). */}
+
+ {/* Banner under avatar in stacking order; fetchPriority still loads the pic first. */}
+
setUploadingBanner(true)}
+ onUploadEnd={() => setUploadingBanner(false)}
+ className="relative z-0 w-full cursor-pointer overflow-hidden"
+ accept={PROFILE_IMAGE_VIDEO_UPLOADER_ACCEPT}
+ maxCompressedSizeMb={5}
+ >
+
+
+ {uploadingBanner ? (
+
+ ) : (
+
+ )}
+
+
setUploadingAvatar(true)}
onUploadEnd={() => setUploadingAvatar(false)}
- className="z-10 w-24 h-24 absolute bottom-0 left-4 translate-y-1/2 border-4 border-background cursor-pointer rounded-full"
+ className="absolute bottom-0 left-4 z-20 h-24 w-24 translate-y-1/2 cursor-pointer rounded-full border-4 border-background md:h-48 md:w-48"
accept={PROFILE_IMAGE_VIDEO_UPLOADER_ACCEPT}
maxCompressedSizeMb={2}
>
-
+
{isVideo(avatar) ? (
-
+
{uploadingAvatar ? (
) : (
@@ -515,28 +537,6 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
)}
-
setUploadingBanner(true)}
- onUploadEnd={() => setUploadingBanner(false)}
- className="relative z-0 w-full cursor-pointer"
- accept={PROFILE_IMAGE_VIDEO_UPLOADER_ACCEPT}
- maxCompressedSizeMb={5}
- >
-
-
- {uploadingBanner ? (
-
- ) : (
-
- )}
-
-