Real gap: there was no way to stop a specific peer's noise (new
readings, replies, hearts) from showing up, short of disconnecting
them entirely. Mute is deliberately narrower than a network-level
block: their content is still fully synced and stored (protocol
correctness for the rest of the network is untouched), only the local
social feed and store insertion for their InterpAuthored/AffirmAdded/
ResponseAdded activity is suppressed. Collaborative-doc mechanics,
display names, and circle invites always apply regardless — muting
someone's chatter shouldn't corrupt a shared document or hide a
functional message addressed to you.
- store: new `muted_peers` table + mute_peer/unmute_peer/muted_peers,
covered by a roundtrip unit test.
- app/feed_item: `is_muted_social_event` is a small pure predicate,
unit-tested directly (red-first: wrote the five cases, then the
three-line match) rather than needing network-level BDD coverage
for what's an entirely local, app-layer concern.
- app: AppModel caches the muted set in memory (loaded once at start)
so the hot SyncStateEvent path never awaits a DB round-trip; a new
"Mute"/"Unmute" button on each connected peer's sidebar row (dimming
their name when muted, mirroring the existing pending-row dim-label
convention) toggles it.