Commit Graph

10845 Commits

Author SHA1 Message Date
Vitor Pamplona e1304ddcab Merge pull request #2147 from vitorpamplona/claude/marmot-protocol-integration-xb3TT
Add Marmot MLS group messaging support to Amethyst
2026-04-05 16:06:12 -04:00
Claude 8b56b3b3bf feat: Phase 6 — Marmot account integration & event routing
Wire Marmot MLS group messaging into the app's Account lifecycle,
event processing pipeline, and relay subscription system.

New files:
- AndroidMlsGroupStateStore: encrypted file-based MLS state storage
  using KeyStoreEncryption (AES/GCM backed by Android KeyStore)
- MarmotManager: central coordinator holding all Marmot components
  (MlsGroupManager, KeyPackageRotationManager, subscription/inbound/
  outbound processors, WelcomeSender)
- MarmotGroupEventsEoseManager: relay subscription manager for
  kind:445 GroupEvent filters via AccountFilterAssembler

Account integration:
- MarmotManager initialized during Account startup with restoreAll()
- Outbound methods: sendMarmotGroupMessage, addMarmotGroupMember,
  publishMarmotKeyPackage(s), createMarmotGroup
- GroupEvent (kind:445) and KeyPackageEvent (kind:30443) added to
  LocalCache.justConsumeInnerInner() dispatch

Event routing:
- GroupEventHandler: processes inbound kind:445 events through
  MarmotInboundProcessor, indexes decrypted inner events in LocalCache
- GiftWrapEventHandler extended: detects kind:444 WelcomeEvent after
  NIP-59 unwrap, routes to MarmotManager.processWelcome(), triggers
  KeyPackage rotation when needed
- WelcomeEvent enhanced with optional "h" tag for group ID routing

https://claude.ai/code/session_01W2LHazEt4E3W4hn8f7gWVW
2026-04-05 19:54:56 +00:00
Vitor Pamplona 3b695dc8b8 Merge pull request #2145 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-05 15:19:18 -04:00
Crowdin Bot a437d169c0 New Crowdin translations by GitHub Action 2026-04-05 18:48:38 +00:00
Vitor Pamplona 4e56f68a8f Merge pull request #2146 from vitorpamplona/claude/relay-subscriptions-messaging-VOv5P
Add Marmot inbound/outbound message processors and subscription manager
2026-04-05 14:47:19 -04:00
David Kaspar e586f4b179 Merge pull request #2144 from vitorpamplona/claude/find-missing-translations-HUpyP
feat: add missing translations for 5 keys across 4 locales
2026-04-05 19:39:05 +02:00
Claude ee8bc2bf62 feat: add missing translations for 5 keys across 4 locales
Add translations for call_bluetooth, closed_polls, feed_group_locations,
open_polls, and poll_view_results to cs-rCZ, pt-rBR, sv-rSE, and de-rDE.

https://claude.ai/code/session_01CMo4zT5ZKRt3J1yHgm23Aq
2026-04-05 17:36:48 +00:00
Claude ae0a0de9fd feat: Phase 5 — Marmot relay subscriptions & message processing pipeline
Add four protocol-layer components for Marmot group messaging:

1. MarmotSubscriptionManager: Coordinates relay subscriptions for
   GroupEvent (kind:445), GiftWrap (kind:1059), and KeyPackage
   (kind:30443) events. Tracks per-group since timestamps for
   pagination and syncs with MlsGroupManager state.

2. MarmotInboundProcessor: Processes incoming GroupEvents through
   outer ChaCha20-Poly1305 decryption → MLS decrypt → inner event
   extraction. Handles commit detection, conflict resolution via
   CommitOrdering, and Welcome processing with KeyPackage rotation.

3. MarmotOutboundProcessor: Builds outbound GroupEvents by MLS
   encrypting inner Nostr events, applying ChaCha20-Poly1305 outer
   layer, and signing with ephemeral keys for sender privacy.

4. MarmotWelcomeSender: Wraps MLS Welcome messages through the
   NIP-59 gift wrap pipeline for delivery to new group members.

All code in quartz/commonMain (protocol layer). Includes 25 tests
covering roundtrip encryption, subscription management, error
handling, ephemeral key usage, and Welcome wrapping.

