fix: use semantic author for zap targeting

When zapping certain event kinds (zaps, streams), use the semantic
author instead of event.pubkey:
- Zaps (9735): Target the zapper, not the lightning service
- Streams (30311): Target the host, not the event publisher

Changes:
- Extract getSemanticAuthor() to shared utility (src/lib/semantic-author.ts)
- Update BaseEventRenderer to use semantic author when opening zap dialog
- Update ZapWindow to resolve recipient using semantic author
- Refactor DynamicWindowTitle to use shared utility

This ensures that when you zap an event, you're zapping the right person
(the one who semantically "owns" or created the event), not just whoever
signed it.
This commit is contained in:
Claude
2026-01-19 10:32:53 +00:00
parent ab64fc75f4
commit 813ee13f20
4 changed files with 52 additions and 31 deletions

View File

@@ -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 ', '@ ')

View File

@@ -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);

View File

@@ -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,
});
};

View File

@@ -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;
}
}