Commit Graph
15076 Commits
Author SHA1 Message Date
Claude 54b09ea6e2 fix(commons): split-aware zap requests stop misrouting funds on multi-party notes
The previous ZapActions.buildEventZapRequest signed a single zap request
to a single recipient. Notes carrying NIP-57 zap-split tags, NIP-53
live-activity host tags, or NIP-89 app-definition metadata expect the
payment to be distributed across multiple parties — so `amy zap event`
silently overpaid one party and underpaid the rest. The correctness
review on the action-set flagged this as the only real bug in the
extracted verbs; this commit fixes it.

  * ZapSplitResolver — new commonMain object mirroring the resolution
    order in ZapPaymentHandler.kt (splits > live-activity hosts > app
    metadata > author fallback). Pure logic; pubkey→LN-address lookup
    is passed in as a suspend lambda so amy reads from its file store
    and Android reads from LocalCache, no shared cache-coupling.

  * ZapActions.buildEventZapRequestsForSplits — high-level helper that
    composes the resolver with per-share LnZapRequestEvent signing.
    Each request's `relays` tag unions sender + author + recipient
    inbox relays so the kind:9735 receipt routes to every interested
    party (matches signAllZapRequests in the Android handler).

  * amy zap event — rewired to the split-aware path. JSON output now
    enumerates each recipient with its share, LN address, request id,
    and BOLT11 invoice (or per-recipient invoice_error). Profile zaps
    (amy zap user) keep the simple single-recipient path since they
    have no split tags.

Tests: 12 new cases — LN-address splits, weighted pubkey splits, author
fallback, drop-silently-on-missing-LN, relay unioning, share rounding.
All 41 action tests green; both Android flavors compile.
2026-05-24 21:10:30 +00:00
Claude 95beed16e1 refactor: KMP WeakReference + drop synchronized(this) from commonMain
Phase 2 of the iOS plan — clears the java.lang.ref.WeakReference
blocker from commons/commonMain. Four model files migrated; one
additional sync primitive replaced.

- Adds expect class WeakReference<T : Any> in
  commons/commonMain/util/, with a jvmAndroid actual that typealiases
  to java.lang.ref.WeakReference. iOS actual will typealias to
  kotlin.native.ref.WeakReference when the target is added.
- Channel / Chatroom / MarmotGroupChatroom: the WeakReference(null)
  initializer relied on platform-type nullability of
  java.lang.ref.WeakReference's constructor. With T : Any in the expect
  class, fields become nullable (WeakReference<...>? = null) and the
  .get() callsites become ?.get(). Behaviorally equivalent.
- UserRelaysCache: same WeakReference migration, plus the
  synchronized(this) double-checked-locking idiom is replaced with
  co.touchlab.stately.concurrency.Lock + withLock (KMP).
  kotlin.synchronized is JVM-only; Lock comes in transitively via
  stately-concurrent-collections already added in the previous PR.

Model-layer @Synchronized usage in Channel/Chatroom/MarmotGroupChatroom/
Note (also JVM-only) is a separate iOS blocker and a separate PR.
2026-05-24 18:30:27 +00:00
Claude bf6467cdcf refactor: drop ConcurrentHashMap from commonMain
Phase 2 of the iOS plan — clears the ConcurrentHashMap blockers from
commons/commonMain. Five files migrated (the four flagged in the
initial audit + ChessLobbyLogic, which used fully-qualified inline
java.util references that the import-based audit missed).

Adds co.touchlab:stately-concurrent-collections 2.1.0 — a small,
mature KMP library that provides ConcurrentMutableMap /
ConcurrentMutableSet with semantics equivalent to ConcurrentHashMap /
ConcurrentHashMap.newKeySet on every Kotlin target. The .block { }
helper covers the compound-update paths (ChessRelayFetchHelper's
per-relay event-count compute, ChessLobbyLogic's bounded-LRU dedup).

- ComposeSubscriptionManager + MutableComposeSubscriptionManager:
  ConcurrentHashMap -> ConcurrentMutableMap
- ChessEventCollector + ChessEventCollectorManager: map and Set
- ChessRelayFetchHelper: in-function event/relay state
- ChessLobbyLogic: replaces dismissedGameIds (synchronizedSet),
  recentlyLoadedGames (ConcurrentHashMap), seenEventIds (bounded LRU
  using LinkedHashSet via Collections.synchronizedSet + synchronized {}).
  seenEventIds keeps insertion-order eviction semantics because
  mutableSetOf returns LinkedHashSet on every KMP target.
2026-05-24 18:24:22 +00:00
Claude 17cee60aac feat(amethyst): expose searchProfiles to Gemini via androidx.appfunctions
First Phase 2 verb wired through to the Android App Functions runtime so
Gemini (and other system agents) can drive Amethyst.

Scope is intentionally narrow:
  * One read-only verb (searchProfiles), built on top of the existing
    SearchActions in commons. No write verbs yet — they need a story
    for NIP-46 / NIP-55 signer prompts from a background dispatcher.
  * Play channel only. appfunctions 1.0.0-alpha09 is a Google AI alpha;
    F-Droid builds continue to ship without any Google AI dependencies.

Architecture:
  * AmethystAppFunctions — plain Kotlin host with @AppFunction methods.
    The KSP-driven appfunctions-compiler discovers them and generates
    the dispatch metadata XML at build time.
  * PlayAmethyst — play-only Application subclass implementing
    AppFunctionConfiguration.Provider; supplies the factory the
    library uses to construct the host class. Manifest replaces
    android:name in the play flavor only; F-Droid keeps the unmodified
    Amethyst class.
  * The androidx-provided PlatformAppFunctionService is registered in
    the play manifest as the bind point — Amethyst doesn't ship a
    custom Service.

KSP is now a project-wide plugin (apply false at the root); applied in
amethyst/ to run the appfunctions-compiler over the play sourceSet.

Amethyst becomes `open class` so PlayAmethyst can extend it. No other
behavior change.
2026-05-24 18:23:59 +00:00
Claude 1b6b699d76 refactor: drop java.util.concurrent atomics from commonMain
Phase 2 of the iOS plan — two of the ~9 small migrations to clear
java.* imports out of commons/commonMain.

- ChessLobbyState: the AtomicLong stateVersionCounter only existed to
  bump a MutableStateFlow<Long>. MutableStateFlow.update is itself
  atomic, so the counter is redundant — replaced with
  _stateVersion.update { it + 1 }. Removes the dep and simplifies the
  code.
- SigningState (GlobalSigningStatus): AtomicInteger is doing real
  cross-thread coordination. Migrated to kotlin.concurrent.atomics.
  AtomicInt (KMP stdlib). The common-API method names differ from
  AtomicInteger — addAndFetch(±1) / store(0) instead of
  incrementAndGet / decrementAndGet / set.
2026-05-24 18:11:20 +00:00
Claude 78ef4fa672 refactor: migrate Base64Image off java.util.Base64
Phase 2 of the iOS plan — first of ~9 small migrations to clear the
java.* imports out of commons/commonMain. Replaces java.util.Base64
with kotlin.io.encoding.Base64 (stdlib, KMP-clean). The two callers
(Android Base64Fetcher, Desktop DesktopBase64Fetcher) use the public
parse() signature only, which is unchanged.

Also documents the full Phase 2 audit in
amethyst/plans/2026-05-24-ios-support.md: out of 335 commonMain files,
21 are real iOS blockers grouped into ~10 small mergeable PRs. The
remaining 183 androidx.compose users and 7 androidx.lifecycle users
already map to JetBrains Compose Multiplatform / AndroidX KMP and need
no work.
2026-05-24 18:07:25 +00:00
Vitor PamplonaandGitHub 2450955478 adjusting format 2026-05-24 13:58:57 -04:00
Vitor PamplonaandGitHub 07d77e1363 Merge pull request #3045 from vitorpamplona/claude/stoic-turing-TTiSd
Restructure privacy policy and add build-variant legal UI
2026-05-24 13:52:09 -04:00
Claude 6f8f5f7d57 docs: tighten PRIVACY.md — concise, truthful, lower-liability
Rewrites the policy/terms doc with three goals:

1) **Concise & easier to read.** Plain English, short sentences,
   removed redundant intros (the "How Amethyst Works (and Why That
   Matters Here)" block restated the Privacy intro), merged the
   "Visibility" + "Permanence" sections into one paragraph, and
   collapsed the Child Safety POC section into a single contact
   block near the top of the document.

