Relays hold NIP-15 bodies far looser than the spec: prices as strings, durations as floats, `shipping` as an object, missing ids, and other apps' JSON on the same kinds (a typing game on 30017, offers on 30019, encrypted blobs on 30020). The strict kotlinx decoders dropped the whole body on the first surprise. - Nip15Json reads each field on its own: a bad field is null, a non-object body is null, nothing throws. Stall, product, marketplace, auction and bid confirmation data classes now have nullable fields; the id falls back to the `d` tag the spec makes equal to it. - AuctionData.read fills a body's gaps from the tag form auction apps publish (title/name, summary, image, starting_bid/start_price, currency, start_date/start_time, duration/end_time), so hash21/mkauction auctions read too. AuctionData.endsAt() adds the merchant's extensions. - Every NIP-15 event caches its parse (a failure included) and gains isWellFormed(): a stall/product/auction has a name, a market a name or merchants, a bid an auction and an amount, a confirmation a status and a bid. Product/auction stallAddress() names the 30017 they are sold from. - Bid confirmations read the spec's `duration_extended` (and the `duration_extension` Quartz used to write) and write the spec's name. - Tag-form auctions now index their title and summary: golden regenerated, searchable-kinds.md updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015aYRQWCjecNq2owE1gcSUw
28 KiB
Searchable kinds — the authoritative implementor table
Every concrete SearchableEvent implementor in Quartz, with the exact indexableContent()
expression. Update this file in the same PR as any change to the searchable set or to an
indexableContent() body (see the maintenance list in this skill's SKILL.md). Verified against the code 2026-10-08.
Counts: 201 concrete classes covering 201 kind values (GitStatusEvent spans 4 kinds;
kind 30063 is shared by two NIPs, kind 38000 by three classes, kind 30301 by two and kind 38385 with other apps — see the footnotes). The kind
set is exactly SearchableKinds.ALL (nip50Search/SearchableKinds.kt). File paths are under
quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/.
Separator legend: NL = joinToString("\n"), SP = joinToString(" ").
| Kind | Class | Package | indexableContent() |
|---|---|---|---|
| 0 | MetadataEvent | nip01Core/metadata | contactMetaData()?.let { listOfNotNull(it.name, it.displayName, it.about, it.nip05, it.lud06, it.lud16, it.website, it.picture, it.banner).joinToString(" ") } ?: "" (SP) |
| 1 | TextNoteEvent | nip10Notes | listOfNotNull(subject(), content) NL |
| 5 | DeletionRequestEvent | nip09Deletions | content (the deletion reason) |
| 9 | ChatEvent | nipC7Chats | content |
| 11 | ThreadEvent | nip7DThreads | listOfNotNull(title(), content) NL |
| 14 | ChatMessageEvent | nip17Dm/messages | listOfNotNull(subject(), content) NL |
| 15 | ChatMessageEncryptedFileHeaderEvent | nip17Dm/files | summary().orEmpty() — content is the encrypted file's URL, not text |
| 20 | PictureEvent | nip68Picture | (listOfNotNull(title(), content) + location() + imageDescriptions()) NL — location tag values, then each imeta's non-blank alt |
| 21 | VideoNormalEvent | nip71Video | inherited RegularVideoEvent: (listOfNotNull(title(), content) + segmentTitles()) NL |
| 22 | VideoShortEvent | nip71Video | inherited RegularVideoEvent: (listOfNotNull(title(), content) + segmentTitles()) NL |
| 24 | PublicMessageEvent | nipA4PublicMessages | content |
| 31 | ExternalCitationEvent | experimental/citations | inherited CitationEvent: listOfNotNull(title(), summary(), content, author(), publishedBy()) NL |
| 32 | HardcopyCitationEvent | experimental/citations | listOfNotNull(title(), chapterTitle(), summary(), content, author(), editor(), publishedIn(), publishedBy()) NL |
| 33 | PromptCitationEvent | experimental/citations | listOfNotNull(title(), summary(), content, author(), publishedBy(), llm()) NL |
| 40 | ChannelCreateEvent | nip28PublicChat/admin | channelInfo().let { listOfNotNull(it.name, it.about, it.picture).joinToString(" ") } (SP) |
| 41 | ChannelMetadataEvent | nip28PublicChat/admin | same as kind 40 (SP) |
| 42 | ChannelMessageEvent | nip28PublicChat/message | content |
| 43 | ChannelHideMessageEvent | nip28PublicChat/admin | reason() — the reason of NIP-28's {"reason": …} JSON content, or the plain-text content |
| 44 | ChannelMuteUserEvent | nip28PublicChat/admin | reason() — as kind 43 |
| 54 | PodcastEpisodeEvent | nipF4Podcasts/episode | listOfNotNull(title(), description(), content) NL |
| 62 | RequestToVanishEvent | nip62RequestToVanish | content |
| 64 | ChessGameEvent | nip64Chess/game | searchText().all() NL — the PGN's Event, Site, White, Black, Annotator, Opening, Variation, WhiteTeam, BlackTeam tag-pair values, then its {…} comments; never the movetext |
| 818 | WikiMergeRequestEvent | nip54Wiki | content |
| 1010 | TextNoteModificationEvent | experimental/edits | listOfNotNull(content, summary()) NL (content first) |
| 1022 | BidConfirmationEvent | nip15Marketplace/bidConfirmation | confirmationData()?.message.orEmpty() — the message field of the JSON content |
| 1063 | FileMetadataEvent | nip94FileMetadata | listOfNotNull(summary(), content) NL |
| 1065 | FileStorageHeaderEvent | experimental/nip95/header | listOfNotNull(summary(), content) NL — the caption; the base64 payload lives in the separate 1064 and is never indexed |
| 1068 | PollEvent | nip88Polls/poll | buildString { append(content); options().forEach { append('\n').append(it.label) } } |
| 1111 | CommentEvent | nip22Comments | (listOf(content) + tags.hashtags()) NL |
| 1163 | ProfileGalleryEntryEvent | experimental/profileGallery | listOfNotNull(summary()) NL |
| 1301 | WorkoutRecordEvent | experimental/fitness/workout | listOfNotNull(title(), content) NL |
| 1311 | LiveActivitiesChatMessageEvent | nip53LiveActivities/chat | (listOf(content) + tags.hashtags()) NL |
| 1312 | LiveActivitiesRaidEvent | nip53LiveActivities/raid | content |
| 1313 | LiveActivitiesClipEvent | nip53LiveActivities/clip | listOfNotNull(title(), content) NL |
| 1315 | RoadEventReportEvent | experimental/roadstr/report | content |
| 1337 | CodeSnippetEvent | nipC0CodeSnippets | listOfNotNull(snippetName(), snippetDescription(), content) NL |
| 1617 | GitPatchEvent | nip34Git/patch | content |
| 1618 | GitPullRequestEvent | nip34Git/pr | (listOfNotNull(subject(), content) + labels()) NL |
| 1621 | GitIssueEvent | nip34Git/issue | (listOfNotNull(subject(), content) + topics()) NL |
| 1622 | GitReplyEvent | nip34Git/reply | content |
| 1624 | GitCoverNoteEvent | nip34Git/coverNote | content (markdown cover note) |
| 1630–1633 | GitStatusEvent | nip34Git/status | inherited GitStatusEvent: content (open/applied/closed/draft) |
| 1808 | AudioHeaderEvent | experimental/audio/header | content |
| 1985 | LabelEvent | nip32Labeling | (listOf(content) + labels().map { it.label }).filter { it.isNotEmpty() } NL |
| 2003 | TorrentEvent | nip35Torrents | (listOfNotNull(title(), content) + fileNames()) NL — the file tags' paths |
| 2004 | TorrentCommentEvent | nip35Torrents | content |
| 2473 | BirdDetectionEvent | experimental/birdstar | listOfNotNull(summary(), speciesName()) NL |
| 3302 | ConcordChatEditEvent | concord/cord03Channels | content |
| 5050 | DvmTextGenerationRequestEvent | nip90Dvms/textGeneration | inputs().filter { it.type == "prompt" || it.type == "text" }.joinToString(" ") { it.value } (SP) |
| 5100 | DvmImageGenerationRequestEvent | nip90Dvms/imageGeneration | listOfNotNull(prompt(), negativePrompt()).joinToString(" ") (SP) |
| 5128 | SiteSnapshotEvent | nip5aStaticWebsites | listOfNotNull(title(), description()) NL |
| 5129 | NappletSnapshotEvent | nip5dNapplets | listOfNotNull(title(), description()) NL |
| 5250 | DvmTextToSpeechRequestEvent | nip90Dvms/textToSpeech | text() ?: "" |
| 5302 | DvmContentSearchRequestEvent | nip90Dvms/contentSearch | searchQuery() ?: "" |
| 5303 | DvmPeopleSearchRequestEvent | nip90Dvms/peopleSearch | searchQuery() ?: "" |
| 5901 | DvmOpReturnRequestEvent | nip90Dvms/opReturn | text() ?: "" |
| 6969 | ZapPollEvent | experimental/zapPolls | buildString { append(content); pollOptionsArray().forEach { append('\n').append(it.descriptor) } } |
| 7516 | GeocacheFoundLogEvent | nipCCGeocaching/foundLog | content |
| 8002 | ArchivedIdentityEvent | buzz/iaIdentityArchival | content |
| 8003 | UnarchivedIdentityEvent | buzz/iaIdentityArchival | content |
| 8333 | OnchainZapEvent | nipBCOnchainZaps/zap | content |
| 9002 | GroupEditMetadataEvent | nip29RelayGroups/moderation | (listOfNotNull(name(), about()) + hashtags()) NL |
| 9007 | CreateGroupEvent | nip29RelayGroups/moderation | listOfNotNull(name(), about()) NL |
| 9021 | GroupJoinRequestEvent | nip29RelayGroups/request | content |
| 9022 | GroupLeaveRequestEvent | nip29RelayGroups/request | content |
| 9035 | ArchiveRequestEvent | buzz/iaIdentityArchival | content |
| 9036 | UnarchiveRequestEvent | buzz/iaIdentityArchival | content |
| 9041 | ZapGoalEvent | nip75ZapGoals | listOfNotNull(summary(), content) NL |
| 9321 | NutzapEvent | nip61Nutzaps/nutzap | content |
| 9734 | ZapRequestEvent | nip57Zaps | content |
| 9735 | ZapReceiptEvent | nip57Zaps | zapRequest?.content.orEmpty() — indexes the embedded 9734's content |
| 9736 | Bolt12ZapEvent | nipB1Bolt12Zaps/zap | content |
| 9737 | Bolt12ZapIntentEvent | nipB1Bolt12Zaps/intent | content |
| 9802 | HighlightEvent | nip84Highlights | listOfNotNull(comment(), searchableContext(), content) NL — searchableContext() is the context tag, else a textquoteselector's prefix and suffix (whitespace-collapsed, space-joined) |
| 9998 | ListHeaderEvent | experimental/decentralizedLists/header | tags.searchableListContent() NL — names (singular, plural), titles (singular, plural), name, title, description, comments, then in tag order every t value that is not isMachineValue and every value of every other tag (except alt, client, imeta) that passes isNaturalLanguageValue (has whitespace or non-ASCII, or is one capitalized letters-only word; never JSON, numbers, URIs of any scheme, addresses, hex ids, UUIDs, bech32) |
| 9999 | ListItemEvent | experimental/decentralizedLists/item | same as 9998 |
| 10003 | BookmarkListEvent | nip51Lists/bookmarkList | listOfNotNull(titleOrName()) NL |
| 10100 | AgentProfileEvent | buzz/agentProfiles | profileOrNull()?.let { listOfNotNull(it.name, it.displayName).joinToString("\n") } ?: "" |
| 10154 | PodcastMetadataEvent | nipF4Podcasts/metadata | listOfNotNull(title(), description()) NL |
| 11316 | CvmServerAnnouncementEvent | contextvm/cep06Announcements | listOfNotNull(serverName(), about()) NL (class lives in CvmAnnouncementEvents.kt) |
| 11333 | SnoAvatarEvent | cyberspace/deck0003Sno | nameTag().orEmpty() — the name tag only; content is SNO geometry JSON |
| 11871 | AttestorProficiencyEvent | experimental/attestations/proficiency | listOfNotNull(description()) NL |
| 12473 | BirdexEvent | experimental/birdstar | (listOfNotNull(summary()) + speciesNames()) NL |
| 15128 | RootSiteEvent | nip5aStaticWebsites | listOfNotNull(title(), description()) NL |
| 15129 | RootNappletEvent | nip5dNapplets | listOfNotNull(title(), description()) NL |
| 30000 | FollowSetEvent | nip51Lists/followSet | listOfNotNull(titleOrName(), description()) NL |
| 30001 | OldBookmarkListEvent | nip51Lists/bookmarkList | listOfNotNull(title()) NL |
| 30002 | RelaySetEvent | nip51Lists/relaySets | listOfNotNull(title(), description()) NL |
| 30003 | BookmarkSetEvent | nip51Lists/bookmarkSet | listOfNotNull(titleOrName(), description()) NL |
| 30004 | ArticleCurationSetEvent | nip51Lists/articleCurationSet | listOfNotNull(title(), description()) NL |
| 30005 | VideoCurationSetEvent | nip51Lists/videoCurationSet | listOfNotNull(title(), description()) NL |
| 30006 | PictureCurationSetEvent | nip51Lists/pictureCurationSet | listOfNotNull(title(), description()) NL |
| 30008 | AcceptedBadgeSetEvent | nip58Badges/accepted | listOfNotNull(title(), description()) NL |
| 30009 | BadgeDefinitionEvent | nip58Badges/definition | listOfNotNull(name(), description(), content) NL |
| 30015 | InterestSetEvent | nip51Lists/interestSet | (listOfNotNull(title(), description()) + publicHashtags()) NL |
| 30017 | StallEvent | nip15Marketplace/stall | stallData()?.let { listOfNotNull(it.name, it.description).joinToString("\n") } ?: "" |
| 30018 | ProductEvent | nip15Marketplace/product | productData()?.let { (listOfNotNull(it.name, it.description) + categories()).joinToString("\n") } ?: "" |
| 30019 | MarketplaceEvent | nip15Marketplace/marketplace | marketplaceData()?.let { listOfNotNull(it.name, it.about).joinToString("\n") } ?: "" |
| 30020 | AuctionEvent | nip15Marketplace/auction | auctionData()?.let { (listOfNotNull(it.name, it.description) + tags.hashtags()).joinToString("\n") } ?: "" — auctionData() reads the JSON body and fills its gaps from the tag form auction apps publish (title/name, summary, else plain-text content as the description) |
| 30023 | LongFormContentEvent | nip23LongContent | (listOfNotNull(title(), summary(), content) + topics()) NL |
| 30024 | LongFormDraftEvent | nip23LongContent/draft | tags.longFormIndexableContent(content) — the same as 30023: (listOfNotNull(title(), summary(), content) + topics()) NL |
| 30030 | EmojiPackEvent | nip30CustomEmoji/pack | (listOfNotNull(titleOrName(), description()) + publicEmojiCodes()) NL — the public emoji tags' shortcodes; content is NOT indexed (it is the NIP-44 ciphertext of the private emoji tags) |
| 30031 | StickerPackEvent | nip30CustomEmoji/stickers | (listOfNotNull(title(), description()) + stickers().map { it.code }) NL (never content) |
| 30040 | PublicationIndexEvent | experimental/publications | listOfNotNull(title(), author(), summary()) NL |
| 30041 | PublicationContentEvent | experimental/publications | listOfNotNull(title(), content) NL |
| 30045 | BookshelfDirectoryEvent | experimental/library | listOfNotNull(title(), summary(), content) NL |
| 30054 | Podcasting20EpisodeEvent | nipXXPodcasting20/episode | (listOfNotNull(title(), description(), content) + personNames() + soundbiteTitles() + topics()) NL |
| 30055 | Podcasting20TrailerEvent | nipXXPodcasting20/trailer | listOfNotNull(title(), content) NL |
| 30063 | ReleaseArtifactSetEvent † | nip51Lists/releaseArtifactSet | listOfNotNull(title(), description(), searchableReleaseNotes()) NL |
| 30066 | LiveChessMoveEvent | nip64Chess/move | content |
| 30068 | LiveChessDrawOfferEvent | nip64Chess/draw | content |
| 30142 | LearningResourceEvent | experimental/library | (listOfNotNull(title(), alternateTitle(), summary(), alternateSummary(), content, author()) + facetLabels()) NL — facetLabels() = resource types, educational levels, subjects, deduplicated |
| 30175 | PersonaEvent | buzz/apPersonas | personaOrNull()?.let { listOfNotNull(it.displayName, it.description, it.systemPrompt).joinToString("\n") } ?: "" |
| 30176 | TeamEvent | buzz/teams | teamOrNull()?.let { listOfNotNull(it.name, it.description, it.instructions).joinToString("\n") } ?: "" |
| 30177 | ManagedAgentEvent | buzz/managedAgents | agentOrNull()?.let { listOfNotNull(it.name, it.systemPrompt).joinToString("\n") } ?: "" |
| 30178 | TeamCatalogEvent | buzz/teamCatalog | catalogOrNull()?.let { catalog -> buildList { add(catalog.name); catalog.description?.let(::add); catalog.instructions?.let(::add); catalog.members.forEach { member -> add(member.displayName); member.systemPrompt?.let(::add) } }.joinToString("\n") } ?: "" |
| 30267 | AppCurationSetEvent | nip51Lists/appCurationSet | listOfNotNull(titleOrName(), description()) NL |
| 30296 | InteractiveStoryPrologueEvent | experimental/interactiveStories | inherited base: (listOfNotNull(title(), summary(), content) + optionLabels()) NL |
| 30297 | InteractiveStorySceneEvent | experimental/interactiveStories | inherited base: (listOfNotNull(title(), summary(), content) + optionLabels()) NL |
| 30301 | KanbanBoardEvent ¶ | experimental/kanban/board | listOfNotNull(title(), description()) NL |
| 30301 | BuildVerificationEvent ¶ | experimental/walletScrutiny/verification | listOfNotNull(description(), report()) NL (both from the JSON content) |
| 30302 | KanbanCardEvent ¶ | experimental/kanban/card | listOfNotNull(title(), description()) NL |
| 30311 | LiveActivitiesEvent | nip53LiveActivities/streaming | listOfNotNull(title(), summary(), content) NL |
| 30312 | MeetingSpaceEvent | nip53LiveActivities/meetingSpaces | listOfNotNull(room(), summary(), content) NL |
| 30313 | MeetingRoomEvent | nip53LiveActivities/meetingSpaces | listOfNotNull(title(), summary()) NL |
| 30315 | UserStatusEvent | nip38UserStatus | content |
| 30382 | UserAssertionEvent | nip85TrustedAssertions/users | (listOfNotNull(petName(), summary()) + topics()) NL — public tags only, never the NIP-44 content |
| 30392 | UserTrustedListEvent | experimental/trustedLists/users | inherited TrustedListEvent: title() ?: "" — the label only; metric/d are machine ids and content is a JSON echo of the membership |
| 30393 | EventTrustedListEvent | experimental/trustedLists/events | inherited TrustedListEvent: title() ?: "" |
| 30394 | AddressableTrustedListEvent | experimental/trustedLists/addressables | inherited TrustedListEvent: title() ?: "" |
| 30395 | ExternalIdTrustedListEvent | experimental/trustedLists/externalIds | inherited TrustedListEvent: title() ?: "" |
| 30402 | ClassifiedsEvent | nip99Classifieds | (listOfNotNull(title(), summary(), content, location()) + categories()) NL |
| 30617 | GitRepositoryEvent | nip34Git/repository | (listOfNotNull(name(), description(), content) + hashtags()) NL |
| 30620 | WorkflowDefEvent | buzz/workflow | listOfNotNull(name(), content) NL |
| 30621 | ProjectEvent | buzz/mpProjects | listOfNotNull(displayName(), description()) NL |
| 30817 | NipTextEvent | experimental/nipsOnNostr | listOfNotNull(title(), content) NL |
| 30818 | WikiArticleEvent | nip54Wiki | listOfNotNull(title(), summary(), content) NL |
| 31337 | AudioTrackEvent | experimental/audio/track | listOfNotNull(subject()) NL |
| 31871 | AttestationEvent | experimental/attestations/attestation | content |
| 31872 | AttestationRequestEvent | experimental/attestations/request | content |
| 31873 | AttestorRecommendationEvent | experimental/attestations/recommendation | listOfNotNull(description()) NL |
| 31890 | FeedDefinitionEvent | feedDefinition | title().orEmpty() (now registered in EventFactory; before that it parsed as a plain Event and was never searched) |
| 31922 | CalendarDateSlotEvent | nip52Calendar/appt/day | (listOfNotNull(title(), summary(), content) + locations() + hashtags()) NL |
| 31923 | CalendarTimeSlotEvent | nip52Calendar/appt/time | (listOfNotNull(title(), summary(), content) + locations() + hashtags()) NL |
| 31924 | CalendarCollectionEvent | nip52Calendar/calendar | listOfNotNull(title(), content) NL |
| 31925 | CalendarRSVPEvent | nip52Calendar/rsvp | content |
| 31987 | RelayReviewEvent | experimental/ratings | content |
| 31990 | AppDefinitionEvent | nip89AppHandlers/definition | appMetaData()?.let { listOfNotNull(it.name, it.username, it.displayName, it.about, it.nip05, it.lud06, it.lud16, it.website, it.picture, it.banner, it.image).joinToString(" ") } ?: "" (SP) |
| 32176 | BlossomPieceIndexEvent | experimental/library | listOfNotNull(title(), summary(), content) NL |
| 32267 | SoftwareApplicationEvent | experimental/nip82SoftwareApps/application | listOfNotNull(name(), summary(), content) NL |
| 33331 | SnoObjectEvent | cyberspace/deck0003Sno | nameTag().orEmpty() — the name tag only; content is SNO geometry JSON |
| 33401 | ExerciseTemplateEvent | experimental/fitness/workout | listOfNotNull(title(), content) NL |
| 33402 | WorkoutTemplateEvent | experimental/fitness/workout | listOfNotNull(title(), content) NL |
| 33534 | RelayRoleEvent | nip43RelayMembers/roles | listOfNotNull(label(), description()) NL |
| 33863 | FundraiserEvent | experimental/agora | listOfNotNull(title(), content) NL |
| 34139 | MusicPlaylistEvent | experimental/music/playlist | listOfNotNull(title(), description(), content) NL |
| 34235 | AddressableNormalVideoEvent | nip71Video | inherited AddressableVideoEvent: (listOfNotNull(title(), content) + segmentTitles()) NL |
| 34236 | AddressableShortVideoEvent | nip71Video | inherited AddressableVideoEvent: (listOfNotNull(title(), content) + segmentTitles()) NL |
| 34259 | EntityRatingEvent | experimental/ratings | content |
| 34550 | CommunityDefinitionEvent | nip72ModCommunities/definition | listOfNotNull(name(), description(), rules(), content) NL |
| 35128 | NamedSiteEvent | nip5aStaticWebsites | listOfNotNull(title(), description()) NL |
| 35129 | NamedNappletEvent | nip5dNapplets | listOfNotNull(title(), description()) NL |
| 36767 | ThemeDefinitionEvent | experimental/profileTheme/definition | listOfNotNull(title(), description()) NL (never the colors, fonts or URLs) |
| 36787 | MusicTrackEvent | experimental/music/track | listOfNotNull(title(), artist(), album(), content) NL |
| 37516 | GeocacheListingEvent | nipCCGeocaching/listing | listOfNotNull(cacheName(), content) NL (the hint is deliberately not indexed — matching a hint is spoiling it) |
| 37517 | GeocacheCurationListEvent | nipCCGeocaching/curation | listOfNotNull(title(), description(), content) NL |
| 38000 | MintRecommendationEvent ‡ | nip87Ecash/recommendation | content |
| 38000 | BallotEvent ‡ | experimental/ballots | buildList { election()?.let { add(it) }; answers().forEach { add(it.answer) } } NL |
| 38000 | PredictionMarketEvent ‡ | experimental/predictionMarkets | (listOfNotNull(title(), description()) + outcomes() + listOfNotNull(resolution(), cancelReason(), socialPost())) NL |
| 38192 | Ps1SaveEvent | experimental/ps1saves | listOfNotNull(summary(), saveTitle(), region(), filename()) NL |
| 38383 | P2POrderEvent | nip69P2pOrderEvents | (listOfNotNull(makerName(), currency()) + paymentMethods().orEmpty()).joinToString(" ") (SP) |
| 38385 | MostroInfoEvent § | nip69P2pOrderEvents/mostroInfo | (listOfNotNull(instanceName()) + fiatCurrenciesAccepted().orEmpty()).joinToString(" ") (SP) |
| 39000 | GroupMetadataEvent | nip29RelayGroups/metadata | (listOfNotNull(name(), about()) + hashtags()) NL |
| 39003 | GroupRolesEvent | nip29RelayGroups/metadata | roles().flatMap { listOfNotNull(it.name, it.description) } NL |
| 39089 | StarterPackEvent | nip51Lists/starterPack | (listOfNotNull(title(), description()) + hashtags()) NL |
| 39092 | MediaStarterPackEvent | nip51Lists/mediaStarterPack | listOfNotNull(title(), description()) NL |
| 39307 | TextTrackEvent | nip71Video/textTrack | if (WebVttText.hasCues(content)) WebVttText.cueText(content) else content — cueText is each cue's payload lines (NL) with tags removed and character references decoded; no header, timings, cue settings, ids, NOTE/STYLE/REGION blocks. Content with no --> timing line is not a caption file and is indexed as written |
| 39701 | WebBookmarkEvent | nipB0WebBookmarks | (listOfNotNull(title(), description()) + hashtags()) NL |
| 39998 | AddressableListHeaderEvent | experimental/decentralizedLists/header | same as 9998 |
| 39999 | AddressableListItemEvent | experimental/decentralizedLists/item | same as 9998 |
| 40002 | StreamMessageV2Event | buzz/stream | content |
| 40003 | StreamMessageEditEvent | buzz/stream | content |
| 40006 | StreamMessageScheduledEvent | buzz/stream | content |
| 40007 | StreamReminderEvent | buzz/stream | content |
| 40008 | StreamMessageDiffEvent | buzz/stream | description() ?: "" — the description tag; the body is a unified diff and stays out |
| 40099 | SystemMessageEvent | buzz/stream | payload()?.let { listOfNotNull(it.topic, it.purpose, it.publicReason).joinToString("\n") } ?: "" — fields of the JSON content |
| 40100 | CanvasEvent | buzz/stream | content |
| 40901 | ChannelSummaryEvent | buzz/stream/sidecars | summary()?.let { listOfNotNull(it.name, it.about, it.topic, it.purpose).joinToString("\n") } ?: "" — fields of the JSON content |
| 42000 | ProductFeedbackEvent | buzz/moderation | content |
| 43001 | JobRequestEvent | buzz/jobs | content |
| 43002 | JobAcceptedEvent | buzz/jobs | content |
| 43003 | JobProgressEvent | buzz/jobs | content |
| 43004 | JobResultEvent | buzz/jobs | content |
| 43005 | JobCancelEvent | buzz/jobs | content |
| 43006 | JobErrorEvent | buzz/jobs | content |
| 45001 | ForumPostEvent | buzz/forum | content |
| 45003 | ForumCommentEvent | buzz/forum | content |
| 45010 | ArtifactEvent | buzz/arArtifacts | listOfNotNull(title(), textBody()) NL — textBody() is content unless blank or JSON (starts with {/[) |
| 46030 | ApprovalGrantEvent | buzz/workflow | content |
| 46031 | ApprovalDenyEvent | buzz/workflow | content |
| 48106 | HuddleGuidelinesEvent | buzz/huddles | content |
† Kind 30063 is shared by NIP-51 release artifact sets and NIP-82 software releases, and
ReleaseArtifactSetEvent parses both. It indexes title() and description(), plus content
(the release notes, via searchableReleaseNotes()) only when it is non-blank and the event
carries the NIP-82 i + version tags — a NIP-51 set may hold encrypted private items in
content, which must never be indexed.
‡ Kind 38000 is shared by NIP-87 mint recommendations and two app formats, and EventFactory
picks the class by tags: MintRecommendationEvent when any k is 38172/38173 (or, with no
k, a non-blank u or an a to a 38172/38173 address); else BallotEvent on a non-blank
election; else PredictionMarketEvent on a market, ≥2 outcomes, or type + end; else
UnrecognizedKind38000Event — addressable (stores still key it by d) but unsearchable (the
spam votes that make up most of the kind). Kind-level probes (EventFactory.probe) answer as
MintRecommendationEvent. A market's title()/description() come from its data tag JSON,
the title tag, or JSON content, in that order; it also indexes its outcome labels, the
resolution, the cancel reason, and content as socialPost() when it is a non-blank, non-JSON post
and the market has no description() it would merely repeat.
§ Kind 38385 is shared by Mostro instance info and other apps (Paygress lease revocations,
bond-trade assignments, game scores). EventFactory builds MostroInfoEvent only when a z of
info or a mostro_version tag is present, else UnrecognizedKind38385Event — addressable but
unsearchable. Kind-level probes answer as MostroInfoEvent. Mostro's 38384 / 38386 and RoboSats'
31986 are split the same way, but none of their classes is searchable.
¶ Kind 30301 is shared by Kanban boards (NIP PR #1665), WalletScrutiny reproducible-build
verifications and an encrypted planner app. EventFactory builds BuildVerificationEvent when a
non-blank i and status are present, else KanbanBoardEvent on a non-blank title or a col
with a name, else UnrecognizedKind30301Event — addressable but unsearchable (the planner's
NIP-44 tasks). Kind-level probes answer as KanbanBoardEvent. A verification indexes the
description and the markdown report (content) of its JSON content. Kind 30302 is split the
same way: KanbanCardEvent on a title or an a to a 30301 board, else
UnrecognizedKind30302Event (Fieldbook team memberships).
Abstract bases (no kind of their own)
| Base class | Body | Concrete kinds |
|---|---|---|
CitationEvent |
listOfNotNull(title(), summary(), content, author(), publishedBy()) NL |
31 (32 and 33 override with their extra fields) |
GitStatusEvent |
content |
1630, 1631, 1632, 1633 |
InteractiveStoryBaseEvent |
(listOfNotNull(title(), summary(), content) + optionLabels()) NL — the non-blank option choice texts |
30296, 30297 |
AddressableVideoEvent |
(listOfNotNull(title(), content) + segmentTitles()) NL — the segment tags' titles |
34235, 34236 |
RegularVideoEvent |
(listOfNotNull(title(), content) + segmentTitles()) NL |
21, 22 |
TrustedListEvent |
title() ?: "" |
30392, 30393, 30394, 30395 |
How to regenerate / verify this table
# The authoritative kind set (sorted, one comment per kind naming the class):
cat quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip50Search/SearchableKinds.kt
# All implementor files:
grep -rln "override fun indexableContent" quartz/src/commonMain
# For each, pair the KIND constant with the indexableContent() body.
# Searchability on the store path additionally requires EventFactory registration:
grep -n "<ClassName>" quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt
Two tests keep the code side from drifting: SearchableKindsTest (commonTest) builds every kind
0–65535 through EventFactory and asserts the searchable ones are exactly SearchableKinds.ALL,
and IndexableContentGoldenTest (jvmTest) records each kind's indexableContent() for a
tag-rich sample event in quartz/src/jvmTest/resources/indexable-content.golden — a CI-diffable
snapshot. Diff SearchableKinds.kt and the golden file at a version bump; this table is still
updated by hand.