Commit Graph

216 Commits

Author SHA1 Message Date
Claude 7ed036bbaa feat: Phase 7 — Marmot group chat UI & ViewModel layer
Add complete UI layer for Marmot Protocol MLS group messaging:

State models (commons/commonMain/):
- MarmotGroupChatroom: tracks decrypted inner messages per group
- MarmotGroupList: manages all group chatrooms with change notifications
- MarmotGroupFeedFilter: feed filter for group message feeds
- MarmotGroupFeedViewModel: ViewModel exposing group messages

Account integration:
- Add marmotGroupList to IAccount interface and implementations
- GroupEventHandler indexes inner events into MarmotGroupList
- AccountViewModel gains sendMarmotGroupMessage, createMarmotGroup,
  publishMarmotKeyPackage helper methods

Android screens (amethyst/):
- MarmotGroupListScreen: lists all groups with last message preview
- MarmotGroupChatScreen: group conversation with message composer
- MarmotGroupChatView: message feed + simple text composer
- CreateGroupDialog: create new MLS group with display name
- AddMemberDialog: add member by npub/hex (KeyPackage fetch TBD)

Navigation & integration:
- Route.MarmotGroupList and Route.MarmotGroupChat routes
- Wired into AppNavigation nav graph
- "MLS Groups" button added to ChannelFabColumn on Messages screen
- Marmot group newest messages appear in ChatroomListKnownFeedFilter

https://claude.ai/code/session_0194SxKfAU61PY92eqP4cCXM
2026-04-05 20:38:23 +00:00
davotoula c0bdde0ce4 ?: 0L forces Long regardless of nullability, so all comparisons are Long vs Long — no cast issue. 2026-04-03 21:08:05 +02:00
Claude 6116f45888 feat: add EphemeralGiftWrapEvent (kind 21059)
Adds an ephemeral variant of GiftWrap for transient encrypted messages
that relays don't need to persist. EphemeralGiftWrapEvent extends
GiftWrapEvent so all existing processing paths (decryption, routing,
notifications, call signaling) automatically handle it via type
hierarchy. Relay subscription filters are updated to request both kinds.

https://claude.ai/code/session_0157X96G6HLTzYxkdX9pyTSJ
2026-04-02 15:55:44 +00:00
Claude b960a4692a feat: migrate BookmarkListEvent from kind 30001 to 10003
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
2026-03-31 13:24:27 +00:00
Vitor Pamplona f291bb126a Merge branch 'main' into claude/migrate-quartz-nip58-XQ8bw 2026-03-31 09:17:11 -04:00
Vitor Pamplona 3dbd79d879 Merge pull request #2052 from vitorpamplona/claude/fix-sorting-order-p23ZY
Improve sorting stability by adding secondary sort keys
2026-03-31 08:42:46 -04:00
Claude 96c4092138 fix: ensure secondary sort by id ascending when sorting by createdAt descending
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
2026-03-31 03:45:24 +00:00
Claude a4bf093cb3 refactor: migrate NIP-58 badges to nip88Polls-style sub-package structure
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
2026-03-31 03:08:25 +00:00
davotoula a7259b21ee Merge branch 'main-upstream' into chess-enhancements-and-bug-fixes 2026-03-30 17:40:40 +02:00
davotoula 06fae3ba31 remove unused imports 2026-03-30 17:26:21 +02:00
davotoula f76fc23840 update logging to new style 2026-03-30 16:42:38 +02:00
davotoula 17f071b0f4 Merge branch 'main' into chess-enhancements-and-bug-fixes
# Conflicts:
#	commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/chess/ChessEventBroadcaster.kt
2026-03-30 13:14:33 +02:00
nrobi144 0a41806d7e fix(desktop): feed loading issues — missing events, broken profile navigation
- 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>
2026-03-30 13:33:18 +03:00
nrobi144 882c39fd0e fix(cache): use getOrCreateNote for reply linking to fix flaky thread test
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>
2026-03-30 11:44:24 +03:00
nrobi144 2b3005797e feat(desktop): render reposts and quoted notes in feed
- Extract GenericRepostLayout + BoostedMark to commons for cross-platform use
- Feed filters accept kind 6/16 reposts with deduplication by original note
- Relay subscriptions request kinds 1, 6, 16
- Cache consumes GenericRepostEvent (kind 16) and uses getOrCreateNote for
  repost originals to handle out-of-order arrival
