A mute made elsewhere, drawn as a mute made elsewhere

The other half of reading Discord's own mute settings: the row goes quiet, and
the menu says why.

`isMutedBuffer` is where this window decides a conversation is muted, so a
mute the account carries belongs there beside the two already in it - the
buffer's own, and its rail entry's.

The menu entry is the part worth being careful about. A mute made on the
account is not this window's to undo, and an "Unmute" that quietly did nothing
would be worse than no entry at all - so where there is one, the entry says
which service holds it and does not pretend to be a switch.

Closes #119.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-10 02:13:46 -04:00
co-authored by Claude Opus 5
parent 84fd94ba6f
commit be32f5e7b6
4 changed files with 29 additions and 3 deletions
+1 -1
Submodule nobilis updated: 2c024de938...a0e5823cbb
+13 -2
View File
@@ -24,7 +24,8 @@ import {
bufferKindGlyph,
classes,
resolveMediaUrl,
serviceIcon
serviceIcon,
serviceLabel
} from '../lib/util'
import type { Account, Member } from '../../../shared/wire'
import { Avatar } from './Avatar'
@@ -877,7 +878,17 @@ function BufferRow({
] as MenuEntry[])
: []),
{ label: pinned ? 'Unpin' : 'Pin', icon: 'push_pin', onClick: onTogglePin },
{ label: muted ? 'Unmute' : 'Mute', icon: muted ? 'notifications' : 'notifications_off', onClick: onToggleMute },
// A mute made on the account itself is not this window's to undo, and an
// "Unmute" that quietly did nothing would be worse than no entry at all -
// so it says where the mute is, and where to go and take it off.
buffer.serverMuted
? {
label: account ? `Muted on ${serviceLabel(account.service)}` : 'Muted on this account',
icon: 'notifications_off',
disabled: true,
onClick: () => {}
}
: { label: muted ? 'Unmute' : 'Mute', icon: muted ? 'notifications' : 'notifications_off', onClick: onToggleMute },
{ separator: true },
// Filing is a drag onto the heading now, not an entry per heading. That
// list grew with the number of headings and had no ceiling - on a server
+5
View File
@@ -251,6 +251,11 @@ export function isMutedBuffer(
// An explicit mute on this buffer always stands.
if (muted.includes(buffer.id)) return true
// And so does one made on the account itself - a Discord server silenced in
// the official client, a Matrix room silenced by a push rule. Every other
// device signed in to that account is already keeping quiet about it.
if (buffer.serverMuted) return true
// A muted server, guild or space silences everything under it.
return !!buffer.groupId && mutedGroups.includes(buffer.groupId)
}
+10
View File
@@ -85,6 +85,16 @@ export interface Buffer {
remoteId?: string
/** Where the service orders it: category rank and channel rank folded. */
position?: number
/**
* Muted on the account itself rather than in this window.
*
* A Discord guild or channel silenced in the official client, a Matrix room
* silenced by a push rule. The setting travels with the account, so every
* device signed in to it is already honouring it - and it cannot be undone
* from here, which is why it is separate from this window's own mute rather
* than folded into it.
*/
serverMuted?: boolean
/**
* Set while the buffer exists but the service has not yet said what is in
* it - a Matrix room between joining it and its first sync. The list draws