Commit Graph

11591 Commits

Author SHA1 Message Date
Claude 900d953fb1 fix(live-chat): keep Top Zappers chips the same height when anonymous
The "Anonymous" chip rendered without a 24dp UserPicture, so its pill
shrank to the text's height and broke horizontal alignment with the
avatar chips in the row. Give the chip row a minimum height equal to
the avatar size so text-only variants stay visually aligned.

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-21 01:05:52 +00:00
Claude 4888d30f6c refactor(live-chat): lift NIP-53 live-stream state into commons
Address the architectural critique from self-review. The leaderboard
math and the shared system card are now platform-neutral primitives in
commons, the on-UI-thread aggregation is gone, and two latent
correctness bugs around subscription lifecycle and zap routing are
fixed.

- Introduce a pure LiveActivityTopZappersAggregator in commons that
  takes plain ZapContribution values and returns a sorted, deduped,
  anon-bucketed top-N list. Covered by a unit-test suite that is
  Compose/Android-independent.
- Introduce LiveStreamTopZappersViewModel in commons/commonMain. It
  owns two partitioned contribution maps (stream-scoped and
  goal-scoped), mutates them under a Mutex on the IO dispatcher in
  response to channel.changesFlow and Note.zaps.stateFlow emissions,
  and publishes the aggregator result via a StateFlow<List<TopZapperEntry>>.
  UI is now a dumb consumer with no aggregation logic left in the
  composable.
- Move StreamSystemCard to commons/commonMain/compose/ so Desktop can
  consume it alongside Android.
- Fix attachZapToLiveActivityChannel to run even when a zap receipt was
  already consumed by another subscription. Previously a zap first seen
  by notifications/profile would never reach the stream's channel
  cache; addNote is already idempotent so this is a safe retro-route.
- Fix ChannelFilterAssemblerSubscription to re-invalidate filters when
  a live-activity channel's metadata arrives. The 30311 stream event
  usually lands after the initial assembler run, so the goal-tag-driven
  subscription never fired. Now it re-runs as metadata resolves and the
  goal id is discovered.

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-21 00:20:01 +00:00
Claude e12b6b4172 feat(live-chat): surface stream clips in the profile gallery tab
Clips are authored by viewers but carry a `p` tag identifying the
stream host, so they naturally belong on the host's profile. Extend
the profile media subscription with `{kinds:[1313], #p:[pubkey]}`,
accept LiveActivitiesClipEvent in UserProfileGalleryFeedFilter when
the clip references a stream hosted by this user, and render the clip's
`r`-tag URL as a MediaUrlVideo tile in GalleryThumbnail.

Also plug LiveActivitiesClipEvent into NoteCompose via the existing
RenderChatClip so tapping a clip tile opens a working thread view
instead of an empty one.

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-20 23:33:55 +00:00
Claude 766274d0b2 refactor(live-chat): polish NIP-53 chat renderers from self-review
- Extract a shared StreamSystemCard composable for the rounded accent
  container used by zap, raid, and clip cards so they share one visual
  language.
- Unify clip caption rendering through CrossfadeToDisplayComment so
  captions get the same NIP-19 / emoji treatment as zap and raid
  messages.
- Iterate every `a` tag when routing zap receipts into live-activity
  channels so a receipt referencing multiple simulcasted streams lands
  in each of them, and fold the host match into the same pass.
- Bucket anonymous and private zaps under a single sentinel key in the
  Top Zappers aggregator so an "Anonymous" chip shows once with the
  combined total instead of one chip per one-time pubkey.
- Add commonTest coverage for LiveActivitiesRaidEvent marker parsing,
  LiveActivitiesClipEvent tag parsing, and LiveActivitiesEvent.goalEventId().

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-20 23:29:05 +00:00
Claude 003603fd81 feat(live-chat): top zappers leaderboard above live stream chat
Add a horizontally-scrollable strip of pill chips showing the top
zappers on a live stream, placed above the goal header. Each chip has
an avatar, lightning icon, and compact sat total (K/M), matching
zap.stream's TopZappers look. Tapping a chip opens the zapper's
profile.

