4888d30f6c
Address the architectural critique from self-review. The leaderboard math and the shared system card are now platform-neutral primitives in commons, the on-UI-thread aggregation is gone, and two latent correctness bugs around subscription lifecycle and zap routing are fixed. - Introduce a pure LiveActivityTopZappersAggregator in commons that takes plain ZapContribution values and returns a sorted, deduped, anon-bucketed top-N list. Covered by a unit-test suite that is Compose/Android-independent. - Introduce LiveStreamTopZappersViewModel in commons/commonMain. It owns two partitioned contribution maps (stream-scoped and goal-scoped), mutates them under a Mutex on the IO dispatcher in response to channel.changesFlow and Note.zaps.stateFlow emissions, and publishes the aggregator result via a StateFlow<List<TopZapperEntry>>. UI is now a dumb consumer with no aggregation logic left in the composable. - Move StreamSystemCard to commons/commonMain/compose/ so Desktop can consume it alongside Android. - Fix attachZapToLiveActivityChannel to run even when a zap receipt was already consumed by another subscription. Previously a zap first seen by notifications/profile would never reach the stream's channel cache; addNote is already idempotent so this is a safe retro-route. - Fix ChannelFilterAssemblerSubscription to re-invalidate filters when a live-activity channel's metadata arrives. The 30311 stream event usually lands after the initial assembler run, so the goal-tag-driven subscription never fired. Now it re-runs as metadata resolves and the goal id is discovered. https://claude.ai/code/session_01WJA5PvDABegBYUu6h42YZi