TextFieldBuffer.addStyle() positions are not adjusted by subsequent
replace() calls. When multiple mentions had different-length display
names, styles for later mentions became misaligned. Split into two
phases: all replace() calls first, then all addStyle() calls with
cumulative-shift-corrected positions.
https://claude.ai/code/session_01SSsxsfJLbRiesBBhQFEVUd
System.loadLibrary("arti_android") runs when ArtiNative is first
accessed. Previously this happened in TorService.init (via
setLogCallback), which ran on main thread during AppModules creation.
Moved setLogCallback into start(), which runs on Dispatchers.IO.
Now all JNI calls — including the native library load — happen off
main thread.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
All log messages go through send_log_to_java() → Kotlin ArtiLogCallback
→ Log.d("TorService"), which already writes to logcat. The android_logger
module was a second FFI call to __android_log_write that duplicated
every line.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
All features and APIs verified present in 0.41:
- tokio, rustls, compression, onion-service-client, static-sqlite
- TorClient::create_bootstrapped, from_directories, connect()
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
- Set default-features = false on arti-client and tor-rtcompat
- Removed bridge-client (UI doesn't expose bridge config yet)
- Narrowed tokio features from "full" to only what the SOCKS proxy
needs: rt-multi-thread, net, io-util, time, macros
Kept: tokio, rustls, compression, onion-service-client, static-sqlite
(all required for Amethyst's .onion relay support)
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
The Guardian Project's arti-mobile-ex AAR has three problems:
1. No 16KB page-aligned binaries (required for Google Play)
2. ArtiProxy's stop()+start() causes state file lock conflicts
(lock is tied to TorClient object lifetime, released only on GC)
3. ~140MB AAR size
Replace with a custom JNI bridge built from Arti source, following
BitChat's proven approach:
Build tooling (tools/arti-build/):
- build-arti.sh: Clones official Arti, compiles with cargo-ndk
for ARM64 + x86_64, NDK 25+ for 16KB page alignment
- Cargo.toml: Minimal deps with size-optimized release profile
- src/lib.rs: Custom SOCKS5 proxy with proper lifecycle:
- initialize() creates TorClient once (holds state lock forever)
- startSocksProxy() binds port and accepts connections
- stopSocksProxy() aborts listener only (TorClient stays alive)
This cleanly separates "stop routing traffic" from "destroy client"
Kotlin side:
- ArtiNative.kt: JNI declarations + ArtiLogCallback interface
- TorService.kt: Uses ArtiNative directly, start() initializes +
starts proxy, stop() only stops proxy (no lock issues)
- TorManager.kt: Restored stop() calls for OFF/EXTERNAL modes
since our native stop is now safe
Removed: arti-mobile-ex dependency from build.gradle and version catalog
Native libraries must be built separately:
cd tools/arti-build && ./build-arti.sh
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Arti's state file lock is released when the TorClient object is
garbage collected, not when stop() is called. Calling stop()+start()
on the same ArtiProxy creates a new internal TorClient that conflicts
with the old lock that hasn't been GC'd yet.
Solution: start ArtiProxy once, let it run for the process lifetime.
When the user turns Tor OFF or EXTERNAL, TorManager simply stops
emitting Active status — OkHttp stops routing through the proxy.
The idle proxy uses negligible resources and drops circuits when no
SOCKS connections are active.
This removes stop(), Mutex, NonCancellable, CompletableDeferred — all
the complexity that was trying to work around a fundamental Arti
design constraint.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
The root cause of file lock conflicts was creating new ArtiProxy
objects on each start. Even after stop() confirmed, build() could
race with OS-level lock release.
Now ArtiProxy is created once in the TorService constructor and
reused for the app's lifetime. start() and stop() just toggle it
on/off on the same instance. No more file lock conflicts.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
The fixed 2s delay was insufficient — Arti's native layer releases
file locks asynchronously. Now stop() waits for the actual
"state changed to Stopped" log confirmation via CompletableDeferred,
with a 10s timeout as safety net. This ensures the file lock is
truly released before start() creates a new ArtiProxy instance.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
EXTERNAL means another Tor process (e.g., Orbot) is running on the
device. No reason to keep our internal Arti alive alongside it.
Also removed the fallback that started Arti when the external port
was invalid — if the user chose EXTERNAL with a bad port, Tor should
be off, not silently falling back to internal.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Users in restrictive countries need Tor circuits torn down when they
switch to OFF — an idle proxy still maintains detectable connections.
stop() is now called only on explicit OFF toggle (user action), not on
flow pause/resume (app lifecycle). This avoids file lock races on
resume while ensuring OFF truly disconnects from the Tor network.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
ArtiProxy holds an exclusive filesystem lock. Destroying it on OFF and
recreating on INTERNAL caused lock conflicts because the native layer
needs time to release the lock.
Instead, create ArtiProxy once and never destroy it. When Tor is OFF
or EXTERNAL, the proxy sits idle with no SOCKS connections — negligible
resource usage. This eliminates all file lock race conditions.
Also wrap start/stop in NonCancellable to prevent transformLatest
cancellation from leaking half-initialized proxy instances.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
The callbackFlow-based TorService created a new ArtiProxy on every flow
collection. When the app paused and resumed, the old instance's file lock
wasn't released before the new one started, causing "Another process has
the lock on our state files" errors.
Redesigned TorService to own a single ArtiProxy with explicit start/stop:
- ArtiProxy is created once and reused across flow re-collections
- State exposed via MutableStateFlow instead of callbackFlow
- Mutex guards start/stop to prevent races
- TorManager calls service.start() and service.stop() explicitly when
switching between INTERNAL/OFF/EXTERNAL modes
- stop() includes a 2s settle delay for native file lock release
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Address gaps found in code review:
- Use AtomicBoolean/AtomicLong for state accessed from native Arti
log callback thread (was a data race)
- Add bootstrap timeout monitor (120s) that restarts Arti once if
bootstrapping stalls, following BitChat's inactivity pattern
- Clean up failed ArtiProxy instances before port retry
- Detect "Another process has the lock" fatal error from Arti logs
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Replace Guardian Project's tor-android (C Tor) and jtorctl with
arti-mobile-ex, a Rust-based Tor implementation. This eliminates
the Android Service binding complexity in favor of an in-process
ArtiProxy object.
Key changes:
- TorService: Replace ServiceConnection to org.torproject.jni.TorService
with direct ArtiProxy.Builder/start/stop API. Bootstrap state detected
via log parsing (following BitChat's pattern).
- TorServiceStatus: Remove TorControlConnection field (Arti doesn't
support jtorctl control protocol).
- RelayProxyClientConnector: Remove DORMANT/ACTIVE/NEWNYM control
signals. Arti manages its own circuit lifecycle internally.
- Dependencies: Replace tor-android + jtorctl with arti-mobile-ex 1.2.3.
TorManager's external API (status/activePortOrNull StateFlows) and all
downstream consumers (DualHttpClientManager, TorSettings, UI) are
unchanged.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Instead of replacing the new bookmark list, the migration now checks for
an existing kind 10003 event and merges old bookmarks into it, skipping
duplicates that already exist.
https://claude.ai/code/session_01U9sjQHQMVVHxYiesoXjqop
Rename the existing BookmarkListEvent (kind 30001) to OldBookmarkListEvent
and create a new BookmarkListEvent with kind 10003 as per the updated spec.
- OldBookmarkListEvent (kind 30001): Kept as-is for backward compatibility,
displayed as "Old Bookmarks" in the Bookmark Lists screen
- BookmarkListEvent (kind 10003): New replaceable event, all new bookmark
operations (save/check/remove) use this kind
- Both kinds are displayed as separate items in the Bookmark Lists screen
- Old Bookmarks screen includes a "Move All to New Bookmarks" button that
migrates public bookmarks to public and private to private
- Created PrivateReplaceableTagArrayEvent base class for replaceable events
with encrypted private tags (kind 10003 is in the 10000-19999 range)
- Updated all relay filters, search queries, and profile views to fetch
both kinds
- Updated Android and Desktop modules
https://claude.ai/code/session_01U9sjQHQMVVHxYiesoXjqop
Adds 6 preview functions covering:
- PictureCardCompose with title, without title, and multi-image
- PictureCardCaption with title, without title, and long content
- Uses ThemeComparisonColumn for light/dark theme comparison
- Uses mock PictureEvent JSON data consumed via LocalCache
https://claude.ai/code/session_01KtuzFmDZXk67tW8QxPqmMy
Wrap all Tor library interactions in try-catch blocks so that failures
in the Tor service (JNI, control connection, bind/unbind) are logged
and gracefully degrade to TorServiceStatus.Off instead of crashing
the entire app.
https://claude.ai/code/session_019JDZMTvVdJyc9z2WmoAimv
The visual transformation for message edit fields now processes nostr:npub1...,
nostr:nprofile1..., and @nprofile1... mentions in addition to the existing @npub1...
pattern. Both UrlUserTagOutputTransformation (new API) and UrlUserTagTransformation
(old API) are updated to resolve these to user display names.
https://claude.ai/code/session_012ijrLmft5PjcQ64zDwXJWj
Implements a dedicated picture feed screen with a custom card layout:
- Full-width image display with user avatar and name header
- Title and 3-line content preview below the image
- Reaction row (reply, boost, like, zap, share) at the bottom
- Top bar with follow list filter (same pattern as polls feed)
- Complete relay subscription pipeline for picture events
- Accessible from the navigation drawer
https://claude.ai/code/session_01KtuzFmDZXk67tW8QxPqmMy
Replace typed consume() overloads that simply forwarded to
consumeRegularEvent() or consumeBaseReplaceable() with direct calls
in the when dispatch block. This removes ~600 lines of boilerplate
without changing behavior.
https://claude.ai/code/session_017dM6dt1on3g3yhWSi69Pwq
When a user hits Quote, the new post screen opens with the nostr URI
pre-filled but the cursor was at the end (after the URI). This moves
the cursor to the beginning so the user can immediately start typing
their commentary.
https://claude.ai/code/session_01RPseKC9GJ8hs3GGBR85ezw
When events share the same createdAt timestamp, the tiebreaker sort by id
must be ascending to produce a stable, deterministic order. Fixed several
locations that either had no secondary sort, used descending id sort, or
used .reversed() which incorrectly flipped both sort directions.
https://claude.ai/code/session_01RvuyPf1x9wLf2DCgsSXLGz
Restructures the flat nip58Badges package into sub-packages following
the established nip88Polls pattern with proper tag classes, TagArrayExt,
and TagArrayBuilderExt for each event type:
- definition/ - BadgeDefinitionEvent (kind 30009) with NameTag, ImageTag,
DescriptionTag, ThumbTag supporting dimensions per spec
- award/ - BadgeAwardEvent (kind 8) with build() method
- profiles/ - BadgeProfilesEvent (kind 30008) with AcceptedBadge tag
for proper paired a+e tag parsing per NIP-58 spec
Adds build() companion methods to all three event types and full
spec compliance including image/thumb dimension support.
https://claude.ai/code/session_019Uvxfa7jJbjeprLxECoJ8s
Add a new notification channel for reactions (kind 7 events) so users
get notified when someone reacts to their posts. Follows the same
pattern as zap notifications with deduplication, time-window filtering,
and grouped summaries.
https://claude.ai/code/session_01J9rAA4oeFEfNcYD6GoJqqt
Enable the message field to receive images/GIFs from Android's keyboard
using Compose Foundation's contentReceiver API. When content is received,
it's routed through the existing selectImage flow for upload.
https://claude.ai/code/session_019GfY3MtCPcYn3wqTHCMSNu
The external signer registration logic was failing when the signer was
wrapped in a `NostrSignerWithClientTag`. Fix by:
1. Updating `ListenToExternalSignerIfNeeded` to unwrap the signer if it's
a `NostrSignerWithClientTag` containing an `IActivityLauncher`
2. Using the unwrapped launcher for intent registration and response handling
3. Renaming the intent callback to `intentLauncher` to avoid shadowing the `ActivityResultLauncher` variable
Use Channel(UNLIMITED) as a lock-free send queue and AtomicBoolean with
atomic exchange for the isSending flag, eliminating all synchronized blocks
while maintaining thread safety via a double-check pattern.
https://claude.ai/code/session_01KLRz7FJgWRtgCcZe3qD1VX
`equalImmutableLists` is public and uses referential equality (`===`) per element rather than structural equality (`==`). Without documentation, callers may assume normal list equality and get false negatives for value-equal but distinct instances. This is a non-obvious contract and should be documented explicitly to prevent subtle logic bugs.
Affected files: ListUtils.kt
Signed-off-by: Nguyen Van Nam <nam.nv205106@gmail.com>
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
Users can now search by name, npub, or NIP-05 identifier when adding
users to relay allow/ban lists, using the same UserSuggestionState
mechanism as the My List screens. The hex pubkey input was not
user-friendly since most users don't know their hex representation.
https://claude.ai/code/session_01SWReoziLKJKxrbwDWprQYs
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
Completes the TextFieldState migration for ShortNotePostViewModel,
LongFormPostViewModel, and their associated post screens (GeoHash,
Hashtag, ShortNote, LongForm).
https://claude.ai/code/session_01FDGf1Zi1pVvzFi3JY5agnJ
Migrates all new post screens from the old BasicTextField(TextFieldValue)
to the new BasicTextField(TextFieldState) API, which properly sets
EditorInfo.contentMimeTypes to enable GIF keyboard input.
Key changes:
- Add TextFieldState-based ThinPaddingTextField overload (keep old for readonly dropdowns)
- Create UrlUserTagOutputTransformation for the new OutputTransformation API
- Create TextFieldState extension functions (insertUrlAtCursor, replaceCurrentWord, currentWord)
- Update IMessageField interface to use TextFieldState + onMessageChanged()
- Migrate all IMessageField implementors: ChannelNewMessageViewModel,
ChatNewMessageViewModel, NewProductViewModel, NewPublicMessageViewModel,
CommentPostViewModel, ShortNotePostViewModel, LongFormPostViewModel
- Update PreviewState to accept String instead of TextFieldValue
- Add TextFieldState overload to UserSuggestionState.replaceCurrentWord()
- Update all post screen call sites
https://claude.ai/code/session_01FDGf1Zi1pVvzFi3JY5agnJ
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
Persist dismissed game IDs locally (SharedPreferences on Android,
java.util.prefs on Desktop) so completed games can be permanently
hidden from the chess lobby. Adds per-game dismiss button and
"Clear all" action when 2+ finished games exist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Mark recentlyLoadedGames timestamp eagerly in refreshGame (before
async fetch) to prevent concurrent fetches for the same game
- Clear focused game in removeGameId when the removed game matches,
so finished games stop being re-polled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Dedup incoming events by ID (bounded LRU set of 500) to prevent
redundant processing when multiple relays deliver the same event
- Filter non-chess kind 30 events early (isStart=false, isMove=false)
- Guard handleGameAccepted with recentlyLoadedGames check to prevent
N concurrent relay fetches when N move events arrive for same game
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
Deduplicate chess notify() into shared notifyChessEvent() helper using BaseChessEvent
Remove addCompletedGameDirectly, extend moveToCompleted with optional liveState param
Hoist currentUser lookup to top of ChessLobbyContent, remove 4 duplicate remembers
Add missing imports in desktop ChessScreen, replace all FQN usages with short names
Remove redundant distinctBy in completed games UI (state already prevents duplicates)
Chess event kinds 30065 (accept) and 30066 (move) were missing from the
notification relay subscription filters, so they were never fetched from
relays for the in-app notification feed. Also bypass the follow-list
filter for chess events since opponents may not be followed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Filter user's own games from public "Live Games" list in lobby
- Fix broken addSpectatingGame filter (playerPubkey is always viewerPubkey)
- Add removeGame() to clean up stale entries when "Game not found"
- Make player avatars clickable to open profile on game screen
- Make completed game cards clickable to view final board state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Android push notifications for chess events:
- LiveChessGameAcceptEvent (kind 30065): notifies when opponent accepts challenge
- LiveChessMoveEvent (kind 30066): notifies when opponent makes a move
- New Chess notification channel alongside existing DM and Zap channels
- Chess kinds added to NotificationFeedFilter for in-app notification feed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds ability to leave a spectated game on both Android and Desktop:
- Added stopSpectating() to ChessLobbyLogic (removes from state + stops polling)
- Both ViewModels now delegate to logic.stopSpectating() instead of bypassing it
- LiveChessGameScreen accepts onLeaveSpectating callback shown in spectator info area
- Android: Leave Game button pops back stack and stops spectating
- Desktop: Leave Game button clears selectedGameId (returns to lobby) and stops spectating
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Finished Games" section to the Android chess lobby screen,
showing up to 10 completed games with opponent avatars, result
(Won/Lost/Draw), and move count. The section only appears when
completedGames is non-empty, matching the Desktop implementation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ChessPlayerChip.kt with three shared composables:
- ChessPlayerChip: single player avatar + name with active border
- ChessPlayerVsHeader: mirrored White vs Black header above the board
- OverlappingAvatars: compact overlapping circular avatars for list cards
Wire avatars into:
- LiveChessGameScreen: vs header with active player green border
- DesktopChessGameLayout: vs header above the chess board
- Android & Desktop lobby cards: overlapping avatars in ActiveGameCard,
ChallengeCard, and OutgoingChallengeCard avatar slots
Uses existing UserAvatar (coil3 AsyncImage + Robohash fallback) from
commons for KMP compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip adding games to spectatingGames when the user is a participant
(playerPubkey or opponentPubkey matches userPubkey) or when the game
is already finished.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire onGameEndDismiss callback so the "Continue" button on game end
overlay moves the game to the completed list. Add auto-detection of
finished games during polling refresh so games transition even if the
user navigates away without clicking the button. Discovered games that
are already finished now go straight to completed instead of appearing
in the active list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The board was non-interactive for participants during pending challenges
on Android because canMakeMoves factored in isPendingChallenge. Desktop
passed isSpectator directly without the pending check, so it worked.
Remove isPendingChallenge from the board interactivity gate to match
Desktop behavior. The header still shows "Challenge Pending" status,
and this also fixes a secondary issue where the isPendingChallenge flag
could get stuck when replaceGameState preserved the old state via its
open-challenge workaround.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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
@@ -7,7 +7,7 @@ description: Use when comparing Android strings.xml locale files to find untrans
## 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 a table ready for translation.
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
@@ -15,6 +15,17 @@ Extract string resource keys from the default `values/strings.xml` that are abse
- 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):
Default locale: `cs-rCZ` if none specified. User may override (e.g., `pt-rBR`, `ja`).
### 2. Find missing keys using cs-rCZ as reference
### 2. Extract and diff keys
Use a single bash pipeline to extract translatable keys from both files and diff them:
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
@@ -107,7 +106,7 @@ class AccountFollowsLoaderSubAssembler(
valsub=
orchestrator.requestNewSubscription(
if(isDebug)logTag+newSubId()elsenewSubId(),
object:IRequestListener{
object:SubscriptionListener{
overridefunonEose(
relay:NormalizedRelayUrl,
forFilters:List<Filter>?,
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.