use super::ShellState;
use crate::AppModel;
use crate::start::{self, StartInputs};
use lernie::mint::SplitMix64;
pub(super) fn run_prepare(model: &mut AppModel, state: &mut ShellState, inputs: StartInputs) {
super::acting::start::prepare(model, state, &inputs);
}
pub fn composer(ui: &mut egui::Ui, model: &mut AppModel, state: &mut ShellState) {
let mint_seed = state.start.mint_seed;
let Some(pending) = state.start.pending.as_mut() else {
return;
};
let (mut send, mut cancel) = (false, false);
let names = crate::nav::convs::names_in_rows(
&super::convs::of(model, pending.workspace.clone())
.value
.unwrap_or_default(),
);
ui.weak(start::identity_preview(
&names,
&SplitMix64::from_seed(mint_seed),
));
ui.label(format!("Start goal → {}", pending.workspace));
let mut fan_n = state.start.fan_n.max(1);
let ctx = model.line_context();
if ctx.project.is_some() && ctx.ball.is_some() {
const N_HOVER: &str = "How many isolated candidates to fire on this ball's target. Each writes in \
its own attempt worktree off one shared base; delivering one advances the \
ball's branch and the rest must rework. ×1 is the ordinary single start. \
Typed, it is `/fan <n>` after a `/prepare`.";
ui.horizontal(|ui| {
if ui.small_button("−").on_hover_text(N_HOVER).clicked() {
fan_n = fan_n.saturating_sub(1).max(1);
}
ui.weak(format!("×{fan_n} candidates"))
.on_hover_text(N_HOVER);
if ui.small_button("+").on_hover_text(N_HOVER).clicked() {
fan_n += 1;
}
});
}
let row = ui.spacing().interact_size.y + 2.0 * ui.spacing().item_spacing.y;
let box_height = (ui.available_height() - row).max(row);
egui::ScrollArea::vertical()
.max_height(box_height)
.show(ui, |ui| {
ui.add_sized(
[ui.available_width(), box_height],
egui::TextEdit::multiline(&mut pending.goal),
)
.on_hover_text(
"The goal this conversation starts with — the agent's first instruction. \
Edit it freely; it is only read when you press Send — or Enter, which \
is the same fire. Typed whole, it is `/prompt <goal…>`.",
);
});
let armed = crate::actions::goal_present(&pending.goal);
ui.horizontal(|ui| {
send = ui
.add_enabled(armed, egui::Button::new("Send (detached prompt)"))
.on_hover_text(
"Launch the conversation: `lernie prompt`, detached, in the workspace \
named above. It keeps running whatever yog does afterwards (Enter).",
)
.on_disabled_hover_text(
"The goal above is empty — say what you want done before this can \
launch anything.",
)
.clicked();
cancel = ui
.button("Cancel")
.on_hover_text(
"Drop this goal without launching anything. A ball claimed on the way \
here stays claimed. Escape does the same.",
)
.clicked();
});
state.start.fan_n = fan_n;
if send {
send_pending(model, state);
}
if cancel {
state.start.pending = None;
state.start.fan_n = 1;
super::focus::request(state);
}
}
pub(super) fn send_pending(model: &mut AppModel, state: &mut ShellState) {
let Some(p) = state
.start
.pending
.clone()
.filter(|p| crate::actions::goal_present(&p.goal))
else {
return;
};
let ws = model.snap.ws_path(&p.workspace).unwrap_or_default();
if state.start.fan_n > 1 {
super::acting::fan::fan(model, state, &ws, &p, state.start.fan_n);
return;
}
super::acting::start::prompt(model, state, &ws, &p, &p.goal);
}