- Add ClientTagMetadata struct with parsing helpers and documentation
- Append Damus client tags when posting across app, share, and drafts flows
- Gate the behavior behind a new publish_client_tag setting (default on)
Changelog-Added: Add client tag to published events to identify Damus
Ref: https://github.com/damus-io/damus/issues/3323
Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
This commit implements a new Storage settings view that displays storage
usage statistics for NostrDB, snapshot database, and Kingfisher image cache.
Key features:
- Interactive pie chart visualization (iOS 17+) with tap-to-select functionality
- Pull-to-refresh gesture to recalculate storage
- Categorized list showing each storage type with size and percentage
- Total storage sum displayed at bottom
- Conditional compilation for iOS 16/17+ compatibility
- All calculations run on background thread to avoid blocking main thread
- NostrDB storage breakdown
Changelog-Added: Storage usage statistics view in Settings
Changelog-Changed: Moved clear cache button to storage settings
Closes: https://github.com/damus-io/damus/issues/3649
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
When a new wallet is connected, clear stale balance and transaction data
from the previous wallet immediately so the view does not display outdated
information while fresh data is being fetched.
Closes: https://github.com/damus-io/damus/issues/3644
Changelog-Fixed: Wallet view now immediately clears stale data when switching wallets
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Co-authored-by: Daniel D’Aquino <daniel@daquino.me>
Tested-by: Daniel D’Aquino <daniel@daquino.me>
When another NWC client (e.g. Alby) connected to the same relay calls
`get_info`, Damus receives the response and previously threw a
DecodingError.typeMismatch, causing an "Oops" error dialog to be shown.
Fix: Make `result_type` optional in `WalletConnect.Response`. Unknown
result types now decode without throwing — `result_type` and `result`
are set to `nil`, and the rest of the existing nil-guarded code paths
handle this silently.
Adds a test to verify `get_info` (and any future unknown result type)
is decoded gracefully.
Closes: #2204
Changelog-Fixed: Fixed issue where the app could display an error message when using another NWC wallet in parallel
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: danieldaquino <24692108+danieldaquino@users.noreply.github.com>
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This PR adds GIFs to Damus using Tenor as the service.
This is a Damus Labs feature to begin with.
In the future we should be able to also query nostr for gif media.
Changelog-Added: Added GIF keyboard support (Damus Labs only)
Signed-off-by: ericholguin <ericholguin@apache.org>
This adds a test to verify that the `getTransactionList` behaves as
expected when passing a nil and not-nil type as argument.
Signed-off-by: Hydra Yse <hydra_yse@proton.me>
Removes the empty `type` field from the wallet's list_transactions
request in favor of a null field, which is parsable by serializers.
Signed-off-by: Hydra Yse <hydra_yse@proton.me>
This fixes a UI issue where the error message from the NWC response
would be incorrectly displayed due to utf-8 formatting.
Signed-off-by: Hydra Yse <hydra_yse@proton.me>
When ndb_filter_end processes an empty filter (no fields added), it calls
realloc(filter->elem_buf.start, 0) which frees the memory and returns NULL.
The existing code only updated the pointer if realloc
returned non-NULL, leaving elem_buf.start pointing to freed memory. This
caused a double-free crash when ndb_filter_destroy later called free() on
the dangling pointer.
Fix by explicitly setting filter->elem_buf.start to NULL when realloc
returns NULL due to zero-size allocation, and update the assertion to
allow NULL pointers for empty filters. ndb_filter_destroy already checks
for NULL before freeing.
Closes: https://github.com/damus-io/damus/issues/3634
Changelog-Fixed: Fix memory corruption crash when creating empty filters
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Root cause:
1. `lookup` looks up a note by its note id, and saving its note key
2. `lookup` then returns early (i.e. does not loading anything from the
network) since it found the note
3. On the view, once it borrows the note from NostrDB (a query using its
NoteKey), the query fails (Most likely due to transaction inheritance
and the fact that the inherited transaction may be an older snapshot
of the database without the note), causing the view loading logic to
fail silently, leading to the infinite loading spinner
The issue was addressed by performing a single query during lookup and
copying the note contents directly at that point to avoid this
transaction inheritance issue.
In the future we should consider a more comprehensive fix to address
other instances where this may happen. I opened
https://github.com/damus-io/damus/issues/3607 for this future work.
Changelog-Fixed: Fixed an issue where notes would keep loading indefinitely in some cases
Closes: https://github.com/damus-io/damus/issues/3498
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
NLLanguageRecognizer.processString() is an expensive NLP operation that
was moved to @MainActor in 5058fb33, causing UI jank when scrolling.
This moves language detection back to the async preload path where it
runs off the main thread. get_preload_plan is synchronous again and
defers language computation to preload_event. Translations still happen
on the first preload pass since preload_event now checks .havent_tried
directly rather than relying on the load_translations flag from the plan.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a visual indicator under the Damus logo when the favorites
timeline is active. Uses fixed height with opacity to prevent
layout bouncing when switching timelines.
The favorites timeline was empty because:
1. The @StateObject filter in InnerTimelineView was captured once at init
2. Favorite events were mixed with follows events and got drowned out
Fixed by:
- Adding viewId parameter to TimelineView to force view recreation on switch
- Creating separate favoriteEvents EventHolder for favorites
- Adding dedicated subscribe_to_favorites() subscription that inserts
directly into favoriteEvents when contact cards are loaded
Extract and use relay hints from bech32 entities (nevent, nprofile, naddr)
and event tag references (e, q tags) to fetch events from hinted relays
not in the user's relay pool.
Changes:
- Parse relay hints from bech32 TLV data in URLHandler
- Pass relay hints through SearchType and NoteReference enums
- Add ensureConnected() to RelayPool for ephemeral relay connections
- Implement ephemeral relay lease management with race condition protection
- Add repostTarget() helper to extract relay hints from repost e tags
- Add QuoteRef struct to preserve relay hints from q tags (NIP-10/NIP-18)
- Support relay hints in replies with author pubkey in e-tags (NIP-10)
- Implement fallback broadcast when hinted relays don't respond
- Add comprehensive test coverage for relay hint functionality
- Add DEBUG logging for relay hint tracing during development
Implementation details:
- Connect to hinted relays as ephemeral, returning early when first connects
- Use total deadline to prevent timeout accumulation across hint attempts
- Decrement lease count before suspension points to ensure atomicity
- Fall back to broadcast if hints don't resolve or respond
Closes: https://github.com/damus-io/damus/issues/1147
Changelog-Added: Added relay hint support for nevent, nprofile, naddr links and event tag references (reposts, quotes, replies)
Signed-off-by: alltheseas
Signed-off-by: Daniel D'Aquino <daniel@daquino.me>
Co-authored-by: alltheseas <alltheseas@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Daniel D'Aquino <daniel@daquino.me
- Mentions.swift: convert_invoice_description now returns non-optional
InvoiceDescription, returning empty description for BOLT11 compliance
(both description and description_hash are optional per spec)
- Block.swift, NdbBlock.swift, NostrEvent.swift, NoteContent.swift:
Updated call sites to use non-optional invoice conversion
- InvoiceTests.swift: Added test for specific failing invoice
Signed-off-by: alltheseas <alltheseas@noreply.github.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Three issues were causing invoices to not render or fetch:
1. bech32.c: Hardcoded MAX_PREFIX limited HRP length, but BOLT11 HRPs
can be arbitrarily long depending on amount. Now derives max HRP
length dynamically from input length (len-6 to match bolt11.c buffer).
2. content_parser.c: bolt11_decode_minimal was passed a pointer into
the content buffer without null-termination. When a note contained
multiple invoices, the decoder would read past the first invoice
into newlines and the second invoice, causing checksum failure.
Fixed by creating a null-terminated copy using strndup.
3. bolt11.c: bech32_decode_alloc allocated buffers using strlen(str)-6
and strlen(str)-8 without checking minimum length first. For inputs
shorter than 8 chars, this caused size_t underflow leading to huge
allocations and potential crash. Added early length guard.
IMPORTANT: bech32_decode callers must allocate hrp buffer of at least
strlen(input) - 6 bytes. This matches existing bolt11.c usage.
Changelog-Fixed: Fixed Lightning invoice parsing and fetching for all amounts
Closes: https://github.com/damus-io/damus/issues/3456
Closes: https://github.com/damus-io/damus/issues/3151
Signed-off-by: alltheseas <alltheseas@noreply.github.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolves a race condition in wallet data fetching that caused views to
hang on loading placeholders. The issue occurred due to:
1. Multiple state updates triggering view re-renders mid-fetch
2. Refreshable tasks getting cancelled before completion
Changes:
- Remove premature state reset in refreshWalletInformation()
- Atomically update balance and transactions together after fetching
- Replace onAppear + manual task cancellation with SwiftUI .task modifier
- Simplify refresh flow to use proper async/await without explicit task management
This ensures the wallet view completes data loading in a single atomic
operation, preventing intermediate loading states from persisting.
Closes: https://github.com/damus-io/damus/issues/2999
Changelog-Fixed: Wallet view no longer hangs on loading placeholder
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit addresses a race condition that happened when the user
initializes the app on the universe view, where the loading function
would run before the relay list was fully loaded and connected, causing
the loading function to connect to an empty relay list.
The issue was fixed by introducing a call that allows callers to wait
for the app to connect to the network
Changelog-Fixed: Fixed issue where the app would occasionally launch an empty universe view
Closes: https://github.com/damus-io/damus/issues/3528
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Implements an actor-based preloading system to efficiently fetch profile
metadata for note authors and referenced users. The EntityPreloader queues
requests and batches them intelligently (500 pubkeys or 1 second timeout)
to avoid network overload while improving UX by ensuring profiles are
available when rendering notes.
Key changes:
- Add EntityPreloader actor with queue-based batching logic
- Integrate with SubscriptionManager via PreloadStrategy enum
- Add lifecycle management (start/stop on app foreground/background)
- Skip preload for pubkeys already cached in ndb
- Include comprehensive test suite with 11 test cases covering batching,
deduplication, and edge cases
- Optimize ProfilePicView to load from ndb before first render
Closes: https://github.com/damus-io/damus/issues/gh-3511
Changelog-Added: Profile metadata preloading for improved timeline performance
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Refactor ProfileActionSheetView to use an enum-based approach for managing
action buttons. Buttons are now conditionally rendered through a
visibleActionButtonTypes computed property, which determines visibility
based on settings and profile state.
Key changes:
- Add ActionButtonType enum to represent button variants
- Create visibleActionButtonTypes to build the list of visible buttons
- Add renderButton ViewBuilder for type-safe button rendering
- Center-align buttons when fewer than 5 are visible, otherwise use
horizontal ScrollView for overflow
Closes: https://github.com/damus-io/damus/issues/3436
Changelog-Fixed: Profile action sheet buttons now center properly when fewer than 5 buttons are displayed
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit improves the ndb snapshot logic by only transferring desired
notes instead of copying the entire database, which could be as big as
10GB.
Closes: https://github.com/damus-io/damus/issues/3502
Changelog-Changed: Improved storage efficiency for NostrDB on extensions
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit splits a subscription that previously gathered DMs as well
as new notes from the follow list, moving the DM subscription to is own
dedicated stream.
This prevents the issue of DM notes getting clipped off when the user
follows too many other users.
Changelog-Fixed: Fixed an issue where DMs may not appear for users with a large contact list
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This makes negentropy optimizations available to the rest of the app via
Subscription Manager.
Changelog not needed because this should not have user-facing changes
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This implements some useful functions to use negentropy from RelayPool,
but does not integrate them with the rest of the app.
No changelog for the negentropy support right now as it is not hooked up
to any user-facing feature
Changelog-Fixed: Fixed a race condition in the networking logic that could cause notes to get missed in certain rare scenarios
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This fixes an arithmetic overflow crash on iOS 17 caused by the fallback
lock.
In iOS 17, Swift Mutexes are not available, so we have a fallback class
for NdbUseLock that does not make use of them. This allows some thread
safety for iOS 17 users, but unfortunately not as much as iOS 18+ users.
This attempts to fix those remaining race conditions and subsequent
crashes by using `NSLock` in the fallback class, which is available on
iOS 17.
Closes: https://github.com/damus-io/damus/issues/3512
Changelog-Fixed: Fixed a crash on iOS 17 that would happen on startup
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This PR improves the load media UI when a user has media previews off.
Changelog-Changed: Changed load media UI
Signed-off-by: ericholguin <ericholguin@apache.org>
The scroll_to_event function defaults to anchor: .bottom, which positions
the selected event at the bottom of the viewport. For longform notes,
this causes the article to open midway or at the bottom instead of the
top where the title is.
Changed the initial scroll anchor to .top only for longform articles
(kind 30023), preserving the existing .bottom behavior for regular notes
which keeps parent context visible in reply threads.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes: https://github.com/damus-io/damus/issues/2481
Closes: https://github.com/damus-io/damus/pull/3488
Changelog-Fixed: Longform articles now open at the top instead of midway through
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tested-by: William Casarin <jb55@jb55.com>
Signed-off-by: alltheseas
Signed-off-by: William Casarin <jb55@jb55.com>
The conditions !blur_images || (!blur_images && X) simplify to just
!blur_images, and the else branch covers the blur_images case.
Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
Previously scrolling up would restore the nav/tab bars. Now only tapping
restores chrome, giving a cleaner reading experience without accidental
restoration while scrolling.
Changelog-Changed: Changed focus mode to only hide navigation on scroll down
Signed-off-by: alltheseas