fix: improve zap/nutzap rendering in chat

- Add mb-1 margin bottom to zap messages for spacing
- Show inline reply preview for zaps that target specific messages
- Fix nutzap amount extraction to handle multiple proof tags
- Extract replyTo from e-tag for nutzaps
- Pass nutzap event to RichText for custom emoji rendering
This commit is contained in:
Claude
2026-01-12 13:38:50 +00:00
parent 50b85a5ce5
commit 9305fb7411
2 changed files with 39 additions and 17 deletions

View File

@@ -181,9 +181,15 @@ const MessageItem = memo(function MessageItem({
// Zap messages have special styling with gradient border
if (message.type === "zap") {
const zapRequest = message.event ? getZapRequest(message.event) : null;
// For NIP-57 zaps, reply target is in the zap request's e-tag
// For NIP-61 nutzaps, reply target is already in message.replyTo
const zapReplyTo =
message.replyTo ||
zapRequest?.tags.find((t) => t[0] === "e")?.[1] ||
undefined;
return (
<div className="pl-2">
<div className="pl-2 mb-1">
<div
className="p-[1px]"
style={{
@@ -213,11 +219,19 @@ const MessageItem = memo(function MessageItem({
<Timestamp timestamp={message.timestamp} />
</span>
</div>
{zapReplyTo && (
<ReplyPreview
replyToId={zapReplyTo}
adapter={adapter}
conversation={conversation}
onScrollToMessage={onScrollToMessage}
/>
)}
{message.content && (
<RichText
content={message.content}
event={zapRequest || undefined}
className="mt-1 text-sm leading-tight break-words"
event={zapRequest || message.event}
className="text-sm leading-tight break-words"
options={{ showMedia: false, showEventEmbeds: false }}
/>
)}

View File

@@ -676,22 +676,29 @@ export class Nip29Adapter extends ChatProtocolAdapter {
const pTag = event.tags.find((t) => t[0] === "p");
const recipient = pTag?.[1] || "";
// Amount is sum of proof amounts from the proof tag
// proof tag format: ["proof", "<JSON proofs array>"]
const proofTag = event.tags.find((t) => t[0] === "proof");
// Reply target is the e-tag (the event being nutzapped)
const eTag = event.tags.find((t) => t[0] === "e");
const replyTo = eTag?.[1];
// Amount is sum of proof amounts from all proof tags
// NIP-61 allows multiple proof tags, each containing a JSON-encoded Cashu proof
let amount = 0;
if (proofTag?.[1]) {
try {
const proofs = JSON.parse(proofTag[1]);
if (Array.isArray(proofs)) {
amount = proofs.reduce(
(sum: number, proof: { amount?: number }) =>
sum + (proof.amount || 0),
0,
);
for (const tag of event.tags) {
if (tag[0] === "proof" && tag[1]) {
try {
const proof = JSON.parse(tag[1]);
// Proof can be a single object or an array of proofs
if (Array.isArray(proof)) {
amount += proof.reduce(
(sum: number, p: { amount?: number }) => sum + (p.amount || 0),
0,
);
} else if (typeof proof === "object" && proof.amount) {
amount += proof.amount;
}
} catch {
// Invalid proof JSON, skip this tag
}
} catch {
// Invalid proof JSON, amount stays 0
}
}
@@ -709,6 +716,7 @@ export class Nip29Adapter extends ChatProtocolAdapter {
content: comment,
timestamp: event.created_at,
type: "zap", // Render the same as zaps
replyTo,
protocol: "nip-29",
metadata: {
encrypted: false,