https://claude.ai/code/session_01XC5umkmsFB7XQ7xdrouArt
2026-04-05 17:11:23 +00:00
Vitor Pamplona 9a7d7578ff Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-05 12:59:05 -04:00
Vitor Pamplona 67184fb72e Add a filter group for locations and moves global and around me to their correct sections 2026-04-05 12:50:52 -04:00
Vitor Pamplona 46f1ff7bd7 Merge pull request #2142 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-05 12:37:21 -04:00
Vitor Pamplona 78010cfd69 Merge pull request #2143 from vitorpamplona/claude/webrtc-nip-ac-tests-f1l1g
Add comprehensive test suite for NIP-AC call state machine
2026-04-05 12:29:30 -04:00
davotoula 9af678bd46 use toast instead of dialogue 2026-04-05 18:27:47 +02:00
davotoula f9c7e3b15c - Added @VisibleForTesting annotation to the three internal methods in ShareHelper.kt — signals test-only intent and triggers lint warnings if called from production code
- Removed redundant deleteOnExit() in test helper — explicit delete() after each test already handles cleanup; the shutdown hook was unnecessary overhead
2026-04-05 18:27:47 +02:00
Crowdin Bot 5648671906 New Crowdin translations by GitHub Action 2026-04-05 16:26:31 +00:00
Vitor Pamplona 6e1526e178 Merge pull request #2137 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-05 12:26:00 -04:00
Claude 1e105f5195 docs: align NIP-AC test vectors with actual test coverage
Add missing test vector entries for scenarios that have tests but were
not listed in the spec:

- E10-E18: single callee detection, P2P flow sequence, group p-tags
  for all event types, group member union, ICE serialization round-trip
- S22-S29: fresh events, wrong call-id handling, ICE forwarding,
  peer-left callback, reset, call-type preservation, caller cancel
- R7: renegotiation preserves call-id
- B1-B12: new ICE Candidate Buffering section covering both global
  and per-session buffer layers
- W1-W18: new Gift Wrap Round-Trip section covering NIP-44
  encrypt/decrypt for all event types including group calls
- I6, I10: renegotiation answer, renumbered full P2P flow

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 16:25:00 +00:00
Vitor Pamplona de73d54a08 Merge pull request #2140 from vitorpamplona/claude/asyncimage-size-check-IDdPa
Add thumbnail disk cache for profile pictures
2026-04-05 12:24:59 -04:00
Vitor Pamplona 19421db09a Merge pull request #2139 from vitorpamplona/claude/poll-view-only-results-7VBnX
Add poll results preview feature with persistent viewing state
2026-04-05 12:24:41 -04:00
Crowdin Bot 690f1a393d New Crowdin translations by GitHub Action 2026-04-05 16:20:30 +00:00
Vitor Pamplona ec5265cffd Merge pull request #2141 from vitorpamplona/claude/marmot-protocol-integration-JnU4w
Add MLS group state persistence and KeyPackage rotation management
2026-04-05 12:19:03 -04:00
Claude ec7b1e537f docs: remove Quartz-specific names from NIP-AC spec
Replace implementation-specific class names (EphemeralGiftWrapEvent,
SealedRumorEvent) with protocol-neutral language so the spec reads as
a standalone NIP independent of any particular codebase.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 16:18:48 +00:00
Claude 86d66673d8 style: rename test methods from snake_case to camelCase
https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 16:12:54 +00:00
Claude 40eb128cb8 feat: Phase 4 — MLS group state persistence & account integration
Add encrypted local storage for MLS group state so groups survive app
restarts, plus key lifecycle management:

- MlsGroupState: TLS-encoded serializable snapshot of complete group state
  (group context, ratchet tree, epoch secrets, private keys, transcript hash)
- MlsGroupStateStore: interface for encrypted per-group storage (platform
  implementations provide EncryptedSharedPreferences / encrypted file)
- MlsGroupManager: high-level coordinator managing group lifecycle, epoch
  secret retention window (N-1 for late messages), state persistence,
  and key export
- RetainedEpochSecrets: bounded window of past epoch decryption secrets
  for out-of-order message handling
