diff --git a/nobilis b/nobilis index e10fe48..de0ad86 160000 --- a/nobilis +++ b/nobilis @@ -1 +1 @@ -Subproject commit e10fe489f0b6b9ad10ada51b95b3bba88b44a511 +Subproject commit de0ad864dc63faae0312ddcc27390e6200112f23 diff --git a/src/renderer/src/app.css b/src/renderer/src/app.css index 32690f6..87ee740 100644 --- a/src/renderer/src/app.css +++ b/src/renderer/src/app.css @@ -4625,6 +4625,15 @@ video.lightbox-media { color: var(--primary); } +/* Not a rank and not a warning - a fact about what is behind the name. Kept + quiet: it belongs beside every one of a bot's lines, and a badge that loud + at that frequency would read as an alert. */ +.sender-badge.bot { + background: var(--surface-container-high); + color: var(--surface-variant-text); + letter-spacing: 0.04em; +} + /* The one badge that is a warning rather than a title: this message came from a device nobody has vouched for. Coloured like an error and left uncoloured nowhere else, because a doubt about who sent something is the diff --git a/src/renderer/src/components/NickList.tsx b/src/renderer/src/components/NickList.tsx index 8c5c5d7..751017b 100644 --- a/src/renderer/src/components/NickList.tsx +++ b/src/renderer/src/components/NickList.tsx @@ -35,6 +35,22 @@ export type ModerationAction = 'kick' | 'ban' | 'mute' | 'op' | 'deop' | 'voice' * person reads the same way in both places. An unfamiliar word keeps its own * first letters rather than being dropped. */ +/** + * What hovering a member says about them. + * + * Everything the network has actually told us, and nothing it has not. On IRC + * that is now a hostmask and a services account where the server offered them + * — the two facts that say which *person* a nick belongs to — and on every + * other service it stays what it was. + */ +function memberTitle(member: Member): string { + const lines = [member.nick] + if (member.account) lines.push(`identified as ${member.account}`) + if (member.host) lines.push(member.host) + if (lines.length === 1 && member.userId) return member.userId + return lines.join('\n') +} + function shortBadge(prefix: string): string { const short: Record = { broadcaster: 'HOST', @@ -540,7 +556,7 @@ function MemberRow({ className={classes('nick-row', member.away && 'away', blocked && 'blocked')} onContextMenu={open} onClick={onMention} - title={member.userId || member.nick} + title={memberTitle(member)} > {/* IRC's rank is one character and sits in a fixed slot before the name. Kick's is a word - "moderator", "subscriber" - which @@ -558,6 +574,14 @@ function MemberRow({ {shortBadge(member.prefix)} )} + {/* The network's own word for it, not a guess from the name. Drawn + after the nick like any other badge, and only where the network + actually said so. */} + {member.bot && ( + + BOT + + )} {blocked && } {menu && } diff --git a/src/shared/wire.ts b/src/shared/wire.ts index d3b2428..9e5ea5e 100644 --- a/src/shared/wire.ts +++ b/src/shared/wire.ts @@ -471,6 +471,24 @@ export interface Member { prefix?: string away?: boolean userId?: string + /** + * `user@host`, where the network says. IRC only, and absent until somebody + * has told us — a WHO on join, `userhost-in-names`, or a line from them. + * + * Carried because it is what identifies a *person* rather than a name: a + * nick is theirs only while they hold it, and an ignore or a ban keyed on + * one is undone the moment they change it. + */ + host?: string + /** + * The services account they are signed in to, where the network says. + * + * Distinct from the nick: anybody can take a free nick, and only the + * account holder can be identified to it. + */ + account?: string + /** The network's own word that this one is a program, not a person. */ + bot?: boolean /** The roles this person holds here, by name. Discord only. */ roles?: string[] powerLevel?: number