Cross-channel ephemeral notifications.
A single in-memory pub-sub the host runtime, every extension, and every channel can use to surface a transient signal - "copied to clipboard", "verify.sh passed", "provider switched" - without embedding it in the answer body or polluting Telemere logs.
Surface (re-exported on com.blockether.vis.core):
(notify! text) (notify! text :level :info|:success|:warn|:error :ttl-ms <long>|nil) (notifications) ;; vec of currently-active entries (dismiss! id) ;; force-clear by id (clear-expired!) ;; prune; called on every read (watch! key (fn [vec] ...)) ;; cross-channel reactivity (unwatch! key)
Entry shape: {:id <uuid> :text <string> :level :info | :success | :warn | :error :created-at <inst> :until <epoch-ms> | nil ;; nil = sticky / manual dismiss
Levels are advisory metadata for channels: TUI uses them for
colour, Telegram could use emoji, CLI could prefix [notice] /
[warn]. The host stores them but never interprets them.
Why a flat module instead of a generic event bus: notifications are a single, narrow concern. A 50-line atom + watcher map serves it without introducing a generic pub-sub abstraction nobody asked for. If we ever grow more event types, this becomes one consumer of a richer system.
Cross-channel ephemeral notifications.
A single in-memory pub-sub the host runtime, every extension, and
every channel can use to surface a transient signal - "copied to
clipboard", "verify.sh passed", "provider switched" - without
embedding it in the answer body or polluting Telemere logs.
Surface (re-exported on `com.blockether.vis.core`):
(notify! text)
(notify! text :level :info|:success|:warn|:error
:ttl-ms <long>|nil)
(notifications) ;; vec of currently-active entries
(dismiss! id) ;; force-clear by id
(clear-expired!) ;; prune; called on every read
(watch! key (fn [vec] ...)) ;; cross-channel reactivity
(unwatch! key)
Entry shape:
{:id <uuid>
:text <string>
:level :info | :success | :warn | :error
:created-at <inst>
:until <epoch-ms> | nil ;; nil = sticky / manual dismiss
Levels are advisory metadata for channels: TUI uses them for
colour, Telegram could use emoji, CLI could prefix `[notice]` /
`[warn]`. The host stores them but never interprets them.
Why a flat module instead of a generic event bus: notifications
are a single, narrow concern. A 50-line atom + watcher map serves
it without introducing a generic pub-sub abstraction nobody asked
for. If we ever grow more event types, this becomes one consumer
of a richer system.(clear-expired!)Drop every entry whose :until has passed. Returns the new vec.
Called implicitly on every notifications read so callers never
see stale entries.
Drop every entry whose `:until` has passed. Returns the new vec. Called implicitly on every `notifications` read so callers never see stale entries.
(dismiss! id)Drop the entry with the given id, regardless of its :until
deadline. Returns true when an entry was actually removed.
Watchers fire when the value changed.
Drop the entry with the given id, regardless of its `:until` deadline. Returns true when an entry was actually removed. Watchers fire when the value changed.
(dismiss-all!)Drop every notification. Returns the new (empty) vec.
Drop every notification. Returns the new (empty) vec.
(notifications)Vec of currently-active notifications, oldest first. Implicitly
prunes expired entries before returning so a paint loop reading
this never has to re-check :until deadlines.
Vec of currently-active notifications, oldest first. Implicitly prunes expired entries before returning so a paint loop reading this never has to re-check `:until` deadlines.
(notify! text & {:keys [level ttl-ms] :or {level :info ttl-ms DEFAULT_TTL_MS}})Push a new notification. Returns the entry's id (uuid string) so
the caller can later dismiss! it manually.
Options:
:level one of #{:info :success :warn :error} - default :info.
Channels use the level for visual treatment (color,
emoji prefix, etc.).
:ttl-ms lifespan in ms. Default 3000. nil = sticky / no auto
expiry; the notification stays until dismiss!d.
The notification is appended to the in-memory vec and every registered watcher is fired with the new full vec.
Push a new notification. Returns the entry's id (uuid string) so
the caller can later `dismiss!` it manually.
Options:
:level one of #{:info :success :warn :error} - default :info.
Channels use the level for visual treatment (color,
emoji prefix, etc.).
:ttl-ms lifespan in ms. Default 3000. nil = sticky / no auto
expiry; the notification stays until `dismiss!`d.
The notification is appended to the in-memory vec and every
registered watcher is fired with the new full vec.(unwatch! key)Remove the watcher registered under key. Returns true when one
was actually removed.
Remove the watcher registered under `key`. Returns true when one was actually removed.
(watch! key f)Register a watcher under key. The fn f is called with the
full active-notifications vec on every push / dismiss / clear.
Replacing an existing watcher under the same key is fine -
typical pattern is (watch! :tui-screen render-bump).
Watchers run synchronously on the mutating thread, AFTER the atom swap. They MUST be cheap (microseconds) - anything heavier should bounce work onto a future.
Register a watcher under `key`. The fn `f` is called with the full active-notifications vec on every push / dismiss / clear. Replacing an existing watcher under the same key is fine - typical pattern is `(watch! :tui-screen render-bump)`. Watchers run synchronously on the mutating thread, AFTER the atom swap. They MUST be cheap (microseconds) - anything heavier should bounce work onto a future.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |