Commit Graph

10859 Commits

Author SHA1 Message Date
Claude 7e32c314f4 fix: change poll re-evaluation ticker from 1 minute to 1 hour
https://claude.ai/code/session_01VY9FRuzHzwTVrePaJiHuHx
2026-04-06 12:11:55 +00:00
Claude 7d0a234fd8 fix: remove expired polls from notification cards via periodic re-evaluation
The open polls notification flow only re-evaluated when new notes arrived
or dismissed IDs changed. Polls that passed their close date remained
visible until something else triggered the flow. Adding a 1-minute ticker
to the combine ensures expired polls are filtered out promptly.

https://claude.ai/code/session_01VY9FRuzHzwTVrePaJiHuHx
2026-04-06 02:33:49 +00:00
Vitor Pamplona a1c7d956ff Merge pull request #2150 from vitorpamplona/claude/improve-mls-implementation-y0mzv
Add comprehensive MLS test suite and improve GroupInfo signing
2026-04-05 20:39:07 -04:00
Claude 8379892078 feat: improve MLS implementation with comprehensive tests, docs, and bug fix
Add three new test files for the Marmot MLS implementation:

- MlsGroupLifecycleTest: End-to-end lifecycle tests covering Welcome
  processing, cross-member encrypt/decrypt, multi-member groups, commit
  processing, external joins, PSK proposals, and ReInit proposals.

- MlsGroupEdgeCaseTest: Security boundary tests for wrong epoch rejection,
  corrupted ciphertext detection, invalid KeyPackage rejection, out-of-range
  leaf indices, empty/large message handling, and add/remove/add cycles.

- MlsConformanceTest: Cross-implementation comparison tests verifying
  KeyPackage structure, GroupInfo signatures, Welcome message format,
  HPKE seal/open, SignWithLabel/VerifyWithLabel, LeafNode signatures,
  deterministic key schedule, and commit structure conformance.

Fix GroupInfo signature bug (RFC 9420 Section 12.4.3.1):
- buildWelcome() and groupInfo() were signing only groupContext.toTlsBytes()
  but verifySignature() checked against encodeTbs() which includes
  GroupContext + extensions + confirmationTag + signer. Now both methods
  build an unsigned GroupInfo first and sign its full TBS encoding.

Enhance MlsGroupManager KDoc with usage examples, responsibility breakdown,
and cross-implementation notes.

8 tests are @Ignore'd documenting a known bug: processCommit() does not
derive the same epoch secrets as commit(), causing cross-member AEAD
failures after epoch transitions.

https://claude.ai/code/session_018f67fqNReg3dEXcDimYLY1
2026-04-05 23:39:06 +00:00
Vitor Pamplona 4435073234 Merge pull request #2149 from vitorpamplona/claude/group-chat-ui-viewmodel-5WKMV
Add Marmot MLS group messaging UI and integration
2026-04-05 18:43:38 -04:00
Claude 3e49e650c2 feat: read MIP-01 group metadata from MLS GroupContext extensions
Implement end-to-end reading of MarmotGroupData (extension 0xF2EE)
from the MLS GroupContext.extensions and sync to the UI layer.

Quartz (protocol):
- MarmotGroupData.decodeTls(): deserialize from TLS wire format
  (version, nostrGroupId, name, description, admin pubkeys, relays,
  image fields)
- MarmotGroupData.fromExtensions(): find and decode from extension list
- MlsGroup.extensions: expose groupContext.extensions publicly

Commons (shared logic):
- MarmotGroupChatroom: add description, adminPubkeys, relays fields
  as MutableStateFlow for reactive UI
- MarmotManager.groupMetadata(): extract MIP-01 data from group
- MarmotManager.syncMetadataTo(): sync metadata + member count to
  a MarmotGroupChatroom

Sync points (amethyst):
- Account init: sync metadata after restoreAll() for all restored groups
- GiftWrapEventHandler: sync after Welcome processing (group join)
- GroupEventHandler: sync after CommitProcessed (epoch advances may
  update extensions via GroupContextExtensions proposals)

UI (GroupInfoScreen):
- Display group description from MIP-01 metadata
- Display relay URLs from MIP-01 metadata
- Mark admin members with "- admin" suffix (from adminPubkeys)

https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
2026-04-05 22:10:33 +00:00
Claude 0748d2a998 feat: add user search with suggestions to AddMemberScreen
Replace the plain text input with the existing UserSuggestionState +
ShowUserSuggestionList pattern used throughout the app (NewGroupDM,
edit post, etc.).

The flow is now:
1. Type a name, npub, or NIP-05 in the search field
2. Debounced search queries local cache + relays + NIP-05 DNS
3. ShowUserSuggestionList displays matching users with profile
   pictures and display names
4. Tap a user to select them — shows their profile in a selected
   user row with picture, display name, truncated pubkey, and a
   Clear button
5. Tap "Add" to fetch their KeyPackage and complete the invite

