crates/nornir-saga/src/lib.rs
//! # nornir-saga
//!
//! Capture a user's navigation (`nav`/`click`/`log`/`error`/`state`) as
//! structured, **toolkit-neutral** events → persist them to the **nornir
//! warehouse** → let the test matrix **REPLAY the path as a generated robot
//! test** for bug-finding + regression. The production sibling of
//! `nornir-testmatrix`: *emit real state from inside → warehouse → read back.*
//!
//! ```text
//! app (egui OR Leptos+Axum)
//! │ record(SagaEvent::nav(...).in_session(&ctx)) ← capture (adapters, phase 2)
//! ▼
//! capture buffer ── drain_saga_events() ──▶ SagaSink::write ──▶ nornir warehouse `saga_events`
//! │ read back
//! ▼
//! ReplayPlan::all_sessions(events) ──▶ ReplayStep[] ──▶ a robot driver ──▶ test-matrix row
//! (egui: kittest/nornir-robotui · web: WebDriver/fantoccini)
//! ```
//!
//! ## The pieces
//! 1. **[`SagaEvent`] + [`EventKind`]** ([`event`]) — one toolkit-neutral row of
//! the journey. **Navigation is the primary event.** Builders
//! ([`SagaEvent::nav`]/[`SagaEvent::click`]/…) + [`SessionCtx`]/
//! [`SagaEvent::in_session`] (a capture adapter stamps session/app identity).
//! 2. **Capture buffer** ([`record`]) — [`record`] / [`drain_saga_events`], gated
//! behind the **`saga`** feature. ON = stamp `seq`+`ts` and buffer; OFF =
//! `#[inline]` no-op + hard-empty drain (release pays zero cost). Same gating
//! discipline as `nornir-testmatrix`'s `testmatrix` feature.
//! 3. **[`SagaSink`]** ([`sink`]) — the warehouse seam: a trait + the portable
//! [`JsonFileSink`] / [`NullSink`]. nornir implements it over its Iceberg
//! warehouse; saga itself stays serde-only and never names a backend.
//! 4. **[`ReplayPlan`] / [`ReplayStep`]** ([`replay`]) — ordered `Nav`/`Click`
//! events → a toolkit-neutral step sequence a robot driver turns into a test.
//! Plain data: the egui driver and the web driver consume the SAME plan.
//!
//! Capture **adapters** (egui at the facett `nav` boundary; Leptos router events
//! + an Axum layer) and the live replay **drivers** are wired by the consuming
//! repo, not here — the lean core lands first.
pub mod analysis;
/// Bench the hot replay-fold execution path (feature `bench`) — registers a
/// `nornir-bench` [`Bencher`](bench::ReplayFold) for the saga replay fold.
#[cfg(feature = "bench")]
pub mod bench;
pub mod event;
pub mod record;
/// The ergonomic *producer* emit-API ([`recorder::SagaRecorder`]): an instance-based
/// recorder an app calls to record its own journey (buffer stamped [`SagaEvent`]s,
/// then durably flush them through any [`store::SagaTapeStore`] — warehouse, gRPC,
/// file). The single shared recording client every app (Njord + leaf apps) consumes.
pub mod recorder;
pub mod replay;
pub mod sink;
/// The warehouse-agnostic tape store seam ([`store::SagaTapeStore`]): land a
/// recorded tape into (and read it back from) any backend — the built-in
/// file-backed [`store::FileTapeStore`], the live Mimir `saga_events` table
/// (nornir's impl), or a gRPC-fronted store (dwarves). See the module docs.
pub mod store;
/// The **live UI-movement viewer + replay** feature (the `nornir-saga` spec):
/// the [`ui::UiMsg`]/[`ui::Saga`] contract, the ELM [`ui::Viewer`], the
/// deterministic [`ui::Player`] replay engine, the [`ui::WebReplayDriver`] seam,
/// the axum+SSE web frontend (feature `web`), and the DEV-ONLY Elm-dispatch
/// [`ui::tap`] (feature `saga`). See the module docs.
pub mod ui;
// ─── flat re-exports (the crate's public façade) ───────────────────────────
pub use analysis::{
frustration_by_session, rank_by_frustration, summarize_sessions, FunnelStep, NavFunnel,
NavTransition, SagaSessionSummary, SessionFrustration, RAGE_CLICK_RUN, REPEATED_ERROR_RUN,
};
pub use event::{EventKind, SagaEvent, SessionCtx};
pub use record::{drain_saga_events, flush_saga_events, ingest, record, MAX_INGEST_BATCH};
pub use recorder::SagaRecorder;
pub use replay::{ReplayPlan, ReplayStep, StepKind};
pub use sink::{JsonFileSink, NullSink, SagaSink};
pub use store::{FileTapeStore, MemTapeStore, SagaTapeStore};