b71024f244
Two optional named themes from Orca's neutral palette join the Appearance picker (Orca Dark keeps a blue accent so white-on-accent buttons stay legible; Orca Light's status colours are darkened to clear 4.5:1 on their chips). A shown notification can play a sound — None (default), Chime, Pop or Alert — chosen globally and per notification type in Settings → Notifications; the WAVs are generated by scripts/gen-notification-sounds.js.
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
/**
|
|
* @file Browser-notification preferences — the single definition of the shape,
|
|
* the defaults, and the `localStorage` read/write. Settings (which edits them)
|
|
* and `useNotifications` (which obeys them) both import from here; they used to
|
|
* carry private copies, so adding a notification type meant editing the same
|
|
* literal twice and a miss went unnoticed until a toggle did nothing. A
|
|
* shown notification also plays a sound: `sound` is the default, and
|
|
* `soundByType` may override it per notification type (`soundFor`).
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
export const NOTIF_KEY = "agent-monitor-notifications";
|
|
|
|
export const NOTIF_SOUNDS = ["none", "chime", "pop", "alert"] as const;
|
|
export type NotifSound = (typeof NOTIF_SOUNDS)[number];
|
|
|
|
/** The per-type flags — every key a notification is gated on. */
|
|
export type NotifType =
|
|
| "onNewSession"
|
|
| "onSessionError"
|
|
| "onAwaitingInput"
|
|
| "onTurnComplete"
|
|
| "onSessionComplete"
|
|
| "onSubagentSpawn";
|
|
|
|
export interface NotifPrefs {
|
|
/** Master switch; when false no notification fires, whatever the flags say. */
|
|
enabled: boolean;
|
|
onNewSession: boolean;
|
|
onSessionError: boolean;
|
|
/** Claude is BLOCKED on the user — a permission prompt or a question. */
|
|
onAwaitingInput: boolean;
|
|
/** Claude finished its turn and is waiting for the next prompt (`Stop`). */
|
|
onTurnComplete: boolean;
|
|
/** The whole session closed (`SessionEnd`), not just one turn. */
|
|
onSessionComplete: boolean;
|
|
onSubagentSpawn: boolean;
|
|
/** The sound a shown notification plays, unless its type overrides it. */
|
|
sound: NotifSound;
|
|
soundByType?: Partial<Record<NotifType, NotifSound>>;
|
|
}
|
|
|
|
// The two "Claude is waiting for you" types default ON: they are the reason
|
|
// notifications exist (issue #29 — the user walks away during a long run and
|
|
// needs to know when they are the blocker). The rest stay as they were.
|
|
export const defaultNotifPrefs: NotifPrefs = {
|
|
enabled: false,
|
|
onNewSession: false,
|
|
onSessionError: true,
|
|
onAwaitingInput: true,
|
|
onTurnComplete: true,
|
|
onSessionComplete: false,
|
|
onSubagentSpawn: false,
|
|
sound: "none",
|
|
};
|
|
|
|
/** The sound for one notification type: its override, else the default. */
|
|
export function soundFor(prefs: NotifPrefs, type: NotifType): NotifSound {
|
|
return prefs.soundByType?.[type] ?? prefs.sound;
|
|
}
|
|
|
|
/** Reads prefs, merged over the defaults so a stored blob written by an older
|
|
* build (missing the newer flags) still yields a complete object. */
|
|
export function loadNotifPrefs(): NotifPrefs {
|
|
try {
|
|
const raw = localStorage.getItem(NOTIF_KEY);
|
|
if (!raw) return { ...defaultNotifPrefs };
|
|
return { ...defaultNotifPrefs, ...(JSON.parse(raw) as Partial<NotifPrefs>) };
|
|
} catch {
|
|
return { ...defaultNotifPrefs };
|
|
}
|
|
}
|
|
|
|
export function saveNotifPrefs(prefs: NotifPrefs) {
|
|
try {
|
|
localStorage.setItem(NOTIF_KEY, JSON.stringify(prefs));
|
|
} catch {
|
|
/* private mode / quota — the toggle still applies for this tab */
|
|
}
|
|
}
|