fux 0.8.0

Minimal persistent terminal multiplexer built on bevy_ecs
Documentation
//! Bounded terminal evidence, independent of the lifetime of pane and workspace entities.
use crate::ecs::components::{Pane, PaneState};
use crate::ecs::resources::{
    Clock, Deadlines, FINAL_RETENTION_MS, FinalRecords, Ids, MAX_FINAL_RECORDS, ServerIdentity,
};
use crate::proto::control::{CommandResult, ErrorCode, FinalRecord, Reply};
use bevy_ecs::prelude::*;

pub fn remember(world: &mut World, pane: Entity) {
    let now = world.resource::<Clock>().now_ms;
    let Some(mut component) = world.get_mut::<Pane>(pane) else {
        return;
    };
    if matches!(component.state, PaneState::Starting) {
        return;
    }
    let record = FinalRecord {
        pane: component.id,
        workspace: component.workspace_name.clone(),
        stream: component.workspace_stream,
        command: component.argv.clone(),
        cwd: component.cwd.clone(),
        exit_status: component.state.exit_code(),
        closed_ms: now,
        expires_ms: now.saturating_add(FINAL_RETENTION_MS),
        input_sequence: component.input_sequence,
        capture: component.terminal.capture_snapshot(0, false, 131_072, None),
    };
    let mut records = world.resource_mut::<FinalRecords>();
    if records.0.len() >= MAX_FINAL_RECORDS {
        let oldest = records
            .0
            .values()
            .min_by_key(|record| (record.closed_ms, record.pane))
            .map(|record| record.pane);
        if let Some(oldest) = oldest {
            records.0.remove(&oldest);
        }
    }
    records.0.insert(record.pane, record);
    expire(world);
}

pub fn read(world: &World, instance: &str, pane: crate::ids::PaneId) -> Reply {
    if instance != world.resource::<ServerIdentity>().instance_nonce {
        return Reply::failed(
            0,
            ErrorCode::Conflict,
            "server instance changed; final evidence belongs to the previous server",
        );
    }
    if world.resource::<Ids>().pane(pane).is_some() {
        return Reply::failed(
            0,
            ErrorCode::Pending,
            "pane has not been released; use capture for current evidence",
        );
    }
    match world
        .resource::<FinalRecords>()
        .0
        .get(&pane)
        .filter(|record| record.expires_ms > world.resource::<Clock>().now_ms)
    {
        Some(record) => Reply::Completed {
            id: 0,
            result: CommandResult::Final {
                record: Box::new(record.clone()),
            },
        },
        None => Reply::failed(
            0,
            ErrorCode::Expired,
            "final evidence unknown, evicted, or expired",
        ),
    }
}

pub fn expire(world: &mut World) {
    let now = world.resource::<Clock>().now_ms;
    world
        .resource_mut::<FinalRecords>()
        .0
        .retain(|_, record| record.expires_ms > now);
    if let Some(next) = world
        .resource::<FinalRecords>()
        .0
        .values()
        .map(|record| record.expires_ms)
        .min()
    {
        world.resource_mut::<Deadlines>().propose(next);
    }
}