Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

dwarves-gui/src/robot_watch.rs

//! **`--robot-watch`** — replay the recorded per-tab robot scenarios in a REAL,
//! VISIBLE eframe window, so a human WATCHES the robot navigate the dwarves shell
//! (World-graph → Warehouse → Forge → … → Profile) instead of the headless
//! off-screen `egui_kittest` drive.
//!
//! This is the CHOOSABLE sibling of the headless `--robot-remote <socket>` mount:
//! both drive the SAME [`ShellApp`](crate::robot_shell::ShellApp) through the SAME
//! [`shell_draw`](crate::robot_shell::shell_draw) closure and the SAME recorded
//! [`crate::scenarios::dwarves_tab_scenarios`] — the ONLY difference is
//! [`RunMode`]: `Watch` opens the paced, visible window;
//! [`RunMode::Headless`](nornir_robotui::RunMode::Headless) fills the CI matrix.
//! The mechanism lives entirely in the shared `nornir-robotui` crate
//! ([`run_scenario_mode`]); this module only WIRES dwarves into it.
//!
//! ## How to launch
//! ```text
//! dwarves-ui --robot-watch [scenario.toml]   # visible window replays the tab tour
//! dwarves-ui --robot-mode watch              # same, no scenario file
//! NORNIR_ROBOT_MODE=watch dwarves-ui         # env selector
//! ROBOTUI_WATCH=1 dwarves-ui                 # env selector (robotui's own toggle)
//! ```
//! Pacing/hold knobs come from `nornir_robotui::WatchConfig::from_env`:
//! `ROBOTUI_WATCH_DELAY_MS` (per step), `ROBOTUI_WATCH_LINGER_MS`,
//! `ROBOTUI_WATCH_HOLD=1` (keep the window open until closed by hand).

use std::path::Path;

use nornir_robotui::{run_scenario_mode, RunMode, RunOutcome, Scenario, WatchConfig};

use crate::robot_mode::all_tabs_scenario;
use crate::robot_shell::{build_shell_app, shell_draw};

/// Open the visible watch window and replay `scenario_path` (a `scenario.toml`), or
/// the flattened tab tour when `None`, against the REAL dwarves shell. Blocks on the
/// native event loop until the window closes (winit requires this run on the main
/// thread — `main` hands control here and returns after).
pub fn run_robot_watch(scenario_path: Option<&Path>) -> Result<(), String> {
    let scenario = match scenario_path {
        Some(p) => Scenario::load_toml(p).map_err(|e| e.to_string())?,
        // The opt-in installer-RAID KVM reel is RETIRED (2026-08-06): it drove the ⚒
        // pane's "▶ Build & Boot" button and that pane is gone. The engine it exercised
        // ([`crate::boot_run::EddaRaidInstallerLauncher`]) is untouched and the scene
        // reaches it through the Console pane, but no recorded WATCH reel drives it yet.
        None => all_tabs_scenario(),
    };

    let mut cfg = WatchConfig::from_env();
    cfg.title = "dwarves-ui — robot watch".to_string();
    // Lay the WHOLE tab strip out on-screen: facett-app's tab strip scrolls, and a tab
    // scrolled past the clip edge is not hit-testable, so a narrow window would leave the
    // last tabs unreachable — the SAME width the headless mount uses to keep every tab in
    // the tree (see `robot_remote::robot_session`).
    cfg.size = egui::vec2(3000.0, 1300.0);

    eprintln!(
        "dwarves-gui: robot-watch — replaying {} step(s) in a visible window",
        scenario.steps.len()
    );
    match run_scenario_mode(
        build_shell_app(),
        shell_draw,
        &scenario,
        "dwarves-gui",
        RunMode::Watch,
        cfg,
    )
    .map_err(|e| e.to_string())?
    {
        RunOutcome::Watched => Ok(()),
        // The chosen mode is Watch, so this arm is unreachable in practice; treat any
        // headless fall-through as a clean close rather than a panic.
        RunOutcome::Headless(_) => Ok(()),
    }
}

// `watch_requested()` USED TO LIVE HERE and it is why `--robot-mode headless` opened a
// window: it parsed `--robot-mode`, compared the value against `"watch"` alone, and
// returned "no watch requested" for everything else — which `main` reads as "open the
// GUI". The parse now belongs to [`crate::robot_mode::requested`], which knows every
// mode and REFUSES an unknown one. This module keeps only the window it owns.