pub struct AppState {Show 22 fields
pub mode: Mode,
pub log: ConversationLog,
pub prompt: PromptBuffer,
pub theme: Theme,
pub engine: Arc<RwLock<EngineState>>,
pub should_quit: bool,
pub pending_input: Option<String>,
pub sink: Option<SessionSink>,
pub overlay: Option<ActiveOverlay>,
pub picker: Option<SlashPicker>,
pub screen_reader: bool,
pub log_scroll: u16,
pub event_ring: EventRing,
pub live_stream_visible: bool,
pub verbose: bool,
pub wrap_off: bool,
pub coaching_notices: Vec<String>,
pub rate_budget: Option<RateBudget>,
pub first_live_trade_recorded: bool,
pub risk_overlay_last_dismissed_at: Option<Instant>,
pub risk_overlay_last_seen_alert_pct: Option<f64>,
pub risk_overlay_last_trigger: Option<RiskOverlayTrigger>,
}Fields§
§mode: Mode§log: ConversationLog§prompt: PromptBuffer§theme: Theme§engine: Arc<RwLock<EngineState>>§should_quit: bool§pending_input: Option<String>Buffered input line waiting for the event loop to dispatch
it. None between submissions.
sink: Option<SessionSink>Optional session persistence. When set, every log entry is recorded.
overlay: Option<ActiveOverlay>Currently visible modal overlay. Some grabs input: the
next key press dismisses (except Ctrl+C, which still exits).
None is the normal prompt-editing state.
picker: Option<SlashPicker>Slash-command picker. Some only when the prompt’s first
row starts with / and there is no modal overlay active.
Rebuilt by AppState::refresh_picker after every input
event; the selection is preserved across rebuilds when the
previous highlighted entry still matches the new filter.
screen_reader: boolWhether the operator has opted in to screen-reader-
friendly rendering (plain ASCII, role prefixes, no dimming).
Toggled by Ctrl+R; persists for the session only.
log_scroll: u16Conversation scrollback offset in rows from the bottom.
0 means “stuck to bottom” — new entries auto-scroll into
view. Any non-zero value means the operator has scrolled up
and should not be yanked back by background traffic.
event_ring: EventRingBounded ring of engine events sourced from the WS
subscriber’s broadcast channel. Rendered by the
live-stream pane (] to toggle). The ring is always
populated — even when the pane is hidden — so toggling
it on immediately shows recent activity rather than a
blank surface.
live_stream_visible: boolWhether the live-stream pane is currently visible. Toggled
by ]. Starts hidden so the conversation pane owns the
full height on launch — operators opt in when they want
the firehose.
verbose: boolWhether verbose rendering is active. Today this expands
log timestamps from HH:MM:SS to YYYY-MM-DD HH:MM:SS
(honest across sessions that span midnight) and is
reserved for future “rich event detail” toggles. Toggled
by /verbose. Starts false so the conversation pane
defaults to compact wording — operators opt in when they
want the fuller trace.
wrap_off: boolWhether the daily-wrap generator is suppressed for this
session. Set by /wrap-off; the operator cannot make it
sticky — per ADDENDUM_A §9.1 next session’s wrap runs
again. The field is session-scoped, never persisted, and
read by the binary’s run_tui exit path (via
crate::AppExit::wrap_off) to decide whether to run
the wrap generator before returning to the shell.
coaching_notices: Vec<String>Outstanding coaching notices waiting for the operator
to acknowledge via /continue. Today the coaching
stream does not emit anything into this buffer (no
engine-side coaching channel has landed), so the field
is initialized empty and /coaching reset is an honest
no-op on the receiving end. Kept here rather than
inside a future coaching module because the dispatcher
already sends a coaching_reset signal, and having the
buffer alongside the other orthogonal toggles keeps the
state contract observable in one place.
rate_budget: Option<RateBudget>Handle on the CLI-side RateBudget attached to the
HttpClient. Cloned at app construction (the handle is
an Arc, O(1) clone). The status-bar widget reads a
BudgetSnapshot from this every frame to render the
rate:N/M segment. None means no bucket was attached
(e.g. --no-persist + no API token + offline-only tests)
— the widget falls back to rate:? in that case.
first_live_trade_recorded: boolLatch for the first-live-trade ceremony (§8.4). Starts
true in two cases: (a) no session store is attached
(--no-persist) — without a store we cannot honor
“once ever”, and a ceremony-on-every-run would be a
regression, so we suppress it; (b) the
zero_session::milestones::FIRST_LIVE_TRADE_AT
milestone is already set in the store — the operator
has traded before and does not need a first-trade
greeting. On ingesting a Positions event whose open
set is non-empty, if this field is still false, the
ceremony is rendered + persisted + the latch flips.
Session-scoped; never reset within a run.
risk_overlay_last_dismissed_at: Option<Instant>M2 §4 — monotonic instant of the most recent risk-
overlay dismissal. The auto-open hook refuses to reopen a
Risk overlay within Self::RISK_DISMISS_COOLDOWN of
this timestamp unless the trigger strictly escalates
(L3 → L4) or a fresh guardrail threshold trips (see
Self::risk_overlay_last_seen_alert_pct). Operators
pushing through Esc on a steady L3 must not be
bombarded — §4 of M2_PLAN.md.
risk_overlay_last_seen_alert_pct: Option<f64>M2 §4 — the Risk.last_drawdown_alert_pct value
observed at the moment the operator last dismissed a
Risk overlay. Compared against the engine’s current
value each tick: a change means the engine has tripped a
new guardrail threshold, which overrides the
dismiss-cooldown and forces the overlay back open. None
means “no dismissal pending” — the first open does not
consult this field.
risk_overlay_last_trigger: Option<RiskOverlayTrigger>M2 §4 — the trigger the most-recently open Risk
overlay was built against. Used to detect L3 → L4
escalation inside the dismiss cooldown: if the incoming
trigger is strictly stronger (L4 beats L3; Proximity +
L3 combined is already L3’s territory and does not
re-open). None when no overlay is currently or
recently open.
Implementations§
Source§impl AppState
impl AppState
Sourcepub const RISK_DISMISS_COOLDOWN: Duration
pub const RISK_DISMISS_COOLDOWN: Duration
Monotonic cooldown between a dismissed Risk overlay and
the auto-open hook re-opening one. 60 s per M2 §4. Does
not apply when the new trigger strictly escalates
(L3 → L4) or when the engine reports a fresh guardrail
threshold (last_drawdown_alert_pct changed value since
the dismiss).
Sourcepub const GUARDRAIL_PROXIMITY_PP: f64 = 0.5
pub const GUARDRAIL_PROXIMITY_PP: f64 = 0.5
Proximity window, in percentage points of drawdown,
that triggers the Risk overlay via
RiskOverlayTrigger::Proximity. Strictly smaller than
zero_operator_state::friction::RiskContext::PROXIMITY_PCT
(1.0 pp) because the overlay is an earlier nudge than
the L3 friction escalation — operators should see the
context surface before they hit a typed-reread gate.
Sourcepub fn new(engine: Arc<RwLock<EngineState>>) -> Self
pub fn new(engine: Arc<RwLock<EngineState>>) -> Self
New state with persistence disabled.
Sourcepub fn new_with_sink(
engine: Arc<RwLock<EngineState>>,
sink: Option<SessionSink>,
) -> Self
pub fn new_with_sink( engine: Arc<RwLock<EngineState>>, sink: Option<SessionSink>, ) -> Self
New state, optionally persisted.
Sourcepub fn append_silent(&mut self, entry: LogEntry)
pub fn append_silent(&mut self, entry: LogEntry)
Append without persisting. Used during replay so rehydrated rows are not rewritten.
Sourcepub fn push(&mut self, entry: LogEntry)
pub fn push(&mut self, entry: LogEntry)
Append + persist. All runtime-generated entries flow here.
pub fn push_system(&mut self, text: impl Into<String>)
Sourcepub fn submit_prompt(&mut self)
pub fn submit_prompt(&mut self)
Submit the current prompt. Echoes the typed line into the log and queues it for async dispatch by the event loop. Short-circuits whitespace-only input with no log noise.
Submission clears the picker and re-attaches the conversation pane to the bottom: the operator sees their command’s output without having to hit PageDown first.
Sourcepub fn apply_dispatch(&mut self, out: DispatchOutput)
pub fn apply_dispatch(&mut self, out: DispatchOutput)
Apply a DispatchOutput — append lines, switch modes,
flip flags. Called by the event loop after dispatch returns.
Sourcepub fn dismiss_overlay(&mut self)
pub fn dismiss_overlay(&mut self)
Dismiss the active overlay, if any. Idempotent. At a friction-pause overlay this is the “cancel” path — the pending command is dropped and never re-dispatched.
For ActiveOverlay::Risk, this stamps
Self::risk_overlay_last_dismissed_at with
Instant::now() and snapshots the engine’s current
last_drawdown_alert_pct so the auto-open hook can
enforce the 60 s cooldown unless the trigger strictly
escalates. The snapshot is taken at dismiss time (not at
open time) so a fresh guardrail trip that happens while
the overlay is visible still counts — the operator sees
the new threshold in the current overlay and the next
reopen fires only for the next fresh trip.
Sourcepub fn dismiss_overlay_at(&mut self, now: Instant)
pub fn dismiss_overlay_at(&mut self, now: Instant)
Test-seam variant of Self::dismiss_overlay that takes
the “now” instant explicitly. Production uses Instant::now;
tests pin a fixed monotonic anchor to verify cooldown
arithmetic deterministically.
Sourcepub fn poll_risk_overlay(&mut self, now: Instant)
pub fn poll_risk_overlay(&mut self, now: Instant)
M2 §4 auto-open hook. Called once per TUI tick with
a monotonic now. Inspects the engine mirror’s
operator-state snapshot and Risk block; opens
ActiveOverlay::Risk when either trigger fires and
the rate-limiter permits it. Never closes an overlay —
that is the operator’s job (via Self::dismiss_overlay).
Precedence of triggers:
- Friction L4 (halted) — always opens / stays open.
- Friction L3 (TILT + proximity) — opens unless cooldown.
- Drawdown proximity ≤
GUARDRAIL_PROXIMITY_PP— opens unless cooldown.
Rules are layered so a single tick that satisfies both
L3 and Proximity surfaces as Friction(L3) (the
higher-signal cause), and an L3 → L4 escalation inside
the cooldown overrides it (safety beats user comfort).
Invariant: this hook never touches a non-Risk overlay.
If the operator is inside /state, /pool, or a
friction pause, the Risk overlay defers until that
overlay closes. The guardrail signal does not vanish —
it re-fires on the next tick.
Sourcepub fn refresh_picker(&mut self)
pub fn refresh_picker(&mut self)
Rebuild Self::picker from the current prompt. Picker
is suppressed whenever an overlay is active (the operator
is inside a modal; no ambient popup on top of it) or the
prompt’s first row does not start with /.
Selection is preserved across rebuilds when the previously
highlighted command name still appears in the new match
list. This keeps Up/Down + typing interleavable without
the selection jumping back to the top on every keystroke.
Sourcepub fn scroll_log_up(&mut self, rows: u16)
pub fn scroll_log_up(&mut self, rows: u16)
Scroll the conversation pane up by rows (toward older
entries). A non-zero offset detaches the pane from the
bottom so new entries no longer auto-scroll.
Sourcepub fn scroll_log_down(&mut self, rows: u16)
pub fn scroll_log_down(&mut self, rows: u16)
Scroll the conversation pane down by rows (toward the
newest entry). Hitting 0 re-attaches to the bottom.
Sourcepub fn scroll_log_to_bottom(&mut self)
pub fn scroll_log_to_bottom(&mut self)
Re-attach the conversation pane to the newest entry. Called on prompt submit so the operator sees their command output without having to scroll back manually.
Sourcepub fn toggle_screen_reader(&mut self) -> bool
pub fn toggle_screen_reader(&mut self) -> bool
Toggle screen-reader mode (Ctrl+R). Returns the new
value so the caller can log it.
Sourcepub fn toggle_live_stream(&mut self) -> bool
pub fn toggle_live_stream(&mut self) -> bool
Toggle the live-stream pane (]). Returns the new
visibility so callers can echo a confirmation line when
the pane is hidden and the operator needs the hint.
Sourcepub fn record_engine_event(&mut self, evt: EngineEvent)
pub fn record_engine_event(&mut self, evt: EngineEvent)
Append a decoded engine event to the live-stream ring. Called by the event loop’s broadcast-receiver arm.
Sourcepub fn record_engine_event_at(&mut self, evt: EngineEvent, ts: DateTime<Utc>)
pub fn record_engine_event_at(&mut self, evt: EngineEvent, ts: DateTime<Utc>)
Record an engine event with an explicit wall-clock time. Exists for snapshot tests that need to pin the rendered timestamp regardless of when the test runs.
Sourcepub fn record_events_lagged(&mut self, skipped: u64)
pub fn record_events_lagged(&mut self, skipped: u64)
Record that the broadcast receiver lagged skipped
frames. A synthetic marker lands in the ring so the
live-stream pane can surface the drop honestly instead
of letting the firehose look calm after a burst.
Sourcepub fn record_events_lagged_at(&mut self, skipped: u64, ts: DateTime<Utc>)
pub fn record_events_lagged_at(&mut self, skipped: u64, ts: DateTime<Utc>)
Deterministic sibling of Self::record_events_lagged.
Sourcepub fn take_confirmed_friction_command(
&mut self,
now: Instant,
) -> Option<Command>
pub fn take_confirmed_friction_command( &mut self, now: Instant, ) -> Option<Command>
If a friction-pause overlay is currently open and its
outcome is Confirmed at now, take the pending command
and close the overlay. Returns Some(cmd) — the caller
(event loop) is responsible for dispatching it via
zero_commands::run_bypass_friction and applying the
resulting output. Returns None when there is no friction
overlay, the outcome is still pending, or the overlay is
not a friction variant.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for AppState
impl !RefUnwindSafe for AppState
impl Send for AppState
impl Sync for AppState
impl Unpin for AppState
impl UnsafeUnpin for AppState
impl !UnwindSafe for AppState
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more