Port the dataToPoints + SparklinesCurve algorithm from react-sparklines
inline, without adding the dependency. Key improvements:
- Smooth cubic bezier (C commands, divisor=0.25) instead of jagged polyline
- Full vertical range utilised: min/max scaling fills the chart height
- Thinner stroke (1.5px vs 2px) for a more delicate look
- Smaller viewBox (50x28) matching the legacy app dimensions
Mirror the server's getTrendingTags logic exactly:
- Use UTC-midnight-aligned calendar day boundaries derived from the
most-recent label event's created_at, not rolling 24h windows from now
- Filter each per-day query with '#t': [tag] so only label events that
mention the specific hashtag are returned, rather than any label event
for that day
- Add TRENDS_PUBKEY author filter to all kind 1985 queries (useTrendingTags,
useTrendingPosts, useTagSparklines) to only trust events from the known
Ditto trends publisher
- Replace synthetic count with real accounts/uses values parsed from t tag
indices 3 and 4 respectively
- Show '{accounts} people talking' in the Trends widget instead of '{count} posts'
- Sparklines continue to use the 'uses' field from per-day label events
navItems useMemo was missing userProfileUrl from its dependency array, so
the Profile link kept the npub URL computed at first render. Adding
userProfileUrl as a dependency causes the nav to recompute once
useNip05Resolve returns and useProfileUrl switches to the NIP-05 URL.
Both hooks were making separate fetches to the same endpoint. Rewrite
useNip05Verify as a thin wrapper that calls useNip05Resolve and compares
the resolved pubkey — they now share the same query key and a single
network request serves both verification and resolution.
The name portion of a NIP-05 identifier is case-sensitive per the spec.
useNip05Resolve was calling .toLowerCase() on it before building the URL
query param, causing lookups like Hyperhidrosis@shitposter-world.mostr.pub
to request ?name=hyperhidrosis and fail. Preserve the original casing for
both the URL request and the primary key lookup; keep the case-insensitive
fallback but compare both sides lowercased.
isNip05Like in NIP19Page only checked for '@', so bare domain identifiers
like 'fiatjaf.com' fell through to NIP-19 decoding and returned NotFound.
Extend the check to match the same dot-based heuristic already used in
ProfilePage.
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.
- Add overflow-hidden to NoteCard article elements in both normal and
threaded layouts to clip content that exceeds card width
- Add overflow-hidden to NoteContent root div to prevent link preview
cards and embedded content from pushing beyond container bounds
- Strip .xdc URLs in EmbeddedNote content preview (was missing from
the media URL regex, causing raw long URLs to render in quote embeds)
- Add overflow-hidden to EmbedContentPreview paragraph for defense
against long unbreakable strings
- Use disableEmbeds on NoteContent inside ReplyComposeModal's embedded
post preview so URLs render as plain links instead of full preview
cards that overflow the clamped container
- Add attachment indicator chips to EmbeddedNote (images, videos, apps,
links) so stripped media URLs still have a visible presence
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.