- FeedNoteCard renders reposts with overlapping avatars + "Boosted" label
- QuotedNoteEmbed renders nostr:nevent/note references as embedded NoteCards
  with reactive metadata observation
- Direct relay subscriptions fetch missing referenced notes and author metadata
- FeedMetadataCoordinator routes all events (not just metadata) back to cache
- consumeMetadata invalidates note flows when author metadata arrives

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 11:44:24 +03:00
nrobi144 0e8c6fa434 fix(cache): route ReadsScreen following-mode events through cache
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.
2026-03-30 11:44:23 +03:00
Vitor Pamplona 03c692a8cf Merge pull request #2005 from davotoula/Introduce-log-level-filtering
Introduce log level filtering
2026-03-29 09:56:21 -04:00
davotoula 380026c7be optimise imports 2026-03-29 15:38:51 +02:00
davotoula 250d89ca0a feat: set Log.minLevel in app entry points and migrate android.util.Log usages
- 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>
2026-03-29 10:56:18 +02:00
Vitor Pamplona 589bb041b2 Fixes refactoring issues by Claude. 2026-03-28 19:58:04 -04:00
Vitor Pamplona d991af72ff Merge pull request #1986 from vitorpamplona/claude/simplify-nostrclient-api-ZpOTB
Refactor Nostr client API with clearer naming conventions
2026-03-28 18:09:12 -04:00
Vitor Pamplona 20eb95ae57 Removing more warnings 2026-03-28 16:30:00 -04:00
Vitor Pamplona f3cefe0e13 resolving some more warnings 2026-03-28 15:35:45 -04:00
Vitor Pamplona 4db617aeef fixes the need for coroutine scopes 2026-03-28 15:26:29 -04:00
Claude 0eb32ae4fb refactor: migrate LocalClipboardManager to LocalClipboard
Replace deprecated LocalClipboardManager with LocalClipboard across
all 21 files in both amethyst and desktopApp modules.

https://claude.ai/code/session_01FE2NjKxhUYUFRvhyqbYpUH
2026-03-28 18:46:02 +00:00
Vitor Pamplona cfa59dab01 removing more warnings 2026-03-28 14:24:13 -04:00
Claude 4be617e64a revert: keep INostrClient/NostrClient naming and restore onEose/isLive
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
2026-03-28 16:43:30 +00:00
Claude 4e2717c5c5 refactor: simplify NostrClient API for beginner-friendliness
Comprehensive rename of the NostrClient relay infrastructure to use
intuitive, self-documenting names instead of protocol-level jargon.

Interface/class renames:
- INostrClient → NostrClient (interface)
- NostrClient → DefaultNostrClient (implementation)
- IRequestListener → SubscriptionListener
- IRelayClientListener → RelayConnectionListener
- IOpenNostrRequest → SubscriptionHandle
- NostrClientStaticReq → StaticSubscription
- NostrClientDynamicReq → DynamicSubscription

Method renames:
- openReqSubscription() → subscribe()
- close(subId) → unsubscribe(subId)
- send() → publish()
- queryCount() → count()
- subscribe/unsubscribe(listener) → addConnectionListener/removeConnectionListener
- renewFilters() → syncFilters()

Callback renames:
- onEose → onCaughtUp
- isLive → isRealTime
- onStartReq → onSubscriptionStarted
- onCloseReq → onSubscriptionClosed

Extension function renames:
- query() → fetchAll()
- downloadFirstEvent() → fetchFirst()
- req() → subscribe()
- reqAsFlow() → subscribeAsFlow()
- reqUntilEoseAsFlow() → fetchAsFlow()
- sendAndWaitForResponse() → publishAndConfirm()
- queryCountSuspend() → count()
- queryCountMergedHll() → countMerged()
- reqBypassingRelayLimits() → fetchAllPages()
- updateFilter() → refresh() on SubscriptionHandle

https://claude.ai/code/session_01JPcYCcRx5eZN4GvgGxGwbf
2026-03-28 16:43:30 +00:00
Vitor Pamplona 943d46e01a Moves public messages out of experimental 2026-03-28 09:18:34 -04:00
nrobi144 9c9c7d3f24 feat(cache): seed NotificationsScreen from cache on compose
- 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)
2026-03-26 07:20:22 +02:00
nrobi144 072e437277 feat(cache): wire State classes on DesktopIAccount for GC retention
- Add BookmarkListState, Kind3FollowListState, Nip65RelayListState
  to DesktopIAccount — pins important AddressableNotes via strong refs