- MlsGroup.saveState()/restore(): roundtrip serialization via TLS codec
- MlsGroup.proposeSigningKeyRotation(): Update proposal with fresh Ed25519
  signing key and X25519 encryption key for forward secrecy
- KeyPackageRotationManager: tracks consumed KeyPackages after Welcome
  processing, handles slot rotation and proactive age-based rotation
- Tests: 16 tests covering state serialization roundtrips, manager lifecycle,
  multi-group independence, and signing key rotation persistence

https://claude.ai/code/session_01MuRS2zSVm6A36HNFwG7M5p
2026-04-05 16:02:03 +00:00
Vitor Pamplona ddf500446c Merge pull request #2133 from davotoula/fix-image-sharing-error
Fix image sharing error
2026-04-05 11:42:41 -04:00
Claude 6fbbcbbf3b refactor: clean up PeerSessionManager and CallController integration
- Split PeerSession.kt out of PeerSessionManager.kt (types, interface,
  manager are now in separate files)
- Remove webRtcSessions duplication in CallController — PeerSessionManager
  is now the single source of truth for session tracking; WebRtcCallSession
  is retrieved via the adapter cast when WebRTC-specific APIs are needed
- Initialize PeerSessionManager eagerly with localPubKey (passed to
  CallController constructor) instead of lazy suspend init — fixes early
  ICE candidates being silently dropped before first suspend call
- Extract FakePeerSession into its own file for reuse across test files
- Remove assertion-only glare tiebreaker tests from NipACStateMachineTest
  (now properly tested with real logic in PeerSessionManagerTest)

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 15:38:28 +00:00
Claude c79ef634de fix: store expiration timestamps in viewedPollResultNoteIds to prevent unbounded growth
Changes viewedPollResultNoteIds from Set<String> to Map<String, Long> where
the value is the expiration timestamp. Uses the poll's endsAt date if set and
in the future, otherwise now + 24 hours. Expired entries are pruned on each
new insertion. Serialized as JSON instead of StringSet.

https://claude.ai/code/session_01EkUYT4giQPUvbAJZ54o1se
2026-04-05 15:38:08 +00:00
Claude 361ef6798e refactor: simplify thumbnail cache API and remove unused code
- ThumbnailDiskCache: combine get()+decodeThumbnail() into single load()
  method. Move bitmap resize logic into generateFromFile() so callers
  just pass a source file and the cache handles decode+resize+save.
- ProfilePictureFetcher: remove unused `options` field, remove @Stable
  annotation, delegate all resize logic to ThumbnailDiskCache.
- UserAvatar: fix stale doc comment.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 15:36:34 +00:00
davotoula fd9de754b1 Merge branch 'main' into fix-image-sharing-error
Resolve conflict in ZoomableContentView.kt: keep viewModelScope
from this branch (prevents cancellation on dialog dismiss) and
toast notification from main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 17:31:19 +02:00
Claude 0df4c5e19c refactor: use typed ProfilePictureUrl class instead of URI scheme string
Replace profilepic:// string scheme with a ProfilePictureUrl data class.
Coil routes to the fetcher by type via Fetcher.Factory<ProfilePictureUrl>
instead of parsing a URI scheme string on every load. This avoids string
concatenation at the call site and string parsing in the fetcher/keyer.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 15:22:38 +00:00
Claude e9f1fcbd2a refactor: extract PeerSessionManager for testable ICE buffering and glare handling
Extract the ICE candidate buffering, answer routing, and renegotiation
glare logic from CallController into a platform-independent
PeerSessionManager in commons/. This makes the three most critical
NIP-AC spec requirements testable as JVM unit tests with FakePeerSession:

1. Two-layer ICE candidate buffering (global + per-session)
   - Global buffer: candidates arriving before any PeerConnection exists
   - Per-session buffer: candidates arriving before setRemoteDescription
   - Candidates buffered while ringing are preserved when accepting

2. Renegotiation glare handling (pubkey comparison tiebreaker)
   - Higher pubkey wins, lower pubkey rolls back local offer
   - Full glare scenario test with two PeerSessionManagers

3. Callee-to-callee mesh initiation (lower pubkey initiates)

