@@ -68,7 +68,9 @@ fn mention_tags(mentions: &[&str]) -> Result<Vec<Tag>, String> {
6868 check_pubkey ( hex) ?;
6969 let lower = hex. to_ascii_lowercase ( ) ;
7070 if seen. insert ( lower. clone ( ) ) {
71+ // Parallel p (fan-out) + mention (explicit intent) for #2296 contract.
7172 tags. push ( tag ( vec ! [ "p" , & lower] ) ?) ;
73+ tags. push ( tag ( vec ! [ "mention" , & lower] ) ?) ;
7274 }
7375 }
7476 Ok ( tags)
@@ -403,6 +405,19 @@ pub fn build_forum_comment(
403405
404406/// Kind 40003 — edit a message with full content, media, emoji, mentions,
405407/// and optional monotonic link-preview suppression.
408+ ///
409+ /// Carries the full new content AND a fresh imeta tag set; the receiver
410+ /// overlays the imeta tags onto the original event so the rendered message
411+ /// reflects exactly the edited state. NIP-30 custom-emoji tags ride along the
412+ /// same way so an edited body's `:shortcode:`s stay resolvable (the send path
413+ /// attaches these too).
414+ ///
415+ /// `mentions` carries the pubkeys of mentions that are *newly added* by this
416+ /// edit (the caller diffs the edited body against the original). Only those get
417+ /// parallel `p` + `mention` tags so the newly-mentioned party is notified/woken,
418+ /// while a typo-fix edit that leaves the mention set unchanged emits neither
419+ /// and never re-wakes anyone. Mirrors the send path's `mention_tags` (dedup +
420+ /// lowercase); the receiver overlays these onto the original event's audience.
406421pub fn build_message_edit (
407422 channel_id : Uuid ,
408423 target_event_id : EventId ,
@@ -930,11 +945,11 @@ mod tests {
930945 assert_eq ! ( event. pubkey. to_hex( ) , TARGET_HEX ) ;
931946 }
932947
933- // ── build_message_edit `p`-tag emission (lane 8ace8eed) ──────────────
948+ // ── build_message_edit p+mention emission (lane 8ace8eed) ──────────────
934949 //
935950 // The composer diffs the edited body's mentions against the original and
936951 // hands `build_message_edit` only the *newly added* pubkeys. These tests
937- // pin the builder's contract given that contract : emit a `p` per added
952+ // pins the builder's contract: emit parallel `p` + `mention ` per added
938953 // mention (deduped, lowercased), and none when the added set is empty
939954 // (typo-fix edit) — so an unchanged mention set re-wakes nobody.
940955
@@ -962,20 +977,21 @@ mod tests {
962977 let tags = edit_tags ( & [ ALICE_HEX ] ) ;
963978 assert_eq ! ( tags[ 0 ] [ 0 ] , "h" ) ;
964979 assert_eq ! ( tags[ 1 ] [ 0 ] , "e" ) ;
965- // The `p` tag rides right after the `e` tag (insertion order).
980+ // `p` + parallel `mention` ride right after the `e` tag (insertion order).
966981 assert_eq ! ( tags[ 2 ] , vec![ "p" . to_string( ) , ALICE_HEX . to_string( ) ] ) ;
982+ assert_eq ! ( tags[ 3 ] , vec![ "mention" . to_string( ) , ALICE_HEX . to_string( ) ] ) ;
967983 }
968984
969985 #[ test]
970986 fn edit_with_no_added_mentions_emits_no_p_tag ( ) {
971987 // Typo-fix edit: mention set unchanged, so the composer passes `&[]`.
972- // The edit event must carry no `p` tag and re-wake nobody.
988+ // The edit event must carry no `p`/`mention` tags and re-wake nobody.
973989 let tags = edit_tags ( & [ ] ) ;
974990 assert ! (
975- !tags
976- . iter ( )
977- . any ( |t| t . first ( ) . map ( String :: as_str ) == Some ( "p" ) ) ,
978- "unchanged-mention edit must not emit any `p` tag , got {tags:?}"
991+ !tags. iter ( ) . any ( |t| {
992+ matches! ( t . first ( ) . map ( String :: as_str ) , Some ( "p" | "mention" ) )
993+ } ) ,
994+ "unchanged-mention edit must not emit p/mention tags , got {tags:?}"
979995 ) ;
980996 }
981997
@@ -995,5 +1011,18 @@ mod tests {
9951011 ) ;
9961012 assert_eq ! ( p_tags[ 0 ] , & vec![ "p" . to_string( ) , ALICE_HEX . to_string( ) ] ) ;
9971013 assert_eq ! ( p_tags[ 1 ] , & vec![ "p" . to_string( ) , BOB_HEX . to_string( ) ] ) ;
1014+ let mention_tags: Vec < & Vec < String > > = tags
1015+ . iter ( )
1016+ . filter ( |t| t. first ( ) . map ( String :: as_str) == Some ( "mention" ) )
1017+ . collect ( ) ;
1018+ assert_eq ! ( mention_tags. len( ) , 2 ) ;
1019+ assert_eq ! (
1020+ mention_tags[ 0 ] ,
1021+ & vec![ "mention" . to_string( ) , ALICE_HEX . to_string( ) ]
1022+ ) ;
1023+ assert_eq ! (
1024+ mention_tags[ 1 ] ,
1025+ & vec![ "mention" . to_string( ) , BOB_HEX . to_string( ) ]
1026+ ) ;
9981027 }
9991028}
0 commit comments