e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
/**
|
|
* Tiny app-wide notification pub-sub. A single ToastViewport (mounted in
|
|
* the workspace layout) subscribes and renders any toast emitted by
|
|
* `notify()`.
|
|
*
|
|
* Why pub-sub instead of a context: emitters can be outside React (WS
|
|
* handlers, fetch wrappers, plain utility code). Coupling a context would
|
|
* force every non-React caller into a hook-friendly shape it doesn't need.
|
|
* Pub-sub lets us emit from anywhere and render in one place.
|
|
*
|
|
* Replaces three hand-rolled toasts that lived inside individual pages
|
|
* (agents, settings, MemorySection) — each with their own state and
|
|
* styling. New code should call `notify()` rather than start a fourth.
|
|
*/
|
|
|
|
export type NotificationTone = "info" | "success" | "error";
|
|
|
|
export interface Notification {
|
|
id: number;
|
|
message: string;
|
|
tone: NotificationTone;
|
|
durationMs: number;
|
|
}
|
|
|
|
type Listener = (n: Notification) => void;
|
|
|
|
const listeners = new Set<Listener>();
|
|
let counter = 0;
|
|
|
|
export function notify(
|
|
message: string,
|
|
options: { tone?: NotificationTone; durationMs?: number } = {},
|
|
): void {
|
|
if (!message) return;
|
|
counter += 1;
|
|
const notification: Notification = {
|
|
id: counter,
|
|
message,
|
|
tone: options.tone ?? "info",
|
|
durationMs: options.durationMs ?? 4000,
|
|
};
|
|
for (const listener of Array.from(listeners)) {
|
|
try {
|
|
listener(notification);
|
|
} catch {
|
|
/* a misbehaving listener should not break siblings */
|
|
}
|
|
}
|
|
}
|
|
|
|
export function subscribeNotifications(listener: Listener): () => void {
|
|
listeners.add(listener);
|
|
return () => {
|
|
listeners.delete(listener);
|
|
};
|
|
}
|