CallController now delegates to PeerSessionManager via a PeerSession
interface, with WebRtcPeerSessionAdapter bridging to WebRtcCallSession.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 15:17:29 +00:00
Claude 3f032710d2 perf: avoid byte array allocation on first profile picture load
On cache miss, instead of reading the entire downloaded image into a
byte array (which could be 50MB+ for a malicious profile picture),
the fetcher now passes the NetworkFetcher result straight through to
Coil's normal streaming decoder pipeline — zero overhead vs current.

Thumbnail generation runs in background from Coil's disk cache file
using file-based BitmapFactory.decodeFile(), which is seekable
(supports two-pass bounds+decode) and never buffers the full file.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 15:14:48 +00:00
Claude 6c9f6531cf fix: prevent race conditions in thumbnail disk cache
- Atomic writes: write to temp file then rename, so readers never see
  a partially written JPEG
- Deduplication: ConcurrentHashMap tracks in-flight URLs so multiple
  composables requesting the same profile picture don't trigger
  duplicate decode+save work

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 15:07:17 +00:00
Vitor Pamplona b6507561af Merge pull request #2135 from davotoula/video-share-download-toast
show toast while downloading video for sharing
2026-04-05 10:58:40 -04:00
Claude c2347c6cee test: add NIP-AC gift wrap round-trip tests with real crypto
Verify the full encrypt/decrypt pipeline for all 6 NIP-AC signaling event
types through Ephemeral Gift Wraps (kind 21059):

  sign inner event → NIP-44 encrypt → gift wrap → unwrap → verify

Tests cover:
- Each event kind round-trips (offer, answer, ICE, hangup, reject, renegotiate)
- Third parties cannot decrypt wraps addressed to others
- Group call per-peer wraps are only decryptable by intended recipient
- "Sign once, wrap per recipient" produces identical inner event IDs
- SDP and ICE candidate special characters survive JSON+NIP-44 round-trip
- Ephemeral wrap keys are unique per wrap and differ from sender
- Inner event signatures are verifiable after unwrapping
- Full P2P call flow (all 7 signaling steps) through gift wraps

Uses real secp256k1 keys and NIP-44 encryption — no mocks.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 14:34:19 +00:00
Claude 80ece9ac9d perf: run thumbnail generation in background on first load
On cache miss, the fetcher now returns the decoded bitmap immediately for
display while saving the thumbnail to disk in a background coroutine. This
means the first load is just as fast as the normal Coil pipeline — no
blocking on resize + JPEG compress + disk write. The thumbnail is ready
for the next load.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 13:58:06 +00:00
Claude fd42c7b725 refactor: use quartz event builders in CallManager test helpers
Replace manual tag assembly in test helpers (makeOffer, makeAnswer, etc.)
with the existing build() methods from quartz event classes. This keeps
test tag structures in sync with production code automatically.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 13:54:03 +00:00
Claude 9b356d31ba feat: add thumbnail disk cache for profile pictures
Profile pictures are displayed at small fixed sizes (18-100dp) but Coil's
disk cache stores full-size originals, causing expensive re-decode on every
memory cache miss. With 60 avatars on screen, this creates significant I/O.

Adds a ProfilePictureFetcher with a dedicated ThumbnailDiskCache that stores
pre-resized 256px JPEG thumbnails (~5-10KB each). On cache hit, reads a tiny
file instead of re-decoding the full original. The zoomable full-screen dialog
bypasses this cache and loads the original via the normal Coil pipeline.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 03:06:52 +00:00
Claude 3a78dd6afe feat: add "View results" option to polls that prevents voting after viewing
Adds a "View results" link below poll voting options. Once a user clicks it,
the poll results are shown and the user can no longer vote on that poll.
The viewed state is persisted in account settings via SharedPreferences.

https://claude.ai/code/session_01EkUYT4giQPUvbAJZ54o1se
2026-04-05 02:22:18 +00:00
Claude 8b944a0c68 feat: show toast while downloading video for sharing
Display a "Downloading video…" toast when the user taps share on a
remote video, so they know the download is in progress before the
share sheet appears. Uses the existing `downloading_video_for_sharing`
string resource that was already translated in multiple locales.

https://claude.ai/code/session_01B7HLXqbnicfj3eg1Wjgz6Z
2026-04-04 16:31:16 +00:00
Claude c88c961488 refactor: simplify scope usage and fix test temp file leak
- Remove unnecessary local alias; use accountViewModel.viewModelScope
  directly at call sites for clarity about scope ownership
