use crate::AppModel;
use crate::cli_outbound::Cli;
use crate::git_tree::Agent;
use crate::nav::convs::root_of;
use crate::theme;
use std::path::{Path, PathBuf};
use super::ShellState;
pub fn center(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
bz: &Cli,
) {
let Some(ws) = model.focused_workspace().map(PathBuf::from) else {
super::bootstrap::render(ui, model, state, lernie, bl);
return;
};
if model.focused_is_replay() {
ui.weak("replay · read-only");
}
let Some(agent) = model.focused_agent().cloned() else {
ui.weak("select a conversation — or start one below");
return;
};
let agent_id = agent.agent_id.clone();
let agent_state = agent.state;
let agent_marks = agent.marks();
let agents: Vec<Agent> = model
.focused_tree()
.map(|t| t.agents.clone())
.unwrap_or_default();
let root = root_of(&agents, &agent_id).unwrap_or_else(|| agent_id.clone());
header(ui, model, &ws, &agents, &root);
for mark in agent_marks {
let (_, hue, phrase) = theme::mark_badge(mark);
ui.colored_label(hue, phrase);
}
let steps = state.inspector.steps_memo.read(
model.derivation(),
(ws.clone(), agent_id.clone(), agent_state),
&mut || crate::steps_view::build(&ws, &agent_id, agent_state),
);
let auth_failed = crate::login::auth::latest_step_auth_failed(steps);
let wound = crate::steps_view::latest_wound(steps);
if auth_failed.offered() {
ui.colored_label(theme::ICHOR, auth_failed.banner());
let state_root = model.state_root().to_path_buf();
super::login_pane::login_section(ui, &mut state.wall.login, bz, &state_root);
}
if state.wound_grace.paints(
&ws,
&agent_id,
wound.wounded(),
model.cadence().wound_grace(),
) {
ui.colored_label(theme::ICHOR, wound.banner());
}
ui.separator();
super::inspector::tabs_and_content(ui, model, state, &ws, lernie, bl);
}
fn header(ui: &mut egui::Ui, model: &AppModel, ws: &Path, agents: &[Agent], root: &str) {
let flight = crate::nav::convs::conversation_flight(agents, root);
ui.horizontal(|ui| {
let heading = ui.heading(crate::nav::convs::display_name_of(agents, root));
if agents
.iter()
.any(|a| a.agent_id == root && a.name_display_only())
{
heading.on_hover_text(theme::NAME_DISPLAY_ONLY);
}
let started = crate::nav::convs::started_at(root);
ui.weak(started.label).on_hover_text(started.hover);
if let Some(class) = flight {
let (glyph, hue, says) = theme::flight_badge(class);
let time = ui.ctx().input(|i| i.time);
ui.colored_label(theme::pulse(hue, time), format!("{glyph} {says}"));
ui.ctx().request_repaint_after(theme::PULSE_REPAINT_DELAY);
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
theme::live_mark(ui, &model.mark_seats());
});
});
if let Some(ball) = model.conversation_ball(root) {
super::conv_ball::header_ball(ui, &ball);
}
let ws_balls = model.ws_balls(ws);
if !ws_balls.is_empty() {
ui.horizontal(|ui| {
ui.weak("workspace balls:");
for ball in &ws_balls {
match &ball.badge {
Some(b) => ui.weak(format!("{} · {b}", ball.id)),
None => ui.weak(&ball.id),
};
}
});
}
}