Commit Graph

4670 Commits

Author SHA1 Message Date
M 645bcf8f44 refactor: lazy-load Namecoin services on Desktop (not kept in memory from start)
Move Namecoin service initialization from App-level (eager, on every startup)
to inside the LoggedIn branch (only created when user logs in and screens
that use Namecoin are reachable). Matches the Android lazy pattern in AppModules.

Also deduplicate NamecoinSettings: Android module now uses a typealias to the
commons module version, matching the original PR intent.
2026-05-08 22:31:33 +10:00
M 5369694b4d refactor: move NamecoinSettings and NamecoinResolveState to commons module
Eliminates code duplication between Android and Desktop:

- Move NamecoinSettings to commons/model/nip05DnsIdentifiers/namecoin/
  (with @Serializable and @Stable annotations)
- Extract NamecoinResolveState sealed class to its own file in commons
- Move NamecoinSettingsTest to commons (shared by both platforms)
- Replace Android NamecoinSettings.kt with a typealias to commons
- Update all imports in Desktop and Android modules
- Remove debug println statements from ImportFollowListDialog and Main
2026-05-08 22:31:33 +10: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 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
David Kaspar 3fb04bd6f2 Merge branch 'main' into fix/plurals-use-format-arg 2026-05-08 13:40:34 +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 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
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
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
Crowdin Bot 20b4fad5d5 New Crowdin translations by GitHub Action 2026-05-08 02:11:48 +00: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
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 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 be8f2e08d3 feat(quic+amethyst): close-under-load + key-update PN gate + loss harness depth + foreground recycle
Working through the punch list from the prior "what's left?" status.