- Add deleteOnExit() to test temp files so they're cleaned up even
  if assertions fail

https://claude.ai/code/session_01Euj5mXfjneNCx9m49YTp92
2026-04-04 15:56:21 +00:00
Claude 074cde0bd1 test: add media extension detection tests for ShareHelper
Make getImageExtension, getVideoExtension, and getMediaExtension
internal for testing. Add tests covering all supported image formats
(JPEG, PNG, GIF, WebP) and video formats (WebM, AVI, MP4, MOV),
plus edge cases (unknown formats, empty files, short headers).

https://claude.ai/code/session_01Euj5mXfjneNCx9m49YTp92
2026-04-04 15:52:21 +00:00
Claude 7a70c98158 fix: image sharing fails due to coroutine scope cancellation
The share media dialog used `rememberCoroutineScope()` which shadowed
the outer `accountViewModel.viewModelScope`. When onDismiss() was called
immediately after launching the share coroutine, the dialog-scoped
coroutine was cancelled, causing CancellationException to be caught
as a generic error and showing "Unable to share image" toast.

Fix: use viewModelScope for share operations so they survive dialog
dismissal, and re-throw CancellationException in shareImageFile.

https://claude.ai/code/session_01Euj5mXfjneNCx9m49YTp92
2026-04-04 15:43:10 +00:00
Claude 9854209843 test: add comprehensive NIP-AC WebRTC call state machine tests
Add 81 tests across two test suites covering the full NIP-AC spec:

- quartz/NipACStateMachineTest (31 tests): Protocol compliance test vectors
  for event structure, tags, P2P/group flows, ICE serialization, staleness,
  renegotiation glare rules, and multi-device support

- commons/CallManagerTest (50 tests): State machine integration tests using
  real NostrSignerInternal with actual crypto, covering:
  * Full call lifecycle (Idle → Offering/IncomingCall → Connecting → Connected → Ended → Idle)
  * Call rejection, busy auto-reject, hangup from any state
  * Self-event filtering (ICE, hangup, answer-elsewhere)
  * Mid-call renegotiation (voice ↔ video)
  * Group calls (mesh discovery, partial disconnect, invite peer)
  * Interface-level tests with real signing + gift wrapping pipeline
  * Full end-to-end P2P flow with two CallManager instances

Also adds test vector tables to NIP-AC.md spec for other implementers.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-04 15:26:20 +00:00
Vitor Pamplona 5349121443 Merge pull request #2131 from vitorpamplona/claude/review-webrtc-spec-4JDNH
NIP-AC: Comprehensive WebRTC call state machine and group call spec
2026-04-04 10:41:46 -04:00
Claude db658f8c96 docs: add group call interoperability details to NIP-AC spec
Add missing protocol details required for interoperable group call
implementations: call state machine, callee-to-callee full-mesh setup
with pubkey tiebreaker, two-layer ICE buffering, renegotiation glare
handling, busy rejection, self-event filtering, group answer broadcast,
and partial disconnect behavior.

https://claude.ai/code/session_01EjVAgjJUuv2NsuqzRiYN2F
2026-04-04 14:39:23 +00:00
Vitor Pamplona c3524d988f Merge pull request #2130 from vitorpamplona/claude/polls-feed-tabs-Cr3Ow
Add tabbed interface to Polls screen for open/closed polls
2026-04-04 09:52:06 -04:00
Vitor Pamplona 8ec6c5e13a Merge pull request #2129 from vitorpamplona/claude/fix-dark-mode-polls-92zS2
Set explicit surface color for Card in notification feed
2026-04-04 09:49:26 -04:00
Vitor Pamplona 19037e8f02 Merge pull request #2128 from vitorpamplona/claude/fix-poll-draft-editing-3j8cG
Add routing support for Poll and ZapPoll draft edits
2026-04-04 09:48:00 -04:00
Vitor Pamplona 53d4fb7910 Merge pull request #2127 from vitorpamplona/claude/faster-broadcast-tracker-disappear-Rm4p9
Improve broadcast completion detection and dismissal timing
2026-04-04 09:47:40 -04:00