Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Chat has sounds. A short warm cue plays for a message in a conversation you have open but aren't looking at, a direct message, a mention of you and an agent finishing something for you, with a soft tick for sending that is off until you turn it on; never for your own messages, a muted conversation, a conversation you are looking at in a window that has the front, or while you have paused notifications, and never twice for one message or more than once in a second and a half. The cues are made by the browser itself in two sets, Soft and Bright, so nothing is downloaded; the Sounds and notifications section of your notification settings turns them on and off, picks the set and the volume, plays each one, and offers desktop notifications for when the window is behind; Do not disturb is the one pause everyone sees, set from the chat sidebar's bell, the settings, or your avatar menu, now with a two-hour choice, and the top bar's bell shows a dot while it's on. Your choices are kept with your account. The sound layer sits behind one small interface so the desktop app can play them natively later. The chat and notifications guides say the rules. | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import type { ChatMessage, FeedNotification } from "@g1t/contracts"; | |
| 5 | import { DEFAULT_SOUND_SETTINGS, type SoundSettings } from "@g1t/contracts/sounds"; | |
| 6 | ||
| 7 | import { | |
| 8 | type Arrival, | |
| 9 | SAME_MESSAGE_MS, | |
| 10 | SOUND_GAP_MS, | |
| 11 | type SoundContext, | |
| 12 | SoundGate, | |
| 13 | arrivalKey, | |
| 14 | cueFor, | |
| 15 | cueForNotificationKind, | |
| 16 | finishedCard, | |
| 17 | lookingAt, | |
| 18 | mentionsMe, | |
| 19 | wantsDesktopToast, | |
| 20 | } from "./chat-sounds.ts"; | |
| 21 | ||
| 22 | const ana = { kind: "user" as const, id: "u1", name: "ana", display_name: "Ana", avatar: null, role: null }; | |
| 23 | const bea = { kind: "user" as const, id: "u2", name: "bea", display_name: "Bea", avatar: null, role: null }; | |
| 24 | const margo = { kind: "agent" as const, id: "a1", name: "margo", display_name: "Margo", avatar: null, role: "Reviews pull requests" }; | |
| 25 | ||
| 26 | function message(over: Partial<ChatMessage> = {}): ChatMessage { | |
| 27 | return { | |
| 28 | id: over.id ?? "msg_1", | |
| 29 | channel_id: "chn_general", | |
| 30 | author: bea, | |
| 31 | kind: "text", | |
| 32 | body: "hello there", | |
| 33 | card: null, | |
| 34 | thread_root: null, | |
| 35 | reply_count: 0, | |
| 36 | last_reply_at: null, | |
| 37 | created_at: "2026-10-10T10:00:00.000Z", | |
| 38 | edited_at: null, | |
| 39 | deleted_at: null, | |
| 40 | ...over, | |
| 41 | }; | |
| 42 | } | |
| 43 | ||
| 44 | function notification(over: Partial<FeedNotification> = {}): FeedNotification { | |
| 45 | return { | |
| 46 | id: "msg_9", | |
| 47 | kind: "dm", | |
| 48 | workspace: "acme", | |
| 49 | title: "Bea", | |
| 50 | body: "hi", | |
| 51 | href: "/acme/-/chat/dm/chn_dm", | |
| 52 | actor: { kind: "user", id: "u2", name: "Bea" }, | |
| 53 | channel_id: "chn_dm", | |
| 54 | created_at: "2026-10-10T10:00:00.000Z", | |
| 55 | ...over, | |
| 56 | }; | |
| 57 | } | |
| 58 | ||
| 59 | /** Ana, in #general, window in front: the quiet case every rule starts from. */ | |
| 60 | function ctx(over: Partial<SoundContext> = {}, settings: Partial<SoundSettings> = {}): SoundContext { | |
| 61 | return { | |
| 62 | me: { id: "u1", username: "ana" }, | |
| 63 | focus: { visible: true, focused: true, viewingChannel: "chn_general" }, | |
| 64 | muted: new Set(), | |
| 65 | dnd: false, | |
| 66 | settings: { ...DEFAULT_SOUND_SETTINGS, ...settings, sound_cues: { ...DEFAULT_SOUND_SETTINGS.sound_cues, ...settings.sound_cues } }, | |
| 67 | ...over, | |
| 68 | }; | |
| 69 | } | |
| 70 | ||
| 71 | const chat = (m: ChatMessage, channelKind: "channel" | "dm" = "channel"): Arrival => ({ source: "chat", message: m, channelKind }); | |
| 72 | const feed = (n: FeedNotification): Arrival => ({ source: "feed", notification: n }); | |
| 73 | ||
| 74 | test("a message in the conversation on screen, in a window in front, is silent", () => { | |
| 75 | assert.equal(cueFor(chat(message()), ctx()), null); | |
| 76 | assert.ok(lookingAt("chn_general", ctx().focus)); | |
| 77 | }); | |
| 78 | ||
| 79 | test("the same message plays `message` once the window is not in front, or the tab is hidden", () => { | |
| 80 | assert.equal(cueFor(chat(message()), ctx({ focus: { visible: true, focused: false, viewingChannel: "chn_general" } })), "message"); | |
| 81 | assert.equal(cueFor(chat(message()), ctx({ focus: { visible: false, focused: false, viewingChannel: "chn_general" } })), "message"); | |
| 82 | }); | |
| 83 | ||
| 84 | test("a message in another conversation you have open plays `message`", () => { | |
| 85 | assert.equal(cueFor(chat(message({ channel_id: "chn_other" })), ctx()), "message"); | |
| 86 | assert.equal(cueFor(feed(notification({ kind: "thread_reply", channel_id: "chn_other" })), ctx()), "message"); | |
| 87 | }); | |
| 88 | ||
| 89 | test("a direct message not from you plays `direct`; one from you plays nothing", () => { | |
| 90 | const dm = message({ channel_id: "chn_dm" }); | |
| 91 | assert.equal(cueFor(chat(dm, "dm"), ctx()), "direct"); | |
| 92 | assert.equal(cueFor(chat({ ...dm, author: ana }, "dm"), ctx()), null); | |
| 93 | assert.equal(cueFor(feed(notification()), ctx()), "direct"); | |
| 94 | assert.equal(cueFor(feed(notification({ actor: { kind: "user", id: "u1", name: "Ana" } })), ctx()), null); | |
| 95 | }); | |
| 96 | ||
| 97 | test("a mention of you plays `mention`, over `direct` and `message`", () => { | |
| 98 | assert.equal(cueFor(chat(message({ channel_id: "chn_other", body: "hey @ana look" })), ctx()), "mention"); | |
| 99 | assert.equal(cueFor(chat(message({ channel_id: "chn_dm", body: "@ana?" }), "dm"), ctx()), "mention"); | |
| 100 | assert.equal(cueFor(feed(notification({ kind: "mention", channel_id: "chn_other" })), ctx()), "mention"); | |
| 101 | // Looking at it, in front: still silent. | |
| 102 | assert.equal(cueFor(chat(message({ body: "@ana" })), ctx()), null); | |
| 103 | }); | |
| 104 | ||
| 105 | test("@you is a whole handle, outside code", () => { | |
| 106 | assert.ok(mentionsMe("thanks @ana", "ana")); | |
| 107 | assert.ok(mentionsMe("@Ana: see this", "ana")); | |
| 108 | assert.ok(mentionsMe("(@ana)", "ana")); | |
| 109 | assert.ok(!mentionsMe("@ana-marie did it", "ana")); | |
| 110 | assert.ok(!mentionsMe("@anatole", "ana")); | |
| 111 | assert.ok(!mentionsMe("mail me@ana.example", "ana")); | |
| 112 | assert.ok(!mentionsMe("run `@ana` in code", "ana")); | |
| 113 | assert.ok(!mentionsMe("```\n@ana\n```", "ana")); | |
| 114 | assert.ok(!mentionsMe("@ana", "")); | |
| 115 | }); | |
| 116 | ||
| 117 | test("your own messages never sound, whichever way they arrive", () => { | |
| 118 | const unfocused = ctx({ focus: { visible: true, focused: false, viewingChannel: "chn_general" } }); | |
| 119 | assert.equal(cueFor(chat(message({ author: ana })), unfocused), null); | |
| 120 | assert.equal(cueFor(chat(message({ author: ana, body: "@ana" })), unfocused), null); | |
| 121 | assert.equal(cueFor(feed(notification({ kind: "mention", actor: { kind: "user", id: "u1", name: "Ana" } })), unfocused), null); | |
| 122 | }); | |
| 123 | ||
| 124 | test("a muted conversation is silent, even for a mention or a DM", () => { | |
| 125 | const muted = ctx({ muted: new Set(["chn_other", "chn_dm"]) }); | |
| 126 | assert.equal(cueFor(chat(message({ channel_id: "chn_other", body: "@ana" })), muted), null); | |
| 127 | assert.equal(cueFor(chat(message({ channel_id: "chn_dm" }), "dm"), muted), null); | |
| 128 | assert.equal(cueFor(feed(notification()), muted), null); | |
| 129 | assert.equal(cueFor(chat(message({ channel_id: "chn_third" })), muted), "message"); | |
| 130 | }); | |
| 131 | ||
| 132 | test("Do not disturb silences everything", () => { | |
| 133 | const quiet = ctx({ dnd: true, focus: { visible: false, focused: false, viewingChannel: null } }); | |
| 134 | assert.equal(cueFor(chat(message({ body: "@ana" })), quiet), null); | |
| 135 | assert.equal(cueFor(chat(message({ channel_id: "chn_dm" }), "dm"), quiet), null); | |
| 136 | assert.equal(cueFor(feed(notification({ kind: "agent_waiting" })), quiet), null); | |
| 137 | }); | |
| 138 | ||
| 139 | test("sounds off, or one cue off, is silent for that", () => { | |
| 140 | const away = { visible: false, focused: false, viewingChannel: null }; | |
| 141 | assert.equal(cueFor(chat(message()), ctx({ focus: away }, { sounds_enabled: false })), null); | |
| 142 | assert.equal(cueFor(chat(message()), ctx({ focus: away }, { sound_cues: { message: false } })), null); | |
| 143 | assert.equal(cueFor(chat(message({ body: "@ana" })), ctx({ focus: away }, { sound_cues: { message: false } })), "mention"); | |
| 144 | assert.equal(cueFor(chat(message({ body: "@ana" })), ctx({ focus: away }, { sound_cues: { mention: false } })), null); | |
| 145 | }); | |
| 146 | ||
| 147 | test("agents' messages count like people's, and a finished card plays `agent_done`", () => { | |
| 148 | const away = ctx({ focus: { visible: true, focused: false, viewingChannel: "chn_general" } }); | |
| 149 | assert.equal(cueFor(chat(message({ author: margo })), away), "message"); | |
| 150 | assert.equal(cueFor(chat(message({ author: margo, channel_id: "chn_dm" }), "dm"), away), "direct"); | |
| 151 | assert.equal(cueFor(chat(message({ author: margo, body: "@ana done?" })), away), "mention"); | |
| 152 | const card = message({ | |
| 153 | author: margo, | |
| 154 | kind: "card", | |
| 155 | body: "", | |
| 156 | card: { kind: "session", title: "Fix the flaky test", detail: "2 files changed", state: "Done", href: "/acme/web/pull/4" }, | |
| 157 | }); | |
| 158 | assert.ok(finishedCard(card)); | |
| 159 | assert.equal(cueFor(chat(card), away), "agent_done"); | |
| 160 | assert.equal(cueFor(chat(card), ctx()), null, "looking at it: silent"); | |
| 161 | assert.equal(cueFor(chat(card), ctx({ focus: away.focus }, { sound_cues: { agent_done: false } })), null); | |
| 162 | // A card still running, or a person's card, is a message. | |
| 163 | assert.ok(!finishedCard({ ...card, card: { ...card.card!, state: "Running" } })); | |
| 164 | assert.ok(!finishedCard({ ...card, author: bea })); | |
| 165 | assert.equal(cueFor(chat({ ...card, card: { ...card.card!, state: "Running" } }), away), "message"); | |
| 166 | // An agent waiting on you, over the feed, is the same cue. | |
| 167 | assert.equal(cueFor(feed(notification({ kind: "agent_waiting", actor: { kind: "agent", id: "a1", name: "Margo" } })), away), "agent_done"); | |
| 168 | }); | |
| 169 | ||
| 170 | test("a deleted message, and an inbox notification, make no sound", () => { | |
| 171 | const away = ctx({ focus: { visible: false, focused: false, viewingChannel: null } }); | |
| 172 | assert.equal(cueFor(chat(message({ deleted_at: "2026-10-10T10:01:00.000Z" })), away), null); | |
| 173 | assert.equal(cueFor(feed(notification({ kind: "inbox", channel_id: null })), away), null); | |
| 174 | assert.equal(cueForNotificationKind("inbox"), null); | |
| 175 | assert.equal(cueForNotificationKind("approval"), "mention"); | |
| 176 | }); | |
| 177 | ||
| 178 | test("sounds come at most one every 1.5 s, and one message sounds once however it arrives", () => { | |
| 179 | const gate = new SoundGate(); | |
| 180 | const t0 = 1_000_000; | |
| 181 | assert.ok(gate.allow("msg:1", t0)); | |
| 182 | assert.ok(!gate.allow("msg:2", t0 + 200), "a burst is one sound"); | |
| 183 | assert.ok(!gate.allow("msg:3", t0 + SOUND_GAP_MS - 1)); | |
| 184 | assert.ok(gate.allow("msg:4", t0 + SOUND_GAP_MS)); | |
| 185 | // The same message, from the conversation's socket and then the feed. | |
| 186 | assert.ok(!gate.allow("msg:4", t0 + SOUND_GAP_MS * 3)); | |
| 187 | assert.ok(!gate.allow("msg:4", t0 + SOUND_GAP_MS + SAME_MESSAGE_MS - 1)); | |
| 188 | assert.ok(gate.allow("msg:4", t0 + SOUND_GAP_MS + SAME_MESSAGE_MS + 1), "long after, it is a new message to the gate"); | |
| 189 | // A key refused by the gap is still remembered, so the feed's copy a moment later stays quiet. | |
| 190 | const again = new SoundGate(); | |
| 191 | assert.ok(again.allow("msg:a", t0)); | |
| 192 | assert.ok(!again.allow("msg:b", t0 + 100)); | |
| 193 | assert.ok(!again.allow("msg:b", t0 + SOUND_GAP_MS + 100), "msg:b was heard of, if not sounded"); | |
| 194 | // No key: only the gap applies. | |
| 195 | assert.ok(again.allow(null, t0 + SOUND_GAP_MS * 2)); | |
| 196 | assert.ok(!again.allow(null, t0 + SOUND_GAP_MS * 2 + 10)); | |
| 197 | }); | |
| 198 | ||
| 199 | test("an arrival's key is its message id, the same over the socket and the feed", () => { | |
| 200 | assert.equal(arrivalKey(chat(message({ id: "msg_7" }))), "msg:msg_7"); | |
| 201 | assert.equal(arrivalKey(feed(notification({ id: "msg_7" }))), "msg:msg_7"); | |
| 202 | }); | |
| 203 | ||
| 204 | test("a desktop notification shows only when wanted, allowed, out of front, and not already pushed", () => { | |
| 205 | const base = { | |
| 206 | notification: { kind: "dm" as const }, | |
| 207 | toast: true, | |
| 208 | ctx: ctx({ focus: { visible: true, focused: false, viewingChannel: null } }, { desktop_toasts: true }), | |
| 209 | permission: "granted" as const, | |
| 210 | pushOn: false, | |
| 211 | }; | |
| 212 | assert.ok(wantsDesktopToast(base)); | |
| 213 | assert.ok(!wantsDesktopToast({ ...base, ctx: ctx({ focus: base.ctx.focus }) }), "off unless turned on"); | |
| 214 | assert.ok(!wantsDesktopToast({ ...base, permission: "default" })); | |
| 215 | assert.ok(!wantsDesktopToast({ ...base, permission: "denied" })); | |
| 216 | assert.ok(!wantsDesktopToast({ ...base, pushOn: true }), "the push shows it instead"); | |
| 217 | assert.ok(!wantsDesktopToast({ ...base, toast: false }), "what the feed would not toast"); | |
| 218 | assert.ok(!wantsDesktopToast({ ...base, ctx: { ...base.ctx, dnd: true } })); | |
| 219 | assert.ok(!wantsDesktopToast({ ...base, ctx: { ...base.ctx, focus: { visible: true, focused: true, viewingChannel: null } } }), "in front: the toast is enough"); | |
| 220 | assert.ok(wantsDesktopToast({ ...base, ctx: { ...base.ctx, focus: { visible: false, focused: false, viewingChannel: null } } })); | |
| 221 | assert.ok(!wantsDesktopToast({ ...base, notification: { kind: "inbox" } })); | |
| 222 | }); |