Defer body inline style removal until CSS variables are injected by
useApplyTheme, preventing a one-frame flash of unstyled background.
Replace preloader logo <img> with CSS mask technique matching the React
DittoLogo component, so the logo uses the theme's primary color instead
of filter:invert() which turned purple into green.
When no user is logged in, generate a random keypair once per hook instance
and use it to sign webxdc events (kind 4932 state updates and kind 20932
realtime). selfAddr/selfName are now derived from the ephemeral key instead
of returning empty strings.
After a file uploads, decode it on a small canvas (64px wide) to
compute the natural dimensions (dim) and a 4x3 blurhash. Both are
injected into the NIP-94 tag array before storing in uploadedFileGroups,
so the published imeta tag includes them automatically.
When the container has an aspect-ratio from the dim tag, switch the
video to absolute inset-0 h-full to fill it cleanly. Fall back to
max-h-[70vh] object-cover for videos without dim data.
- Extract blurhash from imeta tags for all event kinds (non-kind-1
via the first imeta tag; kind-1 by matching the URL against imeta)
- New MediaTile component renders Blurhash canvas before the image/video
loads, falling back to a Skeleton when no blurhash is available
- Tile uses absolute positioning so the placeholder fills the square cell
- Move aspect-ratio and height constraints onto the button container so
both placeholder and image can fill it with absolute inset-0
- Pass integer decode resolution to Blurhash and size the canvas via
CSS width/height 100% instead of relying on string props
- Remove stale per-element skeletonStyle/heightStyle juggling
- Parse NIP-94 dim and blurhash fields in parseImetaMap (NoteCard + PostDetailPage)
- ImageGallery/GridImage: use dim for correct aspect-ratio skeleton sizing; render Blurhash canvas placeholder when available
- VideoPlayer: set aspect-ratio container from dim; show Blurhash canvas before first frame/poster loads
- Install react-blurhash (+ blurhash) for canvas rendering
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.