#2 close-under-load (CloseUnderLoadTest, 3 cases)
==================================================
Pins three races between connection close and active stream
traffic that the existing idle-driver close test doesn't cover:
  - closeWhileBulkStreamRetirementIsRunning — server ACKs 100
    in-flight client-bidi streams in one shot, retire pass + close
    fire concurrently. Asserts CLOSED status and no Flow leak.
  - closeWhileAppCoroutinesAreOpeningStreamsDoesNotDeadlock —
    pins the lock-ordering invariant: streamsLock (openers) and
    lifecycleLock (close) don't fight.
  - closeWhilePeerStreamsAreInFlight — close fires mid-stream of
    50 server-uni group streams (half FIN'd, half not). Every
    incoming Flow terminates promptly with whatever bytes the
    parser had already delivered.

#3 PN-gate / try-previous-fall-through-to-next for key updates
==============================================================
Closes the KNOWN-LIMITATION I documented in the prior round.
QuicConnectionParser previously routed mismatched-KEY_PHASE
packets unconditionally to previousReceiveProtection if non-null,
which silently dropped consecutive-rotation packets (KEY_PHASE
wraps back to its prior value, prior keys are now wrong, AEAD
fails, connection wedges).

Fix follows neqo's shape: try previous keys; on AEAD failure fall
through to next-phase derivation. Two AEAD attempts on a
mismatched-phase packet are cheap; KEY_PHASE mismatch is rare.
The previously-disabled twoConsecutiveRotationsCommitCorrectly
test now passes.

#4 loss harness depth (MoqLiteLossHarnessTest, 3 added cases)
=============================================================
First-pass harness from the previous round was a single 5%-loss
moq-lite shape. Added:
  - listenerToleratesPacketReorderingOnGroupStreams — random
    permutation of 50 group-stream datagrams, asserts 100%
    delivery. Pins the reorder contract for moq-lite.
  - listenerSurvivesExtremeTwentyPercentLoss — 200 streams at
    20% loss, asserts ≥ 60% delivery and connection stays
    CONNECTED. Catches catastrophic-collapse regressions in
    flow-control / ACK-tracker / retired-id ring under stress.
  - reliableBidiStreamRecoversFromMidStreamPacketLoss — drops
    the middle two of four STREAM frames on a reliable bidi
    stream, retransmits, asserts the consumer surfaces the full
    contiguous payload. Pins the reliability contract distinct
    from the best-effort moq-lite path.

#1 foreground-resume recycle (AppForegroundRecycleHook, 5 tests)
================================================================
Closes the user-visible production gap. ReconnectingNestsListener
already orchestrates retry on terminal state and observes
NestNetworkChangeBus for network-handover recycles. The missing
piece was a foregrounding signal: when Android reclaims the app's
UDP socket FD after backgrounding (typical at ~30 s+, network
itself still up so the connectivity callback doesn't fire), the
QUIC connection sits dead until the next send-loop throw — which
landed last round.

This hook publishes a NestNetworkChangeBus event when the app
returns to foreground after spending ≥ 5 s in background. The
pre-existing wiring observes that event and calls
recycleSession() on every active listener / speaker. Pure-state
core (AppForegroundCounter) is testable without Robolectric;
JUnit-4 unit tests pin the threshold logic, multi-activity
counter behaviour (e.g. PIP), and consecutive-cycle correctness.

Wired into Amethyst.Application.onCreate via
registerActivityLifecycleCallbacks.

https://claude.ai/code/session_018KPKWRg5baX5Anf7zfEyec
2026-05-07 23:44:14 +00:00
Claude 67f5260070 feat(payments): add copy-to-clipboard option in payment targets popup
Lets users grab a payment target's address even if no payto:// handler is
installed, by adding a second M3ActionRow per target that copies the
authority string to the clipboard.
2026-05-07 22:53:18 +00:00
Claude a43783879b perf(amethyst): use FilterIndex for LocalCache.observables fanout
Replaces the ConcurrentHashMap<Observable, Observable> registry with
a FilterIndex<Observable>. observeNotes / observeEvents /
observeNewEvents(Filter) call register(filter, observer); the
predicate-only observeNewEvents(predicate) overload uses
registerUnindexed because the index can't introspect an opaque
predicate.

refreshNewNoteObservers now iterates index.candidatesFor(event)
instead of every observer. refreshDeletedNoteObservers still uses
forEach — the index doesn't help on deletes (every observer might
hold the deleted note in its result set, no event-shape to consult).

Same FilterIndex<S> the relay's LiveEventStore uses; one structure,
two call sites, identical fanout shape.
2026-05-07 22:37:44 +00:00
Crowdin Bot 9afa51d819 New Crowdin translations by GitHub Action 2026-05-07 20:37:07 +00:00
davotoula d9f1237ff3 Fix scheduled-post card edges around the rounded corners 2026-05-07 21:51:49 +02:00
davotoula 1c4159f3b0 Consolidate strings 2026-05-07 21:51:49 +02:00
davotoula 474a957f2b Code review:
- @Volatile ScheduledPostNotifier.channel: two workers firing in the
  same window can race on ensureChannel from different IO threads.
  createNotificationChannel is idempotent, but the field write needs a
  visibility fence so both threads see the cached reference.
- @Volatile PoolEventOutbox.eventOutbox map ref + PoolEventOutboxState
  .relaysRemaining set ref. The new pendingPublishRelaysFor polling
  path reads these from the WorkManager IO thread; mutations still
  happen on NostrClient's IO scope. Pre-existing visibility gap that
  this poll surface exposed; @Volatile is the minimal fix.
- ScheduledPostsScreen.SectionLabel: read the locale from
  LocalConfiguration.current.locales[0] (the Compose-resolved locale)
  rather than Locale.getDefault() (the system locale, which can drift
  from app config and breaks Turkish I/i casing).
2026-05-07 21:51:49 +02:00
davotoula c9f6762751 Screen re-design 2026-05-07 21:51:49 +02:00
davotoula f6991bee15 Wait for relay OK ack before marking SENT
- Quartz: expose pendingPublishRelaysFor(eventId) on INostrClient
- Worker: after publish(), poll pendingPublishRelaysFor every 500ms up
to OK_TIMEOUT_SEC=30s
- notify user when a scheduled post fires or fails
2026-05-07 21:51:49 +02:00
davotoula 28defaf96e Translate 44 new strings into cs-rCZ, de-rDE, pt-rBR, sv-rSE 2026-05-07 21:51:49 +02:00
davotoula a3024d855d Code review:
- Switch list-screen formatters from java.text.DateFormat to
  java.time.format.DateTimeFormatter. DateFormat / SimpleDateFormat
  are documented not-thread-safe; the file-scope singletons are at
  risk under any future multi-threaded recomposition path.
  DateTimeFormatter is immutable and thread-safe.
- LogoutButton: when decodePublicKeyAsHexOrNull fails, bail before
  the Toast fires so we don't claim "Logged out" while logOff's
  coroutine has aborted its cleanup. Theoretical case (npub from
  prefs is well-formed in practice) but the previous flow was
  misleading on the failure path.
- ScheduledPostStore.purgeStale: document the
  terminatedAtSec ?: lastAttemptAtSec ?: createdAtSec fallback so
  the legacy-row migration semantics are clear from the source.
- DrawerContent: drop fully-qualified inline references, add proper
  imports for Amethyst, ScheduledPostStatus, and Material3 Badge.
- ScheduledPostsScreen: drop the redundant `posts` collection — derive
  the empty-state branch from `groups` directly. Use `group.day` as the
  sticky-header key instead of an interpolated string. Cache the SHORT
  time and FULL date `DateFormat` instances at file scope (matches the
  pattern in TimeAgoFormatter). Pass `today` into `DayHeader` so we
  don't call `LocalDate.now()` per recomposition.
- ScheduledPostsViewModel: drop the intermediate public `posts`
  StateFlow now that no consumer needs it; fold the filter+sort into
  the `groupedPosts` chain.
- ScheduledPostStoreTest: collapse the two `newStore()` overloads into
  one with a defaulted `now: () -> Long` parameter.
2026-05-07 21:51:49 +02:00
davotoula 0bbbdc2f65 Polish:
- Delete scheduled posts on logout
- presets, list grouping, drawer badge, always-on prompt, logout toast
- bech hardening, retention, live countdown
- Replace `bechToBytes()` chains at three account sites with the null-safe
  `decodePrivateKeyAsHexOrNull` / `decodePublicKeyAsHexOrNull`. A malformed
  npub no longer crashes the LogoutButton composable tree or leaves the
  logoff path half-cleaned (deleted account row but cache + scheduled
  posts still in memory).
2026-05-07 21:51:49 +02:00
davotoula 214a35d620 Code review
- catch CancellationException explicitly
- document the deliberate tradeoff of holding the data mutex across the disk write in persist()
- migrate hardcoded strings to R.string.*
- Store.mutate: use value equality (==) instead of reference (===)
- Store.persist: drop the redundant parent.exists() check
- ViewModel: drop the Context parameter from publishNow(id)
- Screen: memoize extractPreview via remember(post.id) so JSON scanning doesn't run on every recomposition of a row.
- Screen: replace hardcoded Color(0xFF...) literals
- Screen.formatPublishMoment: delegate the past-tense branch to the existing timeAgoNoDot() helper instead of hand-rolled TimeUnit math.
2026-05-07 21:51:49 +02:00
davotoula 8e5e3c634a feat(scheduled-posts): warn when scheduling without always-on notifications
feat(scheduled-posts): add screen + drawer entry to view, push, or delete
feat(scheduled-posts): use scheduled time as created_at + add diagnostic logs
feat(scheduled-posts): add picker UI and toolbar toggle to post composer
feat(scheduled-posts): wire schedule branch into ShortNotePostViewModel
feat(scheduled-posts): add storage + worker for delayed post publishing
2026-05-07 21:51:49 +02:00
Vitor Pamplona 879a778fd9 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-05-07 10:38:08 -04:00
Claude 9c4e87b937 feat(blossom): route image fetches through local Blossom cache
Adds support for the local-blossom-cache spec
(https://github.com/hzrd149/blossom/blob/master/implementations/local-blossom-cache.md):
when http://127.0.0.1:24242 responds 2xx to HEAD /, image and video
fetches are routed through it with xs= upstream hints so it can proxy
on miss. Toggle defaults ON per account; disable from Media Servers
settings.

Covers both blossom:// URIs and plain http(s) URLs that carry an imeta
sha256, by rewriting the latter to a synthetic blossom:<sha>?xs=<host>
URI before handing it to Coil/ExoPlayer.
2026-05-07 12:10:06 +00:00
Claude 832ad78a6e fix(nests): green speaking ring no longer cropped + gates on actual speech
The full-screen Nest stage's green speaking indicator had two issues:

1. **Cropping at the top.** The glow halo (drawBehind, up to MAX_GLOW_RADIUS
   past the avatar) and detached outer ring rendered onto the avatar's own
   draw layer, which only had the avatar's bounds (75dp circle). The
   surrounding stage Surface (RoundedCornerShape(20dp)) clipped the topmost
   pixels of the first row's glow. Wrap the avatar+badges in an outer Box
   that reserves [ringPadding] (max of MAX_GLOW_RADIUS / OUTER_RING_GAP +
   OUTER_RING_MAX_WIDTH) and move the drawBehind there, drawing relative to
   the inner avatar circle. Badge corner-alignment stays on the inner Box
   so role/hand/mic badges still anchor to the avatar.

2. **Lit up on mic-on, not on actual speech.** `onSpeakerActivity` was
   called once per MoQ object received — i.e. every ~20 ms while the mic
   was open, regardless of whether the frame contained voice, silence, or
   room noise. That meant the green ring was effectively a "mic is on"
   indicator. Split the heartbeat (kept in `onSpeakerActivity`, which
   still drives the cliff detector and clears the connecting overlay)
   from the speaking detector (now in `onAudioLevel`, gated on the
   decoded peak amplitude clearing SPEAKING_LEVEL_THRESHOLD = 0.06,
   ~-24 dBFS). The existing 250 ms expiry job gives natural hysteresis
   between syllables.
2026-05-06 21:32:44 +00:00
Vitor Pamplona c1c29aedb7 When pressing the button, start unmuted because it already pressed the "unmute" icon... 2026-05-06 16:18:35 -04:00
Vitor Pamplona e144226eee Merge pull request #2750 from vitorpamplona/claude/debug-audio-dropout-n0g6Z
Add audio format negotiation and cliff detection for Nests
2026-05-06 16:07:59 -04:00
Claude 8a49486b37 fix(nests): G4+G5 plumb catalog sampleRate through decoder + AudioTrack
G4: Audit-9 (catalog-driven decoder reconfig) plumbed numberOfChannels
end-to-end but left sampleRate hardcoded at AudioFormat.SAMPLE_RATE_HZ
in MediaCodecOpusDecoder.buildOpusIdHeader / buildFormat and in
AudioTrackPlayer's MinBufferSize / 250 ms target / setSampleRate
calls. For Opus this is benign in practice — Codec2 always emits
48 kHz PCM regardless of OpusHead inputSampleRate — but hardcoding
the constant means a future codec or container variant whose
decoder DOES respect input sample rate would mis-clock playback.
And the OpusHead identification header should match what the
catalog declares either way.

Thread sampleRate alongside channelCount through every layer:

  - MediaCodecOpusDecoder constructor takes
    `sampleRate: Int = AudioFormat.SAMPLE_RATE_HZ`. buildFormat
    and buildOpusIdHeader both take it as a parameter.

  - AudioTrackPlayer constructor takes the same. Used in
    AudioTrack.getMinBufferSize, the 250 ms-equivalent target
    bytes calculation (`(sampleRate / 4) * BYTES_PER_SAMPLE *
    channelCount`), AndroidAudioFormat.Builder.setSampleRate, and
    the diagnostic log.

  - decoderFactory and playerFactory in NestViewModel become
    `(channelCount: Int, sampleRate: Int) -> ...`.

  - awaitDecoderChannelCount → awaitAudioPipelineConfig, returning
    a private `AudioPipelineConfig(channelCount, sampleRate)`
    struct so openSubscription handles both fields uniformly.
    `sampleRate` falls back to AudioFormat.SAMPLE_RATE_HZ on
    timeout / non-positive declaration with a warning log.

  - NestViewModelFactory + NestViewModelTest updated to the
    two-arg factory shape.

G5: documented the SUBSCRIBE_BUFFER safety budget on
CATALOG_AWAIT_TIMEOUT_MS's kdoc. With the current 500 ms timeout +
SUBSCRIBE_BUFFER = 64-frame DROP_OLDEST flow + 50 fps Opus, at
most 25 frames buffer during the wait — leaving ≥ 39 frames of
margin (≈ 780 ms) before the oldest frame would be evicted. Even
at the production framesPerGroup = 50 (1 group/sec) cadence this
never trips during normal startup. No code change; just pinning
the rationale so a future timeout bump is checked against the
buffer size.

https://claude.ai/code/session_014JfZJHSTvyYYWJbC9VbB47
2026-05-06 19:36:05 +00:00
Claude 1c47fd2403 feat(nests): drive Opus decoder + AudioTrack channel count from catalog
Web speakers (kixelated/hang reference) emit stereo Opus when the
user's AudioContext picks a stereo input — and our catalog now lets
us know that ahead of decoder construction. Previously the decoder
+ AudioTrack were both hardcoded to mono via AudioFormat.CHANNELS, so
a stereo web publisher's frames either threw on the Opus TOC byte
mismatch, decoded with downmix artifacts, or output left-channel-only
depending on the device's MediaCodec implementation.

Wire the catalog-discovered numberOfChannels through:

- MediaCodecOpusDecoder accepts `channelCount: Int = 1`. Drives
  MediaFormat.createAudioFormat's channel count, the OpusHead CSD-0
  channel byte, and the per-frame output buffer size (stereo frame =
  2× shorts vs mono). Validates 1..2 — RFC 7845 mapping family 0
  covers both mono and stereo without an explicit channel mapping
  table; multichannel needs family 1 which we don't support.

- AudioTrackPlayer accepts `channelCount: Int = 1`. Drives the
  AudioTrack channel mask (CHANNEL_OUT_MONO vs CHANNEL_OUT_STEREO)
  and the 250 ms wall-clock buffer-size target (stereo doubles bytes
  per sample).

- NestViewModel.openSubscription now starts the catalog fetch
  BEFORE waiting for it, then awaits `_speakerCatalogs[pubkey]` for
  CATALOG_AWAIT_TIMEOUT_MS (500 ms) before constructing the decoder
  + player. With the speaker-side emit-on-subscribe hook in place
  (3417e44), the catalog frame is on the wire within one round-trip
  of the SUBSCRIBE_OK, so the wait typically resolves in tens of ms.
  Falls back to mono if the catalog never arrives — covers legacy
  publishers that don't emit a catalog and the failure-mode where
  the relay never forwards the catalog group.

- decoderFactory + playerFactory signatures change from
  `() -> X` to `(channelCount: Int) -> X`. Two call sites updated:
  NestViewModelFactory (production) and NestViewModelTest's
  newViewModel helper.

Speaker side stays mono-only — our encoder is fixed at
AudioFormat.CHANNELS=1 and the catalog we publish declares
numberOfChannels=1. This change is exclusively about handling
incoming audio from publishers that don't share that constraint.

https://claude.ai/code/session_014JfZJHSTvyYYWJbC9VbB47
2026-05-06 17:26:47 +00:00
davotoula fe2cb7da66 Centralise the NIP-05 wildcard check (name != "_") on the data class itself. 2026-05-06 18:32:11 +02:00
davotoula 54c48bc5b7 extract Nip05OrPubkeyLine into shared composable
Extract nip05 value calculation into helper
2026-05-06 18:32:11 +02:00
Claude a34a5e0af7 fix(profile): hide '_@' prefix on NIP-05 in profile header
Per NIP-05, when the local part is '_', the address should display as
just the domain. The profile header was calling Nip05Id.toValue() which
always returns 'name@domain', producing '_@houseofeff.com' on screen.
Match the pattern already used in NIP05VerificationDisplay,
AwardBadgeScreen, RelayManagementScreen, and NewCommunityScreen.
2026-05-06 18:32:11 +02:00
Crowdin Bot 010fc03292 New Crowdin translations by GitHub Action 2026-05-06 13:32:35 +00:00