use crate::AppModel;
use crate::boundary::reply::Reply;
use crate::boundary::{Action, Query};
use crate::cli_outbound::Cli;
use crate::world::marks;
use std::path::{Path, PathBuf};
const BRANCH_HINT: &str = "The balls branch this agent records its tasks on. `balls/tasks` is the \
project's shared board — the branch an agent is pointed at when it is raised to work an \
existing project. Anything else is this agent's own task space, which no other agent's \
churn reaches. Typed, it is `/marks <branch>`.";
pub struct MarksPane {
workspace: Option<PathBuf>,
branch: String,
input: String,
space: String,
status: String,
}
impl MarksPane {
pub fn resolve() -> Self {
Self {
workspace: None,
branch: String::new(),
input: String::new(),
space: String::new(),
status: String::new(),
}
}
}
pub fn render(
ui: &mut egui::Ui,
model: &mut AppModel,
pane: &mut MarksPane,
lernie: &Cli,
bl: &Cli,
) {
ui.heading(
egui::RichText::new("task branch (this agent's balls space)")
.color(crate::theme::integration_hue("bl")),
);
let Some(workspace) = model.focused_workspace().map(Path::to_path_buf) else {
ui.weak("focus a workspace to read or amend the branch it tracks on");
return;
};
ui.monospace(workspace.display().to_string());
if pane.workspace.as_ref() == Some(&workspace) && !pane.branch.is_empty() {
ui.label(format!("branch: {}", pane.branch));
ui.weak(format!("space: {}", pane.space));
}
ui.weak(
"each agent tracks on a branch of its own; subagents inherit the space they were \
dispatched from",
);
ui.horizontal(|ui| {
if ui
.button("Read current")
.on_hover_text(
"Ask which branch this agent's tasks are recorded on right now. \
Changes nothing. Typed, it is `/marks` with no branch.",
)
.clicked()
{
pane.status = read_marks(model, pane, lernie, bl, &workspace);
}
if ui
.button("Point at the project's board")
.on_hover_text(
"Track on `balls/tasks` — the project's shared board, where a claim this \
agent makes is the same claim `bl list` shows. Typed, it is \
`/marks balls/tasks`.",
)
.clicked()
{
pane.status = apply_marks(model, pane, lernie, bl, &workspace, marks::SHARED_BRANCH);
}
});
ui.horizontal(|ui| {
ui.label("branch:").on_hover_text(BRANCH_HINT);
ui.text_edit_singleline(&mut pane.input)
.on_hover_text(BRANCH_HINT);
if ui
.button("Set branch")
.on_hover_text(
"Point this agent's task space at the branch named beside it. Typed, it \
is `/marks <branch>`.",
)
.clicked()
{
let named = pane.input.trim().to_owned();
pane.status = if marks::lawful(&named) {
apply_marks(model, pane, lernie, bl, &workspace, &named)
} else {
marks::REFUSAL.to_owned()
};
}
});
if !pane.status.is_empty() {
ui.weak(&pane.status);
}
}
fn read_marks(
model: &AppModel,
pane: &mut MarksPane,
lernie: &Cli,
bl: &Cli,
workspace: &Path,
) -> String {
let deps = model.boundary_deps(lernie, bl);
let query = Query::Marks {
workspace: workspace.to_path_buf(),
};
match model.answer(&deps, &query, super::now_unix()) {
Ok(reply) => landed(pane, workspace, &reply),
Err(e) => e,
}
}
fn apply_marks(
model: &mut AppModel,
pane: &mut MarksPane,
lernie: &Cli,
bl: &Cli,
workspace: &Path,
branch: &str,
) -> String {
let deps = model.boundary_deps(lernie, bl);
let action = Action::SetMarks {
workspace: workspace.to_path_buf(),
branch: branch.to_owned(),
};
match model.dispatch(&deps, &super::now_ts(), &action) {
Ok(reply) => format!("applied — {}", landed(pane, workspace, &reply)),
Err(e) => e,
}
}
fn landed(pane: &mut MarksPane, workspace: &Path, reply: &Reply) -> String {
let Reply::Marks { branch, space } = reply else {
return format!("unexpected reply: {reply:?}");
};
pane.workspace = Some(workspace.to_path_buf());
pane.branch.clone_from(branch);
pane.space = space.display().to_string();
format!("tracking on {branch}")
}