- Create stub Kind3FollowListRepository + Nip65RelayListRepository
  (no persistence yet, null backups)
- Update followingKeySet() to read from Kind3FollowListState.flow
- Revert accidental long-form branch changes in DeckColumnContainer
2026-03-26 07:17:39 +02:00
nrobi144 cf6a74b162 feat(cache): add periodic memory cleanup for Desktop
- 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)
2026-03-26 07:14:15 +02:00
nrobi144 3dbbc039b3 feat(cache): replace BoundedLargeCache with LargeSoftCache on Desktop
- 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"
2026-03-26 07:13:27 +02:00
nrobi144 3b489fb3cb feat(cache): seed Reads and Bookmarks screens from cache on compose
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>
2026-03-26 07:13:27 +02:00
nrobi144 f27c8811d5 fix(cache): don't reset followersCount to 0 on subscription restart
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>
2026-03-26 07:13:27 +02:00
nrobi144 f2169e0236 feat(cache): cache profile metadata and follower/following counts
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>
2026-03-26 07:13:27 +02:00
nrobi144 3e41bab5d5 fix(cache): use relayStatuses (available) instead of connectedRelays for subscriptions
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>
2026-03-26 07:13:27 +02:00
nrobi144 1e1bc1b172 fix(cache): add missing relay subscriptions for FeedScreen and UserProfileScreen
FeedScreen and UserProfileScreen used DesktopFeedViewModel (cache-backed)
but never subscribed to relays for kind 1/kind 3 events, causing 0 notes,
0 followed, and 0 relays on the home feed.

Adds rememberSubscription calls for:
- Contact list (kind 3) in FeedScreen → populates followedUsers
- Global/following feed (kind 1) in FeedScreen → populates cache
- Profile notes (kind 1) in UserProfileScreen → populates cache