2) **More truthful.** Two corrections:
   - F-Droid build uses UnifiedPush for notifications, not FCM. The
     previous text only mentioned Google Firebase Cloud Messaging,
     which was inaccurate for the F-Droid distribution.
   - Replaced "We rely on Google Play's age verification to make sure
     the user downloading the app is an adult" with "Amethyst's Google
     Play listing is rated 17+. The app does not request or store age
     information." Google Play does not actually verify user age, so
     the old wording overstated the protection.

3) **Lower liability.** Several specific changes:
   - Dropped the "We aim to acknowledge child-safety reports within
     72 hours" service-level commitment that the solo developer cannot
     reliably meet.
   - Softened "we will recommend that the offending relay be removed"
     and "What we can do: acknowledge the report, forward..." to
     discretionary "may forward" / "may stop recommending" phrasing.
   - Removed the absolute "data is strictly confidential and cannot
     be accessed by other apps" guarantee. Replaced with the narrower,
     verifiable claim that other apps cannot read app-local storage
     on a standard, non-rooted Android device.
   - Narrowed "Amethyst is built and distributed to comply with
     applicable child safety laws and regulations" to "Amethyst is
     distributed under Google Play's Child Safety Standards policy and
     applicable law" — same in spirit, smaller surface for dispute.

Content that the Google Play Child Safety Standards checklist
requires is unchanged: explicit CSAE prohibition, child-safety point
of contact (amethyst@vitorpamplona.com), in-app feedback mechanism
(Report Post / Report Account / Block Post / Block Account / Block
Relay / Mute), method for addressing CSAM (in-app report → block
relay → NCMEC/INHOPE → optional developer notice), compliance
statement, and references to the app name "Amethyst" and the Google
Play publisher "Vitor Pamplona". The F-Droid carve-out also remains:
the MIT License in LICENSE is identified as the only instrument
governing source-built distributions, with no additional terms.
2026-05-24 16:50:45 +00:00
Claude 2628f45788 refactor: move ToS / legal links into flavor source sets
The Play-Store-only Terms-of-Use checkbox and the "About & Legal"
Settings section both contain hardcoded GitHub URLs to the published
PRIVACY.md. Previously they were in src/main and gated at call sites
by `BuildConfig.FLAVOR == "play"` — which works at runtime but still
compiles the GitHub URLs into the F-Droid APK.

This commit moves the URL-bearing UI into src/play and adds empty
src/fdroid stubs with the same signatures, so the F-Droid build is
physically free of the URLs.

New composables (same package + signature in both flavor source sets,
so callers in src/main link to whichever flavor is being built):

- com.vitorpamplona.amethyst.ui.screen.loggedOff.legal.TermsGate
  * src/play: renders an AcceptTerms checkbox with the PRIVACY.md link
    and the "acceptance required" error text.
  * src/fdroid: empty body.

- com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.LegalSettingsSection
  * src/play: renders the SettingsSection with Privacy Policy and
    Child Safety Standards rows that open the GitHub-hosted PRIVACY.md.
  * src/fdroid: empty body.

Call-site changes:

- LoginScreen: drop `BuildConfig.FLAVOR == "play"` guard and call
  `TermsGate(...)` inside the existing `isFirstLogin` block. The
  flavor source set picks the right body.
- SignUpScreen: drop `BuildConfig.FLAVOR == "play"` guard, call
  `TermsGate(...)` unconditionally.
- AllSettingsScreen: drop `BuildConfig.FLAVOR == "play"` guard and
  the inline About & Legal SettingsSection, call
  `LegalSettingsSection()` instead. LocalUriHandler and BuildConfig
  imports removed.

The old `src/main/.../loggedOff/AcceptTerms.kt` is deleted; its body
moves into the play-flavor TermsGate as a file-private helper.

