diff --git a/src/components/DynamicWindowTitle.tsx b/src/components/DynamicWindowTitle.tsx index 834bed7..0ab9d65 100644 --- a/src/components/DynamicWindowTitle.tsx +++ b/src/components/DynamicWindowTitle.tsx @@ -23,9 +23,7 @@ import { import { getEventDisplayTitle } from "@/lib/event-title"; import { UserName } from "./nostr/UserName"; import { getTagValues } from "@/lib/nostr-utils"; -import { getLiveHost } from "@/lib/live-activity"; -import type { NostrEvent } from "@/types/nostr"; -import { getZapSender } from "applesauce-common/helpers/zap"; +import { getSemanticAuthor } from "@/lib/semantic-author"; // import { NipC7Adapter } from "@/lib/chat/adapters/nip-c7-adapter"; // Coming soon import { Nip29Adapter } from "@/lib/chat/adapters/nip-29-adapter"; import type { ChatProtocol, ProtocolIdentifier } from "@/types/chat"; @@ -37,31 +35,6 @@ export interface WindowTitleData { tooltip?: string; } -/** - * Get the semantic author of an event based on kind-specific logic - * Returns the pubkey that should be displayed as the "author" for UI purposes - * - * Examples: - * - Zaps (9735): Returns the zapper (P tag), not the lightning service pubkey - * - Live activities (30311): Returns the host (first p tag with "Host" role) - * - Regular events: Returns event.pubkey - */ -function getSemanticAuthor(event: NostrEvent): string { - switch (event.kind) { - case 9735: { - // Zap: show the zapper, not the lightning service pubkey - const zapSender = getZapSender(event); - return zapSender || event.pubkey; - } - case 30311: { - // Live activity: show the host - return getLiveHost(event); - } - default: - return event.pubkey; - } -} - /** * Format profile names with prefix, handling $me and $contacts aliases * @param prefix - Prefix to use (e.g., 'by ', '@ ') @@ -474,8 +447,21 @@ function useDynamicTitle(window: WindowInstance): WindowTitleData { const countHashtags = appId === "count" && props.filter?.["#t"] ? props.filter["#t"] : []; - // Zap titles - const zapRecipientPubkey = appId === "zap" ? props.recipientPubkey : null; + // Zap titles - load event to derive recipient if needed + const zapEventPointer: EventPointer | AddressPointer | undefined = + appId === "zap" ? props.eventPointer : undefined; + const zapEvent = useNostrEvent(zapEventPointer); + + // Derive recipient: use explicit pubkey or semantic author from event + const zapRecipientPubkey = useMemo(() => { + if (appId !== "zap") return null; + // If explicit recipient provided, use it + if (props.recipientPubkey) return props.recipientPubkey; + // Otherwise derive from event's semantic author + if (zapEvent) return getSemanticAuthor(zapEvent); + return null; + }, [appId, props.recipientPubkey, zapEvent]); + const zapRecipientProfile = useProfile(zapRecipientPubkey || ""); const zapTitle = useMemo(() => { if (appId !== "zap" || !zapRecipientPubkey) return null; diff --git a/src/components/ZapWindow.tsx b/src/components/ZapWindow.tsx index 2580eff..aea9b09 100644 --- a/src/components/ZapWindow.tsx +++ b/src/components/ZapWindow.tsx @@ -49,6 +49,7 @@ import { } from "@/lib/create-zap-request"; import { fetchInvoiceFromCallback } from "@/lib/lnurl"; import { useLnurlCache } from "@/hooks/useLnurlCache"; +import { getSemanticAuthor } from "@/lib/semantic-author"; export interface ZapWindowProps { /** Recipient pubkey (who receives the zap) */ @@ -98,8 +99,10 @@ export function ZapWindow({ ); }, [eventPointer]); - // Resolve recipient: use provided pubkey or derive from event author - const recipientPubkey = initialRecipientPubkey || event?.pubkey || ""; + // Resolve recipient: use provided pubkey or derive from semantic author + // For zaps, this returns the zapper; for streams, returns the host; etc. + const recipientPubkey = + initialRecipientPubkey || (event ? getSemanticAuthor(event) : ""); const recipientProfile = useProfile(recipientPubkey); diff --git a/src/components/nostr/kinds/BaseEventRenderer.tsx b/src/components/nostr/kinds/BaseEventRenderer.tsx index 9b4986a..1c43ca3 100644 --- a/src/components/nostr/kinds/BaseEventRenderer.tsx +++ b/src/components/nostr/kinds/BaseEventRenderer.tsx @@ -21,6 +21,7 @@ import { getSeenRelays } from "applesauce-core/helpers/relays"; import { EventFooter } from "@/components/EventFooter"; import { cn } from "@/lib/utils"; import { isAddressableKind } from "@/lib/nostr-kinds"; +import { getSemanticAuthor } from "@/lib/semantic-author"; /** * Universal event properties and utilities shared across all kind renderers @@ -173,9 +174,12 @@ export function EventMenu({ event }: { event: NostrEvent }) { }; } + // Get semantic author (e.g., zapper for zaps, host for streams) + const recipientPubkey = getSemanticAuthor(event); + // Open zap window with event context addWindow("zap", { - recipientPubkey: event.pubkey, + recipientPubkey, eventPointer, }); }; diff --git a/src/lib/semantic-author.ts b/src/lib/semantic-author.ts new file mode 100644 index 0000000..b28a2b3 --- /dev/null +++ b/src/lib/semantic-author.ts @@ -0,0 +1,41 @@ +/** + * Semantic Author Utilities + * + * Determines the "semantic author" of an event based on kind-specific logic. + * For most events, this is event.pubkey, but for certain event types the + * semantic author may be different (e.g., zapper for zaps, host for streams). + */ + +import type { NostrEvent } from "@/types/nostr"; +import { getZapSender } from "applesauce-common/helpers/zap"; +import { getLiveHost } from "@/lib/live-activity"; + +/** + * Get the semantic author of an event based on kind-specific logic + * Returns the pubkey that should be displayed as the "author" for UI purposes + * + * Examples: + * - Zaps (9735): Returns the zapper (P tag), not the lightning service pubkey + * - Live activities (30311): Returns the host (first p tag with "Host" role) + * - Regular events: Returns event.pubkey + * + * This function should be used when determining: + * - Who to display as the author in UI + * - Who to zap when zapping an event + * - Who the "owner" of the event is semantically + */ +export function getSemanticAuthor(event: NostrEvent): string { + switch (event.kind) { + case 9735: { + // Zap: show the zapper, not the lightning service pubkey + const zapSender = getZapSender(event); + return zapSender || event.pubkey; + } + case 30311: { + // Live activity: show the host + return getLiveHost(event); + } + default: + return event.pubkey; + } +}