yog 0.0.1

yog: a balls-oriented session manager for lernie loops (egui frontend)
Documentation
//! The phase-1 toolchain pane (§16.6 W5, §16.4) + the §8.3 Login surface.
//! Coverage-excluded egui glue (the `src/shell/*` precedent, §12): it renders the
//! classified host-tool state ([`AppModel::toolchain`]) plus the phase-1
//! disclaimer and the on-demand re-probe — **read-only, it gates nothing here**;
//! the mutating verbs' refusal composes in [`super::input_bar`], this pane only
//! *shows* why a verb is refused.
//!
//! Its one mutating affordance is **Login** (§8.3 as amended, bz's sole
//! interactive surface): a per-provider button spawns `bz --login --provider
//! <row>` through the streamed-piped class ([`crate::login`]) and paints its
//! device-code / URL lines live, converging to an outcome (and, on a non-zero
//! exit, the exact run-by-hand fallback). Everything it calls — provider-row
//! derivation, [`LoginRun`] poll/finalize — is covered; only these widgets are not.

use std::path::Path;

use crate::AppModel;
use crate::cli_outbound::Cli;
use crate::config_edit::brazen::{BzRunner, RealBzRunner};
use crate::login::{self, LoginRun};
use crate::theme;
use crate::world::toolgate::PHASE1_DISCLAIMER;

use super::LoginHolder;

/// Render the toolchain state as a collapsing roster section: one line per tool
/// (its capability verdict + remediation), a Re-probe button, the §8.3 Login
/// surface, and the phase-1 disclaimer.
pub fn pane(ui: &mut egui::Ui, model: &mut AppModel, login: &mut LoginHolder, bz: &Cli) {
    let state_root = model.state_root().to_path_buf();
    ui.collapsing("Toolchain", |ui| {
        for (name, state) in model.toolchain().rows() {
            ui.horizontal(|ui| {
                ui.colored_label(theme::integration_hue(name), name);
                let health = if state.permits() {
                    theme::HYDRA
                } else {
                    theme::ICHOR
                };
                ui.colored_label(health, state.describe());
            });
            if let Some(fix) = state.remediation() {
                ui.small(fix);
            }
        }
        if ui.button("Re-probe").clicked() {
            model.refresh_toolchain();
        }
        ui.separator();
        login_section(ui, login, bz, &state_root);
        ui.separator();
        ui.small(PHASE1_DISCLAIMER);
    });
}

/// The Login surface (§8.3): refresh the provider rows from `bz --dump-config`
/// (#20/#21), offer a Login per row, and paint the active streamed run.
/// `pub(super)`: the conversation center banners the same surface beside an
/// auth-failed latest step (§11) — one machinery, two seats.
pub(super) fn login_section(
    ui: &mut egui::Ui,
    login: &mut LoginHolder,
    bz: &Cli,
    state_root: &Path,
) {
    ui.label("Login (bz device flow)");
    if ui.button("↻ providers").clicked() {
        let dump = RealBzRunner::new(bz.clone()).dump_config_effective();
        login.providers = login::provider_rows(&dump.stdout);
    }
    // Collect the clicked provider first — the button loop borrows `providers`
    // immutably, the spawn writes `run` mutably.
    let mut chosen = None;
    for provider in &login.providers {
        if ui.button(format!("Login {provider}")).clicked() {
            chosen = Some(provider.clone());
        }
    }
    if let Some(provider) = chosen
        && let Ok(run) = login::start(bz, &provider, state_root, &super::now_ts())
    {
        login.run = Some(run);
    }
    if let Some(run) = login.run.as_mut() {
        run.poll();
        render_run(ui, run);
    }
}

/// Paint the active login run: the device-code / URL lines verbatim, then — once
/// settled — the outcome and, on a non-zero exit, the run-by-hand fallback (§8.3).
fn render_run(ui: &mut egui::Ui, run: &LoginRun) {
    let view = run.view();
    for line in &view.lines {
        ui.monospace(line);
    }
    if let Some(exit) = view.outcome {
        let color = if exit == 0 {
            theme::HYDRA
        } else {
            theme::ICHOR
        };
        ui.colored_label(color, format!("login exited {exit}"));
        if let Some(command) = &view.fallback {
            ui.small("or run by hand:");
            ui.code(command);
        }
    }
}