The LoginViewModel / SignUpViewModel still use `BuildConfig.FLAVOR` to
decide the initial `acceptedTerms` value (so the login/signup button
isn't disabled on F-Droid). That check is logic only, contains no
URLs, and is safe for F-Droid distribution.

Verified: `grep -r "github.com/vitorpamplona/amethyst/blob"` against
src/main + src/fdroid returns zero matches. Both
:amethyst:compileFdroidDebugKotlin and :amethyst:compilePlayDebugKotlin
compile cleanly.
2026-05-24 16:45:48 +00:00
Claude b27fc34786 refactor: migrate FeedDefinitionSerializer to kotlinx.serialization
The one Jackson holdout in commons/commonMain. Migrating it unblocks
the iOS purity gate for :commons (Phase 1 of the iOS plan).

- Rewrites FeedDefinitionSerializer with the kotlinx.serialization JSON
  tree API (JsonObject / JsonArray / JsonPrimitive). Wire format is
  byte-identical, so users' existing on-disk custom-feed definitions
  keep deserializing — covered by a new parsesLegacyJacksonOutput test
  that pins a hand-written Jackson-shaped JSON blob.
- Adds :commons:verifyKmpPurity (mirrors the one in :quartz) and wires
  it into the CI lint job alongside :quartz:verifyKmpPurity.
- Pulls in kotlinx-serialization-json as a commonMain dep; the
  serialization plugin was already applied on :commons.
2026-05-24 16:41:00 +00:00
Claude 2e47cb7110 feat(commons): add NIP-57 zap verbs in shared actions package
Third verb extraction alongside FollowActions / SearchActions, scoped
to event building so the action stays target-agnostic (commonMain,
no JVM/Android coupling).

  * buildUserZapRequest / buildEventZapRequest wrap the two
    LnZapRequestEvent.create overloads with a uniform call shape and
    sensible defaults (PUBLIC zap, no LNURL, no poll).
  * extractLnAddress pulls lud16 (preferred) or lud06 from a kind:0
    metadata event, returning null when neither is set.
  * satsToMillisats covers the sats→msats conversion that every
    caller would otherwise duplicate.

Wires up amy zap user|event as the first consumer. The Lightning
round-trip (LNURL fetch + invoice retrieval) goes through the existing
LightningAddressResolver in commons/jvmAndroid; the BOLT11 invoice is
printed but not auto-paid since amy has no NWC wallet wired up yet.
2026-05-24 16:33:15 +00:00
Claude f058662349 fix(fdroid): hide Play-only ToS gate and Privacy/Child-Safety links
F-Droid distributes Amethyst as MIT-licensed free software with no
acceptable-use terms layered on top — only the Play Store build needs
a ToS-acceptance checkbox at login/signup and links to the published
Child Safety Standards.

Gates added behind `BuildConfig.FLAVOR == "play"`:

- Settings → "About & Legal" section (Privacy Policy + Child Safety
  Standards) is now Play-only; F-Droid settings no longer link to
  PRIVACY.md.
- AcceptTerms checkbox in LoginScreen and SignUpScreen is now Play-only.
- LoginViewModel.load() / clear() and SignUpViewModel pre-accept
  `acceptedTerms` on F-Droid so the login/signup button isn't disabled.

The Play build is unchanged: first-time login still requires checking
the ToS box, and the About & Legal section still surfaces the published
Child Safety Standards link required by Google Play.
2026-05-24 16:24:57 +00:00
Claude cde609203c feat(commons): add NIP-50 search verbs in shared actions package
Introduce SearchActions alongside FollowActions as the second of the
shared "verbs" usable by amy CLI and a future Android App Functions
adapter for Gemini.

  * searchProfilesFilter / searchNotesFilter build the relay-side
    Filter with the NIP-50 `search` field set; blank queries return
    null so callers don't issue unconstrained searches that relays
    would reject anyway.
  * resolveSearchRelays picks the caller's kind:10007 list when
    configured (decrypting NIP-44 private entries via the signer) and
    falls back to DefaultSearchRelayList — the same set the Android UI
    uses when the user has no list of their own.

Wires up amy search user|note as the first consumer.
2026-05-24 16:23:34 +00:00
Claude c3e03308f8 docs: clarify Child Safety Standards are a policy, not a license restriction
F-Droid requires that apps add no restrictions to the FOSS license that
ships with the source. The previous wording ("Amethyst strictly prohibits
the use of the app to...") could be read as an EULA clause that
restricts use beyond what the MIT LICENSE grants.

Reframe the prohibition as a published community standard / acceptable-
use policy — which is exactly what Google Play's Child Safety Standards
policy requires anyway — and add an explicit "Free Software License"
note clarifying that the MIT license terms in LICENSE are unchanged,
and that F-Droid users and source redistributors retain every right
granted by MIT.

Google Play's requirements remain satisfied: the prohibition of CSAE is
still explicit, the in-app reporting mechanism is documented, the
child-safety point of contact and NCMEC escalation path are unchanged,
and the app/developer name is still referenced.
2026-05-24 16:15:34 +00:00
Claude e0c3b18731 ci: add iOS test job for quartz + commonMain purity gate
Phase 1 of the iOS support plan (amethyst/plans/2026-05-24-ios-support.md).
Two independent guards so JVM-only imports can't silently appear in
quartz's iOS-bound source sets:

- :quartz:verifyKmpPurity (Linux, ~1s): scans commonMain + apple/native
  source sets for com.fasterxml.jackson / okhttp3 references and fails
  the build with a clear pointer to the offending file:line. Wired into
  the existing lint job so it runs on every PR.

- test-quartz-ios (macos-latest): runs :quartz:iosSimulatorArm64Test on
  the simulator (NIP-04, NIP-17, NIP-19, NIP-49 vectors + AES-GCM and
  chatroom-key tests already in quartz/src/iosTest) and additionally
  compileTestKotlinIosArm64 to catch device-variant compile drift.
2026-05-24 16:12:34 +00:00
Claude 5a276ce8d7 docs: expand Child Safety Standards for Play Store compliance
Google Play rejected v446 because the published Child Safety Standards
did not explicitly prohibit CSAE, name a child-safety point of contact,
describe the in-app reporting mechanism, or reference the app/developer
as listed on Play.

Rewrites the section in PRIVACY.md to:

- Explain that Amethyst is a client, not a host: third-party relays host
  content and are responsible for moderation and any NCMEC reporting
  obligations (e.g. 18 U.S.C. §2258A).
- Explicitly prohibit CSAE/CSAM in the app.
- Document the in-app tools users have: Report Post, Report Account,
  Block Post/Account, Block Relay (NIP-51 Blocked Relay List), Mute
  Words/Hashtags.
- Describe the escalation path: report in-app, block the hosting relay,
  report to NCMEC CyberTipline / INHOPE, optionally email the developer.
- Provide a child-safety point of contact (amethyst@vitorpamplona.com)
  with realistic scope of action (forward to relay ops, drop the relay
  from default lists).
- Reference the app name "Amethyst" and developer "Vitor Pamplona" as
  required by the checklist.

Also surfaces the document from inside the app by adding an
"About & Legal" section to Settings with two items: Privacy Policy and
Child Safety Standards (anchor link to the section).
2026-05-24 16:06:52 +00:00
Claude 257756438d feat(commons): extract follow/unfollow verbs into shared actions package
Introduce commons/.../actions/FollowActions as the canonical, non-UI
entry point for NIP-02 kind:3 mutations. Accepts pubkeys as HexKey
rather than the Compose-bound User model, so callers without a cache
(amy CLI, future Android App Functions adapter for Gemini, automation
scripts) can drive follow/unfollow directly.

Kind3FollowListState.follow/unfollow now delegate to FollowActions,
preserving the existing Account.follow(user) signature on Android.
Behavior is unchanged for UI callers.

Wires up amy follow/unfollow as the first consumer — fetches the
freshest kind:3 from outbox relays before mutating so concurrent
follows from another client are preserved.
2026-05-24 16:04:20 +00:00
Claude f10973c06e docs: add iOS support plan 2026-05-24 15:59:59 +00:00
Vitor Pamplona a9306dcea5 update dependencies 2026-05-23 17:42:26 -04:00
Vitor PamplonaandGitHub 74a646c7eb Merge pull request #3040 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-23 17:30:01 -04:00
Crowdin Bot 05eb35a4ca New Crowdin translations by GitHub Action 2026-05-23 21:14:31 +00:00
Vitor PamplonaandGitHub 4758562f88 Merge pull request #3043 from vitorpamplona/claude/multi-npub-external-signer-login-U5GIb
Fix account cache race condition in setDefaultAccount
2026-05-23 17:12:55 -04:00
Vitor PamplonaandGitHub d82143e392 Merge pull request #3042 from vitorpamplona/claude/reaction-row-padding-bug-DaQO0
Fix reaction row layout for icon-only rightmost items
2026-05-23 17:09:55 -04:00
Claude 99b7ca76be fix: only skip the weighted slice for an icon-only last reaction
The previous attempt weighted every item, which made even Share collapse
to the left of its slice instead of pinning to the right edge.

Restore the natural-width carve-out for the last item, but gate it on
`!showCounter` — Share/Pay have no counter so they stay flush against
the right padding as before; Zap/Like/etc. become weighted when last so
the counter doesn't sprawl out to the edge and the row stays balanced.
2026-05-23 21:06:42 +00:00
Vitor PamplonaandGitHub 2ef738de14 Merge pull request #3039 from vitorpamplona/claude/fix-zaps-display-tHV2a
NIP-BC onchain zaps: add verification state machine & reverify driver
2026-05-23 17:04:22 -04:00
Claude e5b0755d9b fix: secondary external-signer login lands on onboarding when switching
LocalPreferences.setDefaultAccount called setCurrentAccount before
saveToEncryptedStorage. setCurrentAccount emits the new list onto the
savedAccounts MutableStateFlow, which AlwaysOnNotificationServiceManager
collects and reacts to by calling loadAccountConfigFromEncryptedStorage
for every saved account — including the just-added one. That call hit
encryptedPreferences(newNpub) before NOSTR_PUBKEY had been written, got
null, and cached the null in cachedAccounts.

cachedAccounts is a process-lifetime map, so the poisoned entry survived
the eventual disk write. Every subsequent switchUser to that account
took the cached null path, fell through to requestLoginUI(), and AccountScreen
rendered LoggedOffSetup — the onboarding screen with TOS unchecked, asking
the user to re-do the Amber handshake.

Write the per-npub file first, then seed the cache with the in-memory
AccountSettings, then publish onto the savedAccounts flow. Also stop
caching null returns in loadAccountConfigFromEncryptedStorage so any
future racy reader can't poison the cache either.
2026-05-23 20:16:13 +00:00
Claude 18fb75285e fix: keep reaction icons evenly distributed when Share is disabled
The reaction row gave every item except the last a `Modifier.weight()`,
which made the last item collapse to its natural width and hug the right
edge of the content area. With Share (icon-only) as the default last
item, all icons appeared evenly distributed.

When the user disabled Share, the last weighted slot moved to Zap. Zap
renders icon + counter, so its natural-width row took more space at the
right and pulled the rightmost icon away from where the other icons sat
(each at the left of a now-wider weighted slice), leaving the row
looking unbalanced.

Give every reaction an equal weighted slice so icons sit at the left of
their slice regardless of which reactions are enabled. The unused space
at the end of the last slice naturally provides the right-side padding
where Share used to sit.
2026-05-23 19:27:19 +00:00
Claude daa83959b6 refactor(onchain-zaps): extract verification coordinator from LocalCache
Moves the asynchronous chain-verification side of NIP-BC onchain zaps out of
LocalCache into a dedicated OnchainZapResolver class living alongside other
NIP-specific subpackages under model/nipBCOnchainZaps/. LocalCache shrinks by
~240 lines and now owns only the synchronous event-dispatch responsibility:
loading the event, attaching the optimistic UNVERIFIED entry for the sender's
own zap, and delegating the verifier launch to the resolver.

The resolver owns:
- launchVerification(event, source, repliesTo) — async fire-and-forget
- reverifyOnchainZapsForNote(note) — used by the gallery's screen-driven loop
- onchainTipHeightFlow — shared chain-tip poller, lazy + WhileSubscribed
- verifyingEventIds / reverifyingNoteIds — in-flight de-duplication
- reverifySemaphore — parallelism cap

OnchainZapGallery now calls LocalCache.onchainZapResolver.{reverifyOnchainZaps
ForNote, onchainTipHeightFlow} directly. consume(OnchainZapEvent) passes the
already-computed repliesTo into launchVerification so the new-event path
doesn't recompute it on the verifier side.

No behavior change — all 22 onchain-zap tests still pass.
2026-05-23 16:13:19 +00:00
Vitor PamplonaandGitHub fc5587f46f Merge pull request #3038 from nrobi144/feat/desktop-wallet-zapping
feat(desktop): wallet zapping, LNURL-pay send, QR receive, and session persistence
2026-05-23 12:05:56 -04:00
Vitor PamplonaandGitHub 0461072254 Merge pull request #3041 from vitorpamplona/claude/jolly-cray-6vKga
Makes the NWC process less strict, while checking for inconsistencies after the request reply is processed.
2026-05-23 12:05:09 -04:00
Vitor PamplonaandGitHub fc7813afbb Merge pull request #3036 from vitorpamplona/claude/affectionate-gauss-UWDJ4
Add NIP-82 Software Applications support with dedicated feed
2026-05-23 12:00:08 -04:00
nrobi144andClaude e33fef2ebd feat(desktop): rich text migration, copy raw JSON, and profile metadata
Replace desktop's 3-segment custom parser with commons' 23-segment
RichTextParser. Add DesktopRichTextViewer rendering all segment types:
hashtags, invoices (with NWC pay), cashu tokens, custom emoji, nowhere
links, emails, relay URLs, markdown, image galleries, and more.

Add 'Copy Raw JSON' to note overflow menu. Add nip05, website, and
lightning address to profile card with right-click copy support.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-23 15:46:46 +03:00
nrobi144andClaude Opus 4.6 e4691f6d93 fix(desktop): remove obsolete ZapDialogLogicTest
Test referenced formatSats, DEFAULT_ZAP_AMOUNTS, and ZapType which
were removed/made private in upstream merge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 15:45:54 +03:00
nrobi144andClaude Opus 4.6 2b14b77acf feat(desktop): support LNURL-pay and lightning addresses in send dialog
Rewrite SendDialog with sealed state machine that auto-detects input
type (BOLT11, LNURL bech32, lightning address). For LNURL/address:
resolves endpoint, shows amount form with min/max hint, optional
comment field, fetches invoice, then pays via NWC. Strips lightning:
URI prefix. Inline copiable errors with retry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 15:40:19 +03:00
nrobi144 a5405fef34 Merge remote-tracking branch 'upstream/main' into feat/desktop-wallet-zapping
# Conflicts:
#	desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/FeedScreen.kt
#	desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/NoteActions.kt
2026-05-23 15:19:47 +03:00
nrobi144andClaude Opus 4.6 4936d187fe fix(desktop): improve send/receive dialogs and LNURL error surfacing
SendDialog: switch to Dialog+Card with X close, inline copiable error
messages, button resets to "Pay Invoice" on error for retry.

LightningAddressResolver: return error body from callback responses so
server error messages (e.g. "Recipient wallet error") surface to user
instead of generic "Failed to fetch invoice". Also check "message"
field in addition to "reason" for error extraction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 15:17:08 +03:00
nrobi144andClaude Opus 4.6 00708d92d3 feat(desktop): redesign receive dialog with QR code and cleaner UX
Replace AlertDialog with Dialog+Card pattern. Invoice created state now
shows centered amount, description, 240dp QR code, and full-width
"Copy Invoice" button. Close via top-right X button. Input form gets
full-width "Create Invoice" button.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 14:35:18 +03:00
nrobi144andClaude Opus 4.6 562cd3355b fix(desktop): fix feed cold-boot race and remove NWC diagnostic println
Add LaunchedEffect that rescans cache when followedUsers populates after
startup, fixing empty feed when contact list arrives after initial scan.
Remove diagnostic println from NwcPaymentHandler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 14:02:48 +03:00
nrobi144andClaude Opus 4.6 e690292bbd fix(desktop): fix nsec session not persisting across restarts
LoginScreen's fire-and-forget save coroutine used rememberCoroutineScope
which got cancelled when the composable left composition after login.
Move saveCurrentAccount() to onLoginSuccess in Main.kt which uses the
app-level scope that survives recomposition. Fixes both nsec login and
generate-new-account flows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-23 14:02:36 +03:00
Vitor PamplonaandGitHub 4391fae915 Merge pull request #3037 from vitorpamplona/claude/pin-followed-chats-iSRJ9
feat: pin followed public chats to the top of the Public Chats feed
2026-05-22 20:00:53 -04:00
Claude 0313dcf3fa fix(onchain-zaps): clear second-audit findings
Addresses the 15 issues from the second audit pass. Key changes:

- Per-event resolution flag (`Note.onchainZapResolved`) replaces the unbounded
  rejection blocklist. The flag is set on terminal verifier verdicts
  (Confirmed or hard-Rejected) and gates the verifier launch in `consume()`.
  Travels with the Note so it clears on `removeAllChildNotes()`.

- Per-event in-flight set (`verifyingEventIds`) deduplicates concurrent
  verifier launches across `consume()` echoes and `reverifyOnchainZapsForNote`
  races. Solves: profile-only zaps bypassing the all-CONFIRMED guard,
  Rejected entries re-firing the verifier on every echo, and the
  consume()/reverify TOCTOU race.

- Per-note reverify gate (`reverifyingNoteIds`) prevents multiple visible
  galleries from launching concurrent reverify passes for the same note.

- `removeOnchainZapForSource` now refuses to remove a CONFIRMED entry — only
  an explicit fresh CONFIRMED replacement can change one. Prevents the
  cross-target downgrade where one target's transient ZERO_VERIFIED_AMOUNT
  erases a sibling target's already-confirmed entry. Also non-nullable
  pubkey parameter to close the null-vs-null comparison hole.

- `innerAddOnchainZap` dedup tightened: exact structural equality skips
  spurious flowSet invalidations on relay echoes, but same-level + equal
  verifiedSats from a DIFFERENT source now replaces (fixes multi-signer
  attribution lock-in).

- Tip flow uses explicit try/catch that re-throws CancellationException
  instead of `runCatching` (same fix the previous audit applied to the
  verifier). Lazy initializer falls back to a constant-null StateFlow if
  `Amethyst.instance` isn't initialized yet, instead of throwing.

- Gallery driver: unconditional first-view kick (no longer waits for the
  tip flow's first non-null emission), separate effect keyed on pending
  entry count so a fresh UNVERIFIED arrival kicks reverify immediately
  instead of waiting up to 60s for the next tip poll.

- `observeNoteZaps`'s memoization now keys on the `onchainZaps` map
  reference so lightning-zap traffic on the same note doesn't churn the
  onchain gallery.

- `reverifyOnchainZapsForNote` uses `supervisorScope` so a single failed
  verifier doesn't cancel its siblings, and the semaphore permits bump
  from 4 → 8 reduces head-of-line blocking when many galleries reverify
  concurrently.
2026-05-22 22:42:25 +00:00
Vitor Pamplona 585b28163a Better rendering of Public Chats 2026-05-22 18:25:07 -04:00
Vitor PamplonaandGitHub 2c8ed6c64f Merge pull request #3031 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-22 18:22:46 -04:00
Crowdin Bot a699920e96 New Crowdin translations by GitHub Action 2026-05-22 22:06:54 +00:00
Vitor PamplonaandGitHub 653ca7ce88 Merge pull request #3034 from nrobi144/feat/desktop-note-action-ux
feat(desktop): note action bar — long-press details popups + right-click customize
2026-05-22 18:05:22 -04:00
Vitor PamplonaandGitHub ae56a295d4 Merge pull request #3035 from greenart7c3/claude/epic-newton-OZLCC
Use URL SHA for Blossom bridge, not imeta hash
2026-05-22 14:53:00 -04:00
Claude 862dce27fe fix(blossom-bridge): always use URL sha, ignore imeta x
On resizing CDNs the imeta `x` (post-resize hash) can differ from the
`ox` (original hash) embedded in the URL. The bridge previously preferred
`explicitHash` over the URL's sha for "authoritative casing", but the
upstream file on `xs` is named after the URL's sha, not the imeta hash.
For URLs like https://image.nostr.build/<ox>.png with imeta x=<post-resize>
the cache would request /<x>.png and 404 on miss.

Always use the sha parsed from the URL path; drop the explicitHash
parameter. `extractSha256FromUrlPath` already lowercases, so the casing
concern is moot.
2026-05-22 16:23:34 +00:00
nrobi144andClaude Opus 4.6 3185df21b6 fix(desktop): reactive counters, quote boost, and boost detail popup
- Add kind 1 (replies) to interaction subscriptions
- Key count reads on FlowSet state for reactive updates
- Wire Quote menu item to ComposeNoteDialog with q-tag support
- Add BoostsPopup on long-press repost icon (who boosted)
- ComposeNoteDialog now accepts quoteOf param with nostr: URI pre-fill

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 14:03:48 +03:00
nrobi144andClaude Opus 4.6 27543ac304 feat(desktop): long-press details popups + right-click customize for note actions
- Long-press zap icon → floating popup with zap receipts (sender, amount, message)
- Long-press like icon → floating popup with reactions grouped by emoji
- Right-click like icon → emoji picker (DropdownMenu with 6 common emojis)
- Right-click repost icon → Repost/Quote options (DropdownMenu)
- Right-click zap icon → custom zap dialog (preserved existing behavior)
- Long-press reply → opens thread (same as click)
- ActivePopup sealed class ensures only one popup open at a time
- Popup + ElevatedCard for rich content, DropdownMenu for option lists
- combinedClickable with explicit ripple preserves IconButton UX
- PopupProperties(focusable = true) for desktop click-outside dismiss
- @Immutable on ZapReceipt for Compose stability
- Note param added to NoteActionsRow, passed from FeedScreen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 07:00:31 +03:00