The HoverCard trigger now wraps only the 'Replying to' label, not the
@username mentions. This prevents the post preview hover card from
conflicting with the profile hover cards on usernames.
ProfileHoverCards on avatar, name, and @mentions inside EmbeddedNote
are now suppressed when rendered within the reply context hover card,
preventing nested hover card nesting.
Hovering over the 'Replying to @user' context on reply posts now shows
an embedded preview of the parent event, making it easy to see what is
being replied to without navigating away.
- Extract getParentEventId to shared utility (src/lib/nostrEvents.ts)
- Pass parentEventId from NoteCard to ReplyContext
- Wrap the reply context line in a HoverCard with EmbeddedNote preview
useLocalStorage's setValue captured 'state' at closure-definition time.
When multiple updateConfig calls fired before a re-render (e.g. NostrSync
and useInitialSync both calling updateConfig on startup), each call's
functional updater received the same stale state snapshot instead of the
accumulated result of previous calls — so later writes silently discarded
earlier ones.
Fixed by using React's built-in functional setState form (setState(prev =>
...)) which always receives the true current state from React's queue,
making all sequential updateConfig calls chain correctly regardless of
render timing.
Three bugs caused settings toggles to get stuck or reset:
1. useEncryptedSettings: onSuccess was updating the parsedSettings cache
under the old event ID, causing NostrSync to re-apply the stale remote
event when the new one arrived with a different ID. Fixed by using the
signed event's ID for both cache entries.
2. ContentTypeRow/SubKindRow: handleToggle was building the Nostr payload
from the local React feedSettings state, which lags behind on rapid
successive toggles. Fixed by merging against the Nostr-cached settings
(nostrSettings?.feedSettings) as the base instead.
3. NostrSync: lastSyncedTimestamp ref reset to 0 on every page reload, so
the 10-second recentlyWritten() guard was the only protection against
stale relay events overwriting local changes. Fixed by seeding the ref
with the current remote lastSync on first settings load, skipping
application of any event that isn't strictly newer than what was
already in the cache.
Replace the single tabbed Settings page with a landing menu that navigates
to dedicated sub-pages, following the Agora pattern:
- Settings landing: card-based menu with icons and descriptions
- Profile: wraps EditProfileForm (auth-gated)
- Appearance: Theme sub-tab with visual theme selector (light/dark/black/pink
+ custom placeholder) and Content sub-tab with existing content settings
- Wallet: dedicated page for NWC wallet management (auth-gated)
- Notifications: push notification enable/disable toggle (auth-gated)
- Advanced: network relays, stats source, and system settings (wallet
section removed since it has its own page now)
- FloatingComposeButton accepts kind prop: kind=1 opens compose dialog, others show 'Coming soon'
- FAB positioned sticky bottom-right within center column (visible on all screen sizes)
- Center column gets bottom padding (pb-24) to account for FAB obstruction
- LayoutContext extended with fabKind option passed through layout system
- Home feed uses kind=1, KindFeedPage passes its kind, StreamsFeedPage uses 30311
The logo was still off-center because the SVG viewBox '-5 -10 100 100'
is asymmetric and doesn't properly frame the logo content.
Changes:
- Update generate-icons.sh: Change viewBox from '-5 -10 100 100' to '0 0 90 80'
- Update vector foreground: Change viewportWidth/Height from 100x100 to 90x80
- Adjust pivot point to match new viewport dimensions
- Regenerate all launcher PNGs and adaptive foreground PNGs with corrected viewBox
The logo should now be properly centered without the left gap.
The logo was off-center with a gap on the left due to the SVG's asymmetric
viewBox (-5, -10, 100, 100). Fixed by adjusting rendering and positioning.
Changes:
- Update generate-icons.sh: Render at 600px and crop to 512px for better centering
- Update vector foreground: Shift translateX from 5 to 7 to compensate for viewBox offset
- Regenerate all launcher PNGs and adaptive foreground PNGs
The logo now appears properly centered in the icon.
Zoomed out the logo further from 50% to 40% for better visual balance
and more padding around the logo.
Changes:
- Update generate-icons.sh: Change content_size from 50% to 40%
- Update vector foreground: Scale from 0.75 to 0.6 to match PNG sizing
- Regenerate all launcher PNGs and adaptive foreground PNGs
The logo now has generous padding and looks more refined in the app launcher.
The logo was zoomed in too far, making it difficult to see the full design.
Reduced the logo size to 50% of the icon canvas for proper padding.
Changes:
- Update generate-icons.sh: Change content_size from 66% to 50%
- Update vector foreground: Add 0.75 scale transformation to match PNG sizing
- Regenerate all launcher PNGs with the smaller, better-proportioned logo
- Regenerate all adaptive foreground PNGs
The logo now has proper breathing room and looks much better in the app launcher.
The APK was showing just a purple circle because the legacy launcher PNG files
(ic_launcher.png and ic_launcher_round.png) were solid purple with no logo.
Changes:
- Update generate-icons.sh to generate legacy launcher PNGs (for Android < 8.0)
- Add ImageMagick as fallback SVG renderer when inkscape/rsvg unavailable
- Replace 'bc' calculations with bash arithmetic for better compatibility
- Generate both square and round launcher icons with white logo on purple bg
- Regenerate all launcher icons (mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi)
The icons now properly display:
- Purple background (#7c52e0)
- White Ditto logo (66% safe zone sizing)
- Round variant with circular mask
This fixes the issue for both:
- Modern devices (Android 8.0+): Uses adaptive icon system with vector foreground
- Legacy devices (Android < 8.0): Uses pre-rendered PNG launcher icons
- Delete conflicting ic_launcher_background.xml drawable with teal grid pattern
- Update adaptive icon XMLs to use vector drawable instead of PNG mipmap
- Change from @mipmap/ic_launcher_foreground to @drawable/ic_launcher_foreground
- Vector drawable (drawable-v24/ic_launcher_foreground.xml) contains proper white Ditto logo
- Background color (#7c52e0 purple) is correctly defined in values/ic_launcher_background.xml
The APK was showing only a purple circle because the vector foreground logo wasn't being used. Now the adaptive icon system will properly composite the white Ditto logo on the purple background.
- Update strings.xml to use 'Ditto' instead of 'ditto'
- Update capacitor.config.ts appName to 'Ditto'
- Update GitLab CI/CD artifact naming to use 'Ditto-' prefix
- Ensures consistency with web version branding
The endless spinner issue was caused by lazy loading pages without a Suspense
fallback. When users navigated to nevent URLs or other NIP-19 identifiers, the
Suspense boundary had no fallback, causing a blank screen or stuck preloader.
Changes:
- Removed all lazy() imports from AppRouter
- Removed Suspense boundary (no longer needed without lazy loading)
- All pages now load eagerly for instant rendering
- Kept the HexIdentifierPage loading skeleton fix from previous commit
This ensures users see immediate visual feedback when loading any page,
especially when navigating directly to event URLs via nevent identifiers.
When users navigate to a raw 64-char hex URL (event ID or pubkey), the app
now shows a proper loading skeleton while resolving the identifier type,
instead of returning null which caused an endless spinner due to Suspense.
- Export PostDetailShell and PostDetailSkeleton from PostDetailPage
- Use proper loading UI in HexIdentifierPage component
- Matches loading pattern used by PostDetailPage and AddrPostDetailPage
Per NIP-18, kind 6 is specifically for reposting kind 1 text notes,
while kind 16 should be used for all other event types. Added
isRepostKind() and getRepostKind() helpers to centralize the logic,
and updated all repost-related code paths: publishing, deletion,
status checks, interactions, feed processing, notifications, and
stream/search filtering.
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.