Also adds 33 integration tests covering the full cache pipeline:
- DesktopCachePipelineTest: cache → filter → ViewModel (25 tests)
- CoordinatorPipelineTest: coordinator → cache → ViewModel (8 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:13:26 +02:00
nrobi144 0621e8f7c2 refactor(cache): P3 simplifications — remove dead code, reduce abstractions
- Replace SubscriptionHealth map + data class with single lastEventAt
  Long? timestamp (only consumer was RelayHealthIndicator taking max())
- Replace consumer HashMap registry with when block (safe casts,
  compiler-checked, 9 kinds doesn't benefit from O(1) lookup)
- Remove dead loadReactionsForNotes() (superseded by requestInteractions)
- Remove unused BoundedLargeCache methods: containsKey, isEmpty,
  mapNotNull, forEach (zero external callers)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:13:26 +02:00
nrobi144 4f5665a2bd fix(cache): address review findings — subscription churn, O(n) size, atomicity
P1 fixes from compound engineering review:
- Fix subscription churn flooding relays: DisposableEffect keyed on
  feedMode/noteId (stable) instead of feedState (changes every 250ms)
- BoundedLargeCache: AtomicInteger counter replaces O(n)
  ConcurrentSkipListMap.size() — eliminates 50K traversal per event
- Non-atomic updateHealth(): use MutableStateFlow.update{} for
  atomic compare-and-set instead of read-modify-write race

P2 fixes:
- DesktopThreadFilter: LinkedHashSet for O(1) containment checks,
  down from O(R²) with MutableList
- consumeContactList: createdAt guard ensures newer events always win
- consumeEvent error logging: include event ID and relay URL

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:13:26 +02:00
nrobi144 c598c6135c feat(cache): ThreadScreen → DesktopFeedViewModel + DesktopThreadFilter
Replace EventCollectionState + 7 rememberSubscription blocks + inline
count maps with DesktopFeedViewModel + DesktopThreadFilter (graph walk).
Keep root note + replies relay subscriptions to populate cache. Use
FeedNoteCard for both root and reply notes (reads counts from Note.flowSet).
Remove ~200 lines of inline state management (zapsByEvent, reactionIdsByEvent,
replyIdsByEvent, repostIdsByEvent, bookmarkList, bookmark subscription).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:13:26 +02:00
nrobi144 8c1e54e147 feat(ui): relay health indicator in NavigationRail
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>
2026-03-26 07:13:26 +02:00
nrobi144 50eb03bc3e feat(cache): UserProfileScreen notes feed → DesktopFeedViewModel
Replace EventCollectionState + createUserPostsSubscription + manual cache
hydration with DesktopFeedViewModel + DesktopProfileFeedFilter. Profile
header subscriptions (metadata, contact list, followers) kept as-is since
they're not feed concerns. DisposableEffect cleanup on profile switch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:13:26 +02:00
nrobi144 c63ace1d9c feat(cache): FeedScreen → DesktopFeedViewModel + Note-based FeedNoteCard
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>
2026-03-26 07:13:26 +02:00
nrobi144 e8ede21c17 feat(cache): Coordinator error handling + interaction subscriptions
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>
2026-03-26 07:13:25 +02:00
nrobi144 684ba158ab feat(cache): kind-based consumer registry + new event types
Replace when-dispatch with HashMap<Int, Handler> for O(1) event routing.
Add consume methods for: RepostEvent (kind 6), ContactListEvent (kind 3),
LongTextNoteEvent (kind 30023), BookmarkListEvent (kind 30001).
Replace @Volatile followedUsers with MutableStateFlow for thread safety
and Compose observability.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:13:25 +02:00
nrobi144 e834dbd1e5 fix(ui): show display names instead of npubs in feed
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>
2026-03-26 07:13:25 +02:00
nrobi144 e80473a7d9 feat(cache): Phase 3 — route all screen events through cache
All screens now pipe relay events into DesktopLocalCache via
coordinator.consumeEvent(). Existing per-screen EventCollectionState
kept for rendering (incremental migration), but data also enters
cache for persistence across navigation.

- FeedScreen: route global + following feed events
- ThreadScreen: route root note + reply events
- UserProfileScreen: route posts + add cache hydration on mount
  (LaunchedEffect queries cache for existing posts → instant display)
- BookmarksScreen: route public + private bookmark events
- ReadsScreen: route long-form feed events (add coordinator param)
- NotificationsScreen: route notification events

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:13:25 +02:00
nrobi144 c198df57fd feat(cache): Phase 2+3 foundation — FeedFilters + DesktopFeedViewModel
Phase 2: 8 FeedFilter implementations:
- DesktopGlobalFeedFilter (kind 1, limit 2500)
- DesktopFollowingFeedFilter (kind 1 + followed pubkeys)
- DesktopThreadFilter (root + reply graph walk)
- DesktopProfileFeedFilter (notes by pubkey, limit 1000)
- DesktopBookmarkFeedFilter (notes by ID set)
- DesktopReadsFeedFilter (kind 30023, limit 500)
- DesktopNotificationFeedFilter (events tagging user)
- DesktopSearchFeedFilter (content text search)

All use AdditiveFeedFilter for incremental updates (O(batch)
after initial load). Limits are 5x Android's.

Phase 3 foundation: DesktopFeedViewModel subclass that calls
refreshSuspended() on init to load existing cache data —
fixes navigation persistence (back shows instant data).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:12:46 +02:00
nrobi144 543f7a329c feat(cache): Phase 1 — cache-centric architecture foundation
- Add BoundedLargeCache: LargeCache wrapper with size enforcement
  (50k notes, 25k users, 10k addressable). Lock-free reads via
  ConcurrentSkipListMap, evicts 10% when cap exceeded.

- Switch DesktopLocalCache from ConcurrentHashMap to BoundedLargeCache.
  Exposes filterIntoSet, values, etc. matching Android's query patterns.

- Add consume methods for kinds 1 (TextNote), 7 (Reaction),
  9734 (ZapRequest), 9735 (Zap). Uses tagsWithoutCitations() for
  reply parsing, originalPost()+taggedAddresses() for reactions,
  zappedPost() for zaps. Follows Android LocalCache pattern.

- Add consumeEvent() bridge in coordinator with BasicBundledInsert
  (250ms batching). Routes relay onEvent callbacks to cache.

- Fix SharedFlow: extraBufferCapacity=64, DROP_OLDEST. Prevents
  blocking emitters and dropped events.

- Set -Xmx2g JVM arg for long desktop sessions.

- Clear cache on logout (coordinator first, then cache).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 07:12:46 +02:00