Remove architecture diagram, NIP support table, module table,
Live Subscriptions explanation, serve method details, production-ready
example, and excessive code comments. Keep the readable Quick Start,
Store options, and Policy documentation in a more concise form.
https://claude.ai/code/session_01U3iW3eRD7bfLwrM3L9gkDc
- NostrServer and IEventStore implement AutoCloseable for .use {} support
- Add NostrServer.serve() to handle session lifecycle automatically
- IRelayPolicy + operator now returns PolicyStack instead of List
- Deprecate shutdown() in favor of close()
- Update RELAY.md guide to use simplified API patterns
https://claude.ai/code/session_013oL9PkQaFyNQHKVg2vw9qs
Companion to the existing RELAY.md, this guide covers the client side:
Ktor WebSocket transport implementation, subscribing to events (Flow
and callback APIs), publishing signed events, filters, key management,
and multi-relay patterns.
https://claude.ai/code/session_01KFos33kA9VoMhKdBPNLquF
- Profile feed now includes reposts (kind 6/16), not just text notes
- Metadata consume returns false to avoid wasteful null note lookups
- Feed subscription limits raised from 50 to 200 for global/following
- Thread replies subscription fetches NIP-22 comments (kind 1111)
- Cache now handles CommentEvent for thread display
- Wire onNavigateToThread through UserProfileScreen so tapping notes opens threads
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes#2001 — ThreadFilter test failed intermittently because
consumeTextNote used getNoteIfExists for reply-to linking. If the
reply event arrived before the root, the link was lost. Now uses
getOrCreateNote to create placeholders, same pattern as reposts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Following-mode long-form feed was not calling consumeEvent(),
so LongTextNoteEvents never reached the cache. Back-navigation
showed empty reads because cache had nothing to seed from.
Fix PlatformLinkTag.parse() off-by-one bug where tag indices were shifted
(platform/uri/entityType read from wrong positions). Add missing TagArrayExt
and EventExt files for definition and recommendation packages to align with
the modern pattern used by nip88Polls. Parameterize DiscoverNIP89FeedFilter
by targetKind and create NIP-89 poll app discovery infrastructure linking
NIP-88 polls (kind 1068) to the NIP-89 app handler discovery system.
https://claude.ai/code/session_013r2LXj11SieWa5PaLesKfC
Each request event now exposes typed reader methods so DVM servers can
extract inputs, parameters, and configuration from incoming requests:
- inputs() returns List<InputTag> with value, type, relay, marker
- Kind-specific readers like language(), model(), searchQuery(), pow(), etc.
- Shared TagArrayExt with dvmParam(), dvmParamAll(), dvmParamValues()
Also adds readers to existing 5300/5301 events (dvmPubKey, user, relays).
https://claude.ai/code/session_017aamEDeRtQ2H9u5U9B5F1C
Cross-referenced the official NIP list from nostr-protocol/nips with
quartz package implementations. All NIPs are now supported except NIP-EE
(MLS Protocol). Added NIP-22, NIP-5A, NIP-B0. Removed merged NIPs
(NIP-12, NIP-16, NIP-20). Updated names to match official titles.
https://claude.ai/code/session_01TyGpa2VEryZ9aqmZvmE4v1
Add request/response event classes for all 19 DVM kinds defined in
nostr-protocol/data-vending-machines: text extraction (5000/6000),
summarization (5001/6001), translation (5002/6002), text generation
(5050/6050), image generation (5100/6100), video conversion (5200/6200),
video translation (5201/6201), image-to-video (5202/6202), text-to-speech
(5250/6250), content search (5302/6302), people search (5303/6303),
event count (5400/6400), malware scanning (5500/6500), event timestamping
(5900/6900), OP_RETURN creation (5901/6901), event publish schedule
(5905/6905), and event PoW delegation (5970/6970).
Also fixes existing events:
- Content discovery response (6300) now parses both "a" and "e" tags
- User discovery response (6301) now has innerTags() to parse "p" tags
All new events registered in EventFactory.
https://claude.ai/code/session_017aamEDeRtQ2H9u5U9B5F1C
Each event kind now gets its own subpackage with dedicated tag classes,
TagArrayExt for parsing, and TagArrayBuilderExt for building. Events use
the eventTemplate pattern instead of manual tag construction.
New structure:
- status/ (kind 7000) with StatusTag, AmountTag
- contentDiscoveryRequest/ (kind 5300) with RelaysTag, ParamTag
- contentDiscoveryResponse/ (kind 6300)
- userDiscoveryRequest/ (kind 5301)
- userDiscoveryResponse/ (kind 6301)
https://claude.ai/code/session_01JeB9nAK4SKuwKZBEd6EAPd
Before (4 steps, manual wiring):
val transport = AndroidBleTransport(context)
val mesh = BleMeshManager(transport, object : BleMeshListener { ... })
transport.setListener(mesh)
mesh.start()
After (2 steps, auto-wired):
val mesh = BleNostrMesh(AndroidBleTransport(context))
mesh.onEvent { event, peer -> saveEvent(event) }
mesh.start()
The facade uses lambda callbacks instead of interface implementation,
and auto-wires the transport listener via AndroidBleTransportContract.
BleMeshManager remains available for advanced use cases.
https://claude.ai/code/session_01Tz5E73Rj7tL48A3qUGS5DT
Reviewed the samiz BLE implementation and fixed compatibility issues:
- Chunk index: changed from 2 bytes to 1 byte (matching samiz/NIP-BE example)
- Chunk overhead: 2 bytes total (1 index + 1 total count), not 3
- chunkSize parameter now means payload size (500), not total chunk size
- Android: TX power HIGH, advertise timeout indefinite (matching samiz)
- Android: request MTU 512 and CONNECTION_PRIORITY_HIGH on connect
- Android: discover services after MTU negotiation (samiz flow)
- Android: set WRITE_TYPE_DEFAULT on write characteristic
- MTU-to-chunkSize: properly accounts for ATT overhead (3) + chunk overhead (2)
These changes ensure wire-level compatibility with samiz devices.
https://claude.ai/code/session_01Tz5E73Rj7tL48A3qUGS5DT
Adds full NIP-BE implementation for BLE-based Nostr peer-to-peer
messaging and synchronization with KMP support across all targets.
Protocol layer (commonMain):
- BleConfig: NIP-BE constants (service UUID, characteristic UUIDs)
- BleMessageChunker: DEFLATE compression + chunk splitting/joining
- BleRole/assignRole: Role assignment based on UUID comparison
- BleTransport: Platform-agnostic BLE transport interface
- BleNostrClient: IRelayClient implementation over BLE
- BleNostrServer: Relay server over BLE with notification support
- BleMeshManager: High-level mesh manager with auto-discovery,
role assignment, and event broadcasting
- BleChunkAssembler: Thread-safe chunk reassembly
Platform support:
- Deflate expect/actual for jvmAndroid, apple, and linux targets
- AndroidBleTransport: Full Android BLE implementation using GATT
server/client APIs with scanning, advertising, and MTU negotiation
Tests: Deflate compression, message chunking, role assignment,
and chunk assembly (25 tests passing).
https://claude.ai/code/session_01Tz5E73Rj7tL48A3qUGS5DT
Adds support for the NIP-15 decentralized marketplace protocol with
all event kinds: StallEvent (30017), ProductEvent (30018),
MarketplaceEvent (30019), AuctionEvent (30020), BidEvent (1021),
and BidConfirmationEvent (1022). Follows NIP-88 Polls structure
with JSON content data models and tag builder extensions.
https://claude.ai/code/session_014tf8Fn7J5CNpEgRsyMnhAy
Add support for peer-to-peer order events as defined in NIP-69,
enabling unified liquidity pools across P2P trading platforms.
Implements P2POrderEvent as a BaseAddressableEvent with all required
tags (k, f, s, amt, fa, pm, premium, expires_at, expiration, y, z)
and optional tags (source, rating, network, layer, name, g, bond).
Follows the nip88Polls structure with per-tag classes, TagArrayExt,
and TagArrayBuilderExt. Reuses existing GeoHashTag and ExpirationTag.
https://claude.ai/code/session_01VpvrrRMLdjpB5C9Pq4VupK
Add ChatEvent in quartz/nipC7Chats with support for kind 9 chat
messages and q-tag based replies per the NIP-C7 specification.
Wire up rendering in NoteCompose and NoteMaster (ThreadFeedView).
https://claude.ai/code/session_01BmAMHBCRKXG612i6zrhhP4
Replace the file-listing style with a UrlPreviewCard-like layout
showing nsite host URL, title, description, and optional favicon.
Remove path rendering and unused string resources.
https://claude.ai/code/session_01XTmqQ9QatUA7aPCWt7NHNt
Add protocol support and UI rendering for NIP-87 which defines ecash
mint discovery via three event kinds: MintRecommendationEvent (38000),
CashuMintEvent (38172), and FedimintEvent (38173).
https://claude.ai/code/session_01JR2nFVPjPGG9jTV4Qq2PUW
Add Quartz protocol support for NIP-5A Pubkey Static Websites with
RootSiteEvent (kind 15128) and NamedSiteEvent (kind 35128), including
tag parsers for path, server, title, description, and source. Wire up
rendering in NoteCompose and ThreadFeedView to display site metadata.
https://claude.ai/code/session_01XTmqQ9QatUA7aPCWt7NHNt
Add support for NIP-7D thread events with title rendering in both
NoteCompose (feed view) and ThreadFeedView (detail view). Replies
use existing NIP-22 kind 1111 comments.
https://claude.ai/code/session_01MR2hLpmq3pkzzT11t86dAt
Changes memberPubKey() to memberPubKeys() returning a list of all p tags
in RelayAddMemberEvent and RelayRemoveMemberEvent. Updates renderers to
display multiple members. Adds NIP-43 event rendering to ThreadFeedView
(NoteMaster) alongside NoteCompose.
https://claude.ai/code/session_01PRqe4bBCb9u62oPh8u9sHx
- Extract private log() helper in JVM and iOS PlatformLog to reduce
copy-paste branching
- Fix JVM formatter to be private and non-nullable
- Convert 60+ interpolated Log.w/e/i calls to lambda overloads,
including hot-path Filter.kt toJson() and LocalCache event processing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Defers string construction until after the level check, avoiding
allocation when debug logging is filtered in release/benchmark builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add optional throwable parameter to d() and i() across all platforms
- Add inline lambda overloads for all log levels to defer message
construction, avoiding string allocation when level is filtered
- Restore VoiceMessagePreview to pass throwable directly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Set Log.minLevel based on BuildConfig.DEBUG in Amethyst.kt init
- Set Log.minLevel = DEBUG in Desktop Main.kt
- Migrate VideoCompressionHelper to quartz Log + LogLevel enum
- Migrate VoiceAnonymizer, VoiceMessagePreview, LiveStatusIndicator to quartz Log
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the NIP-60 specification for cashu-based wallets with four event types:
- CashuWalletEvent (kind:17375) - replaceable wallet definition with encrypted mints/privkey
- CashuTokenEvent (kind:7375) - unspent cashu proofs with NIP-44 encrypted JSON content
- CashuSpendingHistoryEvent (kind:7376) - transaction history with encrypted tag array
- CashuMintQuoteEvent (kind:7374) - optional mint quote state with NIP-40 expiration
Follows the same structural patterns as the NIP-88 polls implementation.
https://claude.ai/code/session_018UfHPzrQCAFMwB1zB1ftDM
- Replace 6 regex operations in Url.removeDotSegments() with simple
string operations (startsWith/indexOf) using a when-expression
- Remove regex-based dropLastSegment() in favor of StringBuilder with
lastIndexOf
- Extract duplicated hex/octal/decimal parsing in DomainNameReader
into shared parseNumericLiteral() helper
- Deduplicate scheme matching by unifying findValidScheme* methods
into findSchemeSuffix() using regionMatches (avoids lowercase allocation)
- Extract readPath() validity check into isPathValid() to remove
duplicated 5-line condition
- Extract trySchemeNoSlashesOrUserPass() from readScheme() to
eliminate duplicated branch logic
- Restructure readCurrent() with when-expression and extract
resetDomainCounters() helper
https://claude.ai/code/session_017oGieyaUiLCxehNJ5aMFDK
Adds full NIP-43 support with Quartz event classes (kinds 13534, 8000,
8001, 28934, 28935, 28936) following the nip88Polls structure pattern.
Includes relay membership list screen with join/leave actions and
NoteCompose rendering for NIP-43 events in the feed.
https://claude.ai/code/session_01PRqe4bBCb9u62oPh8u9sHx
* 'main' of https://github.com/vitorpamplona/amethyst:
fix: keep screen on during PiP playback and survive screen lock
feat: add RelayDiscoveryEvent renderer for feed display
feat: add OpenGraph preview to WebBookmark cards
revert: keep INostrClient/NostrClient naming and restore onEose/isLive
refactor: simplify NostrClient API for beginner-friendliness
PiP video now keeps the screen on while playing (matching YouTube behavior)
and no longer kills the activity when the screen locks, allowing audio to
continue through the PlaybackService.
https://claude.ai/code/session_01QJzmFaabSA9YQ3oCQwEHTJ
Replace manual AutofillNode/LocalAutofillTree/LocalAutofill usage with
the new Modifier.semantics { contentType = ContentType.Password } API
from androidx.compose.ui.autofill in KeyTextField, PasswordField, and
AccountBackupScreen.
https://claude.ai/code/session_018e5E6dFATZTEnGtTZbkKhP
* 'main' of https://github.com/vitorpamplona/amethyst:
increase android CI build to 45 minutes
New Crowdin translations by GitHub Action
update translations: CZ, DE, PT, SE
Reverts three naming changes from the previous refactor:
- Interface stays INostrClient (not NostrClient)
- Class stays NostrClient (not DefaultNostrClient)
- onEose stays onEose (not onCaughtUp)
- isLive stays isLive (not isRealTime)
All other renames from the API simplification are preserved:
subscribe, unsubscribe, publish, count, fetchAll, fetchFirst, etc.
https://claude.ai/code/session_01JPcYCcRx5eZN4GvgGxGwbf
Deprecated APIs in Quartz are kept for library consumers but internal usages
now carry @Suppress annotations so the module builds warning-free. Also
replaces redundant .toInt() on hex Int literals in ChaCha20Core and migrates
deprecated java.net.URL to URI.resolve() in ServerInfoParser.
The spotless license header delimiter is updated to recognize @file: annotations.
https://claude.ai/code/session_01EwS56YAGGnnac5EuwhaUSs
Change the action lambda to `suspend (EventStore) -> Unit` so callers
can use suspend functions directly. Remove the now-unnecessary
runBlocking wrapper around delay() in ExpirationTest.
https://claude.ai/code/session_01TQRzVTAXBVWRGstcsapA5F
Each EventStore uses an independent in-memory SQLite database, so tests
can safely run concurrently. This launches all 16 DB configurations in
parallel via coroutines on Dispatchers.Default instead of running them
sequentially.
https://claude.ai/code/session_01TQRzVTAXBVWRGstcsapA5F
Add full compliance with NIP-85 by implementing all four assertion event
kinds and their associated tag types in the nip85TrustedAssertions package:
- Kind 30382 (User Assertions): Add all 15 missing tag parsers and
builders (first_created_at, post_cnt, reply_cnt, reactions_cnt, all
zap/report/topic/active_hours tags) to ContactCardEvent
- Kind 30383 (Event Assertions): New EventAssertionEvent with rank,
comment_cnt, quote_cnt, repost_cnt, reaction_cnt, zap_cnt, zap_amount
- Kind 30384 (Addressable Event Assertions): New
AddressableAssertionEvent with same tags as 30383
- Kind 30385 (External ID Assertions): New ExternalIdAssertionEvent
with rank, comment_cnt, reaction_cnt
- Add ProviderTypes for all new assertion kinds (30383/30384/30385)
- Register all new event kinds in EventFactory
- Add comprehensive tests for all assertion event types
https://claude.ai/code/session_01XZQsSnkHJ6tFiPS7fRLzPJ
Add a dedicated Polls-only feed screen (NIP-88 kind 1068, non-zap polls)
accessible from the left navigation drawer. The screen includes the
standard top nav filter bar for Global/Follows/etc filtering, reusing
the Discovery follow list settings.
New files:
- PollsFeedFilter: filters LocalCache for PollEvent only
- PollsFilterAssembler + PollsSubAssembler: relay subscriptions
- PollsScreen: main screen with pull-to-refresh feed
- PollsTopBar: top bar with Global/Follows filter spinner
- Relay filter subassemblies for authors, follows, global, hashtag
Wiring:
- Route.Polls added to navigation
- Polls entry in left drawer with ic_poll icon
- pollsFeed added to AccountFeedContentStates
- PollsFilterAssembler added to RelaySubscriptionsCoordinator
https://claude.ai/code/session_013JWrsYpQczmZtpW3WwRRfK
- Add KindTag to LnZapRequestEvent.create() when zapping events, as required
by NIP-A4 ("NIP-57 zaps MUST include the k tag to 24")
- Enforce e tag prohibition in all PublicMessageEvent.build() methods by
stripping any e tags from the tag builder output (NIP-A4: "e tags must not
be used")
- Add comprehensive tests for both changes
https://claude.ai/code/session_019JsSsTTXivnNWiAtE5DE1R
NIP-24 defines an optional birthday object with year, month, and day
fields for kind 0 metadata events. This was the only NIP-24 field
missing from Quartz's UserMetadata model.
https://claude.ai/code/session_01Sbj2DF5XDtEuDCeJ7yR6oS
Add kind 1985 LabelEvent with L (namespace) and l (label) tag support
for distributed moderation, content classification, and license assignment.
Includes tag parsers, builder extensions, self-reporting helpers, and tests.
https://claude.ai/code/session_011t5ZoP1BdgZTT5Cen9GH5z
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
sonar fixes
update translations: CZ, DE, PT, SE
New Crowdin translations by GitHub Action
update video compression library to latest
update to use new limit short size bit more aggressive compression defaults
add local maven repo for easier library development
Subscribe to kind 30166 (RelayDiscoveryEvent) events for the relay being
viewed and display monitoring data from multiple providers. Shows RTT
metrics (open/read/write), network type, relay type, supported NIPs,
and access requirements reported by each monitor.
https://claude.ai/code/session_01KWuXTp3Z7HST9cu6RWQ4G5
Replace MutableStateFlow caches with Flow.map derived from bannedPubkeys
and allowedPubkeys. Use LocalCache.checkGetOrCreateUser to safely resolve
pubkeys, filtering out invalid ones (null). Remove raw pubkey display
from PubkeyUserCard since invalid pubkeys are already excluded.
https://claude.ai/code/session_018x2PcJX6VGyJmVuphkbK54
Resolve pubkeys to User objects via LocalCache in RelayManagementViewModel,
caching the mapping. Render banned/allowed pubkeys using SlimListItem with
user picture, name, and NIP-05 verification, with the remove action button
in trailingContent. Falls back to HexEntryCard for unknown pubkeys.
https://claude.ai/code/session_018x2PcJX6VGyJmVuphkbK54
When creating a new web bookmark, automatically fetches OpenGraph
metadata (title, description) after the user leaves the URL field.
Only populates fields that are still empty, preserving any user input.
https://claude.ai/code/session_01RteQotL6WHqJtDr38fUHan
Implement pinned notes using NIP-51 kind 10001 event. This allows users
to pin notes to their profile and view pinned notes on other profiles.
- Rewrite PinListEvent from non-standard kind 33888 to NIP-51 kind 10001
using e tags and BaseReplaceableEvent
- Add PinListState for reactive pin state management
- Add pin/unpin actions to Account, AccountViewModel, and 3-dot menu
- Add Pinned Notes tab to user profile page
- Add Pinned Notes tab to My Bookmarks screen
- Subscribe to kind 10001 in account metadata and profile relay filters
https://claude.ai/code/session_012GQzb2qcGfAcizC6jqZArD
Add a new screen that fetches and displays all existing NIP-62 Request
to Vanish events from connected relays. Each entry shows the target
relays and event date. Per-relay "Test" buttons query the relay for
events older than the vanish date to check NIP-62 compliance - if
events are found, the relay is flagged as non-compliant.
https://claude.ai/code/session_019Xrprdfq6pVN8beYrYUSr4
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
update translations: CZ, DE, PT, SE
fix: restore local WatchAndDisplayNip05Row in ShowUserSuggestionList
refactor: remove Namecoin label from search results
feat: show Namecoin label on .bit search results without ViewModel state
refactor: remove Namecoin label from search results per review
refactor: simplify .bit search resolution per review feedback
feat: resolve bare .bit domains and show Namecoin resolution status in UI
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
refactor: rename trustAllCerts → usePinnedTrustStore
fix: harden TLS for GrapheneOS and security-conscious Android ROMs
doc: verify .onion ElectrumX cert via Tor, document pinning
fix: bypass cert pinning for .onion ElectrumX servers
feat: TOFU cert pinning for custom ElectrumX servers
feat: add Test Connection diagnostics to Namecoin settings
fix: pin ElectrumX server certs instead of trust-all TrustManager
# Conflicts:
# amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt
# amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt
The local private WatchAndDisplayNip05Row uses NonClickableObserveAndDisplayNIP05
(plain text domain), while the shared one in ShowQRScreen uses the clickable
ObserveAndDisplayNIP05. They are not the same function — restore the local copy
to preserve the correct non-clickable behavior in user suggestions.
- Add localCache param to NotificationsScreen
- Seed reactions and zaps from cache on initial compose
- Notifications now persist across tab navigation
- All screens now have cache seeding (Feed, Thread, Profile,
Bookmarks, Reads, Notifications)
- Move BookmarkListState from amethyst/ to commons/commonMain/
- Change cache param from LocalCache to ICacheProvider
- Android file becomes typealias to commons version
- No settings or decryptionCache dependencies (simplest State class)
- Pins bookmarkList AddressableNote via strong ref for GC retention
- Add cleanMemory() to DesktopLocalCache (sweeps stale WeakRef entries)
- Add startCleanupLoop() to Coordinator with heap monitoring
(MemoryMXBean, checks every 30s, cleans at >75% heap or every 5min)
- cleanObservers() clears unused NoteFlowSets on notes + addressables
- Try-catch per operation so one failure doesn't skip the rest
- 2-minute startup grace period before first cleanup
- Cleanup job cancelled on clear() (account switch / logout)
- DesktopLocalCache now uses LargeSoftCache (WeakReference-based, GC-driven)
instead of BoundedLargeCache (strong refs, 50k cap, arbitrary eviction)
- Delete BoundedLargeCache.kt — no longer needed
- Rewrite findUsersStartingWith to use forEach instead of values()
- Remove BoundedLargeCache eviction tests (no longer applicable)
- Notes now only disappear when nothing references them, not arbitrarily
Per Vitor's feedback: "this maximum size approach might not work well,
as things will just disappear"
- Move LargeSoftCache from amethyst/model to commons/jvmAndroid/model/cache
so both Android and Desktop share the same WeakReference cache implementation
- Change ICacheProvider return types from Any? to proper types (User?, Note?)
since these model classes already live in commons
- Remove unnecessary casts in ThreadAssembler, ChatNewMessageState, SearchBarState
- Improve LargeSoftCache.cleanUp() to use single-pass iterator (zero allocation)
- Update Android imports in LocalCache, LargeSoftCacheAddressExt, and test
Per Vitor's feedback on PR #1905: BoundedLargeCache evicts arbitrarily.
LargeSoftCache uses WeakReferences so GC respects the reference graph.
Both screens now show cached data instantly on navigation instead of
showing loading states while re-fetching from relays.
- BookmarksScreen: reads cached BookmarkListEvent from addressableNotes,
seeds public bookmark events from notes cache
- ReadsScreen: seeds long-form notes (kind 30023) from notes cache
Relay subscriptions still run to fetch fresh data in parallel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cached value now stays visible until relay data exceeds it, preventing
the count jumping from cached→0→ticking back up on each navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Profile screen now reads initial values from cache so navigating back
shows data instantly instead of re-fetching everything from scratch.
- Seed displayName/about/picture from User.metadataOrNull() on compose
- Add followerCounts/followingCounts maps to DesktopLocalCache
- Write counts on each update, read on profile open
- Follower count still ticks up live (good UX) but starts from cached value
- 4 new tests for profile count caching + metadata cache reads
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause of "0 relays, 0 notes, 0 follows": all screens used
connectedRelays as the relay set for subscriptions, but NostrClient
uses relay-on-demand — relays only connect when openReqSubscription
is called with their URL. This created a deadlock: screens waited for
connected relays, but relays only connect when screens subscribe.
Fix: use relayStatuses.keys (registered/available relays) which are
populated by addDefaultRelays() at startup. openReqSubscription
triggers connection on-demand.
Affected screens: FeedScreen, UserProfileScreen, ThreadScreen,
NotificationsScreen, BookmarksScreen, ReadsScreen, NewDmDialog.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add RelayHealthIndicator component — shows elapsed time since last
relay event. Hidden when <30s (healthy), shows "45s ago" / "3m ago"
when stale. Placed above BunkerHeartbeatIndicator in NavigationRail.
Reads from Coordinator's subscriptionHealth StateFlow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite FeedScreen from inline EventCollectionState + 8 rememberSubscription()
calls to cache-centric DesktopFeedViewModel pattern:
- ViewModel keyed on feedMode with DisposableEffect cleanup (destroy())
- FeedState pattern matching (Loading/Loaded/Empty/Error)
- FeedNoteCard takes Note instead of Event, reads counts from Note.flowSet
- DisposableEffect for note.clearFlow() cleanup in LazyColumn
- DisposableEffect for Coordinator interaction subscription lifecycle
- LaunchedEffect for rate-limited metadata loading via Coordinator
- Extract FeedHeader into separate composable
- Add destroy() to DesktopFeedViewModel (ViewModel.clear() is internal in KMP)
- Update UserProfileScreen to look up Note from cache for FeedNoteCard
Removes ~350 lines of inline state management (EventCollectionState,
zapsByEvent, reactionIdsByEvent, replyIdsByEvent, repostIdsByEvent,
followedUsers, bookmarkList, 8 rememberSubscription blocks).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add try-catch in consumeEvent() so one bad event doesn't kill the
pipeline. Add requestInteractions()/releaseInteractions() with Job
tracking via ConcurrentHashMap for screen-scoped subscription lifecycle.
Add SubscriptionHealth tracking (lastEventReceivedAt, eoseReceived)
exposed as StateFlow for UI consumption.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
toNoteDisplayData() now reads User.toBestDisplayName() from cache
instead of always showing npub. Falls back to npub if no metadata.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Resolve LruCache vs LargeCache: use LargeCache (lock-free ConcurrentSkipListMap)
with BoundedLargeCache wrapper for size enforcement on put()
- Confirm LargeCache available on desktop via quartz jvmAndroid source set
- Detail FeedNoteCard rewrite: Note model field mapping, 5 subscription removals
- Fix filter examples to use filterIntoSet (LargeCache API)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add quartz protocol package for NIP-86 JSON-RPC relay management with
NIP-98 HTTP authorization, and a management UI accessible from the
relay information screen when the relay advertises NIP-86 support.
Quartz (nip86RelayManagement/):
- Nip86Method: all 21 method constants from the spec
- Nip86Request: serializable JSON-RPC request with factory methods
- Nip86Response: response model with typed result parsing helpers
- Nip86Client: builds NIP-98 auth headers, serializes requests,
parses responses for each result type (pubkeys, events, kinds, IPs)
Amethyst:
- Nip86Retriever: OkHttp-based HTTP executor for NIP-86 calls
- RelayManagementViewModel: state management for all NIP-86 operations
- RelayManagementScreen: tabbed UI (Pubkeys, Events, Kinds, IPs,
Settings) with add/remove dialogs, moderation queue, and relay
settings fields
- Route.RelayManagement added to navigation
- "Manage Relay" button shown in RelayInformationScreen top bar when
relay's supported_nips includes "86"
https://claude.ai/code/session_01QckCAm1T8pJqqmURBiNtaQ
Add support for NIP-77 (Negentropy Syncing) protocol messages using
the negentropy-kmp library for efficient set reconciliation between
client and relay.
New files:
- NegOpenCmd, NegMsgCmd, NegCloseCmd: Client-to-relay commands
- NegMsgMessage, NegErrMessage: Relay-to-client messages
- NegentropySession: Client-side reconciliation orchestrator
- NegentropyServerSession: Server-side reconciliation handler
- NegentropyManager: High-level sync manager with listener callbacks
Updated serializers:
- Jackson MessageSerializer/Deserializer for NEG-MSG, NEG-ERR
- Jackson CommandSerializer/Deserializer for NEG-OPEN, NEG-MSG, NEG-CLOSE
- Kotlin MessageKSerializer/CommandKSerializer for all NIP-77 types
Compatible with strfry relay's negentropy implementation via the
negentropy-kmp library which implements the same Protocol V1 spec.
https://claude.ai/code/session_01Dc6W1G1jURAAR9kzyVvk6g
Add a new screen allowing users to send NIP-62 relay deletion requests.
Users can select a specific relay or ALL RELAYS, pick a date (data
created before that date will be requested for deletion), and provide
an optional reason. Includes confirmation dialog with clear warnings
about the irreversible nature of vanish requests.
https://claude.ai/code/session_019Xrprdfq6pVN8beYrYUSr4
Derive the label from the existing searchTerm — if it ends with .bit,
show a chain-emoji label above each user result. No new StateFlow or
ViewModel state management needed.
Remove namecoinResolvedUser StateFlow and the chain-emoji label
from search results. .bit resolution still works through the
existing NIP-05 path — just no special UI decoration.
Address @vitorpamplona's review comments on the last commit:
1. Remove NamecoinResolutionState sealed class and status banners
— the existing NIP-05 resolver already handles .bit, no need for
separate Namecoin-specific UI state (spinner, chain emoji, error)
2. Remove separate toNip05IdOrNull() resolution path
— widen the existing `if (term.contains('@'))` gate in both
SearchBarViewModel and UserSuggestionState to also accept bare
.bit domains (synthesize _@domain.bit → existing NIP-05 flow)
3. Remove d/ and id/ identifier support from search
— per feedback, only NIP-05 style resolution (.bit) in search
4. Remove special Namecoin result rendering in ImportFollowList
— just show UserLine for all results, no separation needed
5. Keep a lightweight 'Namecoin' label on resolved users in search
— namecoinResolvedUser StateFlow tracks the .bit-resolved user,
shown as a small label above the UserCompose row
Net: -405 lines, +50 lines across 6 files.
Widen the existing NIP-05 resolution gate in UserSuggestionState and
SearchBarViewModel to also accept bare .bit domains and d//id/ Namecoin
identifiers. A synthesised Nip05Id is passed to the existing
nip05Client.get() path — which already routes .bit to the
NamecoinNameResolver — so no separate resolution logic is needed.
Add NamecoinResolutionState (Idle/Resolving/Resolved/Error) as a
StateFlow so the UI can show progress. ImportFollowListSelectUserScreen
and SearchScreen display an animated status banner (spinner while
resolving, chain emoji on success, warning on error) and label
Namecoin-resolved profiles in the results list.
The field no longer means 'trust all certificates' — since the Samsung
One UI 7 fix, it means 'use the pinned trust store (hardcoded +
TOFU-pinned certs + system CAs) instead of system-only trust.'
The old name was actively misleading and could cause a future
contributor to interpret the flag literally, potentially reintroducing
the trust-all pattern that Samsung Knox and GrapheneOS reject.
Renamed across all 4 files:
- ElectrumxServer.kt: field definition + updated KDoc
- ElectrumXClient.kt: createSocket() usage + comments
- NamecoinSettings.kt: parseServerString() usage + comments
- NamecoinSettingsTest.kt: test assertions
Add support for NIP-B0 web bookmarks with a complete protocol
implementation in Quartz and a full UI in Amethyst for adding,
editing, browsing, and deleting web bookmarks.
Quartz:
- WebBookmarkEvent (kind 39701) as an addressable event
- Tag builder/parser extensions for title, published_at, hashtags
- Registered in EventFactory
Amethyst:
- WebBookmarksScreen with FAB to add, and per-card edit/delete/open
- WebBookmarkEditDialog for add/edit with URL, title, description, tags
- WebBookmarkFeedFilter querying addressable notes by kind + pubkey
- Account.sendWebBookmark() and Account.deleteWebBookmark()
- LocalCache.consume() for WebBookmarkEvent
- Drawer navigation entry with Language icon
- Route.WebBookmarks and navigation registration
https://claude.ai/code/session_01UzfLJttwuJzovtb8HX5F9n
Address compatibility and security issues identified by reviewing
GrapheneOS source (hardened Conscrypt, strict TLS enforcement):
1. .onion: prefer pinned factory, fall back to trust-all
GrapheneOS patches Conscrypt with stricter TLS enforcement that may
reject no-op X509TrustManagers even for proxied sockets. The .onion
server's cert is already in PINNED_ELECTRUMX_CERTS (same operator as
electrumx.testls.space), so we now use cachedPinnedSslFactory() as
the primary path. onionSslFactory() (trust-all) is kept as a fallback
on SSLHandshakeException only — this handles cert rotation or unknown
.onion servers gracefully.
2. TOFU: require explicit user confirmation before pinning
Previously, Test Connection auto-pinned every cert on success. An
attacker performing MITM during first test would get their cert
permanently trusted. Now each new cert triggers an AlertDialog showing
the full SHA-256 fingerprint. Users must explicitly accept ('Trust')
or reject each cert — aligning with GrapheneOS's philosophy of
explicit trust decisions.
3. Clarify trustAllCerts semantics
The field name is misleading post-refactor: it now means 'use pinned
trust store' not 'trust all certificates'. Added TODO to rename to
usePinnedTrustStore and updated inline comments to prevent future
misinterpretation.
Note: hostname verification on raw SSLSocket is a pre-existing gap
(not introduced by the Samsung fix PR) — SSLSocket.createSocket() uses
the host parameter for SNI only, not hostname verification. A follow-up
should add endpointIdentificationAlgorithm='HTTPS' or fingerprint-based
verification for pinned certs.
* 'main' of https://github.com/vitorpamplona/amethyst:
reuse hex methods.
spotless
delete voice files on failuer
added progress and test plan to doc
fix: delete abandoned compressed video when larger than original fix: eagerly delete intermediate temp files in upload pipeline fix: delete temp file from MediaCompressorFileUtils after image compression refactor: simplify temp file cleanup logic - Remove TOCTOU anti-pattern (file.exists() before file.delete()) - Consolidate double deleteTempUri calls into single conditional - Remove restating comments
analysis for temporary files
fix: clean up temp files after upload completes
perf: optimize ChaCha20 and XChaCha20-Poly1305 for speed
style: apply spotless formatting to ChaCha20/Poly1305 sources
feat: replace libsodium with pure Kotlin ChaCha20/Poly1305 implementation
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
feat: implement NIP-45 HyperLogLog for probabilistic event counting
- Share DesktopHighlightStore and DesktopDraftStore at app level instead
of per-column to fix cross-deck reactivity and draft persistence
- Replace broken clipboard-based highlight creation with
LocalContextMenuRepresentation that piggybacks on Copy to get selected text
- Render highlights as highlight:// links instead of bold/italic markers
- Add collapsible highlights panel in article reader with edit/delete/publish
- Publish highlights to relays as NIP-84 events via HighlightPublishAction
- Add Reads tab (kind 30023) and Highlights tab (kind 9802) to profile
- Rewrite MarkdownToolbar with MarkdownEditorState for selection-aware
toggle, Material icons, and keyboard shortcuts (Cmd+B/I/E/K)
- Wire DraftsScreen "New Draft" button to ArticleEditorScreen navigation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add client-side HyperLogLog support as specified by NIP-45:
- HyperLogLog object with merge, estimate, encode/decode, and
computeOffset for deterministic filter-based offset calculation
- Add hll ByteArray field to CountResult for relay HLL data
- Update CountResultKSerializer to serialize/deserialize hll hex field
- Add queryCountMergedHll extension for multi-relay HLL merging
- 20 unit tests covering HLL operations and serialization round-trips
https://claude.ai/code/session_01CQzygEkwckLCiXzSRPMpBa
Introduces a signer decorator that automatically appends the NIP-89
client tag to every event before signing, ensuring all events carry
client identification without modifying individual event creation sites.
Works transparently with internal, external (NIP-55), and remote (NIP-46) signers.
https://claude.ai/code/session_01WRtuk1ySCfqXzfFrieKsDs
Relay feeds were previously grouped under the generic "Feeds" category
in the top nav bar filter dialog. This adds a dedicated "Relays" group
so users can more easily find their favorite relay feeds.
https://claude.ai/code/session_016gjbPgYRXrQCoY1zKPz5PX
Connected to the .onion hidden service via Tor SOCKS proxy and
confirmed it serves the SAME certificate as electrumx.testls.space:
SHA-256: 53:65:D5:BB:26:19:F5:40:1C:D8:8E:FC:AF:FB:A5:B2:...
The .onion cert is already covered by the first pinned cert entry.
Added comments documenting this relationship and instructions for
fetching .onion certs via Tor for future updates.
The .onion still uses onionSslFactory() (trust-all) as the primary
path — this is correct since Tor provides its own authentication.
The pinned cert serves as defense-in-depth.
Tor hidden services are authenticated by their onion address (the
public key hash), making TLS certificate verification redundant.
The .onion server in TOR_ELECTRUMX_SERVERS was going through
pinnedSslFactory() but its cert isn't in the pinned list (and can't
be fetched without Tor). This would cause .onion connections to fail
with SSLHandshakeException when Tor mode is active.
Fix: .onion addresses now use a dedicated onionSslFactory() with
trust-all, which is safe because:
1. Tor provides end-to-end encryption
2. The onion address IS the server identity proof
3. Samsung Knox trust-all rejection doesn't apply to proxied sockets
When a user adds a custom ElectrumX server and runs Test Connection,
the server's TLS certificate is automatically captured and pinned
(Trust On First Use). This allows custom servers with self-signed
certificates to work on Samsung/Xiaomi/OnePlus devices.
Changes:
- ElectrumXClient: addPinnedCert(), setDynamicCerts() for runtime
cert management; testServer() now captures server cert PEM and
SHA-256 fingerprint from SSL session
- NamecoinSharedPreferences: persist pinned certs to DataStore
- AppModules: load pinned certs on startup, sync to ElectrumXClient
- NamecoinSettings: custom servers always set trustAllCerts=true
(ElectrumX servers almost universally use self-signed certs)
- UI: shows cert fingerprint in test results, auto-pins on success
- ServerTestResult: new serverCertPem + certFingerprint fields
Flow: Add server → Test Connection → cert auto-captured and pinned →
future connections trust that cert even on Samsung Knox devices.
Add per-server connection testing with detailed error reporting:
- Test Connection button tests each ElectrumX server individually
- Shows streaming results: ✅ success with response time, ❌ failure
with human-readable error (TLS handshake failed, connection refused,
timeout, DNS failed, invalid response, etc.)
- Diagnostic card shows: last test timestamp + success count, device
info (manufacturer/model/Android/API), TLS version negotiated
- ElectrumXClient.testServer() method for single-server diagnostics
with TLS version capture from SSL session
This gives users (and bug reporters) immediate visibility into why
Namecoin resolution may be failing silently on their device.
Samsung One UI 7 (Android 16) silently rejects TLS connections that use
a no-op X509TrustManager which accepts all certificates. This breaks
Namecoin resolution on all Samsung devices running One UI 7, including
Galaxy A15 (SM-A156E) and Galaxy S24 Ultra (SM-S938B).
Replace trustAllSslFactory() with pinnedSslFactory() that:
- Pins the actual self-signed certificates of known ElectrumX servers
- Also includes system CA certificates for servers with real certs
- Uses a proper TrustManagerFactory chain that Samsung Knox accepts
Additional OEM compatibility hardening:
- Cache the SSLSocketFactory (avoid expensive rebuild per connection)
- Request TLSv1.2 explicitly (Xiaomi MIUI/HyperOS and OnePlus ColorOS
Conscrypt forks may default to TLS 1.0 for raw socket upgrades)
- Enforce TLSv1.2+ enabled protocols on the SSLSocket
- KeyStore fallback to PKCS12 if default type fails (Xiaomi)
- Defensive try/catch on system CA cert re-insertion (some OEMs return
certs that cannot be added to a new KeyStore)
Tested on Android 16 (API 36) emulator — all ElectrumX servers connect
(hostname, IP-address, factory reuse) and .bit resolution works E2E.
Pinned certs:
- electrumx.testls.space:50002 (expires 2027-05-04)
- nmc2.bitcoins.sk:57002 (expires 2030-10-22)
Replace the bottom bar AnonymousPostButton with a tap-on-avatar activation scheme.
Tapping the user picture enables anonymous mode and shows the incognito icon in its
place. Tapping the incognito icon toggles back to normal mode.
https://claude.ai/code/session_013MaeT2T9Bg4HaVe2ACNADy
Route lambdas in NavHost run during navigation composition and should be
kept lightweight. Cache lookups (getNoteIfExists, checkGetOrCreateNote,
getOrCreateAddressableNote) and object construction (RoomId,
RelayUrlNormalizer) are now deferred to the screen composables themselves,
either via remember {} or inside LaunchedEffect blocks.
Affected screens: PublicChatChannelScreen, LiveActivityChannelScreen,
EphemeralChatScreen, GeoHashPostScreen, HashtagPostScreen,
ReplyCommentPostScreen, NewProductScreen, LongFormPostScreen,
ShortNotePostScreen, NewPublicMessageScreen,
ImportFollowListPickFollowsScreen.
https://claude.ai/code/session_01NrVHL4zdCghQvqE8xmi4Gf
When a user clicks on the reply preview (inner quote) in a chat message,
the chat feed now scrolls to the original replied-to message in the same
screen instead of doing nothing.
https://claude.ai/code/session_01UF4VxoWGvvvwtLYBLBYw9w
Allow users to reply to notes using a fresh, randomly generated keypair
that is not linked to their account. When the "Anonymous" toggle is
activated in the compose screen, the reply is signed with a new
throwaway identity and broadcast to the user's outbox relays without
being cached as the user's own event.
- Add AnonymousPostButton composable (PersonOff icon toggle)
- Add wantsAnonymousPost state to ShortNotePostViewModel
- Add signAnonymouslyAndBroadcast to Account (creates temp KeyPair)
- Show warning banner and hide user avatar when anonymous mode is on
- Add string resources for anonymous post UI
https://claude.ai/code/session_013MaeT2T9Bg4HaVe2ACNADy
* 'main' of https://github.com/vitorpamplona/amethyst:
code review: - Removed redundant equality guards in SideEffect — MutableState already suppresses no-op writes for value types - Changed mutableStateOf({}) to mutableStateOf(null) with explicit nullable type — clearer intent, no accidental no-op invocation - stopRecording() now early-returns with ?: return when not recording, so the Toast only shows for genuinely failed recordings (too short)
entire solid recording indicator bar stops recording when tapped, not just the small stop icon.
New Crowdin translations by GitHub Action
update skill
update translations: CZ, DE, PT, SE
- Wire Article, Editor, Drafts into DeckColumnType + DeckColumnContainer
- Add Drafts to sidebar nav, Add Column dialog, and Window menu
- Add article navigation (onNavigateToArticle) in SinglePaneLayout
- Add NoteActionsRow to ReadsScreen LongFormCards (zaps, reactions, replies)
- Add Cmd+/Cmd- zoom in ArticleReaderScreen via fontScale on RenderMarkdown
- Add kind 30023 to ProfileSubscription for long-form in user profiles
- Add fontScale param to RenderMarkdown using scaled LocalDensity
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Removed redundant equality guards in SideEffect — MutableState already suppresses no-op writes for value types
- Changed mutableStateOf({}) to mutableStateOf(null) with explicit nullable type — clearer intent, no accidental no-op invocation
- stopRecording() now early-returns with ?: return when not recording, so the Toast only shows for genuinely failed recordings (too short)
Video controls are now hidden by default when playback begins.
Users can tap to reveal them. Previously, controls were shown for
2 seconds before auto-hiding (TwoSecondController) or shown by
default (VideoViewInner).
https://claude.ai/code/session_01WpAqbodB5w8oDJpN6MLBs6
MetadataStripper, MediaCompressor, and EncryptFiles create intermediate
temp files in cacheDir during the upload pipeline, but these were never
deleted after the upload finished. Over time this causes unbounded disk
growth. Wrap upload calls in try/finally to ensure all intermediate temp
files (compressed, stripped, encrypted) are deleted regardless of
success or failure.
https://claude.ai/code/session_01YS3dbZ1k84N2EHS3AruYRV
The progress scrubber can now be dragged to seek through the video,
in addition to the existing tap-to-seek. During drag, the scrubber
enlarges for visual feedback and tracks the finger position in
real-time. Seeking is applied on drag end.
https://claude.ai/code/session_01QvYAvNdC35zGgkdpnujFn2
ChaCha20Core.chaCha20Xor:
- Parse key/nonce once into initial state, reuse across blocks
- XOR at word level (4 bytes at a time) instead of byte-by-byte
- Reuse working IntArray across blocks instead of allocating per block
XChaCha20Poly1305:
- Add hChaCha20FromNonce24 to avoid nonce copyOfRange(0,16) allocation
- Add chaCha20PolyKey to generate only 32 bytes instead of full 64-byte block
- Use single result array instead of ciphertext + tag concatenation
Benchmark (98-byte padded message, JVM):
Before: encrypt 2286 ns/op, decrypt 2062 ns/op
After: encrypt 1544 ns/op, decrypt 341 ns/op
https://claude.ai/code/session_01YHBwqnb3Z7Q7PX31tJ2G3i
Tests the StringIndexOutOfBoundsException fix in Url.getPart() by directly
constructing Url objects with UrlMarker indices that exceed the trimmed
originalUrl length — the exact scenario that occurs when readEnd() strips
a trailing character (e.g. ':') while the PORT/QUERY marker still points
to the original buffer position.
https://claude.ai/code/session_013rJ9iYndYVJgpYK2VazxL2
Tests cover the StringIndexOutOfBoundsException in Url.getPart() that
occurred when readEnd() trimmed trailing characters (e.g. ':') from a
detected URL but urlMarker indices remained pointing past the trimmed
string's end.
- UrlMarkerTest: three cases testing PORT/QUERY index at or beyond
string length (the boundary conditions fixed by minOf clamping and
the startIndex >= length guard)
- UrlsDetectorTest: regression for "今北産業" (no crash, 0 URLs) and
for a bare "example.com:" host
- UrlParserTest: end-to-end regression for "今北産業" and "example.com:"
https://claude.ai/code/session_013rJ9iYndYVJgpYK2VazxL2
When UrlDetector.readEnd() trims the last character from URLs ending
with characters like '.', ',', etc., the urlMarker indices remain
based on the original buffer length. This caused getPart() to call
substring() with an end index beyond the trimmed URL's length.
Fix by clamping the end index to originalUrl.length and guarding
against an out-of-bounds start index.
https://claude.ai/code/session_014Q2SvTE5vKgUm1w8dPVjYo
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
update translations: CZ, DE, PT, SE
New Crowdin translations by GitHub Action
JesterContent fields version, fen, and history were omitted from
serialized JSON because kotlinx.serialization skips default values.
Jester's isStartGameEvent checks arrayEquals(json.history, []) which
fails when the field is absent (undefined !== []).
Added @EncodeDefault to version, fen, and history so they are always
included in the JSON output regardless of value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jester.nyo.dev uses offchain.pub as one of its relays. Adding it gives
us 2 shared relays (relay.damus.io + offchain.pub) instead of 1,
improving challenge visibility between Amethyst and Jester clients.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ChessEngine.makeMove() now normalizes SAN notation before passing to
chesslib, fixing games against jester.nyo.dev and other clients that
use different notation:
- Castling: 0-0 → O-O, 0-0-0 → O-O-O (zeros to letters)
- Annotations: strips !, ?, !!, ??, !?, ?! suffixes
Previously, chesslib rejected 0-0/0-0-0 causing ChessStateReconstructor
to mark games as desynced and skip remaining moves. Games appeared
permanently stuck, even after app restart.
Also fixes discoverUserGames() silently dropping games classified as
spectator — now adds them to spectatingGames instead.
23 new interop tests covering SAN variants, reconstruction, spectator
detection, and missing start event handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updated CHANGELOG for release v1.06.0, detailing new features, improvements, and removals including support for NIP-85 Polls, custom NIP-40 Expirations, and various UI enhancements.
Replace the requireAuth: Boolean parameter in NostrServer with a
pluggable AuthPolicy interface. Each policy has trigger points for
EVENT (acceptEvent), REQ (acceptReq with filter rewriting), COUNT
(acceptCount), and live event delivery (canSendToSession).
Built-in policies: OpenPolicy (allow all) and RequireAuthPolicy
(require auth for all commands, matching the previous behavior).
https://claude.ai/code/session_017vdjbdxdYK1oJMH66koVZE
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
update translations: CZ, DE, PT, SE add: find missing translations skill
Add server-side AUTH challenge/response handling per NIP-42 spec:
- RelaySession generates a unique challenge per connection and tracks
authenticated pubkeys (multiple users can auth on one session)
- NostrServer gains relayUrl and requireAuth parameters; when requireAuth
is true, EVENT/REQ/COUNT are rejected with auth-required: prefix
- AUTH handler validates kind 22242, created_at within 10 min, challenge
and relay tag matching
- AuthCmd now accepts Event (not just RelayAuthEvent) so the server can
gracefully reject wrong-kind auth attempts
- 11 new tests covering auth success, wrong challenge/relay/kind/timestamp,
multi-user auth, and requireAuth gating
https://claude.ai/code/session_017vdjbdxdYK1oJMH66koVZE
Replace LocalCache.notes.forEach iteration with observeNotes using a
Filter for PollEvent.KIND and ZapPollEvent.KIND by the current user.
This makes the open polls list reactive — it updates automatically
when new polls arrive instead of only computing once on composition.
https://claude.ai/code/session_01LLLN5MDA5nJFYx38MVo81Q
* 'main' of https://github.com/vitorpamplona/amethyst:
code review fixes: 1. options.isNotEmpty().also → if guard 2. Hardcoded English strings in FeedGroup enum 3. Hardcoded accessibility label 4. Raw 14.sp literals (×6) → replaced with Font14SP theme constant 5. Raw 12.sp literal → replaced with Font12SP theme constant 6. Modifier.size(20.dp) → replaced with existing Size20Modifier theme constant
update gitignore
add grouped feed filter dialog with Material 3 styling add icons, reduce text size, center group headers in filter dialog
modernize SpinnerSelectionDialog with Material 3 styling
feature switch chess icon on android: visible in debug/benchmark client only
New Crowdin translations by GitHub Action
Intentionality: check the return value and log a warning via Log.w() when deletion fails
Update CS, DE, SV, PT
- Replaced manual .semantics with the simpler .clickable - avoids conditional Modifier allocation and a redundant semantics block
- Moved LocalClipboardManager.current inside the if
- Merged two consecutive postNostrUri?.let blocks into a single one
- Wrapped the "Copy & Gallery" M3ActionSection in a condition
- Removed duplicate string resource
Replace DropdownMenu with M3ActionDialog, M3ActionSection, and M3ActionRow
components. Move dialog outside ClickableBox. Preserve wantsToEditPost and
reportDialogShowing state holders unchanged.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace DropdownMenu with M3ActionDialog in UserProfileDropDownMenu,
structured into share, moderation, and report sections.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Load AttestationEvent (31871), AttestationRequestEvent (31872),
AttestorRecommendationEvent (31873), and AttestorProficiencyEvent (11871)
into LocalCache via consumeBaseReplaceable
- Create beautiful card-based UI renderers for all four attestation types
with color-coded status indicators, validity badges, and kind chips
- Add attestation kinds to Home feed data source (FilterHomePostsByAuthors)
and DAL filter (HomeNewThreadFeedFilter)
- Add attestation kinds to user profile feed data source
(FilterUserProfilePosts) and DAL filter (UserProfileNewThreadFeedFilter)
- Add string resources for attestation status labels
https://claude.ai/code/session_01BEMFoHZENBwzmzS6TMPxVE
Adds an "Open Polls" section at the top of the notification feed that
displays the user's PollEvent (NIP-88) and ZapPollEvent (kind 6969)
that are still active. Polls without a closing time are assumed to
expire 1 day after creation.
https://claude.ai/code/session_01LLLN5MDA5nJFYx38MVo81Q
NIP-04 encryption for sending DMs is now deprecated. All new messages
are sent using NIP-17 (gift-wrapped sealed messages). Reading NIP-04
messages remains supported for backward compatibility.
When a recipient lacks both a DM relay list (kind 10050) and NIP-65
inbox relays, the send button is disabled and a warning is shown
explaining that messages cannot be delivered.
Changes:
- ChatNewMessageState: Remove nip17/requiresNip17 toggles, add
recipientsMissingDmRelays state, always send via NIP-17
- ChatNewMessageViewModel: Always use NIP-17, remove NIP-04 send
paths, add recipient relay status checking
- ToggleNip17Button -> Nip17Indicator: Replace toggle with static
NIP-17 indicator (always on)
- PrivateMessageEditFieldRow: Show warning when recipients lack
DM relay lists, hide message input
- ChatFileSender: sendAll() always uses NIP-17
- Desktop ChatPane: Remove NIP-17 toggle, show relay warning
- ChatroomView: Reactively check recipient DM relay availability
https://claude.ai/code/session_01T7QhUW9cZogk4DxDXbbbJJ
Show a small lock icon next to the timestamp on each DM message:
- NIP-17 (ChatMessageEvent, ChatMessageEncryptedFileHeaderEvent): filled
lock in primary color — relay can't see sender/recipient
- NIP-04 (PrivateDmEvent): open lock in muted gray — legacy encryption,
metadata visible to relays
Matches Android's IncognitoBadge pattern. Both NIP-04 and NIP-17 messages
in the same 1-on-1 conversation produce identical ChatroomKeys, so they
merge into one conversation — the badge is the only way to tell them apart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The GiftWrap inner event handler only processed ChatMessageEvent (kind 14)
and ReactionEvent. ChatMessageEncryptedFileHeaderEvent (kind 15) was
silently dropped, so encrypted file attachments never appeared in DMs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Right-click on encrypted file attachments in DMs now shows a context menu:
- "Save to disk" — decrypts and saves via native file dialog
- "Forward" — placeholder callback for forwarding to another conversation
Also caches decrypted bytes (not just image bitmap) so save doesn't
re-download. Non-image files also get decrypted on load for save support.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Attach button always visible in DM input (not gated on NIP-17 mode)
- Drag-and-drop always enabled
- When files are attached in NIP-04 mode, auto-switches to NIP-17
with snackbar: "Switched to NIP-17 — file attachments require
encrypted messaging"
- NIP-17 toggle remains for manual control
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
compactMode was hardcoded in the Messages branch of RootContent, which is
shared between deck and single-pane. Now compactMode is a RootContent
parameter: deck passes true, single-pane defaults to false.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The new ByteArray upload overload on DesktopBlossomClient made the
positional any() mock call ambiguous. Specify explicit type parameters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In multi-deck mode, Messages column now uses stacked navigation instead
of side-by-side split pane. Full-width contact list OR full-width chat —
clicking a conversation navigates to chat, back arrow returns to list.
Single-pane mode keeps the existing split layout (280dp list + flex chat).
Changes:
- Add compactMode param to DesktopMessagesScreen (default false)
- Extract SplitMessagesContent and CompactMessagesContent composables
- Add onBack callback to ChatPane with back arrow in header
- Remove hardcoded 280dp from ConversationListPane (caller controls width)
- Pass compactMode=true from deck RootContent
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DeckColumnContainer was creating its own DesktopIAccount (and ChatroomList)
for the Messages column, while Main.kt created a separate one for DM relay
subscriptions. Events were added to one ChatroomList but the UI read from
the other, so DMs never appeared.
Fix: thread the single iAccount from Main.kt through DeckLayout →
DeckColumnContainer → RootContent → DesktopMessagesScreen. Removed the
duplicate DesktopIAccount and DmSendTracker creation from RootContent.
Audited all other deck column types — no similar instance duplication found.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add full send + receive encrypted media support in desktop DM chat:
- Paperclip attach button and drag-and-drop in ChatPane (NIP-17 mode only)
- AES-GCM encryption before upload to Blossom server
- ChatMessageEncryptedFileHeaderEvent (kind 15) wrapped in GiftWrap
- sendNip17EncryptedFile() added to IAccount interface and implementations
- DesktopUploadOrchestrator.uploadEncrypted() with proper encrypted hash
- DesktopBlossomClient ByteArray upload overload for encrypted blobs
- LRU cache in EncryptedMediaService to avoid re-downloading
- Error handling: retry on failure, disable send during upload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code reuse
- Extracted remuxTracks() helper to deduplicate video/audio remux loop (~40 lines)
- Extracted stripAfterCompression() to deduplicate identical blocks in upload()/uploadEncrypted()
- Replaced 8 identical showStrippingFailureDialog() copy-pastes with shared SuspendableConfirmation utility
Privacy
- 4 metadata ViewModels now error on strip failure instead of silently uploading unstripped media
Efficiency
- Eliminated temp input file copy for video/audio — MediaExtractor reads URIs directly
Bug fixes
- Fixed Long.toInt() overflow for MP3 files >2GB
- Fixed MP3 temp file leak on exception and null InputStream paths
Cleanup
- Mutex field and wrapping the suspendCancellableCoroutine in mutex.withLock. Concurrent callers now queued
- Converted MetadataStripper from class to object (stateless)
- Moved StrippingFailureState from service layer to UI as generic ConfirmationCallbacks
already handles metadata stripping)
- Pass stripMetadata=false when toggle is hidden for compressed video
- Hide compression quality for audio and other non-compressible media types
- refactor
- Post type toggle (Note vs Picture/kind 20) in compose dialog, only
shown when image files are attached. Text input disabled in picture mode.
- Fix LazyVerticalGrid crash in GalleryTab: bounded height via
fillParentMaxHeight() when nested inside LazyColumn.
- Phase 1 testing plan: all pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When images are attached, a Note/Picture toggle appears letting the user
publish as kind 20 (PictureEvent) instead of kind 1. Text input is
disabled in picture mode. Selector only shows for image file types.
Updates testing plan: Phase 1 all pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server selector dropdown appears when files are attached, letting the
user choose which Blossom server to upload to. Updates testing plan:
Phase 2 & 3 all pass, Phase 9 audio tested with volume bug tracked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Passes :start-volume media option to VLC on play. Removes polling/delay
volume hacks. Documents 9.7 volume bug in testing plan — VLC ignores
initial volume on macOS, needs further investigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Media playback now survives navigation. A GlobalMediaPlayer singleton owns
VLC players and exposes StateFlows. Composables are thin viewports.
NowPlayingBar has full controls (volume, mute, save, fullscreen).
GlobalFullscreenOverlay renders video fullscreen above all screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 6th Boolean parameter to onAdd lambdas in all 6 files that call
ImageVideoDescription. The parameter is unused (_) for now in 5 sites
and named in ShortNotePostScreen for future plumbing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add stripMetadata state and SettingSwitchItem toggle. Extend onAdd
callback signature with 6th Boolean parameter for stripMetadata.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename strip_location_metadata_label/description to strip_metadata_label/description
to reflect broader scope. Add new strings for the metadata stripping failure dialog.
Update references in NewMediaView.kt and ChatFileUploadDialog.kt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the compression quality slider is set to UNCOMPRESSED (value 3),
no encoding happens so the H264/H265 codec choice is irrelevant. Hide
the codec toggle in this case to avoid confusion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Audio has no compression support in MediaCompressor (falls through to
no-op), so showing the compression quality slider for audio files is
misleading. This removes audio from the slider visibility condition in
ImageVideoDescription and wraps the unconditionally-shown slider in
ChatFileUploadDialog with a media type check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Harden VLC player pool with dedicated thumbnail acquisition, better error
logging, and defensive buffer checks. Simplify lightbox/video controls,
fix NoteCard media sizing across feed, thread, bookmarks, and search screens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change the event counter from a single global counter to per-filter
tracking using Filter.match(). Each filter's limit is now respected
individually - fulfilled filters are excluded from subsequent pages,
and pagination stops when all filters with limits are satisfied.
https://claude.ai/code/session_01Baaira4xoEHEsaSQCX52Sy
Extract the paginated relay download into a reusable INostrClient
extension (NostrClientDownloadFromRelayExt.kt) in the Quartz
commonMain accessories package. Uses Channel-based event collection
for KMP compatibility (no JVM-only atomics).
EventSyncViewModel.downloadFromRelay now delegates to the Quartz
extension, keeping the ViewModel thin.
Add NostrClientDownloadFromRelayTest integration test against
wss://nos.lol with Filter(kinds = [MetadataEvent.KIND], limit = 10)
to verify paginated downloads return correctly-typed events.
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
- Convert filteredTransactions from cold Flow to StateFlow via stateIn()
to ensure reliable state updates with collectAsState()
- Add 30s timeout to NWC requests (fetchBalance, fetchTransactions,
loadMoreTransactions) so loading state resets if wallet doesn't respond
https://claude.ai/code/session_01CdNY7qYDRHJr1jhAFvVgzj
Replace aspectRatio modifier with BoxWithConstraints that manually
calculates height from aspect ratio and clamps it to the max height
constraint. Video now stays within its allocated space in the card,
so controls are accessible and content below is not overlapped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove fillMaxWidth before aspectRatio so the caller's heightIn(max)
constraint is respected. Video now sizes to fit within the NoteCard
without overlapping the author header or overflowing the card.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Single hover zone on entire video — no more flickering from competing
zones. Play button always visible when paused, bottom controls appear
on hover (works in fullscreen too)
- Media height capped to 50% of window (min 200dp) so post text is
never pushed off-screen by large media
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Center play button always visible when paused
- Pause button appears on center hover when playing
- Bottom controls bar only appears when hovering the bottom area
- Controls also show when paused for discoverability
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Controls (play button, seek bar, volume) are hidden by default and
appear when the mouse enters the video area. They auto-hide 2s after
the mouse leaves.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add VideoThumbnailCache that grabs the first rendered frame from VLC
and caches it in memory. Videos in the feed now show a thumbnail
preview instead of a blank background before the user clicks play.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace plain-text NotePreviewCard with full NoteCard in search results
so images, videos, and author avatars are visible inline without having
to open the note.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove maxLines/TextOverflow from RichTextContent so post text is
never truncated
- Media height = window height minus 200dp chrome, so each image/video
fits in the visible area without scrolling
- Use ContentScale.Fit for images to scale down while keeping aspect
ratio instead of filling width and overflowing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove maxLines=10 on post text so content is never truncated
- Profile header/card/tabs now scroll with content instead of staying
sticky — scrolls away naturally as you browse posts
- Floating compact header (back + avatar + name) slides in when
scrolling up and the full header is out of view
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded 400.dp max with window-relative height so media
(images and videos) never overflow the visible area and controls
are always accessible without scrolling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace WindowPlacement.Fullscreen (macOS maximize-only) with
GraphicsDevice.setFullScreenWindow() for true exclusive fullscreen that
hides menu bar and dock. Split single cycling view mode button into two
distinct toggles (theater + fullscreen). Hide all navigation chrome
(sidebar, nav rail, dividers, arrows, overlay menus) during fullscreen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bundle VLC via ir.mahozad.vlc-setup plugin (all plugins for network playback)
- BundledVlcDiscoverer (Win/Linux) and MacOsVlcDiscoverer for bundled VLC discovery
- Lazy player activation — no VLC calls during composition, only on play click
- Pre-init VLC on background thread at startup
- Video controls: seek slider, volume, mute, fullscreen (two-row layout, no clipping)
- Buffering indicator (CircularProgressIndicator) while loading
- ActiveMediaManager enforces single-player — pauses others when new video plays
- Fullscreen passes seek position so lightbox resumes from current playback point
- LightboxOverlay renders videos (DesktopVideoPlayer) or images (ZoomableImage)
- Gitignore downloaded VLC binaries (appResources/{linux,macos,windows}/)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Upload APK as a GitHub prerelease asset for direct .apk download
(no zip wrapping like artifacts)
- Remove commit comment, only comment on open PRs
- Clean up existing release with same tag before creating
https://claude.ai/code/session_0138kUcZaPQoQcAc7nA1KDbf
Use the artifact-id output from upload-artifact to construct a direct
GitHub artifact download URL. No third-party service needed — the URL
points to the artifact on GitHub which triggers a download when clicked.
https://claude.ai/code/session_0138kUcZaPQoQcAc7nA1KDbf
GitHub artifact URLs require authentication and don't provide direct
file downloads. nightly.link proxies artifact downloads, giving a
direct download link that works for anyone without GitHub login.
https://claude.ai/code/session_0138kUcZaPQoQcAc7nA1KDbf
- Build IMetaTag (NIP-92) from upload metadata (mime, sha256, dim, blurhash, size)
- Use TextNoteEvent.build directly with tag initializer for imeta injection
- Add drag-and-drop file support via Compose DragAndDropTarget
- Filter dropped files by media extension whitelist
- Visual border highlight on drag-over state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Notes/Gallery PrimaryTabRow to UserProfileScreen
- Subscribe to PictureEvent (kind 20) for gallery grid
- Wire GalleryTab composable with image click callbacks
- Wire LightboxOverlay for full-screen image viewing
- Pass onImageClick to FeedNoteCard in notes tab
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ChatPane: display ChatFileAttachment for encrypted file header events
- DesktopPreferences: add blossom server list persistence
- Settings: add MediaServerSettings section above relay settings
- ComposeNoteDialog: use preferred blossom server from preferences
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused: MediaAspectRatioCache, VideoPlayerState, resetZoom,
onDoubleClick, delete/headUpload/createDeleteAuth, encryptAndUpload,
EncryptedUploadResult, unused options params, formatAudioTime dupe
- Fix VlcjPlayerPool.release() accumulating stale event listeners
- Fix SaveMediaAction FileDialog running on IO instead of EDT
- Pre-allocate video frame ByteArray to avoid ~240MB/s GC pressure
- Pool audio players in VlcjPlayerPool instead of factory-per-instance
- Remove listener in onDispose before returning player to pool
- Fix acquire() race condition by moving poll inside synchronized
- Reduce memory cache to 15%/256MB with weak references
- Parallelize server health checks
- Remove redundant Content-Type/Content-Length headers
- Deduplicate BlurHashFetcher bitmap conversion via bufferedImageToSkiaBitmap
- Hoist audioExtensions to top-level constant, rename allMediaUrls
- Remove nonfunctional Save button and dead decryptedBytes state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 9: AudioPlayer composable using VLCJ --no-video factory,
play/pause/seek controls, NoteCard splits audio from video URLs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 8: MediaServerSettings UI for add/remove/reorder Blossom servers,
ServerHealthCheck with 5s timeout HEAD requests, status indicators.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 7: PictureDisplay composable for kind 20 events with image-first
layout, GalleryTab with adaptive grid of thumbnails, lightbox support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 6: AESGCM encrypt/decrypt for DM file attachments, Blossom
upload of encrypted files, ChatFileAttachment composable with
auto-decrypt for images and file type display.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 5: Full-screen lightbox overlay with mouse wheel zoom + pan,
left/right gallery carousel, keyboard shortcuts (Esc/arrows/Ctrl+S),
save to disk via FileDialog. Images in NoteCard are clickable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 4: VLCJ player pool (max 3 instances), DirectRendering via
CallbackVideoSurface → Skia Bitmap → Compose Image. Video controls
with auto-hide, seek, play/pause. Graceful fallback when VLC missing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3: Native file dialog, AWT clipboard paste handler, media
attachment UI with thumbnails, and ComposeNoteDialog media integration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Claude Code web egress proxy does not include dl.google.com in its
allowlist, causing the session-start hook to abort entirely (due to
set -euo pipefail) when Android SDK downloads fail. This cascading
failure also prevented local.properties creation and ANDROID_HOME export.
Additionally, JAVA_TOOL_OPTIONS contains nonProxyHosts entries for
*.google.com and *.googleapis.com, which causes the JVM to bypass the
proxy for Google Maven, resulting in connection failures.
Changes:
- Remove set -euo pipefail; use per-section error handling instead
- Fix JAVA_TOOL_OPTIONS: strip *.google.com from nonProxyHosts
- Fix no_proxy: remove *.google.com and *.googleapis.com entries
- Move local.properties and env exports before SDK download section
- Make SDK downloads non-fatal with clear warning messages
- Install standalone ktlint as fallback for spotlessApply
- Create spotless-apply wrapper script for proxy-blocked environments
- Update Stop hook to fall back to spotless-apply when Gradle fails
https://claude.ai/code/session_01HeWiRdKDTnMGfEBUegk3d1
Add PR commenting to the build-benchmark-apk workflow. When a build
completes, it now finds any open PR for the branch and posts a comment
with a direct download link to the benchmark APK artifact.
https://claude.ai/code/session_0138kUcZaPQoQcAc7nA1KDbf
- Use MessagingStyle for DM notifications with sender avatars and
conversation formatting (integrates with Android Bubbles/conversation space)
- Add Direct Reply action so users can respond to DMs from the
notification shade without opening the app
- Add Mark as Read action with SEMANTIC_ACTION_MARK_AS_READ for
Android Auto/Wear OS integration
- Apply notification grouping with InboxStyle summary notifications
for both DMs and Zaps (group keys were defined but never used)
- Use BigTextStyle for zap notifications so long messages expand
- Set proper notification categories (CATEGORY_MESSAGE for DMs,
CATEGORY_SOCIAL for zaps) for OS-level prioritization and DND bypass
- Fix PendingIntent flag from FLAG_MUTABLE to FLAG_IMMUTABLE where
mutability is not needed (security improvement)
- Replace blocking image load (executeBlocking) with suspend-friendly
Coil execute() on IO dispatcher
- Upgrade DM channel importance to IMPORTANCE_HIGH for heads-up display
https://claude.ai/code/session_012w6K8H4nvZYxeRryW2USAp
Adds filter chips (All / Zaps / Non-Zaps) to the transactions list.
Zap transactions are identified by having nostr metadata. The filter
is applied client-side via a combined Flow in WalletViewModel.
https://claude.ai/code/session_01CdNY7qYDRHJr1jhAFvVgzj
Replace the ElevatedButton in a sticky header with a delete icon
(Icons.Default.Delete) in the top bar actions area. The confirmation
dialog is preserved and now lives at the screen level, reading the
current feed state directly when confirmed.
https://claude.ai/code/session_01AbiBeDnT4KPKD16sKKaLD8
Add infinite scroll pagination to the wallet transactions screen.
When the user scrolls within 5 items of the bottom, the next page of
transactions is automatically fetched and appended. Uses the existing
NWC list_transactions limit/offset support and total_count to know
when all transactions have been loaded.
https://claude.ai/code/session_01CdNY7qYDRHJr1jhAFvVgzj
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
drawerGesturesEnabled is true whenever the drawer is in transition (target != current)
Split the logic between onPreScroll and onPostScroll
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
Unnecessary ?. / ?: / !! on non-null Missing @OptIn annotations Parameter name mismatches Icons.Filled.* / Icons.AutoMirrored.* No cast needed / remove inline LocalLifecycleOwner import fix NIP-51 name() → title() Chess gameId → startEventId Nullable DecimalFormat safe calls DelicateCoroutinesApi opt-in
use 50/50 split for pager vs dock zones
fix: allow swipe-to-close when drawer is open on pager screens
Keep drawer gestures enabled when the drawer is open so users can
swipe left to close it. Only disable gestures when the drawer is
closed (to let the pager and ZonedSwipeModifier handle gestures).
When swiping back on a DM screen with an empty message, the BackHandler
was calling sendDraftSync() which checks if the message is blank and then
calls deleteDraftIgnoreErrors(). This creates and signs a NIP-09 DeletionEvent,
prompting the external signer to sign a deletion event.
The fix adds a check to only call sendDraftSync() when there's actual
content in the message, avoiding the unnecessary signing prompt.
Spotless reformatted this file during merge. Restoring upstream's
version to avoid unintended changes to the mobile app.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Split build.yml into lint, test (matrix: 3 OS), build-android, and
build-desktop (matrix: 3 OS with native packaging) jobs
- Tests now run on ubuntu, macos, and windows to validate cross-platform
compatibility
- Desktop distributions (DEB, DMG, MSI) built on their native OS and
uploaded as artifacts
- Split create-release.yml into create-release, deploy-android, and
deploy-desktop jobs so desktop distributions are included in releases
- Android APK/AAB builds and Quartz publishing remain on Linux
https://claude.ai/code/session_016dxFUErY4P6WRir4xxhCEr
* 'main' of https://github.com/vitorpamplona/amethyst:
Rename build step for Benchmark APK in workflow
compilation error fix: Changed map to mapNotNull
spotlessApply
Replace all TODO stubs in the iOS actual implementation with delegation
to KotlinSerializationMapper. Wraps SerializationException into
IllegalArgumentException to match the jvmAndroid error handling pattern.
https://claude.ai/code/session_01QBc4Pb7a6m4TfLZSJKVdjw
Add 15 KSerializer implementations in commonMain that replicate the exact
behavior of all 21 Jackson serializer/deserializer registrations:
- EventKSerializer, TagArrayKSerializer, FilterKSerializer
- EventTemplateKSerializer, RumorKSerializer, CountResultKSerializer
- MessageKSerializer (8 message types), CommandKSerializer (5 command types)
- BunkerRequest/Response/MessageKSerializer (NIP-46)
- Nip47Request/Response/NotificationKSerializer (NIP-47)
- KotlinSerializationMapper as the main entry point
Uses JsonElement-based approach for performance. All serializers produce
identical JSON output to Jackson. Includes comprehensive test suite (35+
tests) verifying exact output parity and cross-deserialization between
Jackson and kotlinx.serialization.
https://claude.ai/code/session_01QBc4Pb7a6m4TfLZSJKVdjw
Add INostrClient.queryCountSuspend() extension functions in Quartz that
wrap NIP-45 COUNT queries as suspend functions, managing subscription
lifecycle internally. Two overloads: single-relay and multi-relay.
Simplify BasicRelaySetupInfoModel and Nip65RelayListViewModel to use the
new utility — removes IRelayClientListener implementation, subId tracking
maps, onIncomingMessage handlers, and cleanup logic from both ViewModels.
https://claude.ai/code/session_016158D5mq5BygS1uBbLNbsA
Replace the plain icon+text count display with rounded pill chips that
have a subtle green border and tinted background. Each count entry gets
its own pill with an icon and bold text, making the event counts more
visually distinct and scannable.
https://claude.ai/code/session_016158D5mq5BygS1uBbLNbsA
Move count query logic from separate RelayEventCountViewModel into each
relay list's own ViewModel. BasicRelaySetupInfoModel now manages counts
via countFilters() override. Nip65RelayListViewModel gets its own count
infrastructure for home (outbox) and notification (inbox) relay lists.
Simplify AllRelayListScreen by removing 7 extra count ViewModels and
their LaunchedEffects.
https://claude.ai/code/session_016158D5mq5BygS1uBbLNbsA
Use NostrClient's NIP-45 COUNT queries to show how many events each
relay stores, with filters specific to each relay role:
- Outbox: events authored by the user
- Inbox: events where the user is p-tagged
- DM Inbox: DM events (kind 4, 1059) tagging the user
- Private Home: events authored by the user
- Proxy: total event count
- Search: total event count
- Indexer: kind 0 and kind 10002 counts separately
- Broadcast: no count (as specified)
https://claude.ai/code/session_016158D5mq5BygS1uBbLNbsA
Add an "Encrypt files" toggle (default: on) to the file upload dialog in
NIP-17 encrypted DMs. When encrypted upload fails, show an informative
dialog explaining that many servers don't accept encrypted files on free
accounts, with an option to retry without encryption. The retry dialog
warns that without encryption anyone with the link can see the content.
https://claude.ai/code/session_012xvKzHrZPq3ZTAzBN2LvrR
ViewModel:
- MAX_ACTIVITY_LOG raised to 5000
- Remove activeRelays from LiveSyncActivity (active relay chips removed)
- Add eventsAccepted: Int to CompletedRelayInfo
- Add totalEventsAccepted: Int to SyncState.Done
- Register IRelayClientListener on account.client for the duration of
the sync to intercept OkMessage(success=true) from destination relays
- sourceRelayOfEvent map attributes each forwarded event to the relay
it was first seen on; ConcurrentHashMap.remove() ensures the first
OK true is counted exactly once per event (dedup across dest relays)
- acceptedCountPerRelay accumulates per-source-relay accepted counts,
read at onRelayComplete time
- downloadFromPool.onEvent changed to (Event, NormalizedRelayUrl) to
carry the source relay into runSync routing logic
Screen:
- Remove ActiveRelaysCard and ActiveRelayChip composables + preview
- Remove animation imports (no longer needed)
- ActivityLogRow now shows two right-hand columns per relay:
recv N — events received from that source relay
new N — events accepted as new by destination relays (highlighted
in primary color when > 0)
- DoneCard now shows three lines:
Forwarded N events to destination relays.
M events accepted as new by destination relays. ← key headline
Completed in N seconds.
Strings: event_sync_done_body replaced by done_sent / done_accepted /
done_duration; event_sync_reading_from and events_found_log removed;
log_recv and log_new added.
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
extract MediaUploadTracker with common methods
bugfixes:
ChatFileUploadState.canPost() was missing isUploadingFile check
ChatNewMessageViewModel.cancel() was never resetting upload flags
Double-reset in ChatFileUploader and ChannelNewMessageViewModel
- All ViewModels track isUploadingFile alongside isUploadingImage, set via hasNonMedia()
- All screen call sites pass source-specific flags so only the initiating button spins
- canPost() methods block posting during file uploads too
- ChatNewMessageViewModel delegates to ChatFileUploadState (fixes pre-existing dead state)
- cancel() methods reset both flags in all ViewModels
ViewModel now emits a LiveSyncActivity StateFlow alongside the existing
SyncState, carrying:
- activeRelays: which relays are currently being queried
- recentCompletions: last 100 completed relays with event counts
- outboxTargets / inboxTargets / dmTargets: destination relay sets
downloadFromPool gains onRelayStart/onRelayComplete(relay, eventsFound)
callbacks; downloadFromRelay returns Int (total events across all pages).
Tracking uses ConcurrentHashMap.newKeySet + synchronized ArrayDeque for
thread safety across 50 concurrent workers.
Screen adds three new live cards:
ActiveRelaysCard — FlowRow of chips with a shared pulsing dot
animation (one InfiniteTransition for all chips)
DestinationRelaysCard — Outbox / Inbox / DMs sections, color-coded
using primary / secondary / tertiary
ActivityLogCard — fixed-height (260dp) inner-scrollable list;
filled dot + count for active relays, muted for
empty/unreachable ones; K/M formatting for counts
String resources updated: event_sync_batch_of → event_sync_relays_progress,
paused_body copy updated to relay language; 7 new strings added.
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
Add NwcTransactionMetadata parser that extracts comment, payer_data,
recipient_data, and nostr zap data from the untyped metadata field.
The transaction list UI now shows the Nostr user profile picture and
name for zap senders/recipients, falls back to payer name/email or
lightning address, and displays comment when it differs from description.
https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
isActive is a CoroutineScope extension and has no CoroutineScope
receiver in plain suspend fun bodies (runSync, downloadFromRelay).
- runSync catch block: split into CancellationException (rethrow) +
Exception, removing the invalid isActive guard entirely
- downloadFromRelay while loop: while(isActive) -> while(true) with
coroutineContext.ensureActive() as the first statement, which works
in any suspend fun and throws CancellationException when cancelled
The isActive import is kept for the supervisorScope lambda in
downloadFromPool, where a CoroutineScope receiver is in scope.
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
The WalletViewModel.init() was called inside LaunchedEffect (async),
but hasWalletSetup() was checked synchronously during the first
composition frame—before init had run. Moved init to run synchronously
during composition and made hasWalletSetup a reactive StateFlow so the
UI updates when wallet state changes.
https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
Instead of waiting for all 50 relays in a batch to finish before
starting the next 50, a Semaphore(MAX_CONCURRENT_RELAYS) keeps the
concurrency window full at all times: the moment one relay is fully
exhausted it releases the semaphore and the next relay starts.
Each relay is handled by downloadFromRelay(), which paginates
independently (until cursor) until the relay returns no events.
supervisorScope ensures a single relay failure does not cancel
the rest of the pool.
Dedup sets and event counter are now thread-safe (ConcurrentHashMap
key sets, AtomicLong) since workers fire concurrently.
SyncState.Running/Paused fields renamed:
chunkIndex/totalChunks -> relaysCompleted/totalRelays
nextChunkIndex -> nextRelayIndex
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
Simplifies the NWC developer experience by providing Nip47Client (for
wallet client apps) and Nip47Server (for wallet service backends) that
handle URI parsing, signer creation, event building, filter construction,
and response decryption. Updates README with quick-start examples.
https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
Adds missing server-side capabilities to the NIP-47 quartz module:
- LnZapPaymentResponseEvent.createResponse(): builds encrypted response
events (kind 23195) for wallet services to reply to client requests
- NwcNotificationEvent.createNotification(): builds encrypted notification
events (kind 23197) for wallet services to push payment notifications
Creates comprehensive README.md documenting the full NIP-47 API with
code examples for both wallet client and wallet service implementations,
covering URI parsing, request/response building, encryption, notifications,
error handling, transaction states, and caching.
https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
Adds an Alby Go-inspired wallet interface accessible from the left drawer menu.
Uses existing NWC connection from zap settings to communicate with the wallet
via NIP-47 methods (get_balance, get_info, pay_invoice, make_invoice, list_transactions).
New files:
- WalletViewModel: manages wallet state and NWC requests
- WalletScreen: balance display with send/receive action buttons
- WalletSendScreen: paste BOLT-11 invoice and pay
- WalletReceiveScreen: create invoice with QR code display
- WalletTransactionsScreen: scrollable transaction history
Also adds generic sendNwcRequest() to NwcSignerState and Account for
sending arbitrary NIP-47 requests beyond just pay_invoice.
https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
Adds a MetadataStripper that removes GPS location, camera info, timestamps,
and other sensitive EXIF/metadata from images, videos, and audio files before
uploading. This protects user privacy by default.
- Creates MetadataStripper using AndroidX ExifInterface for images and
MediaMuxer for video/audio remuxing
- Adds "Strip location metadata" toggle to all upload dialogs (NewMediaView,
ChatFileUploadDialog) so users can disable stripping per upload
- Persists the setting in AccountSettings (stripLocationOnUpload, default true)
- Integrates stripping into UploadOrchestrator before compression
- Covers all upload paths: media posts, DM attachments, channel uploads,
profile pictures, list/channel metadata images
https://claude.ai/code/session_01974zpAMSRD9GeqBt9ax6XD
Add a new post screen specialized for writing and publishing long-form
content events (kind 30023) with markdown support. Includes:
- MarkdownPostViewModel with title, summary, cover image, and markdown
body fields, draft support, and LongTextNoteEvent creation
- MarkdownPostScreen with Edit/Preview tabs, monospace editor, markdown
preview using existing RenderContentAsMarkdown, and media upload that
inserts markdown image syntax
- Navigation route (Route.NewMarkdownPost) and AppNavigation entry
- String resources for the new screen
https://claude.ai/code/session_012pBNvnWtVtQ6aKSheWJUDt
Add tests using exact JSON payloads from Alby Hub's test suite:
- Real bolt11 invoice strings from Alby's mock data
- pay_keysend with TLV records and preimage
- make_invoice with nested metadata objects
- make_hold_invoice with 64-char payment hash
- settle_hold_invoice with preimage
- list_transactions with unpaid_outgoing filter
- create_connection with isolated=true
https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
- Add get_budget, sign_message, create_connection request/response types
- Add NwcTransactionType (incoming/outgoing) and NwcTransactionState
(PENDING/SETTLED/FAILED/ACCEPTED) constants
- Add missing error codes: BAD_REQUEST, NOT_FOUND, EXPIRED
- Add settle_deadline field to NwcTransaction
- Add total_count to ListTransactionsResult
- Add metadata and lud16 to GetInfoResult
- Add unpaid_outgoing and unpaid_incoming to ListTransactionsParams
- Update Jackson deserializers for new method types
- Add AlbyInteropTest with 25+ tests verifying compatibility with
Alby Hub's JSON formats for all request/response types
- Update existing tests for new constants and error codes
https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
Relays cap event responses at ~500 per request. After each EOSE, the
oldest createdAt seen on a relay becomes the next 'until' cursor and
the relay is re-queried for the next page. This repeats until a relay
returns no events (exhausted) or cannot be reached.
All active relays in a batch are queried in parallel each round; only
the relays that still have pages remaining participate in the next round.
Dead relays (onCannotConnect) are dropped immediately.
ConcurrentHashMap is used for per-relay counters and cursors since
IRequestListener callbacks can arrive from concurrent relay threads.
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
- Process relays in batches of 50 instead of one at a time; each batch
opens a single subscription covering all relays simultaneously, so
5 000 relays become 100 batches rather than 5 000 sequential round-trips.
- Collapse the three sequential phases into one combined query per batch:
send both the 'authored by me' filter and the 'p-tagged me' filter in
the same subscription, then route events to outbox/inbox/DM relays by
their properties. This halves the total number of relay connections.
- Make the procedure pausable: cancel() now transitions to SyncState.Paused
(carrying the next chunk index and events-sent count) instead of Idle.
The screen shows a Resume button that continues from the saved checkpoint
and a Start Over button to restart from scratch.
- The mobile-data dialog correctly routes to resume() vs start() depending
on whether a checkpoint exists.
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
After swiping a draft to delete, the red confirmation background now
shows both a "Delete" button (left) and a "Cancel" button (right).
Tapping Cancel resets the swipe state, allowing users to dismiss
the delete action without deleting the draft.
https://claude.ai/code/session_01ULVR1fKwka5TgfgSucPE4r
* 'main' of https://github.com/vitorpamplona/amethyst:
feat: replace dialog with swipe-to-reveal delete button for drafts
feat: add confirmation dialog when swiping to delete a draft
Replace the disabled-Start + secondary-Start-Anyway button pattern
with a single always-enabled Start button that shows an AlertDialog
when the connection is metered/mobile, requiring explicit confirmation
before running a potentially gigabyte-scale operation.
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
Move EventSyncViewModel from a screen-scoped ViewModel to a plain class
held by AccountViewModel (activity-scoped). The sync job now runs on
AccountViewModel's viewModelScope, so it continues when the user
navigates away. The back button no longer cancels the sync.
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
Instead of showing a confirmation dialog, the swipe now stays open
revealing a red background with a clickable "Request Deletion" button.
Supports swiping from both left-to-right and right-to-left directions.
The user taps the revealed button to confirm deletion.
https://claude.ai/code/session_01JWJxejv2EKrPj2KGEfPPxU
* 'main' of https://github.com/vitorpamplona/amethyst:
feat: add ZIP/JSON relay export and format picker dropdown
refactor: extract relay export logic into RelayExporter class
feat: add export button to relay settings screen
- Create RelayListCollection to bundle all relay lists with section metadata
- Create RelayZipExporter that exports each relay category as a separate
JSON file (array of relay URL strings) inside a zip archive, shared
via FileProvider
- Refactor RelayExporter to use RelayListCollection
- Replace single export button with a dropdown menu letting users pick
between text export and ZIP (JSON) export
https://claude.ai/code/session_013PhuahpBFh6djVHzSsmNKM
Covers all 10 request methods (create, serialize, deserialize), all success
and error response types, notification deserialization, EncryptionTag and
NotificationsTag parse/assemble/isTag, NwcInfoEvent build and capabilities,
NwcNotificationEvent structure, LnZapPaymentRequestEvent create/decrypt
with NIP-04 and NIP-44, and Nip47WalletConnect URI parsing and serialization.
https://claude.ai/code/session_01JFogiwyR4CPVP8cRznqahJ
- Add optional r/p/e/a/emoji tags to StatusEvent.create() for full spec compliance
- Add GENERAL and MUSIC type constants to StatusEvent
- Filter expired statuses in UserStatusCache.addStatus() and add removeExpired()
- Add p tag (profile reference) display in DisplayStatusInner
- Add NIP-30 custom emoji rendering in status display via CreateTextWithEmoji
- Allow status type parameter in UserStatusAction.create()
https://claude.ai/code/session_0117asp4mDiR65dxbAGKi2hg
The swipe-to-delete gesture on the drafts screen was too sensitive,
causing accidental deletions. Added a confirmation dialog that appears
after the swipe gesture completes, requiring the user to confirm before
the draft is actually deleted.
https://claude.ai/code/session_01JWJxejv2EKrPj2KGEfPPxU
Adds a share icon button in the top bar of the relay settings screen
that exports all relay configurations (except Connected Relays) as
human-readable text via Android's share intent. Each section includes
a header and description, with one relay URL per line.
https://claude.ai/code/session_013PhuahpBFh6djVHzSsmNKM
- Translate To: shows all available languages with searchable list instead
of only device locales
- Don't Translate From: uses InputChip FlowRow with an Add button and
searchable language picker to add new languages
- Language Preferences: displays as OutlinedCards with radio buttons for
choosing display language, includes delete and add-new-pair functionality
with guided source/target selection
- Added new string resources for the refreshed UI
https://claude.ai/code/session_011iTJ1gg8weaxaALiUQNH4C
- Add EventSyncScreen composable with UI for event sync configuration
- Add EventSyncViewModel for managing sync state and business logic
- Add route and navigation entry for EventSync screen
- Add entry point in AllRelayListScreen
- Add string resources for EventSync UI
https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
feat: add missing translation settings to Translation settings menu
RootContent now stays composed when overlays are shown, preventing
search results and other state from being destroyed on navigation.
Overlay wrapped in opaque Surface to cover root content visually.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allows users to add an expiration date to posts via NIP-40. The feature
follows the same pattern as content warning (toggle button in the bottom
action bar, expandable section in the post body) and uses the same
calendar/time picker UI as poll deadlines.
- ExpirationDateButton: timer icon toggles orange when active
- ExpirationDatePicker: date+time picker with "Expires in X" display
- ViewModel: wantsExpirationDate + expirationDate state, wired into
both TextNoteEvent and PollEvent builders, loaded from drafts
- Strings: add_expiration_date, expiration_date_label, etc.
https://claude.ai/code/session_016QjmArr6zfM1Qx58iNrntA
Expose the translateTo and languagePreferences fields from
AccountLanguagePreferences in the Translation settings screen
(UserSettingsScreen), which previously only showed the
dontTranslateFrom setting.
New settings added:
- Translate To: dropdown to select the target translation language
from the device's configured locales, with a checkmark on the
currently selected language.
- Language Display Preferences: for each saved language-pair
preference (source → target), a dropdown to choose which language
to show first; only visible when at least one preference exists.
https://claude.ai/code/session_01BheCJbDDZDAAYo6GAJBtFf
Adds a new "Bitcoin Explorer (OTS)" settings page in App Settings,
modeled after the existing Namecoin settings page.
Users can configure a custom Mempool-compatible blockchain explorer
API URL for OpenTimestamps proof verification. When a custom URL is
set it takes priority over the automatic Tor-aware selection (mempool.space
when Tor is active, blockstream.info otherwise).
Changes:
- OtsSettings: immutable data class for explorer config
- OtsSharedPreferences: DataStore-backed persistence (global/app-level)
- TorAwareOkHttpOtsResolverBuilder: accepts customExplorerUrl lambda
- OtsSettingsSection: UI section with active explorer display, URL input,
known-explorers reference list, and reset button
- OtsSettingsScreen: full screen wrapping the section
- Routes.OtsSettings: new navigation route
- AllSettingsScreen: new entry under App Settings
- AppModules: wires otsPrefs and passes custom URL to resolver builder
https://claude.ai/code/session_01GX7k36uepGxU3U1fBKYFrx
Implements a pure-Kotlin (commonMain) parser for the BUD-10 blossom: URI
scheme, which allows sharing blob references with discovery hints.
Format: blossom:<sha256>.<ext>[?xs=<server>&as=<pubkey>&sz=<bytes>]
- BlossomUri data class holds sha256, extension, servers (xs), authors (as), size (sz)
- BlossomUri.parse() returns null for non-blossom URIs or invalid SHA-256
- Supports repeated xs/as params (multiple servers and authors)
- Percent-encodes server URLs on serialisation; decodes on parse
- BlossomUri.toUriString() round-trips back to a canonical URI
- Includes commonTest coverage for all parsing cases
https://claude.ai/code/session_01T4jyLbkNWd3f7i6MwsBcD4
- Add contentWarningReason() helper to TagArrayExt and EventExt for reading the reason from existing events
- Add contentWarningDescription state to ShortNotePostViewModel, wired into event building and draft load/save/reset
- Update ContentSensitivityExplainer to accept description state and render an OutlinedTextField for user input
- Pass description state from ShortNotePostScreen to ContentSensitivityExplainer
- Add "Reason (optional)" placeholder string resource
https://claude.ai/code/session_01P2nkYdNiWXiAcfMPTRTVEW
Extract the Namecoin settings from PrivacyOptionsScreen into a
dedicated NamecoinSettingsScreen with its own Route.NamecoinSettings
route. Add a "Namecoin Settings" entry to AllSettingsScreen so users
can navigate to it directly from the settings list.
https://claude.ai/code/session_013gEw6fJYFiFoe4zETo8dpC
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
fix: resolve conflicts with upstream PR #1789 merge
fix(desktop): remove noisy debug logs and silence SLF4J warnings
feat(desktop): add bunker heartbeat indicator to sidebar navigation
fix(desktop): verify user pubkey via get_public_key after nostrconnect login
fix(desktop): address code review findings for NIP-46 bunker login
feat(desktop): add nostrconnect:// login flow for QR-based signer pairing
revert: remove QR code scanning from login
feat(desktop): webcam QR scanning via ffmpeg for bunker login
feat(desktop): add webcam QR code scanning for bunker login
feat(desktop): add QR code scanning for bunker login
fix(desktop): clear stored credentials on logout
feat(desktop): update login hints to mention bunker:// option
fix(desktop): simplify settings logout to just a button
feat(desktop): add logout option to menu bar and settings
test: add NIP-46 test suite for desktop, quartz, and fix pre-existing chess test errors
feat(desktop): add NIP-46 remote signer (bunker://) login
Overlay screens (UserProfileScreen, ThreadScreen) already render their
own back+title header. Remove the redundant SinglePaneHeader bar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace raw MutableSet with synchronized EventDeduplicator class.
Remove dual dedup in addNoteResults — trackRelayEvent now gates all
event processing. SearchScreen callbacks skip dupes early via return
value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove feedSub/contactList debug prints from FeedScreen, suppress
GiftWrapEvent decryption failure log (expected for others' wraps),
and add slf4j-nop to silence "No SLF4J providers" console warnings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pulsating green heart icon shows NIP-46 signer connection status in both
DeckSidebar and SinglePaneLayout NavigationRail. Tooltip displays last
ping time. SignerConnectionState moved to commons for KMP sharing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The nostrconnect flow was trusting params[0] from the signer's connect
message as the user's pubkey. Some signers (e.g. nsec.app) don't put
the actual user pubkey there, causing all relay subscriptions to query
the wrong identity — contact lists, profiles, and DMs all returned empty.
Now calls remoteSigner.getPublicKey() after the handshake to get the
verified pubkey from the signer. Also stabilizes FeedScreen relay
subscriptions with distinctUntilChanged() and adds relay.primal.net
to defaults.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds client-initiated NIP-46 (nostrconnect://) flow so users can pair
with a remote signer (e.g. Amber) by scanning a QR code instead of
manually copying a bunker:// URI.
Changes:
- Fix RemoteSignerManager to decrypt NIP-44 content before parsing
- Add CoroutineScope to NostrSignerRemote for async event callbacks
- Add loginWithNostrConnect() to AccountManager with 120s timeout
- Add QrCodeCanvas composable using ZXing for QR generation
- Add "Connect" tab to LoginCard showing QR + copyable URI + waiting state
- Wire nostrconnect flow through LoginScreen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces webcam-capture (broken on Apple Silicon) with ffmpeg
avfoundation capture. Opens a live preview dialog that scans
for QR codes from the FaceTime camera. Works natively on arm64.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses webcam-capture + ZXing to scan QR codes from the camera.
Click the QR icon on the login screen to open a webcam scanner
dialog that auto-detects bunker:// URIs from Amber's QR display.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a QR scan button to the login card that decodes QR codes from
clipboard images or image files using ZXing. Useful for scanning
bunker:// URIs from Amber's QR display.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All user-initiated logouts now pass deleteKey=true to remove
nsec from secure storage, clear last_npub, and delete bunker state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace boolean isSearching with counter-based activeSubscriptionCount
so loading state stays true until both subs (people + notes) complete
- Add indeterminate LinearProgressIndicator overlaid on search bar bottom
border with matching 12dp rounding, replacing "Searching relays..." text
- Send search REQs to all configured relays (relayStatuses) instead of
only connected ones — fixes NIP-50 relays never receiving search queries
- Move clearResults/startSearching out of remember() into LaunchedEffect
to avoid side effects during composition
- Fix history saving partial queries by replacing LaunchedEffect with
snapshotFlow to properly observe isSearching transitions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract DateUtils (isLeapYear, dateToUnix, timestampToDate) to deduplicate
identical implementations in QueryParser and QuerySerializer
- Move SavedSearch data class from desktopApp to commons/search
- Replace local formatTimestamp with existing toTimeAgo from commons/util
- Remove dead code: resolveAuthorName, _authorSuggestions (never called)
- Remove dead code: createOrFilters (never wired into SearchScreen)
- Remove unused ICacheProvider dependency from AdvancedSearchBarState
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* 'main' of https://github.com/vitorpamplona/amethyst:
fix: preserve search screen scroll position across navigation
feat: add audio and PDF file upload support to ShortNotePostScreen
New Crowdin translations by GitHub Action
Add a new "attach file" button that opens a document picker filtered to
audio/* and application/pdf MIME types. This uses OpenMultipleDocuments
contract since PickVisualMedia only supports images/videos.
- Add SelectFromFiles composable with AttachFile icon button
- Add isAudio() and isDocument() helpers to SelectedMedia
- Add FilePreviewPlaceholder for audio/PDF thumbnail display
- Add upload_file string resource
https://claude.ai/code/session_018naEzfHjLRQLgaWAtY4aSz
Adds UI rendering for kind 31922 (CalendarDateSlotEvent) and kind 31923
(CalendarTimeSlotEvent) in the note feed. Each event renders with a header
image, title, date/time range, location, and summary.
* 'main' of https://github.com/vitorpamplona/amethyst:
fix(quartz): connect() returns Unit, caller calls getPublicKey separately
fix(quartz): fix NIP-44 key mutation and NIP-46 connect response handling
feat: add custom ElectrumX server settings for Namecoin resolution
feat: import follow list with Namecoin resolution during signup
Phase 6: Integration and polish.
- Escape key closes advanced panel or clears search
- Search history shown in empty state (recent + saved searches)
- Auto-save to history when search completes
- Updated empty state with operator hint examples
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 5: Persistent search history using Java Preferences API.
- Last 20 queries stored, deduped by serialized text
- Saved searches with labels and timestamps
- Uses QuerySerializer/QueryParser for round-trip serialization (no new deps)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 4: Desktop UI composables for advanced search.
- Rewrite SearchScreen.kt to use AdvancedSearchBarState with TextFieldValue
- Add AdvancedSearchPanel with kind presets, author chips, date range, hashtags, language
- Add SearchResultsList with sticky headers for People/Notes/Articles/Other sections
- Make QueryParser.parseDateToTimestamp and QuerySerializer.timestampToDate public
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3: State holder with MutableStateFlow<SearchQuery> as SSOT,
sourceOfChange discriminator to prevent parse/serialize loops,
debounced query for subscriptions, results management, author
name resolution via cache autocomplete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 of advanced search. Recursive descent parser for operators
(from:, kind:, since:, until:, lang:, domain:, #tag, -exclude, OR),
serializer for bidirectional sync, kind alias registry with pseudo-kind
support. 68 unit tests all passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Per Amber maintainer: connect never returns a pubkey — response is
"ack" or the secret string. Rewrote ConnectResponse.parse() to check
result/error fields directly on base BunkerResponse (matching Jackson
deserialization). connect() is now a pure protocol handshake; callers
call getPublicKey() separately as a domain concern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two bugs in quartz's NIP-46 remote signer:
1. FixedKey.getEncoded() returned the raw byte array reference. javax.crypto.Mac
zeroes the key via destroy() after HMAC computation, corrupting the NIP-44
saltPrefix ("nip44-v2") after the first computeConversationKey call. All
subsequent NIP-44 decryptions with different key pairs fail with Invalid Mac.
Fix: return key.copyOf() instead of key.
2. NostrSignerRemote.connect() only handled pubkey responses. Amber (and other
signers) may respond with "ack" or "already connected" error. Added
ConnectResponse parser that maps these to success and falls back to
getPublicKey() to retrieve the actual pubkey.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a 'Namecoin Resolution' section that lets users configure custom
ElectrumX servers for .bit / d/ / id/ name resolution. When custom
servers are configured, they are used EXCLUSIVELY — the hardcoded
public defaults are completely ignored. This gives privacy-conscious
users full control over which servers observe their name lookups.
Server format: host:port (TLS default) or host:port:tcp (plaintext).
Supports .onion addresses for Tor-routed lookups.
New files:
NamecoinSettings.kt
Immutable settings data class with server string parsing/formatting.
NamecoinSharedPreferences.kt
DataStore-backed persistence following the TorSharedPreferences
pattern. Provides a synchronous snapshot for the serverListProvider
lambda and observable StateFlow for the UI.
NamecoinSettingsSection.kt
Compose UI: enable/disable toggle, active server display with
DEFAULT/CUSTOM badge, add/remove servers with validation, reset.
NamecoinSettingsTest.kt
Unit tests for parsing, formatting, round-trips, and edge cases.
Integration:
AppModules.kt — added namecoinPrefs (NamecoinSharedPreferences) and
wired customServersOrNull into the existing serverListProvider so
user-configured servers take priority over the Tor/default logic.
Refactored from an earlier patch to avoid duplicating code already in
main. The quartz-layer ElectrumXClient, NamecoinNameResolver, and
supporting types are reused unchanged.
- Organize zap settings into three clear sections with primary-colored
headers: Quick Zap Amounts, Zap Privacy, and Nostr Wallet Connect
- Replace plain Buttons with InputChip for amount selection — each chip
shows a lightning bolt, the amount, and a close icon to remove it
- Add animateContentSize() to the chips FlowRow so adding/removing
amounts animates smoothly
- Add descriptive sub-text under each section header explaining what
the control does
- NWC section: show an animated "Connected / Not Connected" status badge
(green CheckCircle vs grey RadioButtonUnchecked) that cross-fades via
AnimatedContent when the connection state changes
- NWC section: add animateColorAsState for the status color transition
- Replace the bare icon-only Add button with an OutlinedButton labelled
"Connect Wallet" for better discoverability
- Move the manual pubkey/relay/secret fields into a collapsible
"Advanced" row animated with AnimatedVisibility (expandVertically +
fadeIn/fadeOut). The section auto-expands when a connection already
exists or when a QR/clipboard import populates the pubkey field
- Add 9 new string resources for the new UI copy
https://claude.ai/code/session_01QzBq319fsxhrT91bGxws6N
- UrlDetector: simplify space handler in readDefault() — both branches
of if/else were calling the same readEnd(InvalidUrl), making the
conditional dead code; now we always call readEnd after attempting
to read the domain
- UrlDetector: remove redundant inner buffer.isNotEmpty() check in
readEnd(), already guarded by the outer condition
- UrlDetector: rename processColon() parameter from length to
startLength to eliminate the var length = length self-shadowing
- UrlDetector: replace ArrayList<Url>() with mutableListOf<Url>()
for idiomatic Kotlin
- DomainNameReader: replace lastSection.deleteRange(0, lastSection.length)
with lastSection.clear() which expresses the intent directly
- DomainNameReader: move isDotPercent() and isXn() extension functions
from inside the class body to private file-level functions — extension
functions defined on a class instance are unusual and confusing
https://claude.ai/code/session_01Pci2AC45yQxQw6F6g7jWdd
* 'main' of https://github.com/vitorpamplona/amethyst: (35 commits)
New Crowdin translations by GitHub Action
fix chess tests
Replace subsequent checks with '!isNullOrEmpty()' call
use forEach: no intermediate list created.
Use filterIsInstance to avoid dual iteration
Lambda argument should be moved out of parentheses
replace null checks with Elvis
merge call chain with flatMap (slight performance improvement)
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
update cz, pt, de, sv
was this meant to be withContext?
add names to boolean params
remove unused imports
Explicit type arguments can be inferred
remove redundant modifiers
correct package name
If-Null return/break/... foldable to '?:'
Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable.
Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable.
...
# Conflicts:
# amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ReactionsSettingsScreen.kt
The NamecoinNameService.init() singleton was the only consumer of
the companion object removed in the previous commit. The
namecoinNameService val itself was never referenced — namecoinResolver
is what's wired into Nip05Client.
- Change `items` state initialization to use `toList()` instead of `toMutableList()` to ensure a fresh immutable snapshot from the flow.
- Reformat chained modifiers in `DraggableItem` for better readability.
Add NamecoinLookupException sealed class to distinguish:
- NameNotFound: name queried successfully but doesn't exist on blockchain
- NameExpired: name exists but expired (>36000 blocks since last update)
- ServersUnreachable: all ElectrumX servers failed with connection errors
Previously, all three cases returned null from nameShowWithFallback,
making it impossible for the UI to show meaningful feedback.
Changes:
- ElectrumxClient.connectAndQuery: throws NameNotFound/NameExpired
instead of returning null for definitive blockchain answers
- ElectrumxClient.nameShow: lets NamecoinLookupException propagate
(only catches connection/IO errors as null)
- ElectrumxClient.nameShowWithFallback: short-circuits on NameNotFound/
NameExpired (no point trying other servers for definitive answers),
throws ServersUnreachable when all servers fail with connection errors
- SearchBarViewModel: new namecoinSearchState flow with typed states
(Idle/Loading/Resolved/NotFound/Expired/ServersUnreachable) for UI
to show loading spinners, resolved profiles, and specific error
messages. Derived namecoinResolvedUser from this flow.
Discovered while porting to notedeck (damus-io/notedeck#1314).
Fixes discovered while porting Namecoin NIP-05 to Primal Android
(PrimalHQ/primal-android-app#934):
1. Race condition: stale intermediate lookups overwrite final result
- Changed map to mapLatest in SearchBarViewModel so that previous
in-flight ElectrumX lookups are cancelled when the query changes
- Without this, typing 'd/testls' character by character causes
stale results from 'd/test', 'd/testl' etc. to arrive after
the correct 'd/testls' result and overwrite it with null
2. CancellationException swallowed in catch block
- mapLatest cancels previous coroutines via CancellationException
- The generic catch was catching it and emitting null, wiping the
valid result. Now re-throws CancellationException
3. Root lookup fails when names object has no underscore key
- extractFromDomainValue tried names[localPart] then names[underscore]
which is the same thing for root lookups
- Now falls back to first available entry for root lookups when
no underscore key exists
- Non-root lookups still fail correctly (no false matches)
4. Global Mutex serializes all lookups
- Single Mutex in ElectrumxClient blocked unrelated lookups to
different servers. A 25s timeout on one server stalled everything
- Now uses per-server mutexes via ConcurrentHashMap
5. customServers set but never read
- NamecoinNameService.setCustomServers() stored the list but the
resolver was constructed with the default serverListProvider
- Now wires customServers through to the resolver
6. resolveLive uses orphaned CoroutineScope
- Now accepts an optional external scope parameter so callers can
tie resolution to their own lifecycle
Adds 2 new tests: root fallback to first entry, non-root no-fallback.
Replace up/down arrow buttons with a drag handle icon that supports
long-press drag gestures for reordering reaction items in settings.
Items visually lift with elevation and scale during drag, and swap
positions when dragged past neighboring item thresholds.
https://claude.ai/code/session_01RYsPFgnoeJ3BmwjFPMvTuU
Add a new Reactions Settings screen that allows users to:
- Enable or disable each reaction button (Reply, Boost, Like, Zap, Share)
- Reorder reaction buttons with up/down arrows
- Show or hide the counter for each reaction type
Changes:
- AccountSyncedSettingsInternal: Add ReactionRowAction enum, ReactionRowItem
data class, and reactionRowItems field to AccountReactionPreferencesInternal
- AccountSyncedSettings: Wrap reactionRowItems in MutableStateFlow
- AccountSettings/Account: Add changeReactionRowItems methods
- AccountViewModel: Add reactionRowItemsFlow() and changeReactionRowItems()
- ReactionsRow: Add showCounter param to BoostReaction, LikeReaction,
ZapReaction, ReplyReaction variants; refactor GenericInnerReactionRow to
render dynamically based on settings
- New ReactionsSettingsScreen with per-button enable/counter toggles and
move-up/move-down ordering
- Routes, AppNavigation, AllSettingsScreen: wire up new settings screen
https://claude.ai/code/session_01QizUFfWDeKjLK2SuhanipJ
- CalendarDateSlotEvent (31922): Add title, summary, image, geohash,
hashtags, participants, references, multiple locations accessors and
build() method with DSL builder pattern
- CalendarTimeSlotEvent (31923): Fix timezone bug (startTzId/endTzId
were parsed as Long instead of String), add all accessors like
DateSlot, rename startTmz/endTmz to startTzId/endTzId, add build()
- CalendarRSVPEvent (31925): Remove incorrect location/start/end
accessors (not in spec), add status/freebusy/calendarEventAddress/
calendarEventId/calendarEventAuthor accessors, update from outdated
L/l label format to current status/fb tag format, add build()
- CalendarEvent (31924): Add title and calendarEventAddresses accessors,
add build() method
- Create NIP-52 tag classes: LocationTag, RSVPStatusTag (with enum),
FreeBusyTag (with enum)
- Create TagArrayBuilderExt.kt with DSL builder extensions for all
four calendar event types
https://claude.ai/code/session_01UMXgE3tNftmqNZqcYwF2kL
Hide GameInfo/MoveHistory/Actions panel by default in deck view
with a chevron toggle to expand. Add Chess to AddColumnDialog.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Resolves merge conflicts in Main.kt, adds Chess column type
to deck layout (DeckColumnType, ColumnHeader, DeckColumnContainer,
DeckState, SinglePaneLayout, View menu). Updates DesktopMessagesScreen
calls for new relayManager/localCache params.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reorganize nip64Chess event classes into individual packages with
dedicated tag classes, TagArrayBuilderExt, and TagArrayExt files,
following the same structure used by nip88Polls.
New package structure:
- challenge/ - LiveChessGameChallengeEvent with PlayerColorTag, TimeControlTag
- accept/ - LiveChessGameAcceptEvent with ChallengeEventTag
- move/ - LiveChessMoveEvent with GameIdTag, MoveNumberTag, SanTag, FenTag
- end/ - LiveChessGameEndEvent with ResultTag, TerminationTag, WinnerTag
- draw/ - LiveChessDrawOfferEvent
- game/ - ChessGameEvent (Kind 64)
- jester/ - JesterEvent, JesterProtocol, JesterContent, JesterGameEvents
Each tag class follows the companion object pattern with isTag(),
parse(), and assemble() methods. Each event package includes
TagArrayBuilderExt for building and TagArrayExt for parsing.
https://claude.ai/code/session_01Qtzhka3p5N3Hbns4xrRbsv
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
update cz, pt, de, sv
New Crowdin translations by GitHub Action
update cz, pt, de, sv
New Crowdin translations by GitHub Action
- Add dialog title "Send Zap" with close button in header
- Add preset amount SuggestionChips from user's saved zap amounts
- Replace TextSpinner dropdown with FilterChip row for zap type selection
- Make amount field full-width with "sats" suffix
- Move ZapButton to bottom as full-width call-to-action
- Add imePadding for keyboard avoidance
- Add RoundedCornerShape(16dp) to dialog Surface
- Add HorizontalDivider separating header from content
- Change message keyboard capitalization to Sentences
- Add send_zap string resource
https://claude.ai/code/session_017gXfNSaqGshRth2x5tdTKz
Replaces the local dialog state in AllSettingsScreen with a proper
Route.AccountBackup, presented as a bottom-slide composable.
- Add `Route.AccountBackup` to Routes.kt (sealed class + getRouteWithArguments)
- Rename `AccountBackupDialog` → `AccountBackupScreen` accepting `INav` instead of `onClose`; remove Dialog wrapper
- Register `composableFromBottom<Route.AccountBackup>` in AppNavigation
- Update AllSettingsScreen to navigate to Route.AccountBackup instead of managing local dialog state
https://claude.ai/code/session_01KRRANB1b7wV7mpxEvpfjc7
Replace the dialog-based UpdateReactionTypeDialog with a
navigation route (Route.UpdateReactionType) that slides up
from the bottom. UpdateReactionTypeScreen replaces the Dialog
wrapper with a plain Scaffold. Call sites in AllSettingsScreen
and ReactionsRow now navigate to the route instead of managing
local dialog state.
https://claude.ai/code/session_01TSjuYdBADTRXwT4w8Yx1vU
- Add Route.UpdateZapAmount to Routes.kt with optional nip47 parameter
- Create UpdateZapAmountScreen.kt as a Scaffold-based screen using SavingTopBar
- Register composableFromBottomArgs<Route.UpdateZapAmount> in AppNavigation.kt
- Replace dialog state in AllSettingsScreen with nav.nav(Route.UpdateZapAmount())
- Replace dialog state in ReactionsRow with nav.nav(Route.UpdateZapAmount())
- Remove UpdateZapAmountDialog composables, keeping UpdateZapAmountContent and authenticate helpers
https://claude.ai/code/session_01N95sLM14khkJupTfvJcoKq
- Add LayoutMode enum (SINGLE_PANE/DECK) persisted in DesktopPreferences
- SinglePaneLayout: NavigationRail + single content area reusing deck routing
- View menu "Deck Layout" toggle with Cmd+Shift+D shortcut
- Column resize: redistribute width on add/remove, double-click header to expand
- Constrain drag resize to window bounds via BoxWithConstraints
- FlowRow headers in FeedScreen/ReadsScreen to wrap on narrow columns
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ExternalIdentitiesEvent (kind 10011) to quartz/nip39ExtIdentities
with createNew/updateFromPast factory methods and identityClaims()
- Generalize TagArrayBuilderExt to work with any Event type (not just
MetadataEvent) so claims/twitterClaim etc. work with both kinds
- Extract replaceClaims logic to List<IdentityClaimTag>.replaceClaims()
shared extension used by both MetadataEvent and ExternalIdentitiesEvent
- Register ExternalIdentitiesEvent in EventFactory (kind 10011)
- Add consume(ExternalIdentitiesEvent) to LocalCache and dispatch in
justConsumeInnerInner
- Add ExternalIdentitiesEvent.KIND to UserMetadataForKeyKinds relay filter
so kind 10011 is fetched alongside kind 0 when loading user profiles
- Add UserExternalIdentitiesViewModel that observes the kind 10011
AddressableNote for a user and exposes List<IdentityClaimTag> as a Flow
- Thread UserExternalIdentitiesViewModel through ProfileScreen →
RenderScreen → ProfileHeader → DrawAdditionalInfo
- DrawAdditionalInfo now shows kind 10011 identities, falling back to
kind 0 for backwards compatibility
- UserMetadataState: add sendNewUserIdentities() for kind 10011 and
remove identity claim params from sendNewUserMetadata() (kind 0)
- NewUserMetadataViewModel: load identities from kind 10011 first
(fallback to kind 0), save identities to kind 10011 separately
https://claude.ai/code/session_017iU6ZdRu5qRXPCeDztVeeV
Adds full protocol support for NIP-C0 code snippet events following the
same structure used for NIP-88 polls.
Quartz (quartz/nipC0CodeSnippets/):
- CodeSnippetEvent (kind:1337) with accessors for all optional metadata
- TagArrayExt: parse language, extension, name, description, runtime,
license, deps (repeatable), and repo from tag arrays
- TagArrayBuilderExt: typed builder functions for CodeSnippetEvent
- Tag classes: LanguageTag (l), ExtensionTag, SnippetNameTag (name),
SnippetDescriptionTag (description), RuntimeTag, LicenseTag, DepTag,
RepoTag
EventFactory: register kind 1337 → CodeSnippetEvent
LocalCache: add consume(CodeSnippetEvent) and dispatch in when block
https://claude.ai/code/session_013ykLNfJNdwWpXh8ZhZaSXY
* 'main' of https://github.com/vitorpamplona/amethyst:
always show playback speed and position/duration text in white regardless of theme
New Crowdin translations by GitHub Action
extract rememberSaveMediaAction to eliminate toast/permission duplication
restore download toast and permission check fix overflow button background to solid
Code review fixes: reduce duplicate code
Code review fixes: Skip-forward clamps against unknown duration Share dropdown is no longer anchored to the overflow button simplify: keep skip amount fixed at 10
Code review fixes: Redundant Box in SkipButton Unused string resources remember { fadeIn() } pattern
Code review fixes: skipSeconds duplication + stale duration read pointerInput(Unit) stale capture ShareMediaAction composed unconditionally Overflow menu background opaque in dark theme
simplify top buttons, move to overflow add skip buttons add gradient overlays and double-tap gesture support
add skip button component, overflow menu component, gradient overlay
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
localize hardcoded Navigate string in UserSearchCard
add user avatar content descriptions in shared components
add content description to poll deadline date icon
correct pause button accessibility label
unrelated code cleanup
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
Query current block height via blockchain.headers.subscribe and reject
names that have expired (>= 36000 blocks since last update). Populate
expiresIn field in NameShowResult for downstream use.
Unconfirmed transactions (height <= 0) are treated as active.
- ElectrumxClient accepts injected SocketFactory (lambda) so connections
respect the user's Tor/proxy settings instead of leaking their IP
through raw sockets
- Add ProxiedSocketFactory for SOCKS5 proxy routing
- Add .onion ElectrumX server as primary when Tor is enabled, with
electrumx.testls.space as clearnet fallback
- NamecoinNameResolver accepts serverListProvider lambda for dynamic
server selection based on current Tor settings
- RoleBasedHttpClientBuilder.socketFactoryForNip05() bridges Amethyst's
Tor settings to the socket factory
- NamecoinNameService now requires explicit init with proxy-aware client
- Remove dead code: Nip05NamecoinAdapter (never referenced),
NamecoinVerificationDisplay (never called)
- Update design documentation
Resolve .bit, d/, and id/ identifiers from the search bar via
ElectrumX blockchain lookups. Typing any Namecoin identifier format
(m@testls.bit, testls.bit, d/testls, id/alice) now queries the
Namecoin blockchain and shows the resolved user at the top of results.
The namecoinResolvedUser flow in SearchBarViewModel detects Namecoin
identifiers, resolves them through NamecoinNameService, and prepends
the result to the standard local cache search results.
Updated docs with search integration details and manual testing guide.
Add censorship-resistant NIP-05 verification using the Namecoin blockchain.
Users can set their nip05 field to a .bit domain (e.g. alice@example.bit)
or direct Namecoin name (d/example, id/alice) and Amethyst will resolve
the pubkey mapping via ElectrumX instead of HTTP.
Resolution uses the standard Electrum protocol (scripthash-based lookups):
- Build canonical name index script matching ElectrumX-NMC indexing
- Query blockchain.scripthash.get_history for the name's tx history
- Parse NAME_UPDATE script from the latest transaction output
- Extract Nostr pubkey from the name's JSON value
Supports both d/ (domain) and id/ (identity) Namecoin namespaces,
simple and extended NIP-05-like value formats with relay hints,
LRU caching with 1h TTL, and self-signed TLS certificates.
New files:
- quartz: ElectrumxClient, NamecoinNameResolver, NamecoinLookupCache
- amethyst: NamecoinNameService, Nip05NamecoinAdapter, NamecoinVerificationDisplay
- docs: namecoin-nip05-design.md
- tests: NamecoinNameResolverTest
Modified:
- Nip05Client: optional namecoinResolver routes .bit to blockchain
- AppModules: wire up resolver
See docs/namecoin-nip05-design.md for full architecture and protocol details.
- Add RelayFeedScreen similar to HashtagScreen but filtered by relay URL
- Follow/Unfollow buttons add/remove the relay from Favorite Relay list
- New post FAB opens the standard note composer (Route.NewShortNote)
- Add RelayFeedFilter (DAL) to show notes seen from a specific relay
- Add SingleRelayFeedViewModel backed by RelayFeedFilter
- Add datasource layer: RelayFeedFilterAssembler, SubAssembler, and
FilterPostsByRelay for subscribing to relay-specific note streams
- Add Route.RelayFeed(url) and register in AppNavigation
- Add relayFeed to RelaySubscriptionsCoordinator
- Add FavoriteRelayListState.addRelay/removeRelay for single-relay ops
- Add Account.followFavoriteRelay/unfollowFavoriteRelay
- Add AccountViewModel.followFavoriteRelay/unfollowFavoriteRelay
- Add observeUserIsFollowingRelay composable observer
https://claude.ai/code/session_013gNQjBzeSSmesYow7HfAjW
When the user types in any "Add a Relay" field in AllRelayListScreen,
the app now searches HintIndexer's relayDB for matching relay URLs
and displays them as clickable suggestions below the text field.
Clicking a suggestion fills the field with the relay URL, matching
the design pattern of ShowUserSuggestionList in ShortNotePostScreen.
New files:
- RelaySuggestionState: debounced Flow-based state filtering relayDB
- ShowRelaySuggestionList: Column of clickable RelayUrlLine rows
https://claude.ai/code/session_0115sYZGVQBZLX7s9oCDcKoi
Adds a new RelayUrlSegment type to RichTextParser that detects and
labels relay URIs (ws:// and wss:// schemes) as a distinct segment
type, separate from generic LinkSegments.
https://claude.ai/code/session_01U21sGdEEMLo4hY8dwxNzPr
Adds a new section before Errors and Notices that shows live relay state:
- REQ subscriptions: each subscription card shows its ID and a visual
breakdown of all active filters using colored chips by kind category
(notes/social/DM/economic), author counts, tag values, and time bounds
- COUNT subscriptions: same display for count queries
- Pending outbox events: chip list of event IDs awaiting delivery
Filter chips use Material3 color roles to indicate kind categories:
primaryContainer for text events (note/repost/reaction), secondaryContainer
for social/list events, tertiaryContainer for DMs/gift wraps, and
errorContainer for economic events (zap requests/receipts/NWC).
https://claude.ai/code/session_012zzv2j63ibraPNrQx8fGeu
Reduce visual clutter by reordering fields, improving labels, and
collapsing rarely-used social proof fields into an expandable section.
- Rename "Name" field to "Username" with @ prefix
- Reorder: Lightning Address, Nostr Address (NIP-05), Website, Pronouns
- Remove deprecated LN URL (lud06) field
- Move social proof fields (Twitter, GitHub, Mastodon) into a
collapsible "Social proof" section (auto-expands if data exists)
- Clearer labels: "Lightning Address", "Nostr Address (NIP-05)"
Follow-up to #1723. When a quoted note is itself a reply, display a
compact "Replying to [username]" label above the content instead of
showing nothing. Uses the event's p-tags to resolve reply targets
directly, matching the Damus-style display.
Adds unPackReply = false to both DisplayFullNote (nostr: bech links)
and DisplayNoteFromTag (tag references) so that quoted notes only
show the quoted post itself without its parent thread context.
RenderTextParagraph previously assembled each word/segment as a separate
composable inside a FlowRow. It now outputs a single Text() composable
with a built AnnotatedString and an InlineTextContent map so images,
videos, and custom emojis are shown inline within the text flow:
- Images and videos (with preview): InlineTextContent with a fixed-size
media placeholder (300×200 sp) containing ZoomableContentView
- Custom emojis: InlineTextContent with emoji-sized placeholder per
image glyph via CustomEmoji.assembleAnnotatedList
- Hashtags: LinkAnnotation.Clickable span + optional InlineTextContent
icon for decorated hashtags (#bitcoin, #nostr, etc.)
- Clickable URLs, emails (mailto:), phones (tel:): LinkAnnotation.Url
spans with primary-colour styling
- Block-level widgets (URL previews, invoices, LNURL withdraw, Cashu,
secret emojis, Nostr note/profile references): InlineTextContent with
a 300×300 sp block placeholder
- User/event tag references without quote preview: InlineTextContent
with a narrow text-height reference placeholder (150 sp × emojiSize)
The spaceWidth / measureSpaceWidth plumbing is removed from
RenderTextParagraph and its callers (RenderRegular callback no longer
receives Dp). measureSpaceWidth is kept because Highlight.kt still uses
it for its own FlowRow layout.
RenderWordWithPreview, RenderWordWithoutPreview, the private
ZoomableContentView string-URL wrapper, RenderCustomEmoji, and
NoProtocolUrlRenderer are all inlined into RenderTextParagraph and
removed. HashtagIcon.kt @Preview is updated to the new API.
https://claude.ai/code/session_01TtfLvFoR5Qpr2bHBWr5z3f
When a user visits their own profile and has no profile picture set,
show a semi-transparent upload button overlay directly on the profile
picture area. Tapping it opens the gallery picker, uploads the image
to the configured server (NIP-96 or Blossom), and immediately saves
the metadata — without requiring the user to navigate to the edit screen.
- Add uploadPictureAndSave() to NewUserMetadataViewModel: loads current
metadata, uploads the image, then saves all metadata with the new picture
- Add ProfilePictureWithUploadOverlay composable that wraps ZoomableUserPicture
and conditionally shows the upload overlay when isMe && no picture
- Add ProfilePictureUploadButton composable for the overlay UI with
loading state support
- Use ProfilePictureWithUploadOverlay in ProfileHeader instead of
ZoomableUserPicture directly
https://claude.ai/code/session_01XemJ8Hx9q4mMd1StZNY9CE
Audio URLs with `audio/*` MIME types in NIP-92 imeta tags were silently
dropped (createMediaContent returned null) because only `image/` and
`video/` prefixes were checked. They now map to MediaUrlVideo, which
Media3/ExoPlayer already handles correctly for audio playback.
Also adds common audio extensions (ogg, wav, flac, aac, opus, m4a) to
the extension-based fallback so audio files without imeta tags are also
detected.
Fixes https://github.com/vitorpamplona/amethyst/issues/1701https://claude.ai/code/session_01YSi64eXRokSy2GzYHoNFES
Covers the full expect/actual contract across all platforms (Android,
JVM, iOS):
- round-trip for empty, single-char, simple, unicode, and JSON strings
- repetitive input compresses to a smaller size
- gzip magic number (0x1F 0x8B) present in compressed output
- compressed bytes differ from raw input bytes
- decompress of invalid data throws an exception
- special/control characters survive a round-trip
https://claude.ai/code/session_0125CGfu6aMAnSv6ZzAxFHvV
Uses platform.zlib (built-in on all Apple targets) via Kotlin/Native
cinterop to provide the actual GZip implementation:
- compress: deflateInit2 with windowBits=31 (MAX_WBITS+16) for gzip
format; deflateBound gives a tight upper-bound so a single Z_FINISH
call is always enough – no output loop required.
- decompress: inflateInit2 with windowBits=47 (MAX_WBITS+32) for
automatic gzip/zlib format detection; output is collected in
fixed-size chunks to handle arbitrary decompressed sizes.
https://claude.ai/code/session_0125CGfu6aMAnSv6ZzAxFHvV
* 'main' of https://github.com/vitorpamplona/amethyst:
feat: consolidate drawer settings into a single Settings hub screen
New Crowdin translations by GitHub Action
Replace six individual drawer items (Relays, Media Servers, Security
Filters, Privacy Options, App Preferences, User Preferences) with a
single "Settings" entry that navigates to a new AllSettingsScreen hub.
The hub lists all six options as clickable rows, each navigating to
their existing screens unchanged.
https://claude.ai/code/session_017Z9x4PNJuMtMQgfYSxTVSU
Adds a comprehensive Claude skill covering Gradle setup, key management,
event creation/signing, relay client, subscriptions, NIP builders, and
platform-specific notes. Also updates the legacy quartz-kmp.md to redirect
to the new skill.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
skipSeconds duplication + stale duration read
pointerInput(Unit) stale capture
ShareMediaAction composed unconditionally
Overflow menu background opaque in dark theme
- Uses new Media3 content view frames
- Redesigns video playback interface to remove unused buttons and modernize it.
- Adds our own buttons and indicators for the video playback
- Checks if PiP is supported and active before showing the button.
- Improves click interactions with the image dialog
- Fixes Surface release bugs
- Creates new interface for the Picture in Picture view
- Fixes muting and play buttons on the Picture in Picture
- Fixes incorrect aspect ratios of the AutoNonLazyGrid when multiple images exist for a single entry
- Adds Play buttons when the image fails to load so that people click and the post appears.
Replace the 80dp NavigationRail + single content area with a TweetDeck-style
multi-column deck. Default layout: Home Feed + Notifications + Messages.
New deck system:
- DeckColumnType sealed class with 12 column types
- DeckState with StateFlow-based column management
- Per-column navigation stack for in-column drill-down
- Draggable dividers for column resize (300-800dp)
- Column config persisted via Jackson JSON in DesktopPreferences
- 48dp thin sidebar with add-column and settings buttons
- AddColumnDialog for picking column types
Keyboard shortcuts:
- Cmd+T: Add column
- Cmd+W: Close focused column
- Cmd+1-9: Focus column by index
- Cmd+Shift+Left/Right: Move column left/right
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix ListChangeFeedViewModel stuck on Loading for empty chatrooms by
adding invalidateData() call in init block
- Add DmBroadcastStatus sealed class and DmBroadcastBanner composable
in commons for relay send progress display
- Add DmSendTracker using sendAndWaitForResponse for confirmed delivery
- Wire send tracking through DesktopIAccount into ChatPane banner
- Fix audit risks: unsubscribe DMs on account change, wait for relay
connect before subscribing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sendWrappedReaction function was creating NIP-17 gift wraps via
NIP17Factory but never broadcasting them. Added sendGiftWraps() to
IAccount interface with implementations in both Desktop and Android.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace TODO stub with sendWrappedReaction using NIP17Factory.
createReactionWithinGroup creates gift-wrapped reactions for group privacy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- DesktopIAccount: sign + broadcast NIP-04/NIP-17 messages via relay layer
- Main.kt: activate DM subscriptions on login, consume events into cache
- NewDmDialog: user search dialog with SearchBarState + UserSearchCard
- DesktopMessagesScreen: wire Cmd+Shift+N and "+" button to NewDmDialog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ChessPosition: include all fields in equals/hashCode
- AcceptedGamesRegistry: add thread safety with synchronized
- ChessEventBroadcaster: use valid filter for connection trigger
- ChessEventBroadcaster: fix misleading relayResults
- Remove println debug logging from ChessSubscription/Broadcaster
- UserProfileScreen: use proper JSON parsing via MetadataEvent
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add focused game mode to ChessPollingDelegate: when viewing a game,
only that game is polled instead of all active games
- Fix pawn promotion by parsing promotion suffix in publishMove
(e.g., "e8q" -> "e8" + QUEEN)
- Add game end overlay with victory/defeat/draw celebration
- Remove all debug println statements for production readiness
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Step 7: ChessViewModelNew.kt - Slim Android ViewModel (~130 lines)
- Delegates all business logic to ChessLobbyLogic
- Exposes StateFlows from logic.state
- Platform adapter creation only
Step 8: DesktopChessViewModelNew.kt - Slim Desktop ViewModel (~120 lines)
- Same pattern as Android, delegates to ChessLobbyLogic
- UserMetadataCache for profile display
- External CoroutineScope injection
Step 9: ChessBroadcastBanner.kt - Shared Compose banner in commons
- Uses ChessBroadcastStatus from ChessLobbyState
- Works on both Android and Desktop via Compose Multiplatform
- Shows broadcasting progress, sync status, errors
Step 10: UI consumers ready for migration
- New ViewModels use ChessChallenge (enriched display data)
- Shared banner ready to replace Android-only ChessStatusBanner
- Existing screens continue to work with old ViewModel
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ChessLobbyState with completedGames, replaceGameState(), moveToCompleted()
- Add challengerAvatarUrl to ChessChallenge
- Add CompletedGame data class for game history
Platform Adapters:
- AndroidChessAdapter: AndroidChessPublisher, AndroidRelayFetcher, AndroidMetadataProvider
- DesktopChessAdapter: DesktopChessPublisher, DesktopRelayFetcher, DesktopMetadataProvider
Shared Infrastructure:
- ChessRelayFetchHelper for one-shot relay queries
- IUserMetadataProvider interface for platform-specific metadata
- ChessFilterBuilder with simple filter methods for fetchers
- ChessSubscriptionController interface for subscription management
Fix ChessSubscription.kt to remove broken dataSources().chess reference
(subscriptions now managed by ChessLobbyLogic)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Mark ChessViewModel, navigation, relay subscriptions as complete
- Update file structure to reflect KMP commons migration
- Update checklist with completed items
- Revise next steps to focus on FAB and badge display
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Subscribe to LocalCache.live.newEventBundles for chess events
- Handle incoming opponent moves (LiveChessMoveEvent)
- Handle game acceptance (LiveChessGameAcceptEvent)
- Handle game endings (LiveChessGameEndEvent)
- Track new challenges directed at user (LiveChessGameChallengeEvent)
- Auto-update badge count on relevant events
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add state management for active games, challenges, badges
- Implement createChallenge for new games
- Add acceptChallenge for joining games
- Add publishMove with both simple (from/to) and full (ChessMoveEvent) APIs
- Add resign and offerDraw for game termination
- Wire up to existing ChessGameScreen and Chess.kt UI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move chess UI components from src/main/java to src/commonMain/kotlin
- Add consume functions for chess events in LocalCache
- Resolve merge conflicts with main branch KMP migration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This file provides instructions for AI agents to fork, customize,
and build branded versions of Amethyst.
Includes:
- Prerequisites and environment setup
- Step-by-step build workflow
- Common customizations (app name, package ID, icons, client tags)
- Troubleshooting guide
- Distribution options
* 'main' of https://github.com/vitorpamplona/amethyst: (48 commits)
Use accountViewModel.viewModelScope instead of rememberCoroutineScope() to allow download/share to finish even when controls auto-close.
Add content parameter to allow sharing of video from video player
New Crowdin translations by GitHub Action
- use a valid hex key - check the value of the tags
Fixed NullPointerException when filter contain tags
Fix assertEquals order
fix Jackson deserialization for empty Filters and add regression test
New Crowdin translations by GitHub Action
Consume File.delete() return values Extract duplicated "https://" literal into a private const val HTTPS_PREFIX
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
Load and save payment targets
refactor(commons): migrate from Moko to Compose Multiplatform Resources
feat(broadcast): Enhance retry, multi-broadcast UI, and post tracking (#1682)
wip: Add BroadcastTracker to AccountViewModel (#1682)
feat(broadcast): Add transparent event broadcasting feedback (#1682)
Enabled the reactions expand control for zapraisers even when there are no zaps
New Crowdin translations by GitHub Action
refactor to reduce complexity encodePcmToAac
...
# Conflicts:
# amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedState.kt
Remove Moko Resources dependency that blocks AGP 9.0 migration.
Migrate to built-in Compose Multiplatform Resources which is compatible
with the new KMP Android plugin.
- Remove moko-resources plugin and libraries from commons module
- Move strings.xml from moko-resources/ to composeResources/values/
- Update imports from SharedRes.strings.* to Res.string.*
- Add compose.components.resources dependency to desktopApp
- Configure resource package as com.vitorpamplona.amethyst.commons.resources
Closes#1675
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Retrying state to RelayResult for in-flight retry tracking
- Implement synced 360° rotating animation for pending/retrying icons
- Refactor BroadcastDetailsSheet for multi-broadcast sections (max 2 expanded)
- Add overflow summary for additional broadcasts beyond 2
- Fix sheet text colors using theme colors for better visibility
- Make sheet fully expandable (skipPartiallyExpanded)
- Add event cache to BroadcastTracker for retry support
- Implement retry that updates existing broadcast in-place
- Add tracked broadcasting for posts and quotes via createPostEvent/consumePostEvent
- Fix relay list computation for new posts (use event-based, not empty note)
- Make post editor close immediately, broadcast continues in background
- Style CompletedBroadcastIndicator consistently with BroadcastBanner
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 2 progress - added BroadcastTracker instance to AccountViewModel.
Integration with reaction buttons pending.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 1 - Core infrastructure:
- BroadcastModels: BroadcastEvent, RelayResult, BroadcastStatus data classes
- BroadcastTracker: State management with live progress updates
- BroadcastBanner: Global progress indicator (animated)
- BroadcastSnackbar: Result notifications with actions
- BroadcastDetailsSheet: Relay status detail bottom sheet
Refs: #1682
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix import paths to use desktop.subscriptions instead of commons.subscriptions
- Update DesktopLocalCache to match ICacheProvider interface changes
- Use correct commons.relayClient paths for ComposeSubscriptionManager
- Add missing justConsumeMyOwnEvent and getOrCreateAddressableNote methods
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolved import conflicts caused by package reorganization:
- upstream moved classes to amethyst/service/relayClient/*
- upstream moved models to commons/model/*
- kept our desktop additions (zaps, bookmarks, search)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cleanup deletes the actual shared file (and on error) instead of the old temp path in
Guard against empty response.body
Gate media-type detection so image sharing only checks image headers
Added unit test for rename-failure fallback
- Bring in the SwiftPackageManager for KMP plugin, to use Swift libraries for iOS implementations(and configure accordingly).
- Bring in cachemap dependency(for iOS LargeCache implementation).
- NotificationsScreen: Show "No notifications yet" after EOSE instead of infinite loading
- FeedScreen: Show "No notes found" after EOSE for both Global and Following feeds
- ThreadScreen: Show "Note not found" if root note missing after EOSE, "No replies yet" for empty threads
Uses existing EmptyState component from commons. Tracks EOSE (End of Stored Events) from relays to distinguish between "still loading" and "no data".
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
remove unused imports
guard for NaN crash with short recordings
fix re-record button
change icon to stop icon
change from "hold to record" to "click to start, click to stop"
Move the thread reply level visualization modifier to shared commons
module for use by both Android and Desktop platforms.
- Add ThreadModifiers.kt to commons/ui/thread/
- Update Android ThreadFeedView to use shared modifier
- Add overload for non-State level parameter
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds androidx.lifecycle.viewmodel.compose and lifecycle.runtime.compose
to commons/commonMain as preparation for extracting ViewModels to shared code.
These dependencies have KMP support since 2.8.0 (currently on 2.10.0).
Preparation for Phase A2: Extract Thread ViewModels to commons.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Support for self, privacy_policy, terms_of_service, grasps
- New UI for the Relay Information Screen
- Improvements to the Debug Message
- Compose-stable objects
- Clickable elements for NIPs, external links, grasps, etc
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
Quartz Feature Parity Table on Multiplatform
enhanced recording button
translations
bug fix: - Allow reply to reply by using BaseVoiceEvent
TODO: Reply to a reply fails to upload Verify deletion of temp voice files
Code review: - Removed DisposableEffect cleanup - Added cancelUpload() to selectRecording()
TOOD: Move re-record higher up tp avoid google assistant on longpress Reply to a reply fails to upload Verify deletion of temp voice files
Register Route in AppNavigation Modify ReplyViaVoiceReaction to Navigate
Add VoiceReply Route Create VoiceReplyViewModel and VoiceReplyScreen Add String Resource(s)
- Improve RelayConnectionManager with better connection handling
- Refine UI components (LoginCard, ProfileInfoCard, RelayStatusCard)
- Update KeyInputField with improved layout
- Fix formatting in FeedHeader and AppScreen
- Add proper number formatting utilities
- Remove .claude config from version control
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract reusable components from desktopApp to commons module:
Utilities (commonMain):
- NumberFormatters: countToHumanReadable, countToHumanReadableBytes
- PubKeyFormatter: toShortDisplay, toDisplayHexKey for key formatting
Utilities (jvmAndroid):
- TimeAgoFormatter: Human-readable time formatting for Nostr timestamps
- ZapFormatter: BigDecimal amount formatting with G/M/k suffixes
UI Components (commonMain):
- AppScreen enum for shared navigation
- LoadingState, EmptyState, ErrorState with refresh/retry support
- FeedHeader with relay status indicator
- NoteCard for displaying Nostr notes
- ActionButtons (AddButton, RemoveButton)
- RobohashImage for avatar display
- PlaceholderScreens (Search, Messages, Notifications)
- RelayStatusColors and shared color definitions
UI Components (jvmAndroid):
- LoginCard with key input field
- NewKeyWarningCard for new key backup warnings
- KeyInputField and SelectableKeyText
- ProfileInfoCard for account display
- RelayStatusCard for relay management
Core (jvmAndroid):
- AccountManager with login/logout/key generation
- RelayConnectionManager base class
- RelayStatus and DefaultRelays
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create PlatformImage expect/actual abstracting Bitmap/BufferedImage
- Move pure blurhash algorithms (Base83, SRGB, CosineCache, BlurHashEncoder) to commonMain
- Rewrite BlurHashDecoder to return PlatformImage instead of Bitmap
- Add PlatformImage.toBlurhash() extension in commonMain
- Add platform-specific Bitmap.toBlurhash() and BufferedImage.toBlurhash()
- Add base64Image toPlatformImage() for both Android and JVM
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The FeedScreen was waiting for connectedRelays to be non-empty before
subscribing, but relays are only added to the NostrClient pool when
subscriptions request them. This created a deadlock where no relays
would ever connect.
Now uses relayStatuses.keys (configured relays) to initiate the
subscription, which triggers the NostrClient to add those relays
to its pool and connect to them.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add DesktopHttpClient and DesktopRelayConnectionManager for relay WebSocket connections
- Implement AccountManager with key generation and nsec/npub login support
- Create LoginScreen with key import and new key generation UI
- Build FeedScreen with live global feed subscription and note cards
- Add ProfileScreen showing account info and logout
- Update RelaySettingsScreen with connection status and relay management
- Configure JDK 21 toolchain for desktop builds
- Add shared UI analysis documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add desktopApp module with JVM entry point and sidebar navigation
- Add Claude specs for AI-assisted development:
- Agent definitions: nostr-protocol, kotlin-multiplatform, compose-ui, kotlin-coroutines
- Skills: quartz-kmp conversion, compose-desktop patterns
- Commands: desktop-run, nip, extract
- Update Gradle configuration with Compose Multiplatform 1.7.1 plugin
- Add coroutines and secp256k1 JVM dependencies to version catalog
Next steps:
- Convert Quartz library to full KMP (expect/actual for crypto)
- Implement relay connections in desktop app
- Share UI components between Android and Desktop
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move re-record higher up tp avoid google assistant on longpress
Reply to a reply fails to upload
Verify deletion of temp voice files
Code review:
- temp file cleanup on disposal
- Made uploads cancellable
- recording must exist; metadata-only no longer enables Send)
- Removed duplication
- Removes old datetime dependencies from Quartz
- Moves compose.runtime dependencies to compose.runtime.annotation
- Adds dependency on coroutines directly (instead of through compose runtime)
- Removes old secp256 target dependencies
- Adds Default scope for NostrClient and Relay Authenticator
- Updates readme
* 'main' of https://github.com/vitorpamplona/amethyst:
fix lint warnings
add custom serializer that accepts both single integers and arrays add tests
New Crowdin translations by GitHub Action
updated cz, de, pt, sv
* 'main' of https://github.com/vitorpamplona/amethyst:
Remove delay, and implement custom moving function that updates the list in one event, rather than two with the previous approach.
Increase delay time in moveBookmark. Need to find a better alternative to delay.
String resources.
Re-introduce display stats for bookmark list, focusing on total bookmarks size per category.
Support moving articles/posts from private to public and vice versa.
* 'main' of https://github.com/vitorpamplona/amethyst: (23 commits)
Fix default permissions
Add Bookmark group metadata edit to routes/nav. Make refactors to make use of it.
Bookmark group metadata edit screen, with VM and string resources. Add methods to LabeledBookmarkListEvent and LabeledBookmarkListsState. Support image urls in LabeledBookmarkList.
String resources. Remove unneeded Todo.
Add bookmark list addition/removal to the note dropdown menu.
Article Bookmark management screen. Add routes and nav entries for both screens.
Post Bookmark management screen.
Other Hashtag/Link related refactors.
Remove HashtagListView and LinksListView due to spec changes, with their entries in BookmarkType.
Use stringResources. Implement the necessary callbacks in BookmarkGroupScreen. Disable component stats display in BookmarkGroupItem until Link/Hashtag support is implemented.
Bring ArticleListView up to speed. Fix callback naming mistake in BookmarkGroupItemOptions and refactor usages accordingly.
First category-based screen: PostListView. Modify NoteCompose to use a different note options menu when in the PostListView.
Fix some methods in LabeledBookmarkListsState. Add useful methods to LabeledBookmarkList and BookmarkGroupViewModel.
Introduce BookmarkGroupScreen, with it's associated ViewModel, and the different category-based views.
Move BookmarkType to an enum instead, for serialization purposes.
Slight function name refactor in LabeledBookmarkListsState, and inclusion of a method to reference a particular bookmark group. Add route for viewing a bookmark group, and wire UI accordingly.
Change the UI of BookmarkGroupItem to add buttons for viewing parts of the bookmark group.
Add support for LabeledBookmarkListEvent in the LocalCache, and EventFactory(to prevent crashes when creating one).
Add bookmark groups option to side menu. Make them work by hooking them up in Account.
Build out UI for the list of labeled bookmarks, or bookmark groups, borrowing some components from elsewhere.
...
* 'main' of https://github.com/vitorpamplona/amethyst:
add a convertExeptions function
added response helper
check for error result
create a RemoteSignerManager
add a fromBunkerUri helper
add a signer result interface for remote signer
implement the functions of nip46
return BunkerResponse from connect
- Add connect, getPublivKey and ping - send NostrConnectEvent to relays
- Add connect, getPublivKey and ping - send NostrConnectEvent to relays
Create NostrSignerRemote
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
New Crowdin translations by GitHub Action
user correct overlad that accepts argument
New Crowdin translations by GitHub Action
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
updated cz, de, pt, sv
New Crowdin translations by GitHub Action
Update strings.xml
# Conflicts:
# amethyst/src/main/res/values/strings.xml
* 'main' of https://github.com/vitorpamplona/amethyst:
optimise imports
synchronize all cache mutations and supply sinceRelaySet so callers get snapshot copies rework groupByRelayPresence to build relay snapshots via sinceRelaySet, filtering with immutable lists to prevent ConcurrentModificationException when relays update mid-iteration
Hardened EOSEAccountFast against concurrent access so callers no longer iterate over live mutable maps
Adjusted subscription cleanup to avoid mutating the watcher map while iterating it, preventing the ConcurrentModificationException when accounts switch
# Conflicts:
# amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt
rework groupByRelayPresence to build relay snapshots via sinceRelaySet,
filtering with immutable lists to prevent ConcurrentModificationException when relays update mid-iteration
* 'main' of https://github.com/vitorpamplona/amethyst:
New Crowdin translations by GitHub Action
code simplification and deduplication
code simplification and deduplication
Add blurhash and dim to Nip96Uploader.kt
added blurhash to blossom upload
New Crowdin translations by GitHub Action
Update README to mark video capture as complete
Parse for video segments and exclude them from image gallery
add VideoSegment and failing tests
Fix location being added to note even after deselecting it
optimise imports
New Crowdin translations by GitHub Action
update cs, de, sv, pt translations
Revert "update buffer to 64kb"
update buffer to 64kb
Change length to Long from Int: avoids potential overflow, Long seems to be used everywhere else
Create a CountingInputStream utility to avoid duplication Prevent INT overflow in BlossomUploader.kt Connection Cleanup in ImageDownloader.kt Added try-finally in Sha256Hasher.jvmAndroid.kt
Add streaming hash utility function to quartz multiplatform, follow the existing pool/worker design Change hashing in ImageDownloader.kt to use streaming
stream file to calculate both hash and size without loading it all at once
# Conflicts:
# amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/followsets/FollowSetScreen.kt
- Adds people and follow list cache on the account class
- Updates TopNav to use the new caching
- Removes redundant viewModels for list feeds
- Adds the ability to add and remove users from the List screen
Swapped the local var orientation by remember { mutableStateOf(...) } for an explicit MutableState: we now create orientationState = remember(firstImage) { mutableStateOf(...) }, update it with
orientationState.value = …, and return orientationState.value.
- Maintain order of incoming messages for relay listeners
- Defers all processing of incoming messages to coroutines via channels, freeing OkHttp's thread as soon as possible.
- Simplifies the main relay class by using attached listener modules for each function of the relay client.
- Migrate defaultOnConnect calls to become listener based and moved to NostrClients
- Treat counts as query only, not subscriptions.
- Coordinates REQs so that if an update is required to be sent but the server has not finished processing events, waits for it to finish and sends it later as soon as EOSE or Close arrives
- Correctly sustain the local state of each Req.
- Creates an Account follow list per Relay state that only includes shared relays as a better source of functioning relays
- Changes UserLoading features in a tentative to make them faster since they are used by all functions in the app.
- Correctly marks EOSE for filters that are aligned with the Req State from NostrClient
- Avoid subsequent REQ updates before EOSE or CLOSE calls.
- Refactoring RelayClient listener to be not dependent of which module is active for a relay client.
- Refactors authenticators to do complete operation as a module
- Breaks down Relay Client modules (Auth, Reqs, Counts, Event submissions) in the Relay Pool class.
- Creates listeners just for special REQ situations
- Move statistics to outside the base relay class as a listener
- Move logs to outside the base relay class as a listener
- Better structures a Standalone Relay client
- More appropriately communicate errors to the listeners
- Remove relay state on listeners
Safe file size lookup using OpenableColumns.SIZE
if (continuation.isActive) before resuming.
Better logging levels (Log.e for errors, Log.w for warnings).
Configurable compression timeout
- Fully converts OpenTimestamp Java codebase to Kotlin, migrating the sync and async HTTP call interfaces to OkHttp and coroutines
- Redesigns parsing of relay commands, messages and filters for performance in Jackson.
- Starts the use of KotlinX Serialization when speed is not a requirement
- Migrates all Jackson field annotations to Kotlin Serialization
- Migrates Regex use in Quarts to Kotlin's Regex class
- Migrates Base64 from Android to Kotlin
- Migrates UUID from Android/Java to Kotlin
- Migrates LRUCache usage from Android/Java to Kotlin collections
- Migrates all String to bytearray conversions to Kotlin methods
- Migrates all System.arraycopy calls to kotlin native ones.
- Separates parsing code from the data classes Companion objects
- Exposes Rfc3986 normalizations to each platform.
- Exposes URI parsing classes to each platform.
- Exposes URL Encoders to each platform.
- Exposes BigDecimal to each platform.
- Exposes the Url Detector to each platform.
- Exposes MacInstances to each platform
- Exposes Diggest instances to each platform.
- Exposes a BitSet to each platform.
- Exposes GZip to each platform.
- Exposes Secp256k1 to each platform.
- Exposes SecureRandom to each platform.
- Exposes Time in seconds to each platform.
- Exposes the LargeCache to each platform.
- Exposes AES CBC and AES GCM encryption/decryption to each platform
- Migrate test assertions to Kotlin Tests
- Exposes Address class to each platform because of the Parceleable
- Creates our own ByteArrayOutputStream.
- Removes Lock features inside the Bloomfilters because we don't need data consistency/
- Migrates UserMetadata parser from Jackson to Kotlin serialization
- Removes the need for Static methods in each tag.
- Adds an event template serializer
- Adds KotlinX Datetime to migrate some of the date-based logs
- Adds support for LibSodium in the JVM platform
- Creates a shared test build for iOS targets
- Fixes several usages of Reflection when serializing classes
- Fixes a bug on loading RelayDB for the HintBloom filter test
- Increases the Bloom filter space to better use hints in the app.
- Removes support for iOS in x86
- Creates a Jackson mapper just for NIP-55, which stays in the Android build only.
- Keeps the event store in the android build as well.
- Removes @Syncronized tags in favor of Mutexes.
- Improved sendAndWaitForResponse NostrClient method to properly account for returns from each relay.
- Removes the need for GlobalScope and async calls in the downloadFirstEvent method.
- Restructures the parser and serialization of the relay messages and commands for performance with Jackson
- Removes the dependency on Jackson's error classes across the codebase.
- Moves the hint to quote tag extension methods to their own packages.
- Speeds up the generation of Bech32 addresses
- Migrates NIP-6 and Blossom uploads to use Kotlin Serialization
Increased maxRequestsPerHost in release version to 20 from default 5 to allow for more concurrent connections to a relay (subscribing to timeline + mentions + DMs + profile updates + notifications, etc..)
- Switches TorSettings to be per Application and not per Account anymore
- Since TorSettings is now global, moves the okHttpClient determinations out of the Account-based classes into the Application.
- Since TorSettings is now global, set's up Coil's image loader only once when creating the Application
- Moves UISettings state to App Modules instead of viewmodel
- Migrates TorSettings and UISettings to DataStore
- Accounts now have their own coroutine scopes to allow cancellation when they are unloaded/logged off
- New tor evaluator service for relay connections now uses all account's trusted relays and dm relays at the same time.
- Migrates composable-state-based UISettings to Flow-based UI settings, while observing connectivity status
- Removes the displayFeatures and windowSizeClass from the shared model
- Fixes not requesting Notification Permissions for APIs older than Tiramisu in the FDroid flavor
- Moves the NIP-11 document cache from singleton to the App Modules
- Avoids using AccountViewModel to check NIP-11 Relay documents
- Moves the UI Settings usage in composables to functions that do not observe the state since they don't need to refresh the screen when changed.
- Refactors UI Settings screen to separate components and remove the sharedViewModel
- Only starts Internal Tor if that option is selected in the TorSettings.
- Turn TorSettings into a data class to observe changes to it
- Drops the SharedPreferences ViewModel to use UISettingsFlow directly from App Modules
- Reorganizes OTS Events after simplification of the OkHttp based on TorSettings.
Simplify collectConsecutiveImageParagraphs: just return the end index
Extracting common signature parameters into a RenderContext data class
Extracting GalleryImage helper function
- Fix onBroadcastList, making sure to get the latest changes to the set before broadcast.
-Make a separate component for FollowSet list items, and make the delete button look a bit better(?).
NostrUserListVM: - Minor cleanup.
According to PR #1444, commit 64df323, which changed the hyphen that was being applied to all languages to a space, this is the adjustment made in English and will be followed for other languages.
[pt_rbr already adjusted]
Changed the hyphen to a space to move the word "auto-translated" (and its translations) to "translations_auto" string and "from" (and its translations) to "translated_from" string, as this hyphen only exists in some languages.
More compact translation, with corrected spelling (there is no "automaticamente-traduzido" with dash in Portuguese, so I changed it to 'autotraduzido' and moved the dash away to use as a ':' ) and visually more pleasing.
- Optimizes the display of the yellow zap when the zap event comes before the pay payment confirmation
- Fixes animations for a new zap over an existing zap.
Moves NIP-55 calls to include an ID per call, not per event.
Adds error handling facilities to the Signer functions.
Moves the indexing of the decrypted objects to outside the LocalCache
Migrates Signers to become suspending functions.
Migrates Decryption caching systems to outside the Events themselves.
Migrates all NIP-51 lists to the new structure.
Migrates Drafts and NIP-04 and NIP-17 DMs to the new structure
Migrates Bookmarks to the new structure.
Changes the Room route to avoid using hashcode.
TODO:
Rename Settings to AppSettings (routes, classes)
Don't show users own languages
Remove language from drop down
Add explainer
Change to dropdown with Xs
- Adds support for creating and replying to NIP-22 hashtag scope posts
- Refactors NIP-73 and NIP-22 on quartz to better support scoped replies
- Creates New post screens for both hashtags and geolocated posts.
- Refactors all Datasource classes into a 3 layer design with composeSubscriptions being registered, passed to a group of EOSE managers that can change eose caching capabilities based on needs, and migrates the old subscription Orchestrator into a simpler controller
- Breaks down all Datasource into multiple filter functions
- Improves EOSE support for many of the base filters like on NIP-04 DMs, notifications, etc
- Moves EOSE cache for public, non-user-dependent items like channels, hashtags, etc to their own caching system.
- Improves the Relay speed logger to better track how many events are being received, and check for duplications
- Adds support for following ephemeral chats
- Adds support for live events at the top of the feed.
- Adds support for NIP-51, kind:10005 public chat lists
- Adds support for Channel feeds
- Moves following of NIP-28 chats from the Contact List to kind: 10005
- Disables following of events at the Contact list
- Improves gallery display to slightly override profile pictures when in list
- Starts the Account refactoring by moving custom Emoji, EphemeralList and PublicChat lists to their own packages
- Refactors NIP-51 lists to use common classes of private tags instead of general list classes.
- Starts to separate all Public chats into their own database.
- Removes old account upgrades from the local storage
- Refactors url NIP-11 loading and unifies icon
- Reduces the dependency of Relay classes in the LocalCache, Notes and User classes
- Adds New product button on the Marketplace tab
- Adds imeta tags for images and urls inside the content of the Classifieds.
- Shows multiple images on the post and thread view.
- Removes the option for NIP-95 images on Classifieds.
- Creates a new route for new products
- Adjusts quarts to process images with iMeta tags
- Moves filter assemblers, viewModels and DAL classes to their own packages.
- Creates Composable observers for Users and Notes
- Deletes most of the secondary LiveData objects in the move to Flow
- Manipulates nostr filters depending on the account of the current screen, not a global account.
- Unifies all FilterAssembly lifecycle watchers to a few classes.
- Prepares to separate The Nostr Client as an Engine of Amethyst.
- Moves the pre-caching processor of new events from the datasource to the accountViewModel
- Reorganizes search to be per-screen basis
- Moves authentication to a fixed Coordinator class for all accounts in all relays.
- Moves NOTIFY command to its own coordinator class for all accounts
- Moves the connection between filters and cache to its own class.
- Significantly reduces the dependency on a single ServiceManager class.
- Migrated services to Flows that are active while their flows remain subscribed
- Improved OTS Decoupling
- Moved OTS verification procedures from the local cache to the data source
- Migrated the forceProxy options to lambdas that return an OkHttpClient
- Isolated Connectivity services, from Compose to Flow
- Isolated Tor services, from Compose to Tor (only starts if the Tor option is marked as internal)
- Isolated Memory trimming services, from Compose to Flow
- Isolated Image Caching services, from Compose to Flow
- Isolated Video Caching services
- Isolated Logging services
- Isolated NIP-95 Caching services
- Isolated Pokey receiver services
- Improved support for Tor in push notifications
- Isolated OkHttpClient services as flows
- Reduced the coupling of Context objects with singleton objects.
- Migrated UserFeedStates, StringFeedStates and ZapFeedStates to MutableStateFlow, avoiding feed updates on Main
- Forces a reconnect into relays that lost connection if any tor, connectivity or account changes
- Speeds up Base64 parser
- Only starts Tor when the "Internal" option is selected on the privacy settings
- Adds a mutex to make sure tor only starts once.
- Fixes bug when setting port before the service is running
- Moves tor start and stop to Compose actions, instead of activity
- Adds a 5 second delay before disconnecting Tor on the App's onPause
- Adds support for the new and old ways to use reverse geolocation services.
- Hits all existing providers for location updates instead of just the network ones
- Uses past last known locations to kick start the cache.
- Improves logging to check potential failures in production.
- Separates all DM attachments as their own objects and holds them in cache until the message is sent.
- Adds previews for any number of urls, events and media uploads on new post screens.
- Adds zap split, zap raiser, geolocation symbols for DMs and channel messages
- Separates each component that can be added to a new post into its own package.
- Unifies processing for User Suggestions, Emoji Suggestions, Url Previews, location, draft versions, among text inputs
- Improves tonal rendering on Light theme
- Lightens card surfaces
- Adjusts surfaceTint on Dark theme to match the elevation differences on the Light theme
- Adds picture upload for NIP-28 metadata
- Adds support for relay hints on NIP-28
- Improves edit field for chats
- Improves keyboard support for relay lists edit fields
- Created a pool of ExoPlayers to avoid creating new instances when scrolling.
- More accurately tracks MediaSessions
- Splits Caching layers into their own files
- Splits VideoView into multiple files
- Specializes the old Playback Manager into pools.
- Reorganizes video playback dependencies inside a single package.
- Structures callback uris to be per media item as opposed to per controller.
- Fixes a bug on edits showing a previous edit.
- Refactors user suggestions and lastword procedures
- Adds a DM inline reply from other parts of the app, like on notification.
- Hides Retweet button from DMs on Notifications
- Fixes colors for drafts in chats.
- Adds a nav function that computes the destination only after the user clicks on it and on a coroutine.
Speeding up Bech32 and Hex processing
Updating Boosts and Reposts to the new way of declaring tags
Renaming the SHA256 hash tag from NIP94 to not confuse with # hashtags
Decoupling Encryption and Decryptions from CryptoUtils
Decoupling live instances of JNI bindings for Secp and LibSodium from CryptoUtils
Decoupling key cache from CryptoUtils
Reorganizes NIP-04 to match new package structure
Adjusts test structures to match
- Define each tag in their own class.
- Allow extension functions to additional responsibilities to other classes
- Migrate from hardcoded tag filters in events to the Tag's parser and assemble functions.
- Migrate hardcoded event.create to builders that use extension functions
- Restructures threading infrastructure for NIP-10
- Decouple the event signing from the Event building functions via event templates
- Create classes to represent Tags and TagArrays and use extension functions to add domain-related methods to the tag array of each nip.
- Uses external functions on event template builders to better point to which functions and which tags can be used in which event kinds.
- Separates Event kinds in packages inside each nip.
- Improves support for NIP-89
- Correctly establishes which imeta params can be used in each nip (video, picture, files)
- Decouples the iMeta builder from any nip.
- Fixes mute list word and user removal when inserted from a different client.
- Migrates the Account class to avoiding having to build each Event inside of it
- System integrations (notifications, file pickers)
**Rationale:** ViewModels contain platform-agnostic state management (StateFlow/SharedFlow) and business logic. Screens consume ViewModels but render differently (Desktop sidebar + content area vs Android bottom nav).
### Step 4: Extract Shared Components
When extracting UI components:
1. Identify reusable composables in Android code
2. Move to `commons/commonMain/` (consult `/compose-expert` for patterns)
3. Create expect/actual declarations for platform-specific behavior (consult `/kotlin-multiplatform`)
4. Update both Android and Desktop to use shared component
**Note:** `quartz/` is protocol-only (no composables). Shared UI goes in `commons/` after converting it to KMP.
## Build Commands
```bash
# Run desktop app
./gradlew :desktopApp:run
# Run Android app
./gradlew :amethyst:installDebug
# Build Quartz for all targets
./gradlew :quartz:build
# Run tests
./gradlew test
# Format code
./gradlew spotlessApply
```
## Quartz KMP Structure
The Quartz library uses expect/actual for platform-specific implementations:
```kotlin
// commonMain - shared protocol logic
expect class CryptoProvider {
fun sign(message: ByteArray, privateKey: ByteArray): ByteArray
fun verify(message: ByteArray, signature: ByteArray, publicKey: ByteArray): Boolean
}
// androidMain - uses secp256k1-kmp-jni-android
actual class CryptoProvider { /* Android implementation */ }
// jvmMain - uses secp256k1-kmp-jni-jvm
actual class CryptoProvider { /* JVM implementation */ }
-`references/immutability-patterns.md` - @Immutable annotation, data classes, ImmutableList/Map/Set
**Differentiation:** Complements kotlin-coroutines agent (deep async). This skill = Amethyst Kotlin idioms (StateFlow state management, sealed for type safety, @Immutable for Compose, DSL builders).
**Status:** ✅ SKILL.md (455 lines) + 4 references created at `.claude/skills/kotlin-expert/`
**10-Step Progress:**
1. ✅ UNDERSTAND - Defined scope (Flow/sealed/DSL/immutability/inline)
2. ✅ EXPLORE - Found 173 @Immutable events, StateFlow in AccountManager/RelayManager, SignerResult generics, TagArrayBuilder
3. ✅ RESEARCH - StateFlow vs SharedFlow, sealed class vs interface best practices 2025
4. ✅ SYNTHESIZE - Extracted Amethyst patterns (hot flows for state, sealed for results, @Immutable for perf)
-`references/desktop-navigation.md` - NavigationRail vs BottomNav patterns
-`references/keyboard-shortcuts.md` - Standard shortcuts by OS with DesktopShortcuts helper
-`references/os-detection.md` - Platform detection, file paths, system integration
**Differentiation:** Desktop-only APIs, OS conventions (Cmd vs Ctrl), NavigationRail, delegates build to gradle-expert and shared code to kotlin-multiplatform/compose-expert.
**Status:** ✅ SKILL.md + 4 references created at `.claude/skills/desktop-expert/`
description: Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector icons, or determining whether to share UI in commonMain vs keep platform-specific. Delegates navigation to android-expert/desktop-expert. Complements kotlin-expert (handles Kotlin language aspects of state/annotations).
---
# Compose Multiplatform Expert
Visual UI patterns for sharing composables across Android and Desktop.
## When to Use This Skill
- Creating or refactoring shared UI components
- Deciding whether to share UI in `commonMain` or keep platform-specific
- Building custom ImageVector icons (robohash pattern)
- State management: remember, derivedStateOf, produceState
- Recomposition optimization: visual usage of @Stable/@Immutable
Expert in Compose Multiplatform Desktop development for AmethystMultiplatform. Covers Desktop-specific APIs, OS conventions, navigation patterns, and UX principles.
## When to Use This Skill
**Auto-invoke when:**
- Working with `desktopApp/` module files
- Using Desktop-only APIs: `Window`, `Tray`, `MenuBar`, `Dialog`
description: Use when comparing Android strings.xml locale files to find untranslated string resources, missing translation keys, or preparing translation work for a specific language
---
# Find Missing Translations
## Overview
Extract string resource keys from the default `values/strings.xml` that are absent in a target locale's `strings.xml`, excluding non-translatable entries. Outputs missing keys and offers to translate them.
## When to Use
- Need to find untranslated strings for a specific locale
- Preparing a batch of strings for a translator
- Checking translation coverage after adding new features
## Target Locales
The default set of locales (unless the user specifies otherwise):
### 2. Find missing keys using cs-rCZ as reference
Always diff against `cs-rCZ` first — it is the most complete locale and serves as the reference. Any keys missing in `cs-rCZ` will also be missing in the other target locales.
```bash
# Extract translatable keys from default (exclude translatable="false")
description: Use when auditing or migrating Log calls to lambda overloads, after adding new logging, or checking for string interpolation in Log.d/i/w/e calls that waste allocations when the log level is filtered out
---
# Find Non-Lambda Log Calls
## Overview
Locates `Log.d/i/w/e` calls that use string interpolation without the lambda overload, wasting string allocation when the log level is filtered out in release builds.
## When to Use
- After merging branches that add new logging
- Periodic audit of logging hygiene
- After migrating `android.util.Log` usages to the shared `Log` wrapper
## What to Flag
Calls with **string interpolation** (`$` in message) that do **not** pass a throwable:
```kotlin
// FLAG - interpolation without lambda, no throwable
### Step 2: Find interpolated Log.w/Log.e without throwable
```
pattern: Log\.(w|e)\("[^"]+",\s*"[^"]*\$
type: kotlin
```
```
pattern: Log\.(w|e)\(\w+,\s*"[^"]*\$
type: kotlin
```
Then **manually exclude** lines where a throwable is passed as third argument (ending with `, e)`, `, throwable)`, etc.). Check the actual line — a catch block catching `e` doesn't mean `e` is passed to the Log call.
### Step 3: Verify no android.util.Log leakage
```
pattern: android\.util\.Log\.(d|i|w|e|v)\(
type: kotlin
```
These bypass the `Log.minLevel` filter entirely. Exclude `PlatformLog.android.kt` which is the wrapper implementation.
## Fix Pattern
```kotlin
// Before
Log.d("Tag","Processing event ${event.id} from ${relay.url}")
// After
Log.d("Tag"){"Processing event ${event.id} from ${relay.url}"}
```
## Do NOT Convert
- Calls passing a `Throwable` parameter - the lambda overload `(tag) { message }` has no throwable parameter
- Static string calls with no `$` interpolation - no allocation benefit
**Key insight:** Dependencies flow DOWN. Lower modules never depend on upper modules. This enables code sharing without circular dependencies.
**The jvmAndroid pattern:** Unique to this project. A custom source set between commonMain and {androidMain, jvmMain} for JVM-specific code shared by Android and Desktop. Not standard KMP, but critical for this architecture.
## Version Catalog Philosophy
All dependencies centralized in `gradle/libs.versions.toml`. Think "single source of truth."
implementation(libs.lazysodium.java)// JAR implicit
implementation(libs.jna)
```
**Critical:** Never put JNA in jvmAndroid or commonMain. Platform-specific packaging only.
### 3. Compose Versions
**The problem:** Two Compose ecosystems (Multiplatform + AndroidX) must align, or duplicate classes.
**Current project config:**
```toml
composeMultiplatform="1.9.3"# Plugin + runtime
composeBom="2025.12.01"# AndroidX Compose BOM
kotlin="2.3.0"
```
**Rule:** Compose Multiplatform version must be compatible with Kotlin version. Check: https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compatibility-and-versioning.html
AmethystMultiplatform uses Gradle's version catalog (`gradle/libs.versions.toml`) to centralize dependency management. This ensures version consistency across all modules and simplifies updates.
## Structure
### sections
```toml
[versions]# Version numbers (referenced by libraries and plugins)
description: Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1) State management patterns (StateFlow/SharedFlow/MutableStateFlow), (2) Sealed classes or sealed interfaces, (3) @Immutable annotations for Compose, (4) DSL builders with lambda receivers, (5) inline/reified functions, (6) Kotlin performance optimization. Complements kotlin-coroutines agent (async patterns) - this skill focuses on Amethyst-specific Kotlin idioms.
---
# Kotlin Expert
Advanced Kotlin patterns for AmethystMultiplatform. Covers Flow state management, sealed hierarchies, immutability, DSL builders, and inline functions with real codebase examples.
## Mental Model
**Kotlin in Amethyst:**
```
State Management (Hot Flows)
├── StateFlow<T> # Single value, always has value, replays to new subscribers
1.**Multiple inheritance**: Subtype can implement other interfaces
2.**Variance**: Supports `out`/`in` modifiers for generics
3.**No constructor**: Can't hold state directly (subtypes can)
4.**Nested hierarchies**: Can create sub-sealed hierarchies
### Sealed Class vs Sealed Interface
| Feature | Sealed Class | Sealed Interface |
|---------|--------------|------------------|
| **Constructor** | ✅ Can hold common state | ❌ No constructor |
| **Inheritance** | ❌ Single parent only | ✅ Multiple interfaces |
| **Generics** | ❌ No variance | ✅ Covariance/contravariance |
| **Use case** | State variants | Result types, contracts |
**Decision tree:**
```
Need to hold common data in base?
YES → sealed class
NO → sealed interface
Need generics with variance (out/in)?
YES → sealed interface
NO → Either works
Subtypes need multiple inheritance?
YES → sealed interface
NO → Either works
```
**Amethyst examples:**
-`sealed class AccountState` - state variants with different data
-`sealed interface SignerResult<T>` - generic result types with variance
**See:**`references/sealed-class-catalog.md` for all sealed types in quartz.
---
## 3. Immutability & Compose Performance
### @Immutable Annotation
**Mental model:**@Immutable tells Compose "this value never changes after construction." Compose can skip recomposition if @Immutable object reference doesn't change.
**Mental model:** Compose tracks state changes by comparing references. If an `@Immutable` object reference doesn't change, Compose skips recomposition.
```kotlin
// Without @Immutable - Recomposes on every parent recomposition
description: Integration guide for using the Quartz Nostr KMP library in external projects. Use when: (1) adding Quartz as a Gradle dependency, (2) setting up NostrClient with WebSocket, (3) creating/signing/sending events, (4) building relay subscriptions with Filter, (5) handling keys with KeyPair/NostrSignerInternal, (6) using Bech32 encoding/decoding (NIP-19), (7) platform-specific setup (Android vs JVM/Desktop), (8) NIP-57 zaps, NIP-17 DMs, NIP-44 encryption in external projects.
---
# Quartz Integration Guide
Reference for integrating `com.vitorpamplona.quartz:quartz` into external Nostr KMP projects.
The Amethyst app for Android does not collect or process any personal information from its users. The app is used to connect to third-party Nostr servers (also called Relays) that may or may not collect personal information and are not covered by this privacy policy. Each third-party relay server comes equipped with its own privacy policy and terms of use that can be viewed through the app or through that server's website. The developers of this open-source project or maintainers of the distribution channels (app stores) do not have access to the data located in the user's phone. Accounts are fully maintained by the user. We do not have control over them.
The Amethyst app for Android does not collect or process any personal information from its users.
The app may collect a per-device token, your public key, and a preferred Relay to connect to and provide push notification services through Google's Firebase Cloud Messaging. Other than that, the data from connected accounts are only stored locally on the device when it's required for the functionality and performance of Amethyst. This data is strictly confidential and cannot be accessed by other apps (on non-rooted devices). Phone data can be deleted by clearing Amethyst's local storage or uninstalling the app.
The app is used to browse third-party Nostr servers (called Relays) that may or may not collect personal information and are not covered by this privacy policy. Each third-party relay server comes equipped with its own privacy policy and terms of use that can be viewed through the app or through that server's website. The developers of this open-source project or maintainers of the distribution channels (app stores) do not have access to the data located in the user's phone. Accounts are fully maintained by the user. We do not have control over them.
Amethyst offers a few options to upload pictures and videos in order to post them online. You can choose the server at your discretion. Similar torelays, such services are independent of the app and have their own privacy policy and terms of use.
The app may collect a per-device token, your public key, and a preferred Relay to connect to and provide push notification services through Google's Firebase Cloud Messaging. Other than that, the data from connected accounts is only stored locally on the device when it's required for the functionality andperformance of Amethyst. This data is strictly confidential and cannot be accessed by other apps (on non-rooted devices). Phone data can be deleted by clearing Amethyst's local storage or uninstalling the app.
### Privacy with Relays services
Amethyst offers several options for uploading pictures and videos to post online. You can choose the server at your discretion. Similar to relays, such services are independent of the app and have their own privacy policy and terms of use.
Your internet protocol (IP) address is exposed to the relays you connect to. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g. a VPN) from trackers online.
### Privacy with Relay services
Your Internet Protocol (IP) address is exposed to the relays you connect to. If you want to improve your privacy, consider utilizing a service that masks your IP address (e.g., a VPN) from trackers online.
The relay can also see which public keys you are using and what information you are requesting from the network. Your public key is tied to your IP address and your relay filters.
Relays have all your data in raw text. They know your IP, your name, your location (guessed from IP), your pub key, all your contacts, and other relays, and can read every action you do (post, like, boost, quote, report, etc) with the exception of the content inside Private Zaps and Private DMs.
While the content of direct messages (DMs) is only visible to you, and your DM Nostr counterparty, everyone can see when you and your counterparty are DM-ing each other. Image uploads in the DM screen use one of the chosen image servers and simply paste the image link on the DM text. Your uploaded pictures are available to anyone with that direct link.
While the content of direct messages (DMs) is only visible to you and your DM Nostr counterparty, everyone can see when you and your counterparty are DM-ing each other. Image uploads in the DM screen use one of the chosen image servers and simply paste the image link into the DM text. Your uploaded pictures are available to anyone with that direct link.
### Visibility & Permanence of Your Content on Nostr Relays
#### Information Visibility
Content that you share can be shared with other relays by any user of the network.
Information that you share is publicly visible to anyone reading from relays that have your information. Your information may also be visible to Nostr users who do not share relays with you.
The information you share is publicly visible to anyone reading from relays that have access to your information. Your information may also be visible to Nostr users who do not share relays with you.
#### Information Permanence
Information shared on Nostr should be assumed permanent for privacy purposes. There is no way to guarantee deleting or editing any content once posted.
## Child safety standards
Amethyst does not knowingly collect information from children. The app has no age verification because it collects no personal information from anyone. The application is 17+. We rely on Google Play's age verification to make sure the user downloading the app is an adult. Since we do not control which relays the user connects to, there is no content moderation beyond the standard block post, block account, and report post and/or account that will hide the content from the user.
## Terms of Use
### For versions downloaded from Google's Play Store
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.