Aggregates zaps from two sources and dedupes by receipt id so a zap
that tags both #a=<stream> and #e=<goalId> doesn't double-count:
  - stream-scoped host-directed zaps already routed into the
    LiveActivitiesChannel cache via the existing #a subscription
  - goal-scoped zaps reachable through goalNote.zaps when a NIP-75
    goal is attached

Sorts contributors by total sats desc, takes top 10, hides the row
when there are none.

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-20 23:09:10 +00:00
Claude 43435427b2 feat(live-chat): show NIP-75 zap goal at top of live stream chat
Add a goalEventId() accessor on LiveActivitiesEvent that reads the
zap.stream `["goal", "<hex>"]` tag. When a stream channel's 30311 event
references a goal, fetch the kind 9041 event and its zap receipts
(#e=<goalId>) so the existing goal-progress aggregation works.

Render a compact LiveStreamGoalHeader above the chat feed showing the
goal title, the existing GoalProgressBar, and a one-tap ZapReaction
that targets the goal event so viewers can contribute directly to the
fundraiser without leaving the stream.

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-20 22:10:54 +00:00
Claude 8368c9d661 feat(live-chat): display live stream clips (kind 1313) inline
Add a Quartz LiveActivitiesClipEvent for zap.stream's kind 1313 clip
convention, parsing the `a` tag (stream address), `p` tag (host), `r`
tag (video URL), and `title` tag. Register it in EventFactory, subscribe
in the live-activity chat filter, and route clips into the source
LiveActivitiesChannel cache. Render clips as an accent-colored card
showing "X created a clip" with the title and an inline video player
for the clip's playable URL.

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-20 21:53:02 +00:00
Claude 4786647762 feat(live-chat): display live stream raids (kind 1312) inline
Add a Quartz LiveActivitiesRaidEvent for zap.stream's kind 1312 raid
convention with root/mention a-tag markers. Wire it into the event
factory, subscribe to it in the live-activity chat filter, and route
received raids into both source and target LiveActivitiesChannel caches
so viewers on either side see the notification. Render raids as an
accent-colored pill showing "X is raiding Y" that navigates to the
target stream when tapped.

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-20 21:29:50 +00:00
Claude 238c9ea626 feat(live-chat): display zap receipts inline in live stream chat
Subscribe to kind 9735 alongside kind 1311 against the stream's #a tag,
route host-directed zaps into the LiveActivitiesChannel cache, and render
them as a centered, lightning-accented row with the zapper, amount, and
optional zap message. Matches zap.stream's chat behavior for
NIP-53 + NIP-57 interop.

https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi
2026-04-20 20:47:12 +00:00
Vitor Pamplona c9880a689d Merge pull request #2465 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-20 16:10:28 -04:00
Crowdin Bot 00ed676c6b New Crowdin translations by GitHub Action 2026-04-20 20:08:38 +00:00
Vitor Pamplona 1d91218455 Merge pull request #2463 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-20 16:07:38 -04:00
Vitor Pamplona 617fb38332 Merge pull request #2464 from vitorpamplona/claude/marmot-invite-event-N55Yh
Add daemon diagnostics and heartbeat logging to invite polling
2026-04-20 16:07:07 -04:00
Claude de78bceeae chore(marmot-interop): diagnostics for invite-delivery failures
Adds a dump_daemon_diagnostics helper (relays per type, wn debug health,
relay-control-state, pending invites, wnd stderr tail) and wires it into
Test 02 and Test 04 on both sides of the poll (pre-invite baseline +
post-timeout). wait_for_invite now prints a ~10s heartbeat with the
pending-invite count and the most recent welcome/giftwrap line from wnd
stderr. On timeout the script also prompts the operator to paste the
Amethyst-side MarmotDbg logcat so both ends of the gift-wrap pipe end up
in one log file.
2026-04-20 20:01:51 +00:00
Crowdin Bot da3d9cf0d7 New Crowdin translations by GitHub Action 2026-04-20 19:29:53 +00:00
Vitor Pamplona 6e61653bca Merge pull request #2462 from vitorpamplona/claude/marmot-interoperability-check-vR5ws
MIP-01/MIP-05 compliance: VarInt encoding, validation, and MDK interop
2026-04-20 15:28:33 -04:00
Vitor Pamplona a01cbbf2e1 Merge pull request #2461 from davotoula/claude/hls-resolution-selection-jzBTG
HLS video: Lowest resolution in feed/PiP, auto resolution in full screen
2026-04-20 15:19:30 -04:00
davotoula cb4a987498 refactor(video): apply code review fixes 2026-04-20 20:51:40 +02:00
davotoula 830e410a79 feat(video): default feed/PiP to lowest HLS resolution, fullscreen to auto 2026-04-20 20:51:14 +02:00
Claude 77c3634745 fix: tighten Marmot MIP-00/01/02/05 + RFC 9420 PublicMessage framing
Follow-up to the earlier MIP-spec alignment commit, addressing the
remaining audit items:

- MIP-00 (KeyPackageUtils.isValid): now performs every strict tag-level
  check that MDK does — d tag is exactly 64 hex chars, mls_protocol_version
  is exactly "1.0", mls_ciphersuite is exactly "0x0001", mls_extensions
  contains both 0xf2ee + 0x000a, mls_proposals contains 0x000a, encoding
  is "base64", content non-empty, i tag present. Adds
  `isCryptographicallyValid` for deep checks: i tag matches the computed
  KeyPackageRef (RFC 9420 §5.2), Basic credential identity equals the
  event's pubkey, KeyPackage signature verifies.

- MIP-01 (MarmotGroupData): encoder now omits the v3-only
  `disappearing_message_secs` field when version < 3, so v2 output
  matches MDK's `tls_codec` 0.4 byte-for-byte. Adds byte-level fixture
  tests pinned to the Rust reference (encode + decode in both directions).

- MIP-02 (WelcomeGiftWrap): replaces the redundant sign-then-rumorize
  step with `RumorAssembler.assembleRumor`, producing a true unsigned
  rumor as NIP-59 requires (no wasted secp256k1 signature).

- MIP-05 kind 449 (TokenRemovalEvent.build): no longer accepts a tag
  initializer. Per the MIP-05 spec the token-removal event MUST have
  empty tags; allowing arbitrary caller-supplied tags risks metadata
  leakage and rejection by strict validators (e.g. the MDK reference).

- RFC 9420 §6 PublicMessage framing: encoder/decoder now branch on
  `contentType`. Application messages keep the `opaque<V>` length prefix;
  Proposal/Commit bodies are emitted/parsed as their typed structs (no
  outer length prefix), per the MLS RFC. Previously encoder used
  `putBytes` raw and decoder used `readOpaqueVarInt`, so neither could
  roundtrip and Proposal/Commit PublicMessages from other MLS clients
  would mis-parse.

- KeyPackageUtilsTest fixtures: switch tiny "0"/"1"/"lr" slot ids to
  realistic 64-char hex slot ids, since `isValid` now enforces the
  MIP-00 d-tag format.
2026-04-20 18:02:12 +00:00
Vitor Pamplona c7b8d178f5 Merge pull request #2460 from vitorpamplona/claude/fix-empty-groups-display-MO6E9
Show placeholder notes for empty Marmot groups in chat list
2026-04-20 13:15:26 -04:00
Claude aea1dc53c3 fix: show empty Marmot groups in Messages screen
Previously ChatroomListKnownFeedFilter dropped any group whose
newestMessage was null, so groups the user just created and groups
received via Welcome events without any activity yet never appeared
in the Messages list.

Emit a stable placeholder Note (carrying the chatroom as a gatherer)
per empty group, route it through the existing Marmot row path, and
invalidate dmKnown on MarmotGroupList.groupListChanges so newly added
or promoted groups rebuild the feed.
2026-04-20 17:08:59 +00:00
Claude 1569b45671 fix: align Marmot implementation with MIP specs for interop with MDK
Brings Amethyst's Marmot code into wire-level interoperability with the
reference MDK (Rust) implementation by resolving four spec violations and
tightening the epoch lookback window:

- MIP-01 (NostrGroupData extension): switch to QUIC-style VarInt length
  prefixes (RFC 9000 §16) instead of fixed uint16. MIP-01 mandates this
  encoding (the one produced by the Rust `tls_codec` crate v0.4+), so
  Amethyst's previous uint16 framing could not round-trip with any MDK
  peer. admin_pubkeys, relays (outer + each inner), name, description,
  image_*, and disappearing_message_secs now all use VarInt prefixes.

- MIP-01: enforce "admin_pubkeys MUST NOT contain duplicates" in the
  MarmotGroupData constructor.

- MIP-05 (Push Notifications): token plaintext padded size was 220 (→ 280
  encrypted); spec MUSTs are exactly 1024 bytes plaintext (1084 encrypted).
  A server expecting MIP-05 tokens would reject Amethyst's frames outright.

- MIP-05: HKDF IKM was sha256(shared_point); spec requires the raw 32-byte
  ECDH x-coordinate. The extra sha256 produced a different PRK, so token
  ciphertext authenticated only within Amethyst. Removed the hash; ECDH
  already returns the x-only compact form via pubKeyTweakMulCompact.

- MIP-03: bump EPOCH_RETENTION_WINDOW from 2 to 5 to match MDK's
  DEFAULT_EPOCH_LOOKBACK, so late-arriving GroupEvents whose ChaCha20
  outer key was derived from a prior epoch's exporter secret can still
  be decrypted after a Commit advances the group.

Hand-crafted MarmotGroupData test blobs were updated to emit VarInt
length prefixes.
2026-04-20 17:06:51 +00:00
Vitor Pamplona d7eb11bb08 Merge pull request #2459 from vitorpamplona/claude/relay-icons-group-info-23CZ8
Track relay activity for Marmot group chats
2026-04-20 13:03:47 -04:00
Claude 42e226bf8d feat: show Marmot group relays as clickable icons with activity status
Replaces the comma-separated relay text on the Marmot Group Info
screen with a FlowRow of 55dp relay avatars that open RelayInfo on
tap and copy the URL on long-press, matching the relay-icon pattern
used elsewhere in the app.

To surface whether each configured relay is actually carrying traffic
for the group, MarmotGroupChatroom now tracks the most recent
kind:445 event timestamp observed from each delivering relay and the
info screen renders a green dot when a relay has delivered a group
event within the last 7 days (gray otherwise).
2026-04-20 16:59:19 +00:00
Vitor Pamplona f76e4f4c49 Merge pull request #2458 from vitorpamplona/claude/investigate-github-issue-k17Fx
Add emoji set support and shortcode validation to NIP-30
2026-04-20 12:36:18 -04:00
Claude 254f5bdcc2 NIP-30: use Address type for emoji-set reference instead of raw string
Makes the API safer: callers can't accidentally pass an unparsed or
malformed identifier. EmojiUrlTag.emojiSet is now Address?, serialized
via toValue() and parsed via Address.parse() (which rejects anything
that isn't a well-formed kind:pubkey:dTag).
2026-04-20 16:35:29 +00:00
Vitor Pamplona 99ce01af4e Merge pull request #2457 from vitorpamplona/claude/fix-members-tag-update-OKFvB
Refactor Marmot group members to use reactive state flow
2026-04-20 12:33:34 -04:00
Vitor Pamplona e0094f163b ⏺ fix: derive nostrGroupId from MLS GroupContext in Welcome processing
The "h" tag is optional in MIP-02 Welcome events — senders like
  whitenoise-rs omit it. Instead of failing when the h-tag is absent,
  derive nostrGroupId from the NostrGroupData extension embedded in the
  Welcome's GroupContext (the authoritative MLS source). An h-tag hint,
  if present, is still validated against the MLS-derived value.
2026-04-20 12:32:42 -04:00
Claude b6e31b21c8 refactor: make Marmot group members list reactive
Expose the member list as MutableStateFlow<List<GroupMemberInfo>> on
MarmotGroupChatroom, populated by MarmotManager.syncMetadataTo alongside
the existing memberCount. MarmotGroupInfoScreen and RemoveMemberScreen
now observe it via collectAsStateWithLifecycle, so both remote commits
(already routed through syncMetadataTo in DecryptAndIndexProcessor) and
local mutations refresh the UI without manual re-queries.

Account.addMarmotGroupMember and removeMarmotGroupMember now call
syncMetadataTo right after the MLS state is mutated, mirroring the
pattern already used by updateMarmotGroupMetadata, so the local change
is visible immediately without waiting for the relay round-trip.
2026-04-20 16:27:31 +00:00
Vitor Pamplona 9766cc5901 Fixes the group id parsing 2026-04-20 12:23:46 -04:00
Claude 93c2041f0f NIP-30: add optional emoji-set address and shortcode validation
Per NIP-30 the emoji tag is ["emoji", <shortcode>, <url>, <emoji-set-address>]
with the fourth field optional. EmojiUrlTag only exposed the first three.
Adds emojiSet as an optional field, preserves existing positional callers
via default null, and introduces isValidShortcode() so callers can validate
user input against the spec (alphanumeric, hyphens, underscores).
2026-04-20 16:21:30 +00:00
Vitor Pamplona 4ccabb01e6 Adding capabilities to Amethyst's groups 2026-04-20 12:14:35 -04:00
Vitor Pamplona 4f2f8e180e Generates keypackages with 64 chars 2026-04-20 12:00:31 -04:00
Claude 7e45042c27 fix: refresh Marmot group members list when returning to info screen
Replace LaunchedEffect with LifecycleResumeEffect so the members list
(and chatroom.memberCount) is re-queried every time MarmotGroupInfoScreen
resumes, not just when nostrGroupId changes. Previously, after adding a
member via the AddMemberScreen and popping back, the members count
displayed in the header and chat top bar stayed stale until the screen
was fully recreated.
2026-04-20 15:58:00 +00:00
Vitor Pamplona f71d8eebb8 fixes the script for keypackages not showing up in wn 2026-04-20 11:42:29 -04:00
Vitor Pamplona e41ad84096 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-20 11:22:50 -04:00
Vitor Pamplona 1c797107bc fixes script on macos 2026-04-20 11:20:38 -04:00
Vitor Pamplona ad624031bf Improves the FetchFirst to not fail in the first eose from the first relay. 2026-04-20 11:15:09 -04:00
Vitor Pamplona d7cc99b196 update dependencies 2026-04-20 10:51:40 -04:00
Vitor Pamplona 37af8b0c45 fixes deprecation on vico charts 2026-04-20 10:51:18 -04:00
Vitor Pamplona 036323fa53 This was added by the secp rewrite, but we moved them out to another repo, so now it can be removed. 2026-04-20 10:43:54 -04:00
Vitor Pamplona 57af16302e Merge pull request #2455 from vitorpamplona/claude/fix-scaffold-scrolling-NHdRa
Refactor DisappearingScaffold to use unified bar state management
2026-04-20 10:42:01 -04:00
Vitor Pamplona 3e488623d4 Merge pull request #2453 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-20 10:24:23 -04:00
Crowdin Bot e808d40834 New Crowdin translations by GitHub Action 2026-04-20 14:19:21 +00:00
Claude 7637596835 Merge remote-tracking branch 'origin/main' into claude/fix-scaffold-scrolling-NHdRa 2026-04-20 14:19:18 +00:00
Vitor Pamplona 5551f2f7d2 Merge pull request #2456 from vitorpamplona/claude/nip51-interest-sets-p0f6r
Add Interest Sets feature for organizing hashtags
2026-04-20 10:17:31 -04:00
Claude 4c82011a25 fix(interest-sets): consume kind 30015 locally + empty-state polish
- LocalCache.consume dispatcher now handles InterestSetEvent via
  consumeBaseReplaceable, so the event created by the user is stored
  and flows through newEventBundles → interestSets.newNotes → listFeedFlow
  refresh. Without this, the just-signed event sat in the sendMyPublicAndPrivateOutbox
  call but the UI never saw the update.
- List screen: icon + centered text for the empty state and dividers
  between rows.
2026-04-20 14:11:06 +00:00
Vitor Pamplona 282b3a436b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-20 09:53:18 -04:00
Vitor Pamplona 0e077c02d0 linting 2026-04-20 09:41:58 -04:00