Use IntersectionObserver on the ComposeBox wrapper in Feed.tsx to track
its visibility. When visible, hide the FAB to avoid redundancy. When the
user scrolls past the compose area, the FAB smoothly fades in with an
upward slide animation. Extends LayoutStore with fabHidden state that
is part of the snapshot so useSyncExternalStore triggers re-renders.
Add responsive padding to lightbox swipe slots to clear the top bar and
bottom bar overlays. Change LightboxImage from w-full/h-full to
max-w-full/max-h-full so images scale within the padded area instead of
stretching behind the UI controls.
Register touchmove handler via addEventListener with passive:false
instead of React's onTouchMove prop, fixing the 'Unable to
preventDefault inside passive event listener' console warning.
Fix swiped images never appearing by checking img.complete on mount —
cached images from the pre-rendered prev/next slots may not fire onLoad,
leaving them stuck at opacity-0.
React Query serves cached data instantly for the previous event while the
new fetch is in-flight, causing stale replies to flash. Track the event id
the current data belongs to and treat any in-flight fetch for a different
event as a loading state, showing skeletons instead of stale replies.
Previously the query was gated on 'open', so when swiping to a new image
the old event's replies stayed visible until the new query resolved.
Now the event is always passed so the query key updates instantly.
- MediaGrid/Lightbox: support video (VideoPlayer) and audio (AudioVisualizer)
with proper per-type rendering in grid thumbs and lightbox strip slots
- Audio thumb shows author avatar with idle-ring animation instead of plain icon
- LightboxSlot renders VideoPlayer/AudioVisualizer constrained to max-w-lg
matching the audio visualizer width; resolves author avatar for audio via useAuthor
- useProfileMedia: add audio kinds 36787, 34139, 30054, 30055 to query
- CommentsSheet: kind-1 events query only kind-1 replies (not 1111), show
'Replies' header and 'Add a reply…' placeholder; all other events use 1111
- PhotoBottomBar: simplified back to single CommentsSheet; removed ReplyComposeModal
- CommentsSheet z-index raised to z-[200] so it appears above the Lightbox (z-[100])
- ProfileRightSidebar: onMediaClick callback; ProfilePage wires it to switch
to media tab and open lightbox at the clicked URL via initialOpenUrl prop
- MediaGrid flat entries now carry indexInEvent + countInEvent; counter in
top bar shows '2 / 4' for the current event only, hidden when single image
- swipeCommitted ref + rAF reset so strip snaps invisibly after commit
- Commit threshold lowered from 30% to 20% viewport width
Swipe: replace React state-driven translation (caused re-renders + jarring
snap-back) with direct DOM mutation via ref. Axis is locked on first
significant movement; at edges resistance is 20% rubber-band. On release,
if past the 30% threshold the next/prev fires and the position resets
without animation; otherwise it springs back with a single cubic-bezier
transition.
Bottom bar: matches top bar rhythm (px-4 py-3 + safe-area-bottom).
Gradient scrim is now a fixed h-32 pseudo-element that extends upward from
the bottom of the bar, independent of the content height, so it fades
cleanly over the image without needing extra pt-10 padding on the strip.
- Extract PhotoBottomBar into its own component; remove the bottom-nav clearance
padding (the full-screen lightbox overlay covers the nav bar, only safe-area
inset is needed)
- Create MediaGrid: generic 3-col square-thumbnail grid that handles imeta tags
(NIP-68/94 native kinds) and kind-1 inline image URLs; self-contains overlay
state and opens Lightbox with PhotoBottomBar + cross-event navigation
- PhotosFeedPage now delegates entirely to MediaGrid + MediaGridSkeleton
- Profile media tab replaced: was a NoteCard list, now renders MediaGrid for a
proper Instagram-style grid experience matching the /photos page
Replace the bespoke PhotoSlide / PhotoCard / PhotoDetailOverlay stack (~250 lines) with a
thin extension of the existing Lightbox component. Lightbox gains bottomBar, onNextEvent,
and onPrevEvent props; ImageGallery forwards them through. PhotosFeedPage now opens a plain
Lightbox with author info + reactions injected via bottomBar, and cross-event swipe/keyboard
navigation wired through onNextEvent/onPrevEvent. Page shrinks from 626 → ~300 lines.
Remove the invisible spacer div hack (w-8 shrink-0) and restore gap-3
on the search row. The actual fix is min-w-0 on the input, which lets
it shrink below its intrinsic minimum width in the flex container.
The search input with flex-1 extended ~32px further right than item
labels below (which have a Plus button consuming that space). Added a
matching w-8 spacer and min-w-0 so the input aligns with item labels
and cannot force the row wider than the dropdown.
The bg-muted/50 background on the search row extended wider than the
item rows below (which have a Plus button consuming right-side space),
creating a visible purple block and visual misalignment that made
content appear truncated on the left.
The More... trigger sits at the bottom of the sidebar, so the dropdown
was opening downward into very little available space, getting clipped.
Use side="top" so it opens upward where there is ample room, and add
collisionPadding for a safety margin from viewport edges.
The original border-b border-border was removed from NoteCard in 9bd646b
for a photos overlay feature that no longer uses NoteCard. This forced
every container to add divide-y divide-border as a workaround, but
several pages were missed (Bookmarks, Events, Trends, Search, etc).
Restore NoteCard's self-bordering and remove the now-redundant divide-y
wrappers. Existing overrides (EmbeddedNaddr !border-b, Notifications
border-0) continue to work correctly.
- Redesign VideosFeedPage: 2-column YouTube-style grid, compact live
strip with drag/edge-hover scroll, shorts horizontal shelf
- Show 4 videos when live streams present, 6 otherwise, with show more
- Add HLS playback support via hls.js for .m3u8 streams
- Generate thumbnails from video frames as fallback when no image tag;
handles MP4/WebM/MOV via loadedmetadata+seeked, HLS via hls.js
- Export useVideoThumbnail from VideoPlayer for reuse in feed cards
- Fix kind 21/22 post detail page: render VideoDetailContent with player,
title, description and hashtags instead of falling through to text note
- Section headers use text-sm uppercase tracking
Replace the fragmented horizontal-scroll sections with a single clean
vertical list of video cards (thumbnail + title + author + timestamp),
matching the normie-familiar YouTube homepage pattern.
- Normal videos (kind 21): full-width aspect-video card with large
thumbnail, YouTube-style info row (avatar left, title/author/time right)
- Shorts (kind 22): compact portrait card (100px wide thumbnail + text),
tapping still opens the TikTok-style snap-scroll player
- Live now shelf: compact horizontal strip with smaller chips, sits above
the feed behind a border separator rather than floating mid-page
- Empty state links directly to 'Switch to Global' inline
NostrSync's internal sync state (lastSyncedTimestamp, profileThemeSynced)
was never reset on account switch, so the new user's encrypted settings
were skipped. Add a prevPubkey effect that resets these refs when the
active user changes, allowing the encrypted settings sync to apply the
new user's theme immediately.
Also handle users who never saved a theme: when switching to an account
whose encrypted settings don't contain a theme (or have no encrypted
settings at all), reset to the app default ('system') so the previous
user's theme doesn't bleed through.