Commit Graph
14003 Commits
Author SHA1 Message Date
Claude b17bb8339e feat(nip82): render bundled assets inside the release card
Replace the placeholder "N assets bundled" line with a real list of
compact rows for each `e`-tagged Software Asset in the release.

Each row loads the referenced asset event id through
`LoadAssetNote` (uses LocalCache first, falls back to
`checkGetOrCreateNote` for ids never seen) and then
`observeNoteEvent<SoftwareAssetEvent>` — which both observes the
LocalCache flow and registers the note with `EventFinder` so the
relay round-trips the missing asset event. When the asset arrives
the row recomposes with MIME, version, optional variant, size,
platform chips, and a Download link to the asset url.

The standalone `RenderSoftwareAsset` card (kind 3063 in a feed or
thread) is unchanged; this only fills out the release detail view.
2026-05-21 21:41:52 +00:00
Claude 9ac32a3a8e feat(nip82): software application visualization + apps feed
Wire NIP-82 Software Applications (kind 32267), Releases (kind 30063)
and Assets (kind 3063) into the Quartz event model and surface them
through a dedicated rendering path in Amethyst.

Quartz: extend the existing experimental NIP-82 builders with topic
(`t`) and NIP-34 app-link (`a`) helpers, and add a small detector
(`isNip82SoftwareRelease`/`asSoftwareRelease`) so kind 30063 events
can be disambiguated from NIP-51 ReleaseArtifactSetEvent at the
renderer layer. Pin behavior with unit tests covering build paths,
disambiguation, and the real-world Amethyst NIP-82 description event.

Amethyst: add modern card visualizations for each kind — application
header with icon/screenshots/platforms/topics/links, release header
with channel pill and bundled-asset count, and asset row with MIME,
size and platforms — and dispatch to them from both `NoteCompose`
and `NoteMaster` (`ThreadFeedView`).

A new "Apps" feed (left nav drawer) mirrors the Picture Feeds shape:
`SoftwareAppsFeedFilter` reads kind 32267 from `LocalCache`, a
`PerUserEoseManager`-backed subscription pulls applications and
releases from outbox relays, and a dedicated screen renders them in
a `LazyColumn` of `RenderSoftwareApplication` cards.
2026-05-21 21:10:18 +00:00
Vitor PamplonaandGitHub a42dee5e12 Merge pull request #3029 from davotoula/i18n-cs-de-pt-sv-and-correct-0-entries-for-english
Calendar Translations  (cs, de, pt-BR, sv) and skill update
2026-05-21 15:56:58 -04:00
Vitor PamplonaandGitHub 766b161363 Merge pull request #3030 from vitorpamplona/claude/intelligent-bohr-Lakt0
Enforce minimum on-chain zap amount and update default zap presets
2026-05-21 15:56:45 -04:00
Claude 9dcce46f67 feat(zaps): lower default zap amounts and add 1k-sat onchain floor
- Lightning quick-zap defaults: 100/500/1000 → 21/50/100 sats
- Onchain quick-zap default: 10k → 5k sats
- Block onchain zaps below 1000 sats in OnchainZapSendDialog so users
  aren't quietly creating transactions where miner fees dwarf the gift.
  The protocol dust threshold (330 sats) still applies underneath.
2026-05-21 19:43:08 +00:00
davotoula ec2f621772 i18n(sv): apply Swedish translation review
Native-speaker review surfaced consistency and wording fixes:

