The _@ prefix denotes the root/default user for a domain. These users
should link to /domain.com (which the profile page resolves as a NIP-05
identifier), not the npub. The domain feed lives at /timeline/domain.com,
not /domain.com.
The cache-peek approach always returned npub because the cache was empty
at render time. Replace it with a proper hook that calls useNip05Verify,
relying on TanStack Query's deduplication to avoid redundant requests.
Also fix the dot separator in NoteCard to hide when NIP-05 is unverified,
and refactor ProfileSearchDropdown to pass the verified URL through
onClick rather than recomputing it in an event handler.
Add useNip05Verify hook that uses Nostrify's NIP05.lookup() to verify
that a claimed NIP-05 identifier resolves to the correct pubkey via
.well-known/nostr.json. Update Nip05Badge and all raw nip05 display
sites to suppress display when verification fails or is pending.
After updating encrypted settings, the onSuccess handler was calling
invalidateQueries which triggered a relay refetch. The relay would often
return the old event (before the new one propagated), causing NostrSync
to see a new lastSync timestamp and overwrite the locally-set theme
with the old value from Nostr.
The setQueryData call already keeps the cache up to date in-place,
so the invalidateQueries was purely counterproductive.
The pubkey was incorrectly hardcoded in the previous commit. Restore it
to AppConfig, the Zod schema, defaultConfig objects, all three
useNip85Stats hooks, and the Stats Source section in AdvancedSettings.
The NIP-85 Only Mode toggle (and its nip85OnlyMode config field) remains
removed — NIP-85 is always used.
Remove nip85StatsPubkey and nip85OnlyMode from AppConfig, AppProvider
schema, and all defaultConfig objects. The stats pubkey is now a
hardcoded constant in useNip85Stats. Remove the Stats Source section
from AdvancedSettings (UI toggle and pubkey input). Simplify
useEventStats to use NIP-85 exclusively — delete the direct-query
fallback, computeStats, extractZapAmount, and parseBolt11Amount.
Simplify useComments to remove the NIP-85-driven limit heuristic and
the nip85Stats return value.
useRepostStatus is the correct pattern (matching useUserReaction) for
tracking the current user's own repost status via a targeted relay query
with optimistic cache support. useEventInteractions now only fetches raw
interaction events for the InteractionsModal — NIP-85 involvement and the
hasNip85Stats query-key trick are removed entirely.
Remove useRepostStatus (which made a separate relay query per post) and
instead look up the current user's pubkey in the reposts list already
fetched by useEventInteractions. Add eventId to RepostEntry so the
deletion event ID is available without an extra query.
Create a reusable FollowButton component and add it to the
ProfileHoverCard, positioned over the banner image. The button
uses the existing useFollowActions hook for safe kind 3 mutations
and automatically hides for the user's own profile or when logged out.
Query the user's own kind 6 events to detect existing reposts, then
show an 'Undo repost' option in the popover menu and highlight the
repost button green when active. Unreposting publishes a kind 5
deletion event (NIP-09) with optimistic UI updates.
The FloatingComposeButton now returns null for logged-out users,
removing the floating Join CTA from mobile. The topbar Join and
other login CTAs are unchanged.
Replace the '?' avatar fallback and 'Anonymous' name placeholder with skeleton
loaders in ComposeBox and LeftSidebar during initial profile load. Expose
isLoading from useCurrentUser and useLoggedInAccounts to support this.
Bypass @emoji-mart/react wrapper which creates new Picker() in useEffect,
causing 'Failed to construct HTMLElement: Illegal constructor' when React
remounts the component (e.g. opening popover in reply or compose box).
Instead, manage the emoji-mart Picker Web Component imperatively via refs.
Type a colon followed by at least 2 characters (e.g. :fi) to trigger
an inline emoji autocomplete dropdown. Navigate with arrow keys, select
with Enter/Tab, or dismiss with Escape. Uses the existing emoji-mart
data for search by shortcode, name, and keywords.
The popover's mouse-leave timeout was firing during the DOM transition
from the quick-select bar to the full emoji-mart picker, causing it to
close immediately. Added an expanded state flag that disables the
hover-to-close behavior when the full picker is shown.
Encrypted settings synced via NIP-78 were parsed with a bare JSON.parse()
and cast with 'as EncryptedSettings', allowing invalid data (e.g. theme as
an object instead of a string) to pass through unchecked and crash the UI
when applied to classList.add(). Now both useEncryptedSettings and
useInitialSync validate decrypted JSON with a shared EncryptedSettingsSchema,
gracefully dropping invalid fields instead of propagating them.
Instead of discarding events from out-of-sync relays, keep all events
for display and compute the pagination cursor from the main timestamp
cluster. Outlier events are still shown but don't drag the cursor
backward, preventing pagination gaps without losing data.
- Add blossom.ditto.pub and blossom.dreamith.to as primary servers
- Mirror uploads to all configured servers via BUD-04 (fire-and-forget)
- Prefer first configured server for upload URLs
- Fall back to alternative Blossom servers when images/videos fail to load
The useLayoutOptions cleanup effect was unconditionally resetting the
layout store on unmount. During page transitions, React runs the new
page's render (which sets the store) before the old page's cleanup
effect fires. The unconditional reset would clobber the new page's
options, causing showFAB to be wiped.
Now the cleanup only resets if the store still holds this component's
options, preventing it from interfering with the incoming page.
- Extract zap amount from kind 9735 events in NostrPoller.java (checks
amount tag then falls back to description tag's zap request)
- Format amounts compactly (e.g. 1.5K sats, 2M sats)
- Set data URI on notification PendingIntent so tapping opens /notifications
- Handle deep link intent in MainActivity (both onCreate and onNewIntent)
- Remove dead localNotificationActionPerformed listener from TypeScript
(only fired for Capacitor-API notifications, not natively posted ones)