058c56dc21
When a replaceable / addressable update arrives in handleInsert, the projection now re-runs Filter.match against the new event for every filter. A v2 that no longer matches a filter (e.g. tag list changed) is removed from that filter's set; a v2 that newly matches a filter joins it. If no filter retains the slot afterwards, it's fully dropped from byId / byAddress. Closes the previous gap where v2 of an addressable kept a stale filter membership inherited from v1. The slot's MutableStateFlow is still updated in place — collectors of that handle still see the content change before the membership update emits. The cap-eviction-with-cleanup logic was extracted into a small `admit` helper since the supersession path and the new-slot path both need it. For the common case (filter on `kinds + authors`, no tag/time constraints), v2 always still matches — the new branches are no-ops and the list reference stays stable, preserving the in-place update guarantee. The behaviour change kicks in for tag, time-window, or id-list filters. New test addressableUpdateDropsSlotWhenFilterStopsMatching: v1 has hashtag "nostr" and matches a `t = nostr` filter; v2 changes to "bitcoin"; the projection drops the slot. 18/18 projection tests pass. https://claude.ai/code/session_01Jny85MTu1ynKgFBgysfWu5