https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
2026-04-05 21:51:47 +00:00
Claude 8c2e498a08 feat: complete AddMember KeyPackage fetch and resolve member display names
AddMember flow (was TODO, now functional):
- Account.fetchKeyPackageAndAddMember(): queries outbox relays for the
  target user's KeyPackageEvent (kind:30443) using client.fetchFirst(),
  decodes the base64 KeyPackage content, and calls addMarmotGroupMember()
  which publishes the commit then sends the Welcome gift wrap
- AddMemberScreen now calls the real implementation and navigates back
  on success

Member display names:
- MemberRow now uses LoadUser to resolve Nostr pubkeys to User profiles
  from LocalCache, showing bestDisplayName() instead of raw hex
- UserPicture composable shows profile pictures next to member names
- "(you)" indicator for the current user's entry

https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
2026-04-05 21:43:51 +00:00
Claude 3e14022e38 refactor: convert Marmot dialogs to full route screens
Replace AlertDialog-based CreateGroupDialog and AddMemberDialog with
proper full-screen routes that slide up from the bottom, following the
existing app patterns (ChannelMetadataEdit, NewGroupDM, etc.).

New screens:
- CreateGroupScreen: uses CreatingTopBar with close/create buttons,
  replaces CreateGroupDialog
- AddMemberScreen: uses ActionTopBar with close/add buttons,
  replaces AddMemberDialog

New routes:
- Route.CreateMarmotGroup → composableFromBottom
- Route.MarmotGroupAddMember(nostrGroupId) → composableFromBottomArgs

Updated call sites:
- MarmotGroupListScreen: FAB navigates to Route.CreateMarmotGroup
- MarmotGroupChatScreen: add-member icon navigates to
  Route.MarmotGroupAddMember
- MarmotGroupInfoScreen: add-member icon navigates to
  Route.MarmotGroupAddMember

Deleted old files:
- CreateGroupDialog.kt
- AddMemberDialog.kt

https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
2026-04-05 21:22:46 +00:00
Claude ddca55fae6 feat: add Marmot group info screen, member list, and leave group
Based on MIP spec review, add missing UI screens:

MarmotManager additions (commons):
- memberPubkeys(): extract Nostr pubkeys from MLS ratchet tree
  leaf nodes via Credential.Basic identity
- memberCount(): get group member count
- groupEpoch(): expose current MLS epoch
- GroupMemberInfo data class for leaf index + pubkey pairs

Account & AccountViewModel:
- leaveMarmotGroup(): publish SelfRemove proposal and clean up
- marmotGroupMembers(): expose member list to UI

MarmotGroupInfoScreen (new):
- Group metadata header (name, ID, epoch, member count)
- Member list with leaf indices, clickable to view profiles
- "Leave Group" with confirmation dialog
- "Add Member" access from group info

Navigation:
- Route.MarmotGroupInfo added and wired into nav graph
- Chat screen title now clickable to navigate to group info

https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
2026-04-05 21:09:25 +00:00
Vitor Pamplona 458443f156 Fixes typemismatch 2026-04-05 16:45:47 -04:00
Vitor Pamplona 4a416abdc9 Merge pull request #2148 from greenart7c3/claude/add-alt-tag-payment-eyb1R
Add ALT tag support to PaymentTargetsEvent
2026-04-05 16:39:59 -04:00
Claude 7ed036bbaa feat: Phase 7 — Marmot group chat UI & ViewModel layer
Add complete UI layer for Marmot Protocol MLS group messaging:

State models (commons/commonMain/):
- MarmotGroupChatroom: tracks decrypted inner messages per group
- MarmotGroupList: manages all group chatrooms with change notifications
- MarmotGroupFeedFilter: feed filter for group message feeds
- MarmotGroupFeedViewModel: ViewModel exposing group messages

Account integration:
- Add marmotGroupList to IAccount interface and implementations
- GroupEventHandler indexes inner events into MarmotGroupList
- AccountViewModel gains sendMarmotGroupMessage, createMarmotGroup,
  publishMarmotKeyPackage helper methods

Android screens (amethyst/):
- MarmotGroupListScreen: lists all groups with last message preview
- MarmotGroupChatScreen: group conversation with message composer
- MarmotGroupChatView: message feed + simple text composer
- CreateGroupDialog: create new MLS group with display name
- AddMemberDialog: add member by npub/hex (KeyPackage fetch TBD)

Navigation & integration:
- Route.MarmotGroupList and Route.MarmotGroupChat routes
- Wired into AppNavigation nav graph
- "MLS Groups" button added to ChannelFabColumn on Messages screen
- Marmot group newest messages appear in ChatroomListKnownFeedFilter

https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
2026-04-05 20:38:23 +00:00
Claude 628660f341 feat: add missing alt tag to PaymentTargetsEvent
Adds ALT constant, FIXED_D_TAG constant, and AltTag import to
PaymentTargetsEvent. The alt tag ("Payment targets") is now included
in both create() and updatePaymentTargets() methods.

https://claude.ai/code/session_013SQtN37Qemiu3vxjXZDbQj
2026-04-05 20:36:26 +00:00
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