Increases the opacity of emoji-mart nav button text from 0.65 to 0.85
by injecting CSS overrides into the shadow DOM. This improves readability
and meets WCAG contrast requirements for the category navigation icons.
Fixes#174
useFeedTab defaulted to 'ditto' when logged out, but pages like
Videos only accept ['follows', 'global'] as valid tabs. The 'ditto'
default was never validated against validTabs, so no feed query
matched and nothing loaded. Now the default is validated and falls
back to the last valid tab ('global') when it's not in the list.
Instead of a generic 'Same as body' placeholder, the title font
combobox now displays the body font's name (rendered in that font's
typeface) when no title font is explicitly set. Falls back to
'Default (Inter)' when neither font is set.
The title font combobox was showing 'Default (Inter)' when no title
font was set, but the actual fallback is the body font. Add a
placeholder prop to FontPicker and pass 'Same as body' from
FontSection for the title picker.
- Refactor FontPicker into a bare combobox (no section header, no
'quick brown fox' preview text)
- Add FontSection component that combines Body and Title font pickers
in a single section with inline labels and a live preview showing
both fonts in context (display name + body text)
- Title font falls back to the body font when not explicitly set, so
the profile display name inherits the theme's body font rather than
the default Inter
- Update ThemeSelector builder dialog and ProfilePage edit dialog to
use FontSection with controlled body/title font props
Embedded/quoted posts (EmbeddedNote, EmbeddedNaddr) no longer display
full image thumbnails. Instead, images are shown as a small icon+label
indicator at the bottom, consistent with how videos and audio are
already represented. This keeps compact views lightweight and focused
on text content.
Implement the title font feature across the full stack:
- Type system: Add titleFont to ThemeConfig, ThemePreset, ThemeDefinition,
ActiveProfileTheme interfaces and Zod schema
- Event encoding: Update f-tag build/parse to emit role markers (body/title)
with backward-compatible parsing of legacy roleless tags
- Font loading: Add loadAndApplyTitleFont with --title-font-family CSS var
- UI: Add Title Font picker to theme builder dialog and profile theme editor,
with configurable label prop on FontPicker
- Profile rendering: Apply --title-font-family to the display name h2
- Data flow: Thread titleFont through ThemeSelector snapshots, theme
presets, publish/update, NostrSync, ThemeContent, and diff detection
Add prose-code:text-foreground so inline code elements use the theme
foreground color instead of the typography plugin default (dark gray),
which was invisible against the muted background in dark mode.
The Tailwind Typography plugin sets bold/strong text to gray-900 by default,
which is invisible on dark backgrounds. Added prose-strong:text-foreground
and other theme-aware color overrides for list markers, blockquotes, rules,
and table headers across all markdown prose views.
Long unbreakable strings (sha256 hashes, URLs, base36 pubkeys) in NIP documents
and articles caused large word-spacing gaps in narrow containers. Added break-words,
horizontal scroll for code blocks, and consistent inline code styling to
CustomNipCard, ArticleContent, and PullRequestCard.
Port NostrURI module from Shakespeare to generate nostr:// clone URIs
from kind 30617 event data (pubkey, d-tag, relays). Display the Nostr
clone URI in GitRepoCard as the sole clone URL, replacing the previous
HTTPS clone URL. Also use the nostr:// URI for Shakespeare clone links.
The FAB background div always had rounded-full applied, which added
border-radius: 9999px even when an emoji mask was active. This caused
square (and other non-circular) avatar shapes to still appear rounded.
Now rounded-full is only applied when there is no emoji shape mask,
matching the pattern used by the Avatar component.
- Create BlobbiDevEditor modal component for direct state editing
- Add useBlobbiDevUpdate hook using standard update/publish flow
- Support editing: stage, state, adult form, all stats
- Support editing: experience, care streak, generation, breeding ready, visibility
- Add stat presets: Max Stats, Starving, Exhausted, Dirty, Sad, Critical Health, etc.
- Add wrench icon button to hero section (DEV only)
- Wire to existing updateBlobbiTags pipeline for consistent Nostr events
- Only renders in development mode (import.meta.env.DEV)
- Create centralized need detection system with configurable thresholds
- Add continuous gravity for items dropped mid-air (fall to ground)
- Blobbi now glances at items it doesn't need (brief look)
- Blobbi shows interest in items it needs (longer attention)
- Add ItemLandedData interface with position info for reactions
- Create useCompanionItemReaction hook for need-based behavior
- Expose triggerAttention from useBlobbiCompanion hook
Since companions can only be baby or adult (not egg), egg-only items
like Shell Repair Kit should never appear in the companion flow.
Changes:
- Update resolveItemsForAction to use centralized canUseItemForStage
- Add item-stage validation in useBlobbiItemUse mutation
- Egg-only items are now filtered at both display and use layers
The filtering is now enforced by:
1. resolveItemsForAction - items won't appear in hanging items menu
2. useBlobbiItemUse - validation prevents use even if somehow displayed
- Create shared ItemEffectDisplay component as single source for effect rendering
- Update BlobbiInventoryModal to show ALL effects (was truncated to 2)
- Update BlobbiShopItemRow to show ALL effects (was truncated to 3)
- Update BlobbiPurchaseDialog to use shared component
- Use canonical stat display order: hunger, happiness, energy, hygiene, health
- Deprecate formatEffectSummary in favor of ItemEffectDisplay component
The root cause was effect display truncation in the UI, not inconsistent data.
All item definitions remain in blobbi-shop-items.ts (single source of truth).
- Replace releasedItemIds (Set) with releasedCountByItemId (Map) to track
how many instances of each item type have been released
- Generate unique instanceId for each dropped item (format: itemId-timestamp-counter)
- Hanging items now show remaining quantity (quantity - releasedCount)
- Multiple instances of the same item type can exist on the ground simultaneously
- Each dropped instance tracks independently via instanceId
- Update all callbacks and state tracking to use instanceId instead of item.id
- When item is used successfully, decrement releasedCount for that item type
This enables the desired UX where clicking a hanging item immediately shows
a new copy in the hanging slot (if quantity > 1), while the released instance
falls independently.
Major architectural fix for Blobbi companion item-use system:
1. Created shared useBlobbiItemUse hook
- Works standalone outside of BlobbiPage
- Uses same real item-use logic as BlobbiPage
- Built-in per-item cooldown tracking
- Fetches companion/profile data on-demand when needed
2. Refactored BlobbiActionsContext
- Now has built-in fallback using useBlobbiItemUse
- Item use works from ANY page, not just /blobbi
- BlobbiPage registration is optional (provides better cache access)
- No more 'canUseItems = false' when BlobbiPage not mounted
3. Fixed retry/flood issues in HangingItems
- Added per-item cooldown (3s on failure, 0.5s on success)
- Implemented zone ENTRY detection (not continuous overlap)
- Items only trigger auto-use when ENTERING the Blobbi zone
- Items must leave zone before re-triggering
- Multiple protection layers prevent spam
4. Fixed useBlobbonautProfile side-effect
- Moved setBootCache from useMemo to useEffect
- Added ref-based signature tracking to prevent loops
- Proper cleanup and stable dependencies
Files changed:
- NEW: src/blobbi/companion/interaction/useBlobbiItemUse.ts
- src/blobbi/companion/interaction/BlobbiActionsContext.tsx
- src/blobbi/companion/interaction/HangingItems.tsx
- src/blobbi/companion/interaction/index.ts
- src/blobbi/companion/components/BlobbiCompanionLayer.tsx
- src/hooks/useBlobbonautProfile.ts
Sign kind 22242 AUTH events automatically when relays send challenges.
Uses a signer ref pattern so the pool doesn't need to be recreated
when the user logs in/out or switches accounts.
Remove gradient background and encryption footer from the full card, use
a clean bordered card with centered sender description. Add
EncryptedMessageCompact for quote posts, reply indicators, and the reply
composer — dispatched from EmbeddedNote like vanish events.
Kind 4 (NIP-04 encrypted DM) events were rendering their encrypted
content as plain text. Now they show a visual card with sender and
recipient avatars connected by a mail icon, with encryption type
detection (NIP-04 vs NIP-44) displayed in a footer badge with a lock icon.
Registered kind 4 across NoteCard, PostDetailPage, and CommentContext.
The fallthrough to EXTRA_KINDS produced plural/categorical labels like
'requests to vanish' and 'user statuses'. Now getKindLabel only uses
KIND_LABELS which has proper singular forms ('a request to vanish',
'a status'). Added missing entries for kinds 6, 16, 62, 30315.
- Themes use Sparkles (matching sidebar), not Palette (that's color moments)
- Profile badges now reads 'Commenting on profile badges by @Mary'
instead of 'Commenting on @Mary's profile badges'
CardsIcon for Magic decks, ChestIcon for geocaches/found logs,
Clapperboard for vines, Music for tracks/playlists, Camera for photos,
Film for videos, GitBranch for repos, GitPullRequest for PRs,
BookOpen for articles, Podcast for episodes/trailers, BarChart3 for polls,
PartyPopper for follow packs, Palette for color moments.
- Add kind-specific suffixes: badge, theme, follow pack, emoji pack, etc.
- Add kind-specific postfixes: apps show 'on Zapstore', releases show 'release'
- Profile badges (30008) show '@User's profile badges' with author hover card
- Add icons for all supported kinds (Award, Palette, Package, etc.)
- Add BookOpen icon inline next to book titles in ISBN context
- Add @ prefix to author names in reaction context
- Expand KIND_LABELS to cover all Ditto-supported kinds with proper articles
- IsbnCommentContext: Wrap book title link in HoverCard showing cover
thumbnail, title, and authors (reusing useBookInfo data)
- CountryCommentContext: New component extracted from ExternalCommentContext,
wraps country link in HoverCard showing flag, region/country label,
name, and parent country for subdivisions
- Both follow the same HoverCard pattern as UrlCommentContext
- EmbeddedNaddrCard: Add MaybeProfileHoverCard wrapper on avatar and
name links with disableHoverCards prop (matching EmbeddedNoteCard)
- NoteCard EventActionHeader: Wrap author name in ProfileHoverCard
- CommentContext: Wrap author links in ReplyToCommentContext,
ProfileCommentContext, and ReactionCommentContext
- LiveStreamPage ParticipantRow: Wrap avatar and name links
Add KIND_SPECIFIC_ICONS override map in extraKinds.ts so kinds in
extraFeedKinds arrays get their own icon instead of inheriting the
parent def's icon. Nsites (15128/35128) now show Globe, patches
show GitPullRequestArrow, etc.
Add getKindLabel() to extraKinds.ts as the canonical resolver for
per-kind labels. Kinds in extraFeedKinds (like nsites 15128/35128)
now show 'nsite' instead of the parent category 'development'.
Embed cards for non-text kinds (nsites, follow packs, etc.) now show a kind
label line with icon below the title/description, giving context about what
the event is. Previously a quoted nsite just showed 'ditto' with no indication
it was an nsite deployment.
Quote posts and hover previews should always be compact (author + title/content).
Rendering a full NoteCard with action headers, buttons, and nested card components
inside a quote post or hover card is too much UI for an inline context.
- Remove NOTECARD_KINDS from EmbeddedNote.tsx and EmbeddedNaddr.tsx
- Remove NoteCard imports from both embed components
- Add tag-based metadata fallback (title/name/d, summary/description) to
EmbeddedNoteCard for non-text kinds with empty content
- Update AGENTS.md checklist to reflect that embeds no longer need per-kind registration
- Extract shared CommentContextRow wrapper to eliminate duplicated div/className/skeleton patterns
- Unify getKindLabel into getEventDisplayName so kind-to-label mappings exist in one place
- Fix inconsistent min-w-0 overflow-hidden between loading and content states
- Add hover card previews to addressable event roots (A tag) using EmbeddedNaddr
- Replace bespoke reply composer embed with shared EmbeddedNote/EmbeddedNaddr components
Root cause analysis and fixes:
1. Drag-to-use freeze/loop (HangingItems):
- Problem: When dropping on Blobbi, item position was set ON Blobbi,
triggering contact detection to also call attemptUseItem, creating a loop
- Problem: attemptUseItem had itemsBeingUsed as a dependency, so when it
changed (inside the callback), the callback identity changed, re-triggering
the contact detection effect
- Fix: Changed itemsBeingUsed from state to ref to avoid callback recreation
- Fix: When dropping on Blobbi, reset item to ORIGINAL position before
attempting use (prevents contact detection from firing)
- Fix: Made attemptUseItem have no dependencies (uses refs for everything)
- Fix: Gated all console.log calls behind import.meta.env.DEV
2. useBlobbonautProfile console flood:
- Problem: Unconditional console.log at line 63 ran on EVERY render
- Problem: queryFn had multiple console.logs that ran on every query
- Fix: Removed/commented out all console.logs in the hook
- Analysis: The hook itself was NOT causing extra renders - it was just
exposing the render frequency with its logging
3. BlobbiActionsContext registration instability:
- Problem: Registration used useState which triggered re-renders on every
update, and the registration effect depended on useItem identity
- Fix: Refactored to use refs instead of state for registration data
- Fix: Added subscription pattern for manual notification only when
canUseItems actually changes (major state change)
- Fix: Consumer hook's useItem callback is now stable (reads from ref)
- Fix: Provider context value is now stable (never changes identity)
Guards now preventing repeated item-use attempts:
- itemsBeingUsedRef.current check at start of attemptUseItem
- Contact detection skips items in itemsBeingUsedRef
- Drag-drop resets item position BEFORE calling attemptUseItem
- attemptUseItem has no dependencies that could trigger recreation