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
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
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
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
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
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
- 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
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