fix(push-demo): preserve thread root for temporary replies

Signed-off-by: Tom Brow <tomb@block.xyz>
Co-authored-by: Codex <noreply@openai.com>
Ai-assisted: true
This commit is contained in:
Tom Brow
2026-08-26 10:37:48 -07:00
parent 808b91b93f
commit aa31a9e091
2 changed files with 53 additions and 4 deletions

View File

@@ -456,10 +456,7 @@ public final class BuzzPushNotificationResolver: BuzzPushNotificationResolving {
let conversation = channel.flatMap {
communicationConversation(event: event, community: community, channel: $0)
}
let rootEventID =
event.tags.first {
$0.count >= 4 && $0[0] == "e" && $0[3] == "root" && !$0[1].isEmpty
}?[1] ?? event.id
let rootEventID = replyRootEventID(for: event)
let replyKind = [45_001, 45_003].contains(event.kind) ? 45_003 : 9
return BuzzPushResolution(
title: profile?.displayName ?? shortPubkey(event.pubkey),
@@ -491,6 +488,21 @@ public final class BuzzPushNotificationResolver: BuzzPushNotificationResolving {
)
}
/// Resolves the NIP-10 root carried by an incoming message. A direct reply
/// to a root commonly carries only a `reply` marker, so that parent is also
/// the thread root. Nested replies carry an explicit `root` marker, which
/// remains authoritative.
private static func replyRootEventID(for event: VerifiedNostrEvent) -> String {
for marker in ["root", "reply"] {
if let value = event.tags.first(where: {
$0.count >= 4 && $0[0] == "e" && $0[3] == marker && !$0[1].isEmpty
})?[1] {
return value
}
}
return event.id
}
private static func ephemeralProfile(
event: VerifiedNostrEvent,
communityID: String,

View File

@@ -129,6 +129,43 @@ final class BuzzPushNotificationResolverTests: XCTestCase {
XCTAssertEqual(result?.0.body, "lower ID")
}
func testReplyContextUsesReplyMarkerAsRootForDirectReply() throws {
let incoming = event(
id: "incoming-reply",
content: "Reply to the thread root",
tags: [
["h", Self.channelID],
["e", "thread-root", "", "reply"],
]
)
let result = BuzzPushNotificationResolver.decodeResolution(
events: [incoming],
community: community()
)
XCTAssertEqual(try XCTUnwrap(result?.0.replyContext).rootEventID, "thread-root")
}
func testReplyContextPrefersExplicitRootForNestedReply() throws {
let incoming = event(
id: "incoming-nested-reply",
content: "Nested reply",
tags: [
["h", Self.channelID],
["e", "thread-root", "", "root"],
["e", "immediate-parent", "", "reply"],
]
)
let result = BuzzPushNotificationResolver.decodeResolution(
events: [incoming],
community: community()
)
XCTAssertEqual(try XCTUnwrap(result?.0.replyContext).rootEventID, "thread-root")
}
func testResolveSucceedsAndMutatesGatewayContent() throws {
let event = try JSONDecoder().decode(
VerifiedNostrEvent.self,