Commit Graph

4524 Commits

Author SHA1 Message Date
ericholguin
84ef5ecf53 gifs: Tenor GIFs
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>
2026-02-20 17:56:04 -08:00
yse
f440f37cbf tests: wallet: add encoding test for list_transactions request
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>
2026-02-18 22:52:09 -08:00
yse
b59816e180 wallet: models: unset type field for list transactions request
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>
2026-02-18 22:52:09 -08:00
yse
609cdcc5f9 wallet: errors: add %@ string formatting instead of %s
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>
2026-02-18 22:33:33 -08:00
Daniel D’Aquino
59498e3256 Fix double-free crash when creating empty NdbFilter
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>
2026-02-18 16:28:00 -08:00
Daniel D’Aquino
546e9eec32 v1.16 changelog
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-02-17 04:19:23 -08:00
alltheseas
cfafcffde2 fix: wait for relay connection before loading nevent URLs
LoadableNostrEventViewModel.load() now calls awaitConnection() before
executeLoadingLogic(), preventing premature "not found" when opening
nevent URLs or search results before relays finish connecting.

Closes: https://github.com/damus-io/damus/pull/3559

Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
Tested-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: Daniel D’Aquino <daniel@daquino.me>
2026-02-16 19:51:21 -08:00
Daniel D’Aquino
4099827169 Fix fulfillment call in testActionBarModel to use its async version
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-02-16 19:12:35 -08:00
Daniel D’Aquino
32c0177049 Fix off-by-one-error in testTimerRestartsAfterSave
No user-facing change

Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-02-16 19:12:35 -08:00
Daniel D’Aquino
2e1a98ff19 Add useful view for note-not-found state in quoted notes
Changelog-Added: Added a view for quotes notes that could not be loaded, including actionable items
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-02-16 13:16:21 -08:00
Daniel D’Aquino
7fa044d205 Fix infinite loading spinner regression
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>
2026-02-16 13:16:21 -08:00
William Casarin
be6b0e2702 Move note language computation off the main thread
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>
2026-02-10 10:16:25 -08:00
Daniel D’Aquino
2d5460b654 Bump version to 1.17
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-02-06 18:12:02 -08:00
Daniel D’Aquino
e271fa90d9 Fix issue that would cause RelayPool to close after ephemeral lease release
Issue was not released, so a changelog item is not needed.

Changelog-None
Closes: https://github.com/damus-io/damus/issues/3604
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-02-06 17:44:17 -08:00
William Casarin
8c5027248b Show "Favorites" label below logo when viewing favorites timeline
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.
2026-02-06 17:30:11 -08:00
William Casarin
434c54f98e Fix favorites timeline not showing events when switching
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
2026-02-06 17:30:11 -08:00
alltheseas
9a1ae6f9b5 Consume NIP-19 relay hints for event fetching
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
2026-02-02 18:52:41 -08:00
Daniel D’Aquino
6f8e2d3064 Reintroduce invoice tests that have been previously disabled
Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-02-02 16:10:09 -08:00
alltheseas
2c3fba5f90 Add test cases for invoices with longer HRP prefixes
Tests for gh-3456 MAX_PREFIX fix:
- lnbc100u (10,000 sats) - 7 char HRP baseline
- lnbc130130n (13,013 sats) - 11 char HRP, requires MAX_PREFIX > 10

Root cause: Invoices with "odd" sat amounts use nano-BTC encoding
which produces longer HRPs that exceeded old MAX_PREFIX limit.

