lernie 0.1.13

lernie: the operator seat — the window and wire client for a yog server
Documentation
//! **What crosses the lock** — a worker's report, and the question set the
//! frame publishes for them.
//!
//! Split from [`super`] at the design-time budget on the seam the module's own
//! doc already draws: [`super`] is the lock and the two sides' one call each,
//! and this is what they say to each other. The first changes when the
//! threading does; the second when a pane learns to ask something new.

use serde_json::Value;

use crate::channel::Reach;
use crate::reply::Read;
use crate::ui::{Aim, Channel, Model};

/// **What one worker heard**, stamped with the channel it came down.
///
/// The stamp is the client's and it is applied here, where the worker that
/// opened the channel still knows which one it was: no origin crosses the wire,
/// and a frame that arrived with no way back to its channel could not be filed
/// against the right roster section.
#[derive(Debug, Clone)]
pub struct Heard {
    pub channel: Channel,
    pub said: Said,
}

/// What a leg produced. **More than one failure outcome**, because a channel
/// this box cannot open is a different sentence from an engine that refused:
/// the first is about this box's own files or the far end being down, the
/// second is the engine answering. A seat that read them alike would send an
/// operator to check a certificate over a workspace name they mistyped.
///
/// The fourth arm is that division taken one step further (bl-3969): what a
/// failed leg means depends on whether the gesture was an ACT, so the poster
/// reports one and the two read workers report the other.
#[derive(Debug, Clone)]
pub enum Said {
    /// One reply frame, exactly as it crossed.
    Frame(Value),
    /// **The held read's accumulation so far, stamped with what it is about.**
    ///
    /// The stamp is what makes a stale tail impossible rather than unlikely.
    /// The engine was asked about a conversation and answers about that one, so
    /// only this end knows the focus has moved — and only the FRAME knows what
    /// it is looking at right now. So the lane says what its frames are about
    /// and the frame decides whether they are still wanted, which is a pure
    /// comparison at the one place that holds the answer, rather than a poll
    /// racing the socket at the other.
    ///
    /// **Already read**, unlike [`Frame`](Self::Frame): a follow frame is an
    /// append (REMOTE §5.5), so it has to be absorbed onto the read's own fold
    /// before it means anything, and the lane is where a read begins and ends.
    /// What crosses is therefore the whole tail, which is what lets the model
    /// go on replacing.
    Live { conversation: String, read: Read },
    /// This seat could not reach the far end, and here is the sentence.
    ///
    /// **A READ's failure, and only a read's.** It is a fact about a
    /// relationship — this channel is not answering — which is why it lands on
    /// that channel's own roster section rather than in the shell's bar
    /// (REMOTE §8.2, bl-e620).
    Unreachable(String),
    /// **An ACT that earned no reply**, and what the transport can say about
    /// whether it crossed (REMOTE §3, bl-3969).
    ///
    /// A fact about an **exchange** and not about a relationship, so it goes to
    /// the bar where a refusal goes. The `op` rides with it because the bar is
    /// one line for the whole window: a sentence about an act that does not
    /// name the act is a sentence about nothing an operator can act on.
    Acted { op: String, reach: Reach },
}

/// **What to ask next.** Derived from the model on every settle, so it cannot
/// drift from the focus and there is nothing to invalidate.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Standing {
    /// Every channel this seat holds: each is asked for its own roster.
    pub channels: Vec<Channel>,
    /// The aimed wall, whose conversations are the second question.
    pub aim: Option<Aim>,
    /// **Whether the tuning pane is open**, which is the aimed wall's fourth
    /// question — what its roles are set to (bl-4a2c).
    ///
    /// A flag rather than a second aim, because the pane holds no aim of its
    /// own: it is about whatever [`Self::aim`] is, and closes when that moves
    /// (`crate::ui::model::acts`). It is keyed on the PANE rather than on the
    /// aim so a seat with no configuration surface open asks nothing about a
    /// file nobody is looking at — the read is cheap, but a standing question
    /// nobody has a use for is still a question the engine answers on every
    /// beat, forever.
    pub tuning: bool,
    /// **Whether the records pane is open**, which is the selected
    /// conversation's own second question — what its loop did and what its
    /// worktree holds (bl-2cf7). Keyed on the PANE for the reason
    /// [`Self::tuning`] is: the reads are cheap, and a standing question
    /// nobody has a use for is still one the engine answers on every beat,
    /// forever.
    pub records: bool,
    /// **Whether the decision queue is open**, which is the one question that
    /// is nobody's focus: `attention` names no workspace, so it is asked of
    /// every channel in [`Self::channels`] rather than of the aim (bl-f0ef).
    /// Keyed on the PANE for [`Self::tuning`]'s reason, and here the reason is
    /// sharper — a read that fans costs one round trip per channel on every
    /// beat, and a seat with nothing open should pay none of them.
    pub queue: bool,
    /// The selected conversation, whose transcript is the third — and whose
    /// live tail is the held read.
    ///
    /// **Not every selection is one.** A conversation this window has just
    /// started is selected under a name the engine resolves nowhere until its
    /// driver writes the branch, and asking about it would earn a refusal per
    /// pass for the whole of a healthy start. `Model::asked` is the reading
    /// that leaves it out (`crate::ui::model::claim`).
    pub conversation: Option<String>,
}

impl Standing {
    /// The question set this model implies.
    pub fn of(model: &Model) -> Self {
        Self {
            channels: model
                .roster
                .iter()
                .map(|chunk| chunk.channel.clone())
                .collect(),
            aim: model.aim.clone(),
            tuning: model.tuning.is_some(),
            records: model.records,
            queue: model.queue,
            conversation: model.asked(),
        }
    }

    /// The channel the aim is on, when there is one and this seat still holds
    /// it. A focus on a channel that has since gone is not a question.
    pub fn aimed(&self) -> Option<(Channel, Aim)> {
        let aim = self.aim.clone()?;
        let held = self.channels.iter().find(|held| held.name == aim.channel)?;
        Some((held.clone(), aim))
    }
}