refactor(quartz): move lowercase-p notification check into PTag

The default Event.notifies body was hand-rolling a tag scan for lowercase
`p`, duplicating the tag-shape knowledge that already lives in PTag.
Give PTag ownership of the check:

    PTag.isNotifying(tags, userHex)  // iterates tags and uses PTag.isTagged

Event.notifies now delegates to it. Kinds that override (CommentEvent,
WakeUpEvent) still work — they compose or replace the default as before.

https://claude.ai/code/session_01GQDJxiHPogdzCNhUBN7Pjc
This commit is contained in:
Claude
2026-04-24 16:36:21 +00:00
parent 10bbd0ddb2
commit 82e4448b58
2 changed files with 29 additions and 12 deletions
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip01Core.core
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.kotlinSerialization.EventKSerializer
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.serialization.Serializable
@@ -48,23 +49,20 @@ open class Event(
/**
* Returns true when this event is intended to notify [userHex].
*
* The default uses the lowercase `p` tag as the notification channel,
* which is the convention for most kinds that address a single recipient
* or a set of mentions (NIP-01 mentions, NIP-04/17 DMs, NIP-25 reactions,
* NIP-28 chat messages, NIP-34 git issues/patches, NIP-57 zap receipts,
* NIP-68 pictures, NIP-71 videos, NIP-84 highlights, NIP-AC calls, chess,
* WakeUps, wiki/long-form/poll mentions).
* The default delegates to
* [com.vitorpamplona.quartz.nip01Core.tags.people.PTag.isNotifying],
* i.e. "any lowercase `p` tag addresses the user" — the convention for
* most kinds that address a single recipient or a set of mentions
* (NIP-01 mentions, NIP-04/17 DMs, NIP-25 reactions, NIP-28 chat
* messages, NIP-34 git issues/patches, NIP-57 zap receipts, NIP-68
* pictures, NIP-71 videos, NIP-84 highlights, NIP-AC calls, chess,
* wiki/long-form/poll mentions).
*
* Subclasses override when the NIP defines additional notification tags
* — e.g. NIP-22 comments use uppercase `P` for the root author in
* addition to lowercase `p` for the direct-reply author.
*/
open fun notifies(userHex: HexKey): Boolean {
for (tag in tags) {
if (tag.size >= 2 && tag[0] == "p" && tag[1] == userHex) return true
}
return false
}
open fun notifies(userHex: HexKey): Boolean = PTag.isNotifying(tags, userHex)
fun toJson(): String = OptimizedJsonMapper.toJson(this)
@@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Tag
import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
@@ -54,6 +55,24 @@ data class PTag(
key: HexKey,
): Boolean = tag.has(1) && tag[0] == TAG_NAME && tag[1] == key
/**
* Returns true if any `p` tag inside [tags] addresses [userHex].
* This is the canonical check for "does this event notify this user
* under the lowercase-`p` convention" and is the default used by
* [Event.notifies]. Kinds that address recipients through other tag
* names (e.g. NIP-22 uppercase `P` for root authors) override
* [Event.notifies] and may combine this with their own checks.
*/
fun isNotifying(
tags: TagArray,
userHex: HexKey,
): Boolean {
for (tag in tags) {
if (isTagged(tag, userHex)) return true
}
return false
}
fun parse(tag: Tag): PTag? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }