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
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
Restore chrome (nav bar + tab bar) when user taps to select a different
event in thread view. Previously the tab bar could stay hidden because
updateChromeVisibility short-circuits when isLongformEvent is false.
Changelog-Fixed: Fixed tab bar staying hidden when switching from longform to non-longform event
Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
When reading longform articles, scrolling down hides the navigation bar and
tab bar for a distraction-free reading experience. Tap anywhere to restore
the chrome, or it automatically restores when leaving the view.
Closes: https://github.com/damus-io/damus/issues/3493
Changelog-Added: Added focus mode with auto-hide navigation for longform reading
Signed-off-by: alltheseas
When viewing the full article (not truncated), remove the card border and
background styling for a cleaner reading experience. Card styling is now
only shown in truncated preview mode (timeline, search results).
Changelog-Changed: Removed card styling from longform preview in full article view
Signed-off-by: alltheseas
Add settings for longform article reading experience:
- Sepia mode toggle for comfortable reading with warm tones
- Line height slider (1.2-2.0x) for adjustable text spacing
Both settings persist and apply to the full longform article view.
Closes: https://github.com/damus-io/damus/issues/3495
Changelog-Added: Added sepia mode and line height settings for longform articles
Signed-off-by: alltheseas
Display estimated reading time in minutes alongside word count for longform
articles. Uses standard 200 words per minute reading rate calculation.
Closes: https://github.com/damus-io/damus/issues/3492
Changelog-Added: Added estimated read time to longform preview
Signed-off-by: alltheseas
Display a thin purple progress bar at top of longform articles (kind 30023)
that tracks scroll position through the content. Uses top/bottom GeometryReader
trackers to measure content bounds and calculates progress linearly.
Closes: https://github.com/damus-io/damus/issues/3494
Changelog-Added: Added reading progress bar for longform articles
Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
When pasting an npub or nprofile into the post composer, automatically
convert it to a human-readable mention link. If the profile isn't
cached locally, fetch it from relays and update the mention display
name when it arrives.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Changelog-Added: Added automatic conversion of pasted npub/nprofile to human-readable mentions in post composer
Closes: https://github.com/damus-io/damus/issues/2289
Closes: https://github.com/damus-io/damus/pull/3473
Co-Authored-By: Claude Opus 4.5
Tested-by: William Casarin <jb55@jb55.com>
Signed-off-by: alltheseas
Reviewed-by: William Casarin <jb55@jb55.com>
Previously, inserting text right before a mention (@user) would remove
the link attribute, breaking the mention. This was because the
intersection check in shouldChangeTextIn would trigger and remove the
link for any edit that touched the link boundary.
Added a new condition to handle insertion at the left edge of a link
separately, similar to the existing handling for the right edge. This
allows users to type before a mention without breaking it.
Added UI test that creates a real mention via autocomplete selection,
then verifies text can be typed before it without corrupting the
mention. The test uses predicate-based waits for reliability and
properly marks the UserView as an accessibility element. Link attribute
preservation is verified in unit tests.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Changelog-Fixed: Fixed mentions unlinking when typing text before them
Closes: https://github.com/damus-io/damus/pull/3473
Closes: https://github.com/damus-io/damus/issues/3460
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tested-by: William Casarin <jb55@jb55.com>
Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
Reviewed-by: William Casarin <jb55@jb55.com>
When composing a new note, the cursor would jump in front of the first
letter after typing it. This occurred because multiple SwiftUI view
updates (text change, placeholder removal, height change) could cause
the cursor position to be incorrectly restored.
The fix explicitly tracks the cursor position after each text change
by calling updateCursorPosition, ensuring the correct position is
always used regardless of view update timing.
Refactored textViewDidChange to use early return pattern for clarity.
Added UI test to guard against cursor position regressions in the
post composer.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Changelog-Fixed: Fixed cursor jumping behind first letter when typing a new note
Closes: https://github.com/damus-io/damus/pull/3473
Closes: https://github.com/damus-io/damus/issues/3461
Co-Authored-By: Claude Opus 4.5
Tested-by: William Casarin <jb55@jb55.com>
Signed-off-by: alltheseas
Signed-off-by: William Casarin <jb55@jb55.com>
Posts with more than the configured number of hashtags (default: 3) are
now automatically filtered from timelines. This helps reduce hashtag spam.
- Add hide_hashtag_spam and max_hashtags settings to UserSettingsStore
- Add hashtag_spam_filter that counts hashtags in content text
- Add toggle and slider UI in Appearance > Content filters settings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Changelog-Added: Added hashtag spam filter setting to hide posts with too many hashtags
Closes: https://github.com/damus-io/damus/pull/3425
Closes: https://github.com/damus-io/damus/issues/1677
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: William Casarin <jb55@jb55.com>
Signed-off-by: alltheseas
Signed-off-by: William Casarin <jb55@jb55.com>
Adds comprehensive tests to prevent regression of issue #3165 where
repost notifications were incorrectly blocked by home feed deduplication.
Tests cover:
- Regression test: notifications not blocked by home dedup (main fix)
- Home feed deduplication still works correctly
- Dedup tracks inner event ID, not repost event ID
- Context isolation (.other context doesn't affect dedup)
Each test documents the expected behavior and provides clear failure
messages to aid debugging if the bug reoccurs.
Signed-off-by: alltheseas
Signed-off-by: William Casarin <jb55@jb55.com>
The repost deduplication logic was incorrectly placed before the context
switch in handle_text_event(), causing notification events to be filtered
out when the same note had already been reposted in the home feed.
This fix moves the dedup logic inside the .home case where it belongs.
Notifications should always show reposts of YOUR posts, even if the same
note was already reposted by someone else in your home feed.
Root cause: commit bed4e00 added home feed dedup but placed the logic
before the context switch, affecting all contexts instead of just home.
Note on nostrdb: This bug is purely in application routing logic and
does not require database-level changes. The existing nostrdb TODO
(about inner event validation) is unrelated to this notification issue.
Changelog-Fixed: Fixed repost notifications not appearing in notifications tab
Closes: #3165
Closes: https://github.com/damus-io/damus/pull/3448
Signed-off-by: alltheseas
Signed-off-by: William Casarin <jb55@jb55.com>
When a view subscribes to profile updates via streamProfile() or
streamProfiles(), the stream now immediately yields any existing
profile data from NostrDB before waiting for network updates.
Previously, subscribers had to wait up to ~1 second for the
subscriptionSwitcherTask to restart the profile listener before
receiving any data. During this window, views would display
abbreviated pubkeys (e.g., "npub1abc...") or robohash placeholders
instead of the cached profile name and picture.
The fix adds a simple NDB lookup when creating the stream. This has
negligible performance impact since:
- It's a one-time operation per subscription (not per update)
- The same lookup was already happening in view bodies anyway
- NDB lookups are fast local queries
A new `yieldCached` parameter (default: true) allows callers to opt
out of the initial cached emission. NoteContentView uses this to
avoid redundant artifact re-renders — it only needs network updates
since its initial render already uses cached profile data.
Furthermore, when a profile has no metadata, the display name now shows
"npub1yrse...q9ye" instead of "1yrsedhw:8q0pq9ye" for a better UX.
Closes: https://github.com/damus-io/damus/issues/3454
Closes: https://github.com/damus-io/damus/issues/3455
Changelog-Changed: Changed abbreviated pubkey format to npub1...xyz for better readability
Changelog-Fixed: Fixed instances where a profile would not display profile name and picture for a few seconds
Signed-off-by: alltheseas <64376233+alltheseas@users.noreply.github.com>
Co-authored-by: Daniel D’Aquino <daniel@daquino.me>
The notifications stream used streamIndefinitely(), discarding EOSE signals. This caused a race condition where:
1. NotificationsView.onAppear fires and calls flush().
2. flush() finds an empty queue (events haven’t arrived yet).
3. Events arrive and queue up in incoming_events.
4. No second flush happens, so notifications stay blank.
Fix: Switch to advancedStream() and handle .ndbEose to flush queued notifications once the local database finishes loading. This mirrors how the Home timeline handles initial data loading.
The ndbEose handler also disables queuing (set_should_queue(false)), so any events arriving after the initial load display immediately.
When the Notifications tab becomes visible, disable queuing so any events arriving afterward insert immediately into the displayed list.
This defensive measure complements the ndbEose flush in HomeModel. Together, they provide belt-and-suspenders protection:
1. ndbEose flush (primary): Triggers when local DB finishes loading, flushing queued events and disabling queuing.
2. onAppear disable (safety net): If the user navigates to notifications before ndbEose fires, this ensures new events aren’t queued forever.
Whichever fires first wins - both set should_queue=false, and the second call is a harmless no-op.
Closes: https://github.com/damus-io/damus/issues/3399
Changelog-Fixed: Fixed an issue where notifications view would occasionally appear blank when the app started.
Signed-off-by: alltheseas <64376233+alltheseas@users.noreply.github.com>
This fixes jumpy cursor bug by clamping cursor restoration and consuming tag diff only once.
Closes: https://github.com/damus-io/damus/issues/747
Changelog-Fixed: Fixed incorrect behaviour on the post editor that would cause the text cursor to occasionally jump beyond the correct location in some editing operations.
Signed-off-by: alltheseas <64376233+alltheseas@users.noreply.github.com>
Co-authored-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
replaced deprecated noteID with neventID in EventMenu.swift. NoteID currently appears in bubble/context menu of each note (top right three dots ellipsis).
Added:
1. Damus implications on user privacy (IP address)
2. Added direct link to Damus github issues for interested contributors
3. Added statement of relation between IP address and public key
Co-authored-by: Max Hillebrand <30683012+MaxHillebrand@users.noreply.github.com>