Related: #3456

Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 16:10:09 -08:00
alltheseas
1505a8f2e4 Simplify Swift invoice handling with non-optional return types
- 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>
2026-02-02 16:10:09 -08:00
alltheseas
845089bed1 Fix Lightning invoice parsing and fetching
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>
2026-02-02 16:10:09 -08:00
Daniel D’Aquino
c88d881801 Fix wallet view hanging on loading placeholder indefinitely
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>
2026-01-28 19:01:05 -08:00
Daniel D’Aquino
fa4b7a7518 Wait for app to load the relay list and connect before loading universe view
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>
2026-01-28 16:58:30 -08:00
Daniel D’Aquino
438d537ff6 Add EntityPreloader for batched profile metadata preloading
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>
2026-01-28 13:16:06 -08:00
Daniel D’Aquino
4eac3c576f Fix profile action sheet button alignment and improve layout logic
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>
2026-01-26 16:49:00 -08:00
Daniel D’Aquino
c22c819bc0 Update tests to the new npub abbreviation format
Changelog-None
Closes: https://github.com/damus-io/damus/issues/3501
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-01-26 16:17:14 -08:00
Daniel D’Aquino
b39996a6a7 ndb: Optimize snapshot storage
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>
2026-01-22 20:18:26 -08:00
Daniel D’Aquino
96fb909d83 Add pull to refresh feature in DMs
Closes: https://github.com/damus-io/damus/issues/3352
Changelog-Added: Added a pull to refresh feature on DMs that allows users to resync DMs with their relays
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-01-22 15:56:30 -08:00
Daniel D’Aquino
ce461b58e6 Move DM subscription to a dedicated stream
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>
2026-01-22 15:56:30 -08:00
Daniel D’Aquino
89a56eebcd Add missing timeout task to advanced stream
Changelog-Fixed: Fixed an issue that could cause certain networking operations to hang indefinitely
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-01-22 15:56:30 -08:00
Daniel D’Aquino
d8f4dbb2aa Integrate Negentropy with Subscription Manager
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>
2026-01-22 14:20:57 -08:00
Daniel D’Aquino
95d38fa802 Implement initial negentropy base functions
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>
2026-01-22 14:20:57 -08:00
Daniel D’Aquino
ac05b83772 Make RelayPool actor a global actor
This allows us to use the same actor for related classes to help with
thread safety.

Changelog-None
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2026-01-22 14:20:57 -08:00
Askeew
ed9971f84f Show Timeline Tip in same way and after other Damus Tips.
Changelog-None:
Signed-off-by: Askeew <askeew@hotmail.com>
2026-01-22 11:06:40 -08:00
Daniel D’Aquino
650d4af504 Fix crash on iOS 17
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>
2026-01-12 11:41:01 -08:00
ericholguin
114dde7883 ui: Improved Load Media UI
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>
2026-01-07 19:59:08 -08:00
alltheseas
b105dadd14 longform: fix note URLs not opening from nevent references
Previously, clicking nevent links pointing to longform notes (kind 30023)
showed "Can't display note" error because .longform was listed as an
unsupported kind in LoadableNostrEventView. This fix adds .longform to
the supported kinds alongside .text and .highlight, routing them to
ThreadModel for proper display.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Closes: https://github.com/damus-io/damus/pull/3487
Closes: https://github.com/damus-io/damus/issues/3003
Closes: https://github.com/damus-io/damus/issues/3485
Changelog-Fixed: Longform article links now open correctly when shared as nevent URLs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2026-01-07 17:12:28 -08:00
alltheseas
078042546b longform: fix opening midway instead of at top
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>
2026-01-07 17:12:28 -08:00
alltheseas
93834f8de2 longform: simplify redundant boolean conditions in LongformPreview blur logic
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>
2026-01-07 17:12:28 -08:00
alltheseas
760d0a8126 longform: change focus mode to only hide chrome on scroll down, tap to restore
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
2026-01-07 17:12:28 -08:00
alltheseas
c934bc7653 longform: fix tab bar staying hidden when switching to non-longform event
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>
2026-01-07 17:12:28 -08:00
alltheseas
527b53a7c8 longform: add focus mode with auto-hide chrome
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
2026-01-07 17:12:28 -08:00
alltheseas
ef262b3c22 longform: remove card styling from preview in full article view
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
2026-01-07 17:12:28 -08:00
alltheseas
28a2c23a76 longform: add sepia mode and line height settings
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
2026-01-07 17:12:28 -08:00
alltheseas
e8e2653316 longform: add estimated read time to longform preview
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
2026-01-07 17:12:28 -08:00
alltheseas
0233f2ae48 longform: add reading progress bar
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>
2026-01-07 17:12:28 -08:00
alltheseas
767b318763 longform: fix stretched/cut-off images in longform notes
Pre-process markdown with ensureBlockLevelImages() to add paragraph breaks
around standalone images, forcing proper block-level parsing. Creates
KingfisherImageProvider for MarkdownUI to handle proper aspect ratio and
image caching.

Changelog-Fixed: Fixed stretched/cut-off images in longform notes
Closes: https://github.com/damus-io/damus/pull/3489
Closes: https://github.com/damus-io/damus/pull/3496
Signed-off-by: alltheseas <alltheseas@users.noreply.github.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2026-01-07 17:12:28 -08:00
alltheseas
4f401c6ce9 input: convert pasted npub/nprofile to mention with async profile fetch
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>
2026-01-07 17:12:28 -08:00
alltheseas
a4ad4960c4 input: preserve mention links when inserting text before them
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>
2026-01-07 17:12:28 -08:00
alltheseas
4941b502d5 input: fix cursor jumping to position 0 after typing first character
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>
2026-01-07 17:12:28 -08:00