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
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
- 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.
- 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>
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
- 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)
- 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"
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>