Extract isAuthorizedAward helper as the single source of truth for
membership award validation, used by both resolveMembership and
useMyCommunities. Simplify resolveCommunityModeration by dropping
the dead banned-reporter guard from pass 1 (impossible under strict
rank ordering). Flip useMembersOnlyFilter default to opt-in to match
the spec's MAY wording, and reword the NIP to match.
Bookmarking a kind 34550 community now writes to the NIP-51 Communities
list (kind 10004) keyed by the addressable coordinate, so the reference
stays valid across community updates. My Communities merges bookmarked
communities as a third discovery source alongside founded and member-of,
with Founder/Member/Bookmarked badges on each card.
Bookmark toasts live on the mutation itself so they survive the more-menu
dialog unmounting between .mutate() and publish resolution.
- Gate social actions by projected stat thresholds (< 70) so visitors
can only help stats in visual distress
- Add energy category with Energy Drink and Power Nap Pillow items
- Apply 6-hour recency window to interaction queries (limit 30)
- Fix BlobbiActionsProvider tree placement so BlobbiPage shares context
with the companion layer
- Preserve event content in dev editor (don't overwrite checkpoint JSON)
- Show Needs Now summary in activity tab with priority badges
- Remove unused need-driven consolidation infrastructure
Regression-of: 9aecefff
Implement zap goals (kind 9041) linked to communities via a-tag.
Includes goal creation dialog, progress tracking from zap receipts,
recipient profile/lightning address display, community link, and
members-only filtering. Goals appear in community detail Fundraising
tab, activity feed, and main feed via NoteCard.
A Birdex is a replaceable per-author index of every bird species the
author has ever confirmed via kind 2473, stored as positional i/n tag
pairs in chronological first-seen order. In feeds, show a compact
tile strip of the most recently-added species with a "+N" capstone
when the list overflows — mirroring how kind 3 follow lists preview
members as an avatar stack plus "+N more". On the post-detail page,
render every species as a responsive grid so visitors can browse the
author's whole life list.
Each tile resolves the species' Wikidata entity through English
Wikipedia to pull a thumbnail and common-name label, reusing the
same fetch path as kind 2473 detection cards. The Wikidata URL is
sanitized before being routed, and the paired n tag provides a
scientific-name fallback while the remote lookup is in flight.
Adds feed support for kind 2473 (bird-by-ear detections) and kind 30621
(user-drawn star figures) from Birdstar. Detections render as species
cards using the existing Wikidata + Wikipedia summary hooks; constellations
render as gnomonically-projected SVG star-maps backed by the Hipparcos
catalog from d3-celestial. The 1.1 MB catalog is code-split via lazy() so
it only loads when a constellation event is actually viewed.
Introduce external social interactions for Blobbi companions. Other users
can feed, play, clean, and medicate a Blobbi they don't own via kind 1124
events. The owner's canonical 31124 state absorbs these interactions
through a checkpoint-based consolidation system with auto-sync on page load.
Key additions:
- Kind 1124 event schema with validation, parsing, and deterministic sort
- Social projection pipeline (read-only stat overlay from pending interactions)
- Owner-side consolidation into canonical state with checkpoint advancement
- Auto-sync hook (useCanonicalSync) triggered when owner opens /blobbi
- Social permission toggle (open/closed tag on 31124)
- Interaction UI: popover with item carousel on feed cards, detail pages,
and the owner dashboard action bar
- Interaction reactions: facial expressions, sparkles, bubbles, floating
hearts with phase-based animation system
- Activity tab showing interaction history with caretaker attribution
- BlobbiSocialActions component with egg gating and cooldown logic
- NIP.md documentation for the new kind
Add two-tier moderation system for hierarchical communities using kind 1984
events scoped via A tags. Authoritative bans use NIP-32 labels
([l, ban, moderation]) and require rank authority. Soft reports use standard
NIP-56 types and trigger content warnings for any valid member.
- Update NIP.md with ban/report classification, NIP-32 label schema, and
reinstatement via kind 5
- Add parseCommunityReport(), resolveCommunityModeration() to communityUtils
- Update resolveMembership() to apply moderation overlay (remove banned members)
- Update useCommunityMembers to fetch kind 1984/5 and resolve moderation
- Add CommunityModerationContext for propagating moderation state
- Add CommunityReportDialog for soft reports (NIP-56 types)
- Add BanConfirmDialog for content removal and member bans with optional reason
- Add CommunityContentWarning component for click-to-reveal reported content
- Wire moderation into NoteMoreMenu (auto-detects community context)
- Wire moderation into CommunityDetailPage (member ban buttons, feed filtering)
- Add Remove content / Ban @user menu items to NoteMoreMenu
- Remove Copy Link to Post and Mention @user from NoteMoreMenu
- Move Mute Conversation into the mute/report section
- Add 13 regression tests for Taproot address derivation, pubkey
validation, npub→address, and mainnet address validation
- Validate pubkey hex format (/^[0-9a-fA-F]{64}$/) in
nostrPubkeyToBitcoinAddress to fail fast on malformed input
- Match tapInternalKey against the signer's x-only pubkey in
signPsbtLocal, per the BITCOIN-SIGNING.md spec ("inputs whose
tapInternalKey does not match the signer's key MUST be left
unchanged"). Throw if no owned inputs are found.
- Use >= DUST_LIMIT (not >) for change-output dust check, so a change
of exactly 546 sats is preserved rather than donated to fees
- Extract formatBTC() helper into lib/bitcoin.ts; remove duplicated
replace(/\.?0+$/, '') from WalletPage, SendBitcoinDialog, and
BitcoinContentHeader
- Register kind 8333 ("Bitcoin zap") in CommentContext KIND_LABELS,
CommentContext KIND_ICONS, NoteCard KIND_HEADER_MAP,
signerWithNudge KIND_LABELS, and shellTitleForKind
- Disambiguate sign_psbt errors in NConnectSignerBtc: only re-wrap as
"doesn't support sending Bitcoin" when the error message looks like
a capability failure (unknown method, not implemented, etc.);
propagate transient errors unchanged
- Show the recipient's derived Bitcoin address in OnchainZapContent
so users can verify the destination before signing
- Clear knownUnsupportedBunkers on logout so a fresh login with an
upgraded bunker isn't tainted by a previous session's rejection
- Reject self-zaps in verifyOnchainZap (sender == recipient)
- Update NIP.md to specify: change-output handling, amount-cap vs
discard semantics, self-zap rejection, mempool/confirmation policy,
and mainnet-only scope
- Delete unused useNsecAccess hook
8333 is the Bitcoin mainnet P2P port, creating a clean semantic parallel
with NIP-57: kind 9735 (Lightning's P2P port) for Lightning zaps, kind
8333 for on-chain zaps. 3043 was the first free kind the generator
returned and carried no meaning.
Introduce kind 3043, a new Nostr event that attests an on-chain Bitcoin
payment against a target event or profile. Because every Nostr pubkey
deterministically maps to a Taproot address, any user can receive an
on-chain zap without configuring lud06/lud16 — the zap button now
appears on every post whose author is not the current user.
Publishing flow: sender builds and broadcasts a Bitcoin transaction
paying the recipient's derived Taproot address, then publishes a
kind 3043 event with an `i` tag (`bitcoin:tx:<txid>`), the recipient's
`p`, the target's `e` / `a`, and a self-reported `amount` in sats.
Before displaying or counting a kind 3043 event clients verify the
referenced transaction on-chain and use the sum of outputs paying the
recipient's address as the authoritative amount, capping the sender's
claim at the verified value to prevent spoofing.
Lightning zaps remain available as an opt-in tab inside the zap dialog
whenever the author has a Lightning address configured; otherwise the
dialog is purely on-chain. Defaults favour on-chain: USD amount presets
($1 / $5 / $10 / $25 / $100), fee-speed selection, and a 3-step
form → confirm → success flow mirroring SendBitcoinDialog.
- Add usePlaylistTracks hook to resolve a-tag refs into ordered track events
- Render track list in PlaylistDetail with Play All button and per-track
playlist playback via playPlaylist()
- Support albums as playlists tagged with t:album, showing release date,
label, and Disc3 icon
- Add All/Playlists/Albums filter toggle to MusicPlaylistsTab
- Show Album badge on playlist cards in grid views
- Add KIND_HEADER_MAP entries for music tracks (36787) and playlists (34139)
- Fix shellTitleForKind to return 'Playlist Details' for kind 34139
- Document music kinds and album convention in NIP.md
- Remove dead code: useSyncTaskCompletions, incrementInteractionTaskTags,
getInteractionCount, getEvolveInteractionCount, unused lookup maps
- Fix task progress showing 0/N on load: compute event-based task counts
directly from Nostr query results (authoritative) instead of relying
solely on the evolution mission store which may not be hydrated yet.
Use max(queryCount, missionCount) so progress displays immediately.
- Fix hydration race: useDailyMissions raw memo now waits for hydration
before creating fresh missions, preventing overwrite of persisted
evolution[] with empty array. Also preserve evolution missions across
daily resets during hydration.
- Fix session store miss: use ensureSessionStore in incubation/evolution
start so evolution missions are always populated even if the store
hasn't been hydrated yet.
- Extract duplicate findMission to shared findEvolutionMission in
evolution-missions.ts
- Document evolution[] field on kind 11125 in NIP.md
Add a Community Kinds section to the overview table and a Community
NIP Specifications section with summaries and links to the full specs
maintained by Chad Curtis, Sam Thomson, and Danifra.
- Port letter protocol (kind 8211, NIP-44 encrypted) from lief
- LettersPage at /letters with inbox and sent tabs
- ComposeLetterSheet with full stationery, font, frame, sticker, drawing support
- LetterCard with expand-to-read animation and deletion
- LetterPreferencesSection stored in encrypted settings (NIP-78)
- /settings/letters route for letter preferences
- Letters added to sidebar nav
- All letter lib utilities: letterTypes, letterUtils, colorUtils extensions, sanitizeSvg, svgDrawing
- StationeryBackground, StationeryPicker, FramePicker, StickerPicker, DrawingCanvas all ported
Extend the font tag format with a required role marker ("body" or
"title") to support a dedicated profile display-name font. Body tags
must be ordered before title tags for backward compatibility. Legacy
3-element f tags without a role are treated as body.
Extend the kind-0 'shape' field to accept any emoji string in addition to
the predefined geometric shapes (circle, triangle, hexagon, star, etc.).
When an emoji is chosen, its native OS glyph is rendered onto a canvas,
the alpha channel extracted as a PNG mask, and applied via CSS mask-image.
Implementation:
- Widen AvatarShape type to accept emoji strings alongside predefined names
- isEmoji() detects any short non-ASCII string (avoids fragile Unicode regex)
- getEmojiMaskUrl() renders emoji at 512px, crops to tight bounding box
(with alpha threshold to ignore shadows/glows), squares the crop, scales
to 256px output, converts to white+alpha mask PNG. Cached per emoji.
- Avatar component applies mask-image for emoji, clip-path for geometric
- ProfileCard uses ring-4 instead of border-4 for emoji shapes to avoid
mask/content misalignment (border is inside the element; ring is outside)
- AvatarShapePicker adds emoji-mart picker in a popover
- Form schemas use z.string() to accommodate both predefined and emoji values
- NIP.md documents emoji shape support
clip-path: path() uses pixel coordinates, making a 0-1 normalized path
invisible. Replaced with a parametric heart curve sampled into a 50-point
polygon using percentage coordinates, consistent with all other shapes.
- Add 'heart' as the 8th avatar shape using SVG path() clip-path with cubic beziers
- Document the kind 0 'shape' metadata extension in NIP.md with all 8 defined values,
client behavior rules, and forward-compatibility guidance
The proprietary SavedFeedFilters JSON schema in kind 16769 tab events was
non-interoperable. Replace it with standard NIP-01 filter objects that any
Nostr client can execute, plus a variable system (var tags) for dynamic
values like follow lists.
- Tab filters are now standard NIP-01 filters with optional NIP-50 search
- Variables ($name) in filters are resolved via var tags that extract tag
values from referenced events (e.g. follow list pubkeys from kind 3)
- $me is the only runtime variable (profile owner's pubkey)
- Variables in arrays are expanded in-place (spliced)
- Add useResolveTabFilter hook for variable resolution with caching
- Update all consumers: ProfilePage, Feed, SearchPage, ContentSettings
- Remove SavedFeedFilters type entirely in favor of TabFilter
Kind 16769 (profile tabs):
- src/lib/profileTabsEvent.ts: encode/decode kind 16769 — tab tags are
['tab', label, filtersJSON], no UUIDs, label is the stable key
- useProfileTabs: queries kind 16769 with authors filter (secure)
- usePublishProfileTabs: publishes kind 16769, invalidates cache
- useProfileSupplementary: now fetches kind 16769 alongside kind 3 and
10001 in a single relay round-trip, seeds profile-tabs query cache
- NIP.md: documents kind 16769 schema
ProfileTabEditModal:
- Save button is now full-width with a Check icon (Cancel below)
- hideFrom + hideSort props on SavedFeedFiltersEditor used here — author
is the profile owner implicitly, sort is irrelevant for profile tabs
ProfileTabsManagerModal:
- dnd-kit drag-to-reorder sortable list of custom tabs
- Per-row edit (pencil) and remove (trash) buttons
- Add custom tab button at the bottom
- Single Save button publishes the full updated list
ProfilePage tab bar (own profile):
- Custom tabs read from kind 16769 via useProfileTabs
- + button opens a DropdownMenu: core tabs listed as already-added
(dimmed with checkmarks), Add custom tab at the bottom
- SlidersHorizontal pencil at end of row opens the manager modal
(only shown when custom tabs exist)
- Per-tab onEdit pencil removed — manager handles all editing
- ProfileTab type aliased to CoreProfileTab to avoid collision with
CustomProfileTab from profileTabsEvent
Reduce font complexity from two-role (title/body) to a single font
that applies globally to all text. Updates NIP.md f tag format from
["f", family, role, url] to ["f", family, url], removes ThemeFonts
wrapper in favor of a single ThemeFont on ThemeConfig.font, and
simplifies FontPicker to a single dropdown.
Replace the u tag with a richer bg tag using imeta-style key-value
pairs (url, mode, m required; dim, blurhash optional). Add f tag for
font declarations (family, role, and font file URL required). Both
tags are documented in a shared definitions section for kind 33891
and kind 11667.
Align theme definition event with kind 11667 by replacing the HSL
color token JSON in the content field with c tags using 6-digit hex
color codes and required markers (primary, text, background).
Replace the HSL color tokens in the content field with c tags using
6-digit hex color codes and required markers (primary, text, background).
Remove the a tag for theme attribution. Add optional title tag.
- Theme definitions are addressable (multiple per user, each with title/description)
- Active profile theme is replaceable (one per user, copies tokens from source)
- Updated all hooks, components, and feed integration for new kinds
- Theme builder preview now shows accent color on streak flame, banner gradient, and repost action