The previous fix that implemented cleanup of temporary snapshots
included a max age for stale temporary snapshots, which was originally
set to 24h.
This improved the situation, but it still meant that the app could
accumulate 24 old snapshots, which leads to a non-trivial storage
amount.
This commit changes that to 30 minutes for better storage efficiency.
The previous fix was not published on the App Store yet, so no changelog
entry is needed.
Closes: https://github.com/damus-io/damus/issues/3696
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Also clean up temp directory on post-replace size mismatch to match
all other error paths in compact_if_needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
"Notes... usually contains" should be "Notes... usually contain" since "Notes" is plural.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Replace the fragile `latestRelayListEventIdHex` UserDefaults lookup in
`getLatestNIP65RelayListEvent()` with a direct nostrdb query using
`ndb.query(filters:maxResults:)` on the AUTHOR_KINDS index. This
eliminates the stale-hex failure mode where relay add/remove operations
would silently fall back to bootstrap or year-old relay lists.
Add an in-memory `lastSetRelayList` cache to bridge the nostrdb async
write gap — `set()` populates it, `getUserCurrentRelayList()` checks it
first, and `load()` clears it once ndb has committed.
Remove `latestRelayListEventIdHex` from: Delegate protocol,
DamusState, SaveKeysView, UserSettingsStore, and all test mocks.
Includes 5 regression tests that fail before the fix and pass after:
- testRemoveRelayDoesNotFallBackToBootstrapList
- testCacheBridgesAsyncWriteGap
- testRapidSequentialRemovesDoNotReintroduceRelays
- testLoadClearsCacheAndReadsFromNdb
- testNdbQueryFindsRelayListWithoutStoredHex
Changelog-Fixed: Fix stale relay list causing inability to disconnect relays
Closes: #3537
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
- Add `compact(to:)` method on `Ndb` using `mdb_env_copy2` / `ndb_snapshot`
with `MDB_CP_COMPACT` flag to produce a smaller compacted database copy
- Add `compact_if_needed(db_path:)` static startup method that reads a
UserDefaults flag, opens a temp Ndb, compacts to a sibling temp dir,
atomically replaces `data.mdb`, cleans up, and clears the flag
- Add `set_compact_on_next_launch()` to schedule compaction from the UI
- Call `Ndb.compact_if_needed()` in `ContentView.connect()` before the
main Ndb instance is opened
- Add `CompactDatabaseButton` to `StorageSettingsView` with a confirmation
dialog that informs the user a restart is needed
- Add `NdbCompactionTests` unit tests covering flag API, no-op path,
missing-DB path, and full round-trip compaction
Closes: https://github.com/damus-io/damus/issues/3680
Changelog-Added: Added "Compact Database" button in Settings → Storage that reclaims unused space by compacting the NostrDB on the next app launch
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Preserve the last good shared snapshot until a replacement is fully staged, and proactively remove stale snapshot_temp_* directories left behind by interrupted snapshot attempts. This prevents orphaned temp snapshots from accumulating while avoiding false cleanup errors after successful promotion.
Changelog-Fixed: Fixed issue where temporary files would not get cleaned up
Closes: https://github.com/damus-io/damus/issues/3684
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Transaction inheritance causes issues such as:
1. higher fragmentation in the db file, causing storage inefficiencies.
2. stale transactions holding an old view of the database, causing notes
to be unavailable in certain scenarios
This should be safe because direct access to transactions is hidden
within the NostrDB Swift modules behind safe interfaces that prevent
direct access, thus reducing or eliminating the chance of accidentally
starting two transactions within the same thread (which would cause a
deadlock).
Closes: https://github.com/damus-io/damus/issues/3681
Changelog-Fixed: Disabled transaction inhe
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Co-Authored-By: alltheseas <alltheseas@users.noreply.github.com>
Users reported total app storage significantly higher than what the
storage settings view shows. To aid debugging, the export now includes
a "Full Container File Breakdown" section that enumerates every regular
file in the app sandbox and shared app group container, sorted by size
descending so orphaned/unexpectedly large files are easy to spot.
Changes:
- StorageStatsManager: add ContainerFileEntry and containerFileBreakdown()
- StorageStatsViewHelper: add formatContainerFileBreakdown() helper and
include it at the end of formatStorageStatsAsText output
Closes: https://github.com/damus-io/damus/issues/3677
Changelog-Added: Full container file breakdown in storage settings export
Remove client tag from EventTop (feed header) and display it inline
next to the timestamp in SelectedEventView instead. Removes unused
ClientTagLabel struct. Applies lineLimit(1) with tail truncation to
guard against unbounded client tag names.
Closes: https://github.com/damus-io/damus/issues/3672
Signed-off-by: alltheseas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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