Skip to main content

AppState

Struct AppState 

Source
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: bool

Whether 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: u16

Conversation 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: EventRing

Bounded 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: bool

Whether 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: bool

Whether 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: bool

Whether 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: bool

Latch 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

Source

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).

Source

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.

Source

pub fn new(engine: Arc<RwLock<EngineState>>) -> Self

New state with persistence disabled.

Source

pub fn new_with_sink( engine: Arc<RwLock<EngineState>>, sink: Option<SessionSink>, ) -> Self

New state, optionally persisted.

Source

pub fn append_silent(&mut self, entry: LogEntry)

Append without persisting. Used during replay so rehydrated rows are not rewritten.

Source

pub fn push(&mut self, entry: LogEntry)

Append + persist. All runtime-generated entries flow here.

Source

pub fn push_system(&mut self, text: impl Into<String>)

Source

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.

Source

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.

Source

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.

Source

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.

Source

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:

  1. Friction L4 (halted) — always opens / stays open.
  2. Friction L3 (TILT + proximity) — opens unless cooldown.
  3. 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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn toggle_screen_reader(&mut self) -> bool

Toggle screen-reader mode (Ctrl+R). Returns the new value so the caller can log it.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn record_events_lagged_at(&mut self, skipped: u64, ts: DateTime<Utc>)

Deterministic sibling of Self::record_events_lagged.

Source

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§

Source§

impl Debug for AppState

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more