- Calendar collection naming: use "kalender" for action labels
  (New / Edit / Delete) matching the English source's user-facing
  simplification; use "kalenderlista" only in the empty-state body
  and route header (matches route_calendar_collections =
  "Kalenderlistor" and English's "Calendar lists"). Same hybrid as
  Czech, pt-BR, and German.
- Capitalize "Nostr" in calendar_share_nostr.
- "on-chain-backend" over "chain-backend" in wallet_onchain_no_backend
  (rest of batch already uses the on-chain- compound).
- "blockchainen" (definite form, matches existing ots_info_description)
  over bare "blockchain" in wallet_onchain_public_dialog_body.
- Tighten Lightning comparison: "större än för Lightning" over
  "större än Lightning" (the bare comparison was elliptical).
- wallet_onchain_public_dialog_body: clearer privacy wording
  ("fyll på från icke-privata konton och skicka tillbaka medel dit"
  over "fyll på och töm från och till"); make antecedent of "spendera"
  explicit ("spendera medlen" over ambiguous "spendera dem").
- Symmetric RSVP pair: "Kommer" / "Kanske" / "Kommer inte"
  (instead of mixed "Kommer" / "Kanske" / "Kan inte"). Matches the
  same fix applied for German and pt-BR.

Note: calendar_rsvp_section / calendar_rsvp_none already use the
established Swedish "OSA" acronym (matching kind_appt_rsvp =
"OSA för möte"), so no rename needed unlike de/pt-BR where the
section noun ("Zusagen"/"Confirmações") had to be replaced.
2026-05-21 09:52:51 +02:00
davotoula 44f8d1bfa8 i18n(de): apply German translation review
Native-speaker review surfaced consistency and wording fixes:

- Calendar collection naming: use "Kalender" for action labels
  (New / Edit / Delete) matching the English source's user-facing
  simplification; use "Kalenderliste" only in the empty-state body
  and route header (matches route_calendar_collections =
  "Kalenderlisten" and English's "Calendar lists"). Same hybrid as
  Czech and pt-BR.
- On-Chain hyphenation: "On-Chain-Backend" over "Chain-Backend"
  in wallet_onchain_no_backend; "Stattdessen On-Chain senden" over
  "Stattdessen on-chain senden" in send_onchain_instead. Matches the
  rest of the batch's German compound-noun convention.
- "Nowhere Event" over "Nowhere Veranstaltung" — aligns with the
  English source's all-English Nowhere brand prefixes (Drop / Forum /
  Petition were already English).
- "RSVPs" / "Antworten" over "Zusagen" for calendar_rsvp_section and
  calendar_rsvp_none — section also includes "Vielleicht" and "Komme
  nicht", so "Zusagen" was misleading. Aligns with the existing
  "RSVP für Termin" usage in kind_appt_rsvp.
- Symmetric RSVP pair: "Komme" / "Vielleicht" / "Komme nicht"
  (instead of mixed "Komme" / "Vielleicht" / "Kann nicht").
- wallet_onchain_public_dialog_body: clearer privacy wording
  ("lade auf und sende zurück" over "befülle und entleere von und
  zu"); make antecedent of "ausgeben" explicit ("die Mittel
  ausgeben" over ambiguous "es").
- contact_list_containing_label: rephrase to dodge German declension
  for n=1 ("Folgenliste (%1$d Nutzer):" over "Folgenliste mit %1$d
  Nutzern:" which is dative-plural-only).
2026-05-21 09:50:09 +02:00
davotoula db8791f5fc i18n(pt-BR): apply Brazilian Portuguese translation review
Native-speaker review surfaced consistency and wording fixes:

- Capitalize "Nostr" in calendar_share_nostr.
- "no blockchain" (masculine, matches ots_info_description) over
  "na blockchain" in wallet_onchain_public_dialog_body.
- Calendar collection naming: use "calendário" for action labels
  (New / Edit / Delete) matching the English source's user-facing
  simplification; use "lista de calendário" only in the empty-state
  body and route header (matches route_calendar_collections = "Listas
  de calendário" and English's "Calendar lists"). Same hybrid we
  resolved for Czech.
- Symmetric RSVP pair: "Vou" / "Talvez" / "Não vou" (instead of mixed
  "Vou" / "Talvez" / "Não posso").
- "RSVPs" over "Confirmações" for calendar_rsvp_section and
  calendar_rsvp_none — the section also includes "Talvez" / "Não vou"
  responses, so "Confirmações" was misleading. Aligns with the
  existing "RSVP do Compromisso" usage in kind_appt_rsvp.
- wallet_onchain_public_dialog_body: clearer privacy wording
  ("abasteça a partir de contas não privadas e envie os fundos de
  volta" over "abasteça e esvazie a partir de e para"); make antecedent
  of "gastar" explicit ("gastar os fundos" over ambiguous "-lo").
- calendar_empty_feed: reorder to drop trailing "ainda" — "Ainda não
  há eventos…" reads more naturally than "…ainda" at end.
- calendar_collection_delete_confirm_message: align tenses (future +
  future "será removido / não serão excluídos" over future + present).
- "backend on-chain" over "backend de chain" in wallet_onchain_no_backend.

Note: contact_list_* strings use the semantically-correct "seguidos"
(people you follow, per kind:3), even though existing
follow_list_selection / follow_sets use the inverted "seguidores".
Recommended follow-up: clean up legacy strings to match.
2026-05-21 09:45:09 +02:00
davotoula a877ef7ec7 i18n(cs): apply Czech translation review
Native-speaker review surfaced several consistency and wording fixes
in the Czech translations from this branch:

- Use "Kanál" for "feed" (matches existing feed/home_feed/global_feed).
- Use "sats" not "satoshi" (matches new_amount_in_sats / amount_in_sats).
- Match Lightning sibling string's "odstraníte" (vs "odeberete") on
  quick_zap_amounts_onchain_explainer.
- Capitalize "Nostr" in calendar_share_nostr.
- Calendar collection naming: use "kalendář" for actions (New / Edit /
  Delete) matching the English source's user-facing simplification;
  keep the disambiguating "seznamy" wording for the empty-state body
  and title (matches route_calendar_collections = "Seznamy kalendářů"
  and English's "No calendar collections yet").
- Rephrase contact_list_containing_label to dodge Czech count
  inflection ("Seznam sledování (%1$d):" rather than "obsahující %1$d
  uživatelů" which is only correct for n ≥ 5).
- Wallet privacy dialog: "vkládejte do peněženky a vybírejte z ní"
  (deposit/withdraw verbs) over "plňte a vybírejte"; "váš nsec" (masc.
  inanimate) over "vaše nsec"; "nesoukromých účtů" over "neprivátních".
- "on-chain backend" over "chain backend" in wallet_onchain_no_backend.
2026-05-21 09:19:12 +02:00
davotoula 2d3a2582ee docs(skill): catch dead quantity="zero" entries in find-missing-translations 2026-05-21 08:45:38 +02:00
davotoula 898dec73b9 i18n: special-case count=0 at call site for calendar day a11y label 2026-05-21 08:45:37 +02:00
davotoula 4d4d81edbd translate calendar + on-chain wallet strings to cs, de, pt-BR, svclose remaining translation gaps in cs, de, pt-BR, sv
Translate the plurals and strings missed by the first pass
2026-05-21 08:33:56 +02:00
Vitor PamplonaandGitHub 6cd4de51aa Merge pull request #3028 from vitorpamplona/claude/fix-send-split-mode-AE6gg
fix: keep Send button visible in onchain zap split mode
2026-05-20 20:37:47 -04:00
Claude 465f3687c1 fix: keep Send button visible in onchain zap split mode
The OnchainZapSendDialog promised a sticky bottom Send button, but the
inner scrollable Column had no weight and the outer Column did not fill
the sheet height. In split mode, SplitsRecipientSection grew the content
past the sheet viewport and pushed the Send button off-screen.
2026-05-21 00:02:38 +00:00
Vitor PamplonaandGitHub 6efee9f1f2 Merge pull request #3027 from vitorpamplona/claude/add-mention-notifications-PIhPf
feat(notifications): include public chat mentions in feed
2026-05-20 19:51:18 -04:00
Claude 39eedeeb17 feat(notifications): include public chat mentions in feed
Add ChannelMessageEvent.KIND (NIP-28 kind 42) to the Notifications feed's
allow-list so channel messages that p-tag the user surface in the in-app
Notifications screen, alongside text-note mentions and DMs.

The relay subscription already requests kind 42 with #p (see
FilterNotificationsToPubkey.NotificationsPerKeyKinds), and push notifications
already route ChannelMessageEvent through notifyMention. Only the in-app
feed filter was rejecting them at the kind check; the existing
tagsAnEventByUser gate handles the per-kind "is this for me" rule via the
BaseNoteEvent branch (reply-to-me, cited-author, citation-in-content).
2026-05-20 23:21:47 +00:00
Vitor Pamplona e0abc4a224 v1.11.0 v1.11.0 2026-05-20 18:59:05 -04:00
Vitor PamplonaandGitHub ee67ba6408 Merge pull request #3025 from vitorpamplona/claude/plan-onchain-zap-events-t1cPq
feat: tighten the zap UI
2026-05-20 18:28:50 -04:00
Vitor Pamplona 7415b1db39 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-05-20 18:27:15 -04:00
Vitor PamplonaandGitHub f19a47007e Merge pull request #3024 from vitorpamplona/claude/fix-illegal-argument-exception-H4omh
fix(ui): restore outlined visual on EditPostView + ForwardZapTo TextF…
2026-05-20 18:26:45 -04:00
Claude 55e536dfd0 feat: tighten the zap UI
ReactionsRow zap popup
- Merge the Lightning and on-chain chips into a single FlowRow so all
  amounts wrap together instead of stacking on two lines.
- Drop the on-chain row's own Tune (settings) button; the Lightning
  row's Tune is the single entry point to the settings screen.

UpdateZapAmountDialog
- Reorder sections: Quick Zap Amounts, Zap Privacy, Quick On-chain Zap
  Amounts, Nostr Wallet Connect — privacy now sits next to the
  Lightning amounts it actually applies to, and on-chain (which has no
  privacy concept) is grouped further down.

OnchainZapSendDialog
- Move the preset amount chips above the sats text field in
  AmountSection so the user sees the quick picks first and the
  free-form input second.

Defaults
- DefaultOnchainZapAmounts is now [10_000] (just one chip) so a fresh
  install / first-upgrade shows 4 chips total in the popup (3
  Lightning defaults + 1 on-chain default), not 6. kotlinx
  serialization defaults handle the back-compat for existing users
  who never set their on-chain choices explicitly.
2026-05-20 22:26:34 +00:00
Claude 05d958d182 fix(ui): restore outlined visual on EditPostView + ForwardZapTo TextFields
Migrating the mention TextFields to BasicTextField(state) routed them
through ThinPaddingTextField, which uses TextFieldDefaults.DecorationBox
(filled, surfaceVariant container). That replaced the original
OutlinedTextField look — visible rounded border + transparent inside —
with a filled gray rectangle. On ForwardZapTo the change was especially
visible since the original outlined border was the only visual cue that
the search row was an input.

Add OutlinedThinPaddingTextField, a sibling of ThinPaddingTextField that
wraps the same TextFieldState pipeline with
OutlinedTextFieldDefaults.DecorationBox + Container. Same thin padding,
same onTextChanged/inputTransformation/outputTransformation surface, but
genuinely outlined (notched label, transparent inside).

  - ForwardZapTo: switch to the new component, drop the
    focused/unfocusedIndicatorColor=Transparent overrides — defaults give
    the proper outlined border the original OutlinedTextField had.
  - EditPostView.MessageField: switch to the new component and use
    OutlinedTextFieldDefaults.colors(focusedBorderColor=Transparent,
    unfocusedBorderColor=Transparent) to keep the inner border invisible
    so the existing 1dp Modifier.border(surface, RoundedCornerShape(8.dp))
    remains the only visible frame, matching the original.
2026-05-20 22:21:20 +00:00
David KasparandGitHub a4310317a0 Merge pull request #3017 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-21 00:04:24 +02:00
Crowdin Bot 40eb0f0b61 New Crowdin translations by GitHub Action 2026-05-20 22:02:32 +00:00
davotoula b04cf3aedc i18n: convert calendar_reminder_settings_lead_choice to <plurals>
The lead-time chip label "%1$d min" pairs a count with a noun, which
inflects in Slavic / Baltic languages. Switch it from <string> to
<plurals> in the default locale and in the three locales that already
have a translation (pl-rPL, zh-rCN, hu-rHU), and update the single
caller in CalendarReminderSettingsScreen to use pluralStringResource.

The abbreviation "min" / "min." / "perc" / "分钟" doesn't decline with
the count in any of these specific languages, but the resource shape
is now correct for future locales that do (e.g. Russian, Ukrainian).
2026-05-20 23:59:28 +02:00
Vitor Pamplona 924101c3f4 Fixes follow list rendering in the top note 2026-05-20 17:56:54 -04:00
Vitor PamplonaandGitHub 0cc435aa02 Merge pull request #3023 from vitorpamplona/claude/add-notecompose-contactlist-sFMff
Add Contact List display and navigation screen
2026-05-20 17:49:40 -04:00
Vitor PamplonaandGitHub 4fba293d60 Merge pull request #3020 from davotoula/feat/calendar-plurals-i18n
i18n: convert calendar count + reminder strings to <plurals>
2026-05-20 17:40:21 -04:00
Claude a1cb5cb50d feat: add preview for DisplayContactList
Mocks a kind-3 ContactListEvent with nine p-tags and renders
DisplayContactList in a ThemeComparisonColumn so the card can be
inspected in the IDE preview pane.
2026-05-20 21:40:02 +00:00
Vitor PamplonaandGitHub 15474cfff4 Merge pull request #3022 from vitorpamplona/claude/fix-illegal-argument-exception-H4omh
Migrate text input to new Compose TextFieldState API
2026-05-20 17:39:06 -04:00
Vitor PamplonaandGitHub 8bf1040f18 Merge pull request #3021 from vitorpamplona/claude/add-public-chip-confirmation-2Q7sh
Add warning dialog for copying public onchain addresses
2026-05-20 17:38:45 -04:00
davotoulaandClaude Opus 4.7 63e6283354 docs(claude): add res/CLAUDE.md note on <plurals> for Slavic locales
Captures the rule we just hit twice in a row (hashtag-limit + calendar
count): any count-bearing string must be <plurals>, every locale must
include the CLDR categories it actually uses, and don't hardcode "1" in
quantity="one" items. Loads automatically whenever a file under res/ is
edited.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:32:24 +02:00
Claude d7a1b631b2 feat: confirm onchain copy with public-address warning
The Taproot address is derived from the Nostr pubkey, so copying it out
is the moment a user is about to share an account-linked, publicly
auditable wallet. Gate the Copy button on the Onchain card with the same
explanation shown by the Public chip, and offer a "Don't show again"
opt-out persisted in the existing UI preferences DataStore.
2026-05-20 21:26:02 +00:00
davotoulaandClaude Opus 4.7 c7226d3293 i18n: convert calendar count + reminder strings to <plurals>
The NIP-52 calendar feature shipped two count-bearing strings as <string>
resources ("%1$d events" and "Starts in %1$d minutes"), which forces the
wrong noun form in Slavic languages where the declension depends on the
threshold integer. Convert both to <plurals> in the default locale and in
zh-rCN/pl-rPL/hu-rHU (the locales that already have translations), and
update the 4 callsites — 3 Composables use pluralStringResource and the
CalendarReminderWorker uses the pluralStringRes helper.

pl-rPL and hu-rHU keep only the "other" quantity, preserving existing text
without regression; proper CLDR fan-out (one/few/many for Polish, one for
Hungarian) will come from Crowdin or a native translator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:25:14 +02:00
Claude 6ecedb72f5 feat: render ContactListEvent in NoteCompose with tap-through user list
Adds a NoteCompose render for kind-3 ContactListEvent showing a label with
follow count and a row of the first 6 user avatars (with a "+N" overflow
chip). Tapping the row opens a new ContactListUsersScreen that lists every
followed user with the same UserCompose row used on the search screen.
2026-05-20 21:18:14 +00:00
Vitor PamplonaandGitHub 995ec3dab2 Merge pull request #3018 from vitorpamplona/claude/plan-onchain-zap-events-t1cPq
Add on-chain split zap support with per-recipient distribution
2026-05-20 16:54:26 -04:00
Vitor PamplonaandGitHub b7e3ba27ac Merge pull request #3019 from vitorpamplona/claude/debug-metadata-relay-loading-opoto
Add fallback relay logic for abandoned users in metadata queries
2026-05-20 16:52:26 -04:00
Claude f6db678249 fix: audit follow-ups (fee retry, perf, self-pay gate)
From an independent audit + my own pass, addressing concrete issues:

OnchainZapSendDialog
- Fee estimate fetch now retries with bounded backoff (4 tries, 1/2/3s
  spacing) instead of giving up after one attempt. Covers two real
  boot races: LocalCache.onchainBackend not yet wired at first
  composition, and a flaky feeEstimates() call. Without retry the
  Send button stayed permanently disabled.
- SplitsRecipientSection now indexes preview shares by pubkey once
  via remember(previewShares) { associateBy { ... } } instead of an
  O(N²) firstOrNull lookup per split row.
- belowDustShares is now wrapped in remember(previewShares) so it
  doesn't re-filter the list on every recomposition.
- canSend now also requires resolvedRecipient != senderPubKey in
  single-recipient mode, so the user can't tap Send when the only
  fallback recipient is themselves (would fail at the builder's
  "cannot zap yourself" check).
- formatWeight no longer prints "50.0%" for whole-percent shares —
  trailing ".0" is stripped (was a Double->String artifact).

OnchainZapSplitter
- Added distributeUnchecked(): same allocation as distribute() but
  never throws on dust; returns every share so the UI preview can
  render the full shape in one pass. distribute() (used by the
  build/send path) still throws via DustRecipientException so the
  real send keeps its dust gate.
- Added check(remainder < splits.size) before the remainder loop to
  pin the invariant that bounds remainder.toInt() and the k % size
  defensive mod.
- Test for distributeUnchecked.

ReactionsRow / ReusableZapButton / ZapCustomDialog
- baseNote.toEventHint<Event>() is now wrapped in remember(baseNote)
  in all three dialog launchers so it's not allocated on every
  parent recomposition.
2026-05-20 20:49:33 +00:00
Claude b2959dcb75 refactor: migrate UrlUserTagTransformation to UrlUserTagOutputTransformation
The legacy VisualTransformation paired with TextField(value, onValueChange)
goes through Compose Foundation's LegacyCursorAnchorInfoBuilder, which
throws "endOffset must be greater than startOffset" when the IME has
requested CURSOR_UPDATE_MONITOR and the field's visible bounds momentarily
collapse to zero width. The OutputTransformation API paired with
BasicTextField(state: TextFieldState) routes through a different,
non-legacy cursor anchor info controller and does not hit that bug.

Migrate the two remaining VisualTransformation call sites
(EditPostView.MessageField and ForwardZapTo) to the OutputTransformation
+ TextFieldState path. This requires:

  - IZapField: forwardZapToEditting becomes TextFieldState; the
    TextFieldValue-shaped updateZapForwardTo() callback is replaced by
    onForwardZapTextChanged() which reads the current state.
  - 8 ViewModels (ChannelNewMessage, ChatNewMessage, NestNewMessage,
    NewProduct, LongFormPost, NewPublicMessage, ShortNotePost,
    CommentPost): clear via clearText() instead of reassigning a new
    TextFieldValue; rewrite onForwardZapTextChanged() to read from
    forwardZapToEditting directly.
  - ForwardZapTo: switch from OutlinedTextField to ThinPaddingTextField
    with MentionPreservingInputTransformation +
    UrlUserTagOutputTransformation.
  - EditPostViewModel: message becomes TextFieldState; updateMessage()
    becomes onMessageChanged() (the state updates itself); the in-place
    replaceCurrentWord/insertUrlAtCursor TextFieldState overloads
    replace the value-based ones.
  - EditPostView: MessageField now uses ThinPaddingTextField with the
    OutputTransformation. Also drops a stale commented-out subject row
    that still referenced the legacy transformation.
  - Delete UrlUserTagTransformation and its androidTest (no callers
    remain).
  - Remove MainThreadCrashGuard and its hook in Amethyst.onCreate: the
    legacy cursor-anchor-info crash surface is gone with the remaining
    mention TextFields, so the framework workaround is no longer
    earning its keep.
2026-05-20 20:46:09 +00:00
Claude e6b6269314 feat: include indexer and proxy relays in trusted relay union 2026-05-20 20:41:33 +00:00
Vitor Pamplona 9d0397080f Adds Payment target to the usual user download 2026-05-20 16:39:05 -04:00
Claude 85dfe93ea7 feat: on-chain handoff from the custom-zap dialog
Adds a "Send on-chain instead" link button at the bottom of
ZapCustomDialog. Tapping it opens OnchainZapSendDialog with the entered
amount and message prefilled, the note as zappedEvent, and the same
split-detection / dust-preview / fee-tier picker that the Reactions
zap chip uses.

This also fixes the participant zap path in nests audio rooms
(ParticipantHostActionsSheet long-press → "Zap") since that menu item
opens ZapCustomDialog under the hood — fixing the custom dialog covers
both entry points transitively.

OnchainZapSendDialog gains a `prefillComment: String = ""` parameter so
the message field carries over from the LN dialog.

Poll-note zaps (FilteredZapAmountChoicePopup) intentionally stay
Lightning-only — the poll_option vote is encoded in the kind:9734 zap
request and counted via kind:9735 receipts; kind:8333 on-chain
receipts have no poll_option analog and the count infrastructure
doesn't index them. This is a protocol design constraint, not a UI
oversight.

Coverage audit summary — every user-facing zap initiation point now
has on-chain support except the poll-voting path:
- ZapReaction (reactions row) 
- ZapAmountChoicePopup (popup chips) 
- ZapCustomDialog (custom amount + message)  (this commit)
- ReusableZapButton (Zap the Devs + DVM buttons) 
- NestActionBar (audio room) 
- ParticipantHostActionsSheet (audio room participant)  (transitive)
- ChatMessageCompose / live-activity headers (use ZapReaction) 
- FilteredZapAmountChoicePopup (poll votes) ⚠️ LN-only by protocol
2026-05-20 20:35:01 +00:00
Claude a90dd47ed4 feat: on-chain option on the Zap the Devs button
ReusableZapButton now passes the user's on-chain zap amount choices to
ZapAmountChoicePopup and renders OnchainZapSendDialog when a chip is
tapped. The dialog receives the release note as the zappedEvent, so
the existing split detection picks up the kind:1 release notes' zap
splits and pays the dev team via one Bitcoin tx with N receipts.

The donation card is the canonical multi-recipient on-chain zap flow —
release notes are tagged with weighted splits across the team, the
sender's own pubkey gets filtered out, and the per-recipient share
preview shows live as the amount is typed.

Other callers of ReusableZapButton (DVM zap buttons, etc.) get the
on-chain row automatically since it's driven by the user's settings;
empty on-chain amounts list hides the row, matching prior behavior.
2026-05-20 20:20:21 +00:00
Claude dfd06e0dfd fix: skip offline relays when fetching user metadata 2026-05-20 20:18:56 +00:00
Claude 45aa6044b7 fix: on-chain zap splits — drop sender from splits, merge duplicates, gate Send on dust
Audit findings from an independent code review:

- HIGH: When the user zaps their own post (a common flow), every split
  that included the post author put the sender on the recipient list,
  and OnchainZapBuilder.buildSplit refused the whole tx with "cannot
  zap yourself". Fix: new OnchainZapSplitter.prepare() filters the
  sender's pubkey out of the splits before they reach the builder.
- HIGH: NIP-57 lets the same pubkey appear in zap-split tags more than
  once (additive weights). buildSplit rejected duplicate recipients.
  Same prepare() helper merges duplicates by summing weights, in
  first-seen order.
- HIGH: The dialog's live preview only showed amounts for recipients
  whose share was BELOW dust (because DustRecipientException only
  carries belowDust). Fix: parent composable computes shares with a
  zero dust threshold for the preview, gating the Send button on a
  separate belowDustShares check so the user can see all amounts and
  can't tap Send into a guaranteed BUILDING-stage failure.
- MEDIUM: OnchainZapSendResult.Failure didn't carry the ids of
  receipts that successfully published before a partial-publish
  failure. Added publishedReceiptEventIds: List<HexKey>.
- LOW: useSplits state was keyed by zappedEvent reference; re-emitted
  bundles would silently reset the toggle. Now keyed on the event id.

Tests added:
- splitter: prepare() drops sender, merges duplicates, filters
  non-positive weights; floating-point weights (0.1 + 0.2) sum exactly
- builder: buildSplit produces N recipient outputs + 1 change at index
  N, conserves sats, rejects duplicates and below-dust shares
- sender: sendSplit publishes one receipt per recipient sharing the
  txid with correct per-recipient amount; partial-publish failure
  carries the broadcast txid and the ids of receipts that did publish
2026-05-20 20:14:44 +00:00
Claude 13a599e282 fix: keep listening on default index+search relays for users with no kind 10002
When pickRelaysToLoadUsers exhausts every candidate tier (outbox / hints /
index / search / connected / common) for a user without finding kind 10002,
it returns an empty map and the REQ closes. A late-published relay list
from that user would never reach the UI for the rest of the session
(unless EOSEAccountFast's LRU evicts the entry).

Compute the set of users we couldn't place this pass and add a permanent
fallback filter on DefaultIndexerRelayList + DefaultSearchRelayList. The
filter is deterministic (sorted authors, set-based relays) so the relay
client de-duplicates it across passes, keeping the REQ open without
re-sending — any future kind 0 / kind 10002 publication will be pushed
to us live.

Offline relays are subtracted via failureTracker.cannotConnectRelays.
2026-05-20 19:57:38 +00:00
Claude 3ed2245d8c feat: on-chain zap splits
Extends NIP-BC onchain zaps to honor a note's NIP-57 zap-split tags: one
Bitcoin transaction pays every pubkey-based recipient atomically, and
one kind:8333 receipt is published per recipient (each receipt carries
the recipient's pubkey + sat share and shares the same i:<txid>).

quartz / OnchainZapBuilder
- new buildSplit(recipients = listOf(pubkey to sats), ...) produces a
  PSBT with one output per recipient + optional change output
- existing build(...) now delegates to buildSplit; coin selection and
  change-vs-dust logic are unchanged for the single-recipient path

commons / new OnchainZapSplitter
- distribute(totalSats, splits, dustThreshold) does the weighted
  integer-math allocation, dropping the rounding remainder onto the
  largest-weight recipient first so the per-recipient sats sum exactly
  to totalSats
- throws DustRecipientException if any share lands below dust; the
  caller surfaces that as a build-stage failure before the tx is built
- unit tests cover equal weights, fractional weights, remainder
  distribution, dust rejection, and input-order preservation

commons / OnchainZapSender.sendSplit
- mirrors send() but takes the precomputed shares, builds via
  buildSplit, and publishes N receipts using the same txid; if one
  receipt publish fails the broadcast txid + already-published receipt
  ids are surfaced in the Failure result

amethyst / Account.sendOnchainZapWithSplits
- thin wrapper that hands off to OnchainZapSender.sendSplit using the
  signer's pubkey

amethyst / OnchainZapSendDialog
- detects pubkey-based zap splits on the zappedEvent and, when present,
  defaults to split mode: a SplitsRecipientSection renders one row per
  recipient with weight % and live per-recipient sats preview
- lnAddress-only splits are filtered out (no pubkey -> no Taproot
  address); a short note tells the user how many recipients were
  skipped
- the send button label switches to "Send X sats, N ways"; an opt-out
  button lets the user fall back to single-recipient mode
- on send: shares are recomputed via OnchainZapSplitter; below-dust
  configurations surface as a BUILDING-stage failure before signing
2026-05-20 19:55:15 +00:00
Vitor PamplonaandGitHub c19f0be57e Merge pull request #3013 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-20 15:53:53 -04:00
Vitor PamplonaandGitHub 96883dbec1 Merge pull request #3015 from quentintaranpino/add-nostrcheck-media-server
Re-add Nostrcheck.me Blossom server to defaults
2026-05-20 15:53:42 -04:00
Claude b0b0a13650 fix: swallow LegacyCursorAnchorInfo IllegalArgumentException
Compose Foundation's LegacyCursorAnchorInfoBuilder.addCharacterBounds
computes a TextRange(startOffset, endOffset) over the visible portion of
a TextField when the IME has requested CURSOR_UPDATE_MONITOR. During a
layout transition (sheet animation, keyboard insets settling, etc.) the
visible region can momentarily collapse so startOffset == endOffset, and
MultiParagraph.fillBoundingBoxes throws "endOffset must be greater than
startOffset". This is a framework bug we can't fix from app code, and
it crashes the whole process from an onGloballyPositioned callback
during dispatchDraw.

Install a main-thread crash guard at app startup that wraps Looper.loop()
and swallows only this very specific signature (IllegalArgumentException
with the exact message AND a stack frame in LegacyCursorAnchorInfo* /
MultiParagraph* / AndroidParagraph / TextLayout). Anything else
re-throws into the default uncaught handler so UnexpectedCrashSaver
still records real bugs. The IME just misses a cursor update on the
swallowed frame — no user-visible impact.
2026-05-20 19:36:33 +00:00