- Use PageHeader + SubHeaderBar arc format matching other feed pages
- Add arc overhang spacer for consistent feed padding
- Move NIP-11 relay info into an inline expanding panel (maxHeight transition)
- Info toggle button in PageHeader top-right corner
- Accept string | undefined in useRelayInfo hook signature
- Register useLayoutOptions({ hasSubHeader: true }) for mobile nav
Consolidate duplicated infinite-scroll boilerplate (auto-fetch page 2,
IntersectionObserver, scroll trigger) into a shared useInfiniteScroll
hook, and extract the flatten+dedup pattern into deduplicateEvents.
Also migrate BadgesPage and ThemesPage to use useFeedTab for
consistent tab persistence across all feed pages.
Send authors: ['$contacts'] in the nostr-push subscription filter
when onlyFollowing is enabled. The nostr-push server resolves this
macro to the user's kind 3 follow list. Toggling the setting off
removes the authors filter so all notifications are delivered again.
Pass followed pubkeys through the Capacitor plugin to the native
polling service. When onlyFollowing is enabled, the relay query
includes an authors filter so only events from followed accounts
trigger native Android notifications.
Disabled notification types (e.g. reactions) still triggered push
notifications and showed the unread dot indicator, even though the
notification tab correctly filtered them out.
Three root causes fixed:
- useHasUnreadNotifications now uses getEnabledNotificationKinds to
only query for enabled types, preventing phantom unread dots
- NotificationSettings now syncs type preference changes to the
nostr-push server via updateSubscription (is_active toggle)
- Native Android poller now receives enabled kinds from the JS layer
and uses them in the relay filter instead of hardcoded kinds
- Upgrade react, react-dom to ^19.2.4 and @types/react, @types/react-dom to v19
- Upgrade @nostrify/react to 0.4.0 (peer deps fix for React 19)
- Upgrade vaul to 1.1.2 and react-day-picker to 9.14.0 for React 19 compatibility
- Fix useRef() calls to pass explicit initial values (required in React 19)
- Update RefObject types to include null (React 19 type change)
- Rewrite Calendar component for react-day-picker v9 classNames API
- Add npm overrides for @emoji-mart/react (only remaining React 18 holdout)
Extract shared useInsertText hook to DRY up the duplicated text
insertion logic across ComposeBox, DMChatArea, and ZapDialog.
Add EmojiPicker (GUI) and EmojiShortcodeAutocomplete (:shortcode
typing) to the zap comment textarea, and also add shortcode
autocomplete to the DM chat input which was previously missing it.
Closes#176
On mobile, toasts enter from the top but previously could only be swiped
right to dismiss. Now swipe direction is responsive: swipe up on mobile
(top-positioned), swipe right on desktop (bottom-right positioned). Exit
animations also match the swipe direction at each breakpoint.
Kind 16767 events previously hardcoded description to undefined, so the
theme description never appeared on 'updated their theme' posts in the
feed or detail view.
Three changes:
- buildActiveThemeTags now accepts and includes a description tag, so
future kind 16767 events carry the description directly
- setActiveTheme accepts description to thread it through publishing
- ThemeContent extracts the description tag from kind 16767 events, and
for older events without one, falls back to querying the source theme
definition via the a-tag reference
- Change KIND_BLOBBONAUT_PROFILE constant from 31125 to 11125
- Add KIND_BLOBBONAUT_PROFILE_LEGACY (31125) for migration support
- Add BLOBBONAUT_PROFILE_KINDS array to query both kinds
- Update useBlobbonautProfile to prefer 11125 over 31125
- Add needsKindMigration flag for legacy profile detection
- Extend useBlobbonautProfileNormalization to auto-migrate legacy kinds
- Refactor onboarding to auto-create profile using kind 0 name
- Remove manual name entry step from onboarding flow
- Delete unused BlobbiProfileOnboarding component
- Update all documentation comments to reference kind 11125
Letters were completely absent from the notification pipeline — users had
to visit the Letters page to discover incoming letters. This integrates
kind 8211 into every layer of the notification system:
- useNotifications: query, grouping, and referenced-event exclusion
- useHasUnreadNotifications: unread dot indicator
- NotificationsPage: LetterNotification component with link to /letters
- NotificationSettings: toggleable Letters row
- notificationTemplates: web push template
- Android NotificationRelayService + NostrPoller: native push support
- EncryptedSettings + schema: letters preference field
Closes#188
The useBadgeFeed hook required a logged-in user before enabling the query,
causing the follows tab to show loading skeletons forever when logged out.
Now fetches the Team Soapbox follow pack (kind 39089) and uses its members
as the authors filter, giving logged-out users a curated badge feed.
- useLetterPreferences: simplify to just expose raw saved prefs + isThemeDefault flag,
no longer conflates theme stationery with saved stationery
- LetterPreferencesSection: always pull from useThemeStationery directly when
isThemeDefault, persist only on explicit user picks (handleSetStationery),
sync preview live when theme changes
- ComposeLetterSheet: same pattern — init from themeStationery, switch to saved
pref once settings load, track explicit user picks to avoid theme override
- LetterEditor drawer: remove bg-background / rounded-b-3xl / border-b card shape
- ComposeLetterSheet: use themeStationery immediately (no parchment fallback),
switch to saved pref once encrypted settings load, sync with theme changes
- ComposeLetterSheet: move send button to inline flow below the card (no more fixed overlay)
- LetterPreferencesPage: remove PageHeader — back button + title now live inside
LetterEditor's headerLeft slot, eliminating the double bar
- 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
Hovering the mouse across the badge image now rotates it in
perspective space with a specular glare overlay that tracks the
cursor, making the badge feel like a tangible, glossy object.
Introduces a reusable useCardTilt hook for pointer-driven 3D
perspective transforms with configurable max tilt and scale.
When no title font was explicitly set (undefined), published theme
events omitted the title font tag entirely. This happened easily
when users selected Inter as the title font (the default), which
toggled back to undefined due to the deselect behavior.
Now resolveThemeForPublishing falls back titleFont to the body font,
ensuring published events always include both font tags when a body
font is present.
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.
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
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
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
Replace inline nostrconnect:// protocol logic with NLogin.fromNostrConnect(),
generateNostrConnectParams(), and generateNostrConnectURI() from upstream
@nostrify/react. Removes ~130 lines of code that is now in the library.