Pattern canonicalization:
- Changed VALID_PATTERNS from ['gradient', 'solid', 'speckled', 'striped']
to ['solid', 'spotted', 'striped', 'gradient'] to match domain model
- 'spotted' is the canonical value (used by BlobbiPattern type, BLOBBI_PATTERNS,
derivePatternFromSeed, normalizePatternTag, and PATTERN_MAP)
Duplicate name fix:
- Removed title from toEggGraphicVisualBlobbi() adapter - the EggGraphic
'title' field is for special designations (e.g., 'Divine'), not pet names
- The duplicate was: input field above egg + title display below egg
- Now only the input field exists, plus a new styled name display
Name styling:
- Added styled name display above the egg using the egg's baseColor
- Styling matches the former bottom title: bg-black/20, backdrop-blur-sm,
font-semibold, text-shadow, and color from preview.visualTraits.baseColor
- Fix getColorRarity() to properly merge both base color palettes by
creating MERGED_BASE_COLORS_BY_RARITY that combines colors per rarity
tier (previous spread syntax overwrote keys instead of merging arrays)
- Update validation error messages to match actual validation logic:
colors now accept any valid hex format, not just specification palettes
- Add Rarity type for better type safety in rarity functions
- Add JSDoc clarifying that getColorRarity returns null for domain model
colors (BLOBBI_BASE_COLORS) which are not in the legacy palettes
Root cause: EggGraphic validation rejected derived colors because
isValidBaseColor() used a hardcoded allowlist that didn't include
the BLOBBI_BASE_COLORS palette (e.g., #F59E0B, #55C4A2, etc.).
Changes:
- Update isValidBaseColor/isValidSecondaryColor to accept any valid
hex color format (palette enforcement at domain level)
- Add visual trait tags (base_color, secondary_color, eye_color,
pattern, special_mark, size) to previewToEventTags for
deterministic rendering
- Improve useMemo dependencies in BlobbiEggVisual to ensure
re-render on preview change
Adds a dedicated CTA card in the Blobbi selector modal and page to allow
existing users to adopt additional Blobbies without going through full
onboarding.
Changes:
- Added AdoptAnotherBlobbiCard component with plus icon, tooltip, and
distinct visual styling (dashed border, centered layout)
- Updated BlobbiOnboardingFlow to support adoptionOnly prop that skips
profile creation and adoption question, going directly to egg preview
- Updated useBlobbiOnboarding hook with adoptionOnly mode support that:
- Derives initial step as 'preview' when adoptionOnly is true
- Generates preview immediately on mount in adoptionOnly mode
- Skips auto-sync logic that would interfere with explicit control
- Added adoption flow modal to BlobbiDashboard with full callback wiring
- Added adoption flow modal to BlobbiSelectorPage (Cases G and H)
- Passed required adoption callbacks through BlobbiDashboard props
UX flow:
1. User clicks 'Adopt another Blobbi' card in selector
2. Selector closes, adoption flow modal opens
3. User sees egg preview directly (no profile/adoption question steps)
4. User can reroll, name, and adopt as normal
5. On completion, modal closes and new Blobbi is selected
1. pettingLevel support:
- Added pettingLevel to BlobbonautProfile type and parsing
- New profiles include pettingLevel: 0 by default
- Created useBlobbonautProfileNormalization hook to auto-add
pettingLevel to existing profiles that are missing it
2. Reroll visual fix:
- Added key prop to BlobbiStageVisual to force remount on preview change
- Added debug logging to track preview identity changes (d/seed/petId)
- Reroll preserves typed name while generating new identity
3. Onboarding stability improvements:
- Enhanced step sync logic in useBlobbiOnboarding to handle all edge cases
- Added defensive checks for profile state changes
- Better debug logging for state transitions
4. Verified invariants:
- Preview remains single source of truth for adopted event
- Name is editable, required for adoption, preserved on reroll
- No any types introduced
- Fixed useBlobbiOnboarding to derive initial step from profile state
- Added useEffect to sync step when profile loads from cache/relay
- Added egg name customization via updatePreviewName() function
- Removed 'Maybe Later' skip option from adoption step
- Refactored BlobbiPage with cleaner state logic and debug logging
- Fixed TypeScript errors (unused vars, empty interface)
Resolves issue where onboarding always started on 'profile' step
even when user already had a profile, and adds name input to
egg preview before adoption.
Addressable events (kinds 30000-39999) like lists get new event IDs when
updated, causing nevent references to break. Use naddr coordinates
(kind:pubkey:d-tag) which remain stable across updates.
Replace silent null returns in EmbeddedNote and EmbeddedNaddr with a
visible tombstone UI showing 'This post could not be loaded' with a
dashed border and icon, so users know a quote was referenced even when
the event can't be fetched from relays.
- Add 'ditto' CoreFeedTab that renders the same hot-sorted curated feed
(useInfiniteHotFeed) previously shown only to logged-out visitors
- Change Global tab default from enabled to disabled
- Update useFeedTab to default to 'ditto' instead of 'global' when logged out
- Add showDittoFeed localStorage toggle in Feed.tsx and ContentSettings.tsx
- Add Ditto Feed toggle row in ContentSettings FeedTabsSection
The icon next to the list title adds visual noise without helping. The
PartyPopper icon is kept for starter packs (kind 39089) where it has
distinct meaning.
For kind 30000 (lists) and 39089 (starter packs), replace the prominent
avatar + bold name header with a content-first layout: the list title,
description, and member avatars are shown as the hero, followed by a
subtle small author attribution line (tiny avatar, muted text, timestamp).
This applies to both normal and threaded layouts, as well as compact/embed
mode used in quote posts and inline embeds.
When a kind 30000 list is quoted, ComposeBox encodes it as a nevent
which routes through EmbeddedNote. EmbeddedNote had no kind-specific
handling, so lists appeared as generic text cards instead of the rich
card with title, member avatars, and description.
Add the same NOTECARD_KINDS check (kind 30000, 39089) that EmbeddedNaddr
already uses, delegating to NoteCard compact for these kinds.
Replace the single Share button on the list detail page with a dropdown
menu offering Share (copy link / native share), Repost (kind 16 generic
repost), and Quote post (opens composer with quoted event).
Also rename all user-facing 'Follow Sets' terminology to 'Lists' across
the UI: UserListsPage, ListDetailPage, FollowPackDetailContent,
AddToListDialog, and SavedFeedFiltersEditor.
- Group reactions, reposts, and zaps on the same post into a single condensed
row with stacked actor avatars (up to 5 + overflow count); single-actor
interactions use the original compact layout
- Deduplicate actors by pubkey within groups
- Sum zap totals across all actors in a zap group
- Add notificationPreferences per-type settings (reactions, reposts, zaps,
mentions, comments, onlyFollowing) to EncryptedSettings and Zod schema
- Per-type kind toggles and onlyFollowing applied at relay query level (kinds
array + authors filter), not client-side; query key includes kindsKey and
authorsKey so preference changes trigger a fresh fetch
- Default push notifications to enabled
- Notification settings UI matches ContentSettings exactly: section headers
with accent underline, p-4 wrapper, broken border rows (border-b on outer
wrapper div), sub-section labels, w-[52px] switch column, KindBadges
- Add 'Only from people I follow' toggle under Filter sub-section
- Filter and push notification prefs also respected in useNativeNotifications
web push subscription via stable ref
- ProfileTabEditModal: add 'People' scope option with AuthorFilterDropdown and
ListPackPicker so users can pick specific pubkeys as tab authors, matching
the filter options available on the normal search filter
- useProfileFeed: add useTabFeed hook that runs a paginated useInfiniteQuery
with full NostrFilter support (kinds, authors, NIP-50 search)
- ProfileSavedFeedContent: replace useStreamPosts with useTabFeed so custom
tab content uses cursor-based pagination (like the posts tab) instead of
streaming, fixing search-keyed queries and enabling infinite scroll
Deduplicates the kind 10063 tag parsing logic that was repeated in both
NostrSync.tsx and useInitialSync.ts. Both now call the shared function
from lib/appBlossom.ts.
Add complete Blobbi onboarding flow:
- Profile creation step with name prefill from kind 0 metadata
- Adoption question step after profile creation
- Egg preview with reroll (10 coins) and adopt (100 coins) options
- Confirmation dialog before adoption
- New profiles start with 200 coins
Key components:
- BlobbiProfileOnboarding: Profile creation with name input
- BlobbiAdoptionStep: 'Ready to adopt?' prompt
- BlobbiEggPreviewCard: Egg preview with visual traits and actions
- BlobbiAdoptionConfirmDialog: Adoption cost confirmation
- useBlobbiOnboarding: State and action orchestration hook
Preview is the source of truth for adoption - same exact data is
used to create the final kind 31124 event. Coins are deducted
from profile before publishing events.
Blossom servers now work exactly like relays:
- App default servers (APP_BLOSSOM_SERVERS) can be toggled on/off
- User's kind 10063 server list is fetched and synced from Nostr
- NostrSync fetches kind 10063 on login and applies it to config
- useInitialSync fetches kind 10063 in parallel on first login
- BlossomSettings UI mirrors RelayListManager with two sections:
App Blossom Servers (with enable toggle) + Your Blossom Servers
- User changes publish a kind 10063 replaceable event to Nostr
- getEffectiveBlossomServers() merges app + user servers like getEffectiveRelays()
- Migration from old blossomServers[] to blossomServerMetadata handled in AppProvider
Non-kind-1 events (articles, photos, videos, etc.) bypassed the client-side
search check via an early return, so streamed events of any extra kind passed
through regardless of the search query. Remove the early return and apply the
query filter to all kinds, also checking title/summary/subject/alt tags for
non-text events. Reply filtering still only applies to kind 1/1111.
The Protocol label used flex layout for the HelpTip icon while the other
three labels did not, causing vertical misalignment of the selects below
them. Give all four labels the same flex items-center gap-1 classes.
Also rename Platform to Protocol.
Kind 6/16 repost events were passed raw to NoteCard without extracting the
embedded original event, so reposts showed nothing. Mirrors the same
parseRepostContent + repostedBy pattern used in useFeed/Feed.tsx.
parseKindFilter returned undefined for 'all', causing useStreamPosts to fall
back to the user's enabled feed-settings kinds instead of every kind in the
kind picker list. Now kindsOverride always contains explicit kind numbers:
all picker kinds when filter is 'all', or the specific selection otherwise.
getEnabledFeedKinds only returns user-toggled extra kinds, never kind 1.
The fallback paths for videos/images/all were missing kind 1 entirely,
so searches returned no results unless the user had explicitly enabled
extra feed kinds.
- Prevent focus steal on first keystroke: the add-to-feed bookmark button was
conditionally mounted when searchQuery became non-empty, causing a DOM mutation
mid-keystroke that stole focus from the input
- Eliminate searchParams feedback loop: the state→URL sync effect had searchParams
in its dependency array, causing a cascade of extra re-renders on every keystroke
after the debounce settled
- Eliminate per-keystroke re-renders: raw input state now lives in a dedicated
SearchInput child component with its own internal debounce; SearchPage only
holds the debounced value and re-renders when typing stops, not on every character
Add BioContent component that tokenizes bio text to render URLs as
clickable links and hashtags as internal navigation links, while
preserving NIP-30 custom emoji support. Applied to ProfilePage,
ProfileHoverCard, and ProfileCard read-only bio displays.
The vine-slide-height CSS class statically subtracted 3.5rem for the
bottom nav. Added a vine-slide-height-expanded class without that
subtraction, toggled by scroll direction so the video fills the full
viewport when the bottom nav slides away.
The Vines page uses an internal snap-scroll container instead of window
scroll, so useScrollDirection (which listened to window.scrollY) never
detected any scroll movement. Add a scrollContainer option to
LayoutOptions so pages with custom scroll containers can communicate
the element to MobileBottomNav, which passes it to useScrollDirection.
Replace Feed/Home button with Profile, which shows the user's avatar at
a small size. Search and Notifications remain. Logged-out users see a
generic User icon for Profile instead of the avatar.
Bring back the mobile bottom navigation bar with Home, Notifications, and
Search buttons. The nav slides out of view when scrolling down and reappears
on upward scroll, keeping the viewport uncluttered while reading. Search
retains the popover account search sheet for fast profile lookup without
navigating away. Updated CSS height calculations for full-screen pages
(AI chat, vines, livestream) and FAB positioning to account for the nav.