NotificationEntry
Persistent notification archive — the storage and data-model layer
backing NotificationLog, NotificationCenterButton, and
NotificationLogDialog.
Public types
| Kind | Name |
|---|---|
struct | NotificationEntry — A single archived notification entry rendered by NotificationLog and persisted under NotificationArchive::Persistent |
struct | NotificationUpdate — One in-place mutation applied when a Toast with the same id as an existing entry is presented again |
enum | ArchivedActionStyle — Visual presentation of an archived action button |
struct | ArchivedAction — A single action stored alongside an archived notification entry |
const | DEFAULT_ARCHIVE_LIMIT — Default per-archive entry cap |
const | ARCHIVE_FILE_NAME — File-name (without extension) used for the persistent archive |
const | UPDATE_HISTORY_LIMIT — How many NotificationUpdate records one row keeps |
enum | NotificationArchive — Storage mode for the notification archive |
struct | NotificationArchiveModel — Shared model — clones share state |
struct | NotificationLogDialog — One-liner modal preset around NotificationLog |
Public functions
NotificationArchive
| Returns | Function |
|---|---|
| Constructors | |
Self | in_memory() |
Self | in_memory_with_limit(limit: usize) |
Self | persistent(file_name: impl Into<String>) |
Self | persistent_with_limit(file_name: impl Into<String>, limit: usize) |
| Methods | |
usize | limit() |
NotificationArchiveModel
| Returns | Function |
|---|---|
| Constructors | |
Result<Self, NotificationArchiveError> | open(archive: &NotificationArchive, paths: &AppPaths, debounce: Duration) |
Self | in_memory() |
| Methods | |
&ListModel<NotificationEntry> | entries() |
&Signal<usize> | unread_count() |
&Signal<u64> | version_signal() |
usize | limit() |
Result<(), SettingsFileError> | flush_now() |
push(entry: NotificationEntry) | |
push_update(entry: NotificationEntry) | |
mark_read_where(mut predicate: impl FnMut(&NotificationEntry) -> bool) | |
mark_all_read() | |
clear() | |
clear_where(mut predicate: impl FnMut(&NotificationEntry) -> bool) | |
remove_by_id(id: u64) |
NotificationLogDialog
Detailed description
Every toast presented through the toast registry is mirrored into a
NotificationArchiveModel when archiving is enabled via
ToastInstallOptions::archive. The model is a
ListModel<NotificationEntry> plus an
unread-count signal — shaped for one-line binding to the notification
UI family. Two storage variants are available: an in-memory session-only
ring buffer (NotificationArchive::InMemory) and a file-backed
persistent store (NotificationArchive::Persistent) that survives app
restarts. Action callbacks attached via raw closures are lost on
archival; actions that should remain re-invokable from the log carry an
intent_name that the log replays through ctx.send_intent(...).
When to use
- Pair with
TeksiloAppBuilder::install_toast_default()to get the full bell-button + log + persistence stack for free. - Construct
NotificationArchiveModel::in_memorydirectly in tests or custom toast setups.
// In app boot, after install_toast:
let archive = ctx.app_state::<Rc<RefCell<NotificationArchiveModel>>>().unwrap();
let log = NotificationLog::new(archive.clone());
API reference
📖 Full rustdoc API for this module
pub struct NotificationEntry
Import: use teksilo::prelude::NotificationEntry; — in teksilo::prelude::*.
A single archived notification entry rendered by NotificationLog and
persisted under NotificationArchive::Persistent. Carries plain owned
fields only — no closures, no Rc<dyn Fn> — so it is Serialize-friendly.
#![allow(unused)] fn main() { pub struct NotificationEntry { /* fields */ } }
Fields
id:u64— Stable per-archive id (separate from the runtimeToast::iddedup key — that one lives indedup_idbelow). Assigned by the archive on first push; never reused.severity:BannerSeverity— Severity of the notice as it now stands: the latest in-place update's, else the original push's. Drives the log's row glyph + severity-chip filter.priority:ToastPrioritytitle:String— Resolved title (LocalizedString::resolve_now()snapshot).body:Option<String>actions:Vec<ArchivedAction>— The actions the notice offers as it now stands. An in-place update replaces them; one that offers none leaves none.timestamp:jiff::Timestamp— Wall-clock time the notice was last raised: its first push, or the latest time a notice with the samededup_idwas raised again after the first had left the screen. In-place updates of a notice still on screen leave it alone. The log's day-bucket computation runs against this in the user's local timezone.group:Option<String>— Optional grouping key for the log's visual section headers.source:Option<String>— Optional originating-feature tag (e.g."build","sync"). Surfaced as a chip in the log row.read:bool— Flipped when the user opens the log popover. Drives the bell badge'sunread_countsignal.dedup_id:Option<String>—Toast::id(...)value, if any — used for update-in-place merge logic. New entries with a matchingdedup_idmerge into the existing row rather than creating a separate one (seeNotificationArchiveModel::pushandpush_update).updates:Vec<NotificationUpdate>— Later presents of the sameToast::id(...): one per in-place update that changed what the row shows, and one per time the notice was raised again. Holds the most recentUPDATE_HISTORY_LIMIT. Empty on a freshly-pushed entry.route:ToastRoute— Mirrored from the originatingLiveEntry::route(seeToastRegistry::entry_to_archive) — drives which bell(s) show this entry and which bell's "mark all read" / "clear" affects it. Defaults toToastRoute::Broadcaston deserialization when the field is absent (anotifications.tomlwritten before this feature existed): treating pre-upgrade history as broadcast keeps it visible in every window's bell, rather than it silently vanishing from all of them the moment routing scopes are introduced.
pub struct NotificationUpdate
Import: use teksilo::widgets::NotificationUpdate;
One in-place mutation applied when a Toast with the same id as an
existing entry is presented again. The archive merges these onto the
existing row — the "Uploading 3 of 7 → Upload complete" pattern.
#![allow(unused)] fn main() { pub struct NotificationUpdate { /* fields */ } }
Fields
timestamp:jiff::Timestamptitle:Option<String>body:Option<String>progress:Option<f32>
pub enum ArchivedActionStyle
Import: use teksilo::widgets::ArchivedActionStyle;
Visual presentation of an archived action button. Maps one-to-one to
ToastActionStyle; re-declared as a self-contained Serialize-friendly
enum so the archive type does not depend on ButtonVariant.
#![allow(unused)] fn main() { pub enum ArchivedActionStyle { /* variants */ } }
Variants
Link— JetBrains-style hyperlink in the body row.PrimaryButton— Filled (primary CTA).SecondaryButton— Plain (secondary).Destructive— Destructive (red-tinted).
pub struct ArchivedAction
Import: use teksilo::widgets::ArchivedAction;
A single action stored alongside an archived notification entry. Only
re-invokable from NotificationLog when intent_name is set — actions
whose live closure has torn down render as inert descriptive labels.
#![allow(unused)] fn main() { pub struct ArchivedAction { /* fields */ } }
Fields
label:String— Resolved label snapshot.intent_name:Option<String>— Intent name for archive replay throughctx.send_intent(...).Nonefor closure-only actions; the log renders these as non-clickable past-action tags.style:ArchivedActionStylecloses_on_invoke:bool— Mirrors the live action'scloses_toastflag. The log uses it informationally only — a replayed Intent fires and the archive row itself doesn't dismiss (since it's not a live toast).
pub const DEFAULT_ARCHIVE_LIMIT
Import: none — DEFAULT_ARCHIVE_LIMIT is public in a private module of teksilo-widgets and nothing re-exports it.
Default per-archive entry cap. IntelliJ's notification log keeps hundreds of entries with no cap visible to the user; we pick a pragmatic limit so persistent files don't grow unbounded.
#![allow(unused)] fn main() { pub const DEFAULT_ARCHIVE_LIMIT: usize = 200; }
pub const ARCHIVE_FILE_NAME
Import: none — ARCHIVE_FILE_NAME is public in a private module of teksilo-widgets and nothing re-exports it.
File-name (without extension) used for the persistent archive.
Resolved through AppPaths::config_file into
<config_dir>/<app>/notifications.toml.
#![allow(unused)] fn main() { pub const ARCHIVE_FILE_NAME: &str = "notifications"; }
pub const UPDATE_HISTORY_LIMIT
Import: none — UPDATE_HISTORY_LIMIT is public in a private module of teksilo-widgets and nothing re-exports it.
How many NotificationUpdate
records one row keeps. A progress notice that reports each step in its
wording records one update per step, and an id an app reuses for every
run of an operation gathers them from every run, so a row keeps the most
recent ones and drops the oldest. The row itself always shows the notice
as it now stands, whatever the history kept.
#![allow(unused)] fn main() { pub const UPDATE_HISTORY_LIMIT: usize = 20; }
pub enum NotificationArchive
Import: none — NotificationArchive is public in a private module of teksilo-widgets and nothing re-exports it.
Storage mode for the notification archive. Passed inside
ToastInstallOptions::archive to the install helper.
#![allow(unused)] fn main() { pub enum NotificationArchive { /* variants */ } }
Variants
InMemory{ limit: usize }— Session-only — entries live in aListModelfor the running session. Cheap, no disk I/O. Default for apps that don't install aSettingsBundle.Persistent{ file_name: String, limit: usize }— File-backed viaPersistedListModel. The path is built at install time fromAppPaths::config_fileusing the configuredfile_name.
Methods
pub fn in_memory() -> Self
In-memory archive with the default 200-entry cap.
pub fn in_memory_with_limit(limit: usize) -> Self
In-memory archive with a custom cap.
pub fn persistent(file_name: impl Into<String>) -> Self
File-backed archive resolved through AppPaths::config_file
at install time. The default file name ("notifications")
yields <config_dir>/<app>/notifications.toml. Apps that
want a different name pass it here; tests pass an arbitrary
name and use AppPaths::for_testing(tmpdir).
pub fn persistent_with_limit(file_name: impl Into<String>, limit: usize) -> Self
pub fn limit(&self) -> usize
pub struct NotificationArchiveModel
Import: none — NotificationArchiveModel is public in a private module of teksilo-widgets and nothing re-exports it.
Shared model — clones share state. Constructed by the install
helper from NotificationArchive + AppPaths; apps reach it
via ctx.app_state::<Rc<NotificationArchiveModel>>().
NotificationLog and NotificationCenterButton
consume this model directly.
#![allow(unused)] fn main() { pub struct NotificationArchiveModel { /* fields */ } }
Methods
pub fn open( archive: &NotificationArchive, paths: &AppPaths, debounce: Duration, ) -> Result<Self, NotificationArchiveError>
Construct from a NotificationArchive config. For
Persistent mode, resolves the path through AppPaths.
Tests use AppPaths::for_testing(tmpdir) + Duration::ZERO
debounce.
pub fn in_memory() -> Self
Convenience: construct an NotificationArchive::InMemory
archive with the default cap, without going through paths.
Mostly useful for tests and apps that explicitly want no
persistence.
pub fn entries(&self) -> &ListModel<NotificationEntry>
Reactive handle on the entries. Bind to a ListView /
Repeater for live UI.
pub fn unread_count(&self) -> &Signal<usize>
Signal of the unread count. Drives the bell-button badge.
pub fn version_signal(&self) -> &Signal<u64>
Reactive handle on the archive's mutation version. Widgets
that render the archive (NotificationLog,
NotificationCenterButton) bind to this at
BindingLevel::Rebuild, in every window — one signal is enough
for N of them, see
ToastRegistry::version_signal
for the history of why that had to be said out loud.
pub fn limit(&self) -> usize
pub fn flush_now(&self) -> Result<(), SettingsFileError>
Force the persistent backing file to disk synchronously.
No-op for InMemory. Tests call this between mutations and
re-opening the file to verify persistence.
pub fn push(&self, entry: NotificationEntry)
Push a notice that has just been raised. Inserts at index 0
(newest first), evicts the oldest if the resulting length exceeds
limit. Stamps the entry's id field from next_id. Bumps
unread_count when the entry is unread (which is the typical case
from a toast push).
If entry.dedup_id matches an existing row, the notice is that row
raised again: an operation that failed this morning and fails again
now, under the same Toast::id. No new row
is inserted. The row moves to the front, takes the entry's
timestamp, records a
NotificationUpdate and
takes the entry's read state, which makes it unread again, even when
the notice says exactly what it said before: the log and the bell show
that it happened again. Every other field is merged as
push_update describes.
pub fn push_update(&self, entry: NotificationEntry)
Push an in-place update of a notice that is still on screen: a
progress notice's next step, or its result. This is what the toast
registry calls when a Toast::id matches a live toast.
If entry.dedup_id matches an existing row, that row is updated in
place: same position, and it keeps the time the notice was raised.
The row then shows the notice as it now stands, the way the live
toast it mirrors does:
title,body,severity,priority,actionsandrouteare the update's. An update offering no actions leaves the row offering none: a progress notice's Cancel must not outlive the notice that said the work was cancelled, nor stand in for the Open and See report a finished one offers.groupandsourceare the update's when it names them, and stay as they were when it does not.id,timestamp(when the notice was raised, which places the row in the log) anddedup_idstay.- A
NotificationUpdateis appended when the update changes what the row shows (its wording, severity or actions), carrying the update's timestamp and whichever of title and body changed. A row keeps the most recentUPDATE_HISTORY_LIMITof them. An update that repeats the row exactly records nothing and leaves its read state alone. - A change gives the row the update's read state, which for a toast
is unread.
unread_countcounts rows, so it grows only when the row had been read.
With no matching row (the user cleared it, or it was evicted), the
update is pushed as a new row, like push.
pub fn mark_read_where(&self, mut predicate: impl FnMut(&NotificationEntry) -> bool)
Mark every UNREAD entry matching predicate as read,
decrementing unread_count by exactly how many were flipped.
This is the scoped counterpart of mark_all_read:
a bell scoped to one window/audience must only mark ITS
entries read on close — calling the unscoped mark_all_read
from a scoped bell would incorrectly clear every OTHER
window's/audience's unread state too.
pub fn mark_all_read(&self)
Mark every archived entry as read; reset unread_count to 0.
Called by NotificationCenterButton when its popover closes.
pub fn clear(&self)
Clear the entire archive (resets unread_count to 0).
pub fn clear_where(&self, mut predicate: impl FnMut(&NotificationEntry) -> bool)
Remove every entry matching predicate, decrementing
unread_count for each removed entry that was unread. The
scoped counterpart of clear: a bell scoped to
one window/audience must only clear ITS entries — the unscoped
clear() wipes the ENTIRE shared archive (every window's
history), which would be wrong for a scoped "Clear" button.
pub fn remove_by_id(&self, id: u64)
Remove the entry with the given stable id (see
NotificationEntry::id — "assigned by the archive on first
push; never reused"). Updates unread_count if the removed entry
was unread. No-op (no version bump) when no entry has that id.
Deliberately id-based rather than index-based: an index is a
snapshot of the list's shape at the moment it was read, and is
meaningless once anything else — a concurrent peer-process reload
merged in via the live archive, another push, another remove —
has shifted rows out from under it. A caller that captured "the row
I want to dismiss" as an index earlier and replays it later against
a since-mutated list can silently remove the wrong entry; keying
off id instead re-resolves the row's current position at the
moment of removal, so it always removes the entry the caller meant.
pub struct NotificationLogDialog
Import: none — NotificationLogDialog is public in a private module of teksilo-widgets and nothing re-exports it.
One-liner modal preset around NotificationLog. Apps usually
wire this to a menu item or shortcut (e.g. "Window → Notification
Log…").
#![allow(unused)] fn main() { pub struct NotificationLogDialog; }
Methods
pub fn show(archive: Rc<NotificationArchiveModel>, ctx: &mut EventContext)
Present the dialog with the standard chrome (title + 720x520 default size, escape-or-click-outside dismissal).
pub fn show_with( archive: Rc<NotificationArchiveModel>, ctx: &mut EventContext, configure: impl FnOnce(NotificationLog) -> NotificationLog + 'static, )
Same as show, but lets the caller configure the embedded
NotificationLog (e.g. attach an on_action_invoked hook
for archive replay).