Commit Graph

13224 Commits

Author SHA1 Message Date
M 0b636d62aa Fix follow list import: validate pubkey length + add crash monitoring
Root cause: decodePublicKeyAsHexOrNull() returns truncated hex for
non-bech32 input (its else branch runs Hex.decode on arbitrary strings).
The relay then rejects the filter with 'Invalid author length'.

Fixes:
- Check raw 64-char hex FIRST (before bech32 parsing)
- Only attempt bech32 decode for strings starting with npub1/nprofile1/nsec1
- Validate all resolved pubkeys are exactly 64 hex chars
- Show specific error messages per identifier type instead of generic fallback
- Improved debug logging with pubkey prefix for each resolution path

Crash monitoring:
- Added Thread.setDefaultUncaughtExceptionHandler in Main.kt
- Logs crashes to ~/.amethyst-desktop-crash.log with timestamp and full stack trace
- Also prints to stderr for Gradle console visibility
2026-05-08 22:31:33 +10:00
M 0bc1c2838d desktop: full relay-integrated Import Follow List dialog
Rewrites ImportFollowListDialog with complete relay integration:

- Resolves identifiers: npub, hex, NIP-05 HTTP (via OkHttp), Namecoin
- Fetches kind 3 (ContactListEvent) from relays via subscription
- Parses follow list (p-tags) into selectable entries
- Fetches kind 0 metadata for display names (best-effort)
- Shows preview with select all/deselect all + individual toggles
- Publishes new kind 3 via broadcastToAll with selected follows
- State machine: Idle → Resolving → Fetching → Loaded → Publishing → Done
- Proper cleanup: DisposableEffect unsubscribes on dismiss
- 15s timeout for kind 3, 20s timeout for metadata enrichment
- Updates Main.kt call site to pass relayManager, account, localCache
2026-05-08 22:31:33 +10:00
M 21133c34c0 Fix search: use resolveDetailed() for proper timeout handling
The search bar was using resolve() which lets
NamecoinLookupException.ServersUnreachable propagate as an exception,
causing 'servers unreachable' to appear immediately instead of waiting
for the actual lookup to complete.

Switch to resolveDetailed() which catches exceptions internally and
returns typed outcomes (Success/NameNotFound/NoNostrField/
ServersUnreachable/InvalidIdentifier/Timeout). The search screen now
shows Loading for the full duration of the attempt (up to 20s) and
only shows the error after all servers have actually been tried.
2026-05-08 22:31:12 +10:00
M a7c4842d60 Wire ImportFollowListDialog into File menu (⇧⌘I)
The dialog was created but never accessible from the UI. Now:
- Added 'Import Follow List…' menu item in File menu (Shift+Cmd+I / Shift+Ctrl+I)
- showImportFollowListDialog state threaded through App composable
- Dialog rendered alongside ComposeNoteDialog and AddColumnDialog
2026-05-08 22:30:55 +10:00
M 5602429f9b Port Namecoin NIP-05 resolution to Desktop app
Add censorship-resistant NIP-05 verification using the Namecoin blockchain
to the Desktop (JVM) app, porting functionality from PRs #1734, #1771,

New files:
- DesktopNamecoinNameService: app-level service wrapping the Quartz
  NamecoinNameResolver with caching, custom server support, and live
  state flows. Uses plain JVM sockets (no Tor support on Desktop yet).
- DesktopNamecoinPreferences: Java Preferences API-backed persistence
  for Namecoin settings (enabled toggle + custom ElectrumX servers).
- NamecoinSettings: Desktop copy of the settings data class (no Android
  dependencies).
- LocalNamecoin: CompositionLocals for threading Namecoin service/prefs
  through the compose tree.
- NamecoinSettingsSection: Compose Desktop UI for configuring ElectrumX
  servers — toggle, active server display with DEFAULT/CUSTOM badge,
  add/remove custom servers, reset to defaults. Uses onPreviewKeyEvent
  for Enter-to-submit instead of Android KeyboardActions.
- ImportFollowListDialog: Dialog for importing follow lists via npub,
  hex, NIP-05, or Namecoin identifiers. Resolves identifiers to pubkeys
  with Namecoin blockchain support.

Modified:
- Main.kt: Instantiate DesktopNamecoinPreferences and
  DesktopNamecoinNameService in App composable, provide via
  CompositionLocals, wire into RelaySettingsScreen.
- SearchScreen.kt: Detect Namecoin identifiers (.bit, d/, id/) in the
  search bar, resolve via DesktopNamecoinNameService with loading/error
  states, display resolved user above standard results. Uses
  LaunchedEffect with key-based cancellation for stale lookups.
- DeckColumnContainer.kt: Pass namecoinPreferences to RelaySettingsScreen
  from CompositionLocal.

Tests:
- DesktopNamecoinPreferencesTest: round-trip persistence, add/remove
  servers, enable/disable, reset, duplicate handling.
- NamecoinSettingsTest: server string parsing/formatting, round-trips,
  edge cases, toElectrumxServers conversion.

The Quartz KMP library (commonMain + jvmAndroid) already contains the
core Namecoin resolution code (ElectrumXClient, NamecoinNameResolver,
NamecoinLookupCache) shared across Android and Desktop.
2026-05-08 22:29:23 +10:00
Vitor Pamplona 2097089d3d Merge pull request #2788 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-08 08:20:44 -04:00
Crowdin Bot 7dc9adb856 New Crowdin translations by GitHub Action 2026-05-08 12:17:53 +00:00
Vitor Pamplona f601836bc3 Merge pull request #2787 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-08 08:16:48 -04:00
Vitor Pamplona 753f259959 Merge pull request #2786 from greenart7c3/claude/add-blossom-cache-support-ts8mK
Add local Blossom cache bridge for transparent media proxying
2026-05-08 08:15:51 -04:00
Vitor Pamplona 06d6425369 Merge branch 'main' into claude/add-blossom-cache-support-ts8mK 2026-05-08 08:15:44 -04:00
Crowdin Bot 4ae51ca8bb New Crowdin translations by GitHub Action 2026-05-08 12:15:30 +00:00
Vitor Pamplona 8b1b49007c Merge pull request #2785 from greenart7c3/claude/disable-client-tags-option-Nb3b4
Add option to disable NIP-89 client tag in published events
2026-05-08 08:13:49 -04:00
Vitor Pamplona e8e1e44807 Merge pull request #2783 from greenart7c3/claude/add-copy-address-button-LgZSZ
Add copy-to-clipboard functionality for payment targets
2026-05-08 08:13:26 -04:00
Vitor Pamplona 90b98fa5f4 Merge pull request #2782 from davotoula/fix/scheduled-posts-lint-fixes
Lint and sonar cleanup on scheduled-posts
2026-05-08 08:13:17 -04:00
Vitor Pamplona 317b205858 Merge pull request #2781 from mstrofnone/fix/selectable-error-messages
fix(desktop): make error messages selectable so users can copy them
2026-05-08 08:12:49 -04:00
Vitor Pamplona fcb5b8cc55 Merge pull request #2779 from davotoula/fix/plurals-use-format-arg
Various language fixes to scheduled posts
2026-05-08 08:12:04 -04:00
Vitor Pamplona 0127ed4412 Merge pull request #2778 from nrobi144/fix/bunker-timeout-and-decrypt
feat(desktop): custom feeds system with creation, discovery, and author search
2026-05-08 08:11:55 -04:00
David Kaspar 3fb04bd6f2 Merge branch 'main' into fix/plurals-use-format-arg 2026-05-08 13:40:34 +02:00
David Kaspar ce9e83b791 Merge pull request #2784 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-08 13:40:07 +02:00
Claude bb871f6c71 fix(blossom): preserve upstream path prefix in xs= hint
The bridge was emitting `xs=https://cdn.nostr.build` for URLs like
`https://cdn.nostr.build/i/<sha>.jpg`, dropping the `/i/` prefix. The
local cache appends `/<sha>` to the xs server hint per spec, so it would
try to fetch `https://cdn.nostr.build/<sha>` — a 404 on nostr.build's
non-Blossom CDN scheme.

Anchor the server-base extraction on the slash immediately preceding the
sha-bearing path segment so we emit `xs=https://cdn.nostr.build/i`.
The cache then appends `/<sha>` and reaches the original blob.

Affects all three bridge entry points:
- MediaUrlContent.toCoilModel (note media)
- bridgeProfilePictureUrl (profile pictures)
- LocalBlossomCacheRedirectInterceptor (everything else via OkHttp)

