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