Flat Blossom paths (`<host>/<sha>`) keep emitting `xs=<host>` unchanged.
2026-05-08 11:35:53 +00:00
Claude aa598b9949 fix(blossom): profile pictures now route through local cache
bridgeProfilePictureUrl previously returned a `blossom:` URI, but
profile pictures go through ProfilePictureFetcher (Coil routes by type
on `ProfilePictureUrl`), which feeds the URL straight to NetworkFetcher
and never sees BlossomFetcher. NetworkFetcher then tries to issue an
HTTP request against the `blossom:` scheme and fails — silently — so
profile pictures from Blossom servers stopped loading.

Return a direct `http://127.0.0.1:24242/<sha>.<ext>?xs=<host>&as=<pub>`
URL instead. The OkHttp request goes to the local cache normally; the
new redirect interceptor sees it's already on localhost and passes
through unchanged.
2026-05-08 10:25:30 +00:00
Claude 9451f7ca9a feat(blossom): catch all sha256-keyed URLs via OkHttp interceptor
Pictures hosted on Blossom servers (URLs with a 64-char sha256 hex in
the path) bypassed the bridge whenever they were rendered through a
composable that wasn't explicitly updated to call toCoilModel — link
previews, video thumbnails, badge images, audio room covers, etc.

Add LocalBlossomCacheRedirectInterceptor as an app-level OkHttp
interceptor that catches every HTTP request transparently:

- Detects a 64-char hex segment in any path segment
- Rewrites the request URL to http://127.0.0.1:24242/<sha>.<ext>?xs=<host>
- Coil's disk cache continues to key by the original URL so cached
  blobs survive toggling the bridge off

Activates only when the master toggle is on, the profile-pictures-only
restriction is off, and the probe sees localhost up. Profile-only mode
still routes profile pics via the existing composable bridge.
2026-05-08 10:02:08 +00:00
Claude 8559712a55 refactor(privacy): move disableClientTag into NIP-78-synced security settings
Aligns the toggle with its Security Filters siblings (warnAboutPostsWithReports,
filterSpamFromStrangers, etc.): stored in AccountSecurityPreferences{Internal},
synced across devices via the app-specific data event, and updated through
account.updateDisableClientTag -> sendNewAppSpecificData.

The signer wrapper now reads the live value from synced settings, so changes
still apply immediately to the next signed event.

https://claude.ai/code/session_01KhNVTm8LLdVphqyxVkZtDS
2026-05-08 09:23:30 +00:00
Crowdin Bot f81fb48bd4 New Crowdin translations by GitHub Action 2026-05-08 09:06:29 +00:00
David Kaspar 6fbab4a601 Merge pull request #2777 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-08 11:04:53 +02:00
davotoula 9565411e3d Extract UNSET_LABEL and ALPN_HQ_INTEROP constants in InteropClient 2026-05-08 11:02:36 +02:00
Claude 119ddf7cad feat(blossom): add as= author hint and profile-pictures-only mode
- `as=<authorPubKey>`: when converting plain http(s) imeta URLs into
  `blossom:` URIs we now include the note author's pubkey, letting the
  local cache consult their kind:10063 BUD-03 server list on miss.
  Plumbed via RichTextParser/CachedRichTextParser/RichTextViewer/
  ExpandableRichTextViewer/TranslatableRichTextViewer.

- Profile-pictures-only mode: a new per-account toggle that restricts
  the bridge to profile pictures (RobohashFallbackAsyncImage). When on,
  feed images and videos skip the bridge entirely. Profile pictures
  recover their sha256 from the URL path when present (covers
  Blossom-hosted avatars like https://nostr.build/i/<sha>.jpg).
2026-05-08 08:45:02 +00:00
Claude f1650ad9ce feat(privacy): add per-account toggle to disable NIP-89 client tag
Adds a "Don't add client tag to my events" switch under Security Filters.
NostrSignerWithClientTag now consults a runtime predicate at sign-time, so
the toggle takes effect immediately on the live session without rewrapping
the signer or rebuilding Account.

https://claude.ai/code/session_01KhNVTm8LLdVphqyxVkZtDS
2026-05-08 08:44:32 +00:00
Claude c6cd2fc95d feat(payments): add copy-to-clipboard option in profile payment button
Mirrors the option added to the reactions-row payment popup so the address
can also be copied from a user's profile header.
2026-05-08 08:21:31 +00:00
davotoula 9bdef97dd0 Log when File.delete() fails in ScheduledPostStore.persist() 2026-05-08 09:03:17 +02:00
davotoula 1a13ce3ad4 Convert scheduled-posts logout strings to <plurals> 2026-05-08 08:52:48 +02:00
davotoula e8db6ec6d9 Add pluralStringRes helper for non-composable scope 2026-05-08 08:52:36 +02:00
m 3e246e9e0b fix(desktop): make error messages selectable so users can copy them
Several user-visible error messages on Desktop are rendered with plain
`Text` composables, which means they can't be selected or copied. That
makes it awkward to share an error in a bug report or paste a hex error
string into a search.

Wrap the error text in `SelectionContainer` at the canonical sites:

- `commons.ui.components.LoadingState`: wrap the description in
  `EmptyState` and the message in `ErrorState`. `EmptyState` is reused
  as the in-feed error renderer (e.g. 'Error loading feed' with the
  underlying error in `description`), so this covers feed/loading
  errors across screens that use these helpers.
- `ComposeNoteDialog`: wrap the validation error and the upload error
  in the compose-note dialog.
- `auth/LoginCard` (Nostr Connect): wrap the connection error.
- `auth/KeyInputField`: wrap the supporting-text error so the inline
  message under the nsec input field can be copied.

No visual changes \u2014 `SelectionContainer` does not affect layout or
styling. Selection works on Compose Desktop (mouse drag) and on Android
(long-press) without further changes.
2026-05-08 16:32:48 +10:00
davotoula 6039e20879 reduce lint errors 2026-05-08 08:08:59 +02:00
davotoula 581a18de95 use %d in scheduled-posts plurals "one" forms. Fix english locale to prevent future translation errors 2026-05-08 07:11:06 +02:00
nrobi144 de879b9472 fix(desktop): use SearchBarState + rememberSubscription for author search
Replace manual relay subscribe + Channel approach with the proven
SearchBarState + rememberSubscription pattern (same as NewDmDialog).
This properly handles relay connection lifecycle and NIP-50 search,
returning results from all connected relays.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-08 07:37:27 +03:00
nrobi144 41920a363a feat(desktop): author search in feed builder with relay NIP-50 + avatars
- Hoist author search state to FeedsDrawerTab (AlertDialog can't run LaunchedEffect)
- NIP-50 relay search via connected relays with Channel-based result streaming
- 28dp UserAvatar in author suggestion rows
- "No users found" empty state
- 32dp dialog margins, 300dp max results height, 30 result limit
- FindUsersTest: 8 tests verifying cache search with/without metadata
- Fix: use connectedRelays instead of unconnected DEFAULT_SEARCH_RELAYS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-08 07:23:44 +03:00
Crowdin Bot 20b4fad5d5 New Crowdin translations by GitHub Action 2026-05-08 02:11:48 +00:00
Vitor Pamplona 4d2019a30c Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	amethyst/src/main/res/values-pt-rBR/strings.xml
2026-05-07 22:05:39 -04:00
Vitor Pamplona cc2c0b2725 Merge pull request #2776 from vitorpamplona/claude/fix-portuguese-strings-quantity-CnV2F
Fix plural string formatting in Brazilian Portuguese translations
2026-05-07 22:04:17 -04:00
Vitor Pamplona 80330864bf solves linit issues 2026-05-07 22:02:48 -04:00
Claude 295b007d35 fix(i18n): use %d in pt-rBR plurals where 'one' covers 0 and 1
Brazilian Portuguese's 'one' quantity matches both 0 and 1, so a
hardcoded '1' is wrong when the count is 0. Switch the 'one' branch
of scheduled_posts_subtitle_due_suffix and scheduled_posts_relay_count
to use the %d argument like the 'other' branch.

https://claude.ai/code/session_01UmYZfFy8QqaDaXbcXguu8f
2026-05-08 02:01:05 +00:00
Vitor Pamplona 8a62a56b8f Merge pull request #2775 from vitorpamplona/claude/fix-permission-check-43aAZ
fix(scheduled-posts): explicit POST_NOTIFICATIONS check before notify
2026-05-07 21:08:03 -04:00
Claude 13f1101338 fix(scheduled-posts): explicit POST_NOTIFICATIONS check before notify
Lint flagged the notify() call as MissingPermission because there was no
visible check that POST_NOTIFICATIONS was granted. Gate the call behind
areNotificationsEnabled() — matching the pattern used in
EventNotificationConsumer — and catch SecurityException for the
narrow window where permission could be revoked between the check and
the notify().
2026-05-08 01:06:45 +00:00
Vitor Pamplona 4db81b72ff Merge pull request #2774 from vitorpamplona/claude/fix-nostr-repeat-sub-test-mOpDe
test(quartz): make repeat-sub test tolerant of resub timing race
2026-05-07 21:04:01 -04:00
Claude 28fb3b0e83 test(quartz): make repeat-sub test tolerant of resub timing race
The mid-stream resub pattern (resub1 at events.size==1, resub2 at
events.size==5) has four receive() calls between the two subscribe
calls. On a slow CI worker that's enough time for resub1
(filtersShouldIgnore, kind 10002 limit 500) to actually reach the
relay and start streaming events before resub2 replaces it, so the
loop's second EOSE can come from filtersShouldIgnore — which can
deliver up to 50 events (the preloaded count for that kind), well
past the previous 1..11 bound.

Drop the phase-specific count asserts and verify only structural
invariants: exactly two EOSEs, the loop stopped on the second, every
non-EOSE entry is a valid 64-char id, and the total event count
stays within the worst-case envelope.

https://claude.ai/code/session_01L4qS7BhX3L7ApiS3HsCozn
2026-05-08 00:59:57 +00:00
Vitor Pamplona 79bf7d708d Merge pull request #2773 from vitorpamplona/claude/harden-quic-audio-rooms-kwqnS
feat(quic): retire fully-settled streams to keep tracker bounded under audio-room churn
2026-05-07 20:19:13 -04:00
Claude 71e14fe639 chore(quic): audit cleanup — drop redundant copy, rename queue, extract test fixture
Three small follow-ups from the audit pass:

1. Drop redundant `challengeData.copyOf()` in
   `queuePathResponseLocked` — the parser produces a fresh
   ByteArray per PATH_CHALLENGE via `QuicReader.readBytes`'s
   `copyOfRange`, so the defensive copy was a wasted allocation.
   One-line fix.

2. Rename `pendingPathResponses` → `pendingPathChallengePayloads`.
   The queue holds inbound CHALLENGE payloads we owe RESPONSES
   for — old name conflated the two. Pure rename across
   QuicConnection / Parser / Writer / PathValidationTest.

3. Extract shared `newConnectedClient(...)` test fixture
   (`ConnectedClientFixture.kt`). The 6 test files each repeated
   ~40 lines of identical handshake-pipe boilerplate; folded into
   one parameterized helper accepting transport-cap overrides.
   Net −164 lines across the test tree; per-test helper is now a
   one-liner that documents the cap shape.

No behavior change. Full quic suite + amethyst hook test green.

https://claude.ai/code/session_018KPKWRg5baX5Anf7zfEyec
2026-05-08 00:03:47 +00:00
Vitor Pamplona 97766b1c5b Merge pull request #2772 from vitorpamplona/claude/review-fanout-index-e1Uqq
Add FilterIndex for inverted-index fanout dispatch
2026-05-07 19:58:17 -04:00
Claude 44a8aeea28 Merge branch 'main' into claude/review-fanout-index-e1Uqq
Conflicts:
- quartz/.../LiveEventStore.kt: main added IngestQueue (group-commit)
  and a fire-and-forget submit() path; this branch replaced the
  SharedFlow live fanout with FilterIndex-driven dispatch. Resolved
  by keeping main's submit/insert pipeline shape (IngestQueue ingest
  ctor param, submit() callback, insert() wrapping submit via
  CompletableDeferred) but routing the on-Accepted fanout through
  FilterIndex.candidatesFor instead of newEventStream.tryEmit.
  Added private fanout(event) helper. Kept main's
  snapshotIdsForNegentropy method.
- geode/.../LoadBenchmark.kt: both sides added a new @Test method.
  Kept fanoutScaling (this branch) and publishPipelinedSingleClient
  (main).
2026-05-07 23:55:00 +00:00