use super::{Address, Field};
use crate::app::Snapshot;
use crate::naming::leaf as leaf_name;
use crate::projects::balls::Ball;
use crate::projects::join::JoinState;
use std::path::{Path, PathBuf};
const GOAL: &str = "goal.md";
type Fields = Vec<(Field, String)>;
pub(super) fn from_snapshot(snap: &Snapshot) -> Vec<(Address, Fields)> {
let mut projects: Vec<&PathBuf> = snap
.balls_by_project
.keys()
.chain(snap.closed_by_project.keys())
.collect();
projects.sort();
projects.dedup();
let mut rows: Vec<(Address, Fields)> = Vec::new();
for project in projects {
let live = snap.balls_by_project.get(project).into_iter().flatten();
let closed = snap.closed_by_project.get(project).into_iter().flatten();
rows.extend(live.chain(closed).map(|ball| ball_row(project, ball)));
}
rows.extend(snap.workspaces.iter().map(|w| {
(
Address::Workspace {
path: w.path.clone(),
},
vec![(Field::Name, leaf_name(&w.path))],
)
}));
rows
}
fn ball_row(project: &Path, ball: &Ball) -> (Address, Fields) {
(
Address::Ball {
project: project.to_path_buf(),
id: ball.id.clone(),
},
vec![
(Field::Name, ball.id.clone()),
(Field::Summary, ball.title.clone()),
(Field::Text, ball.body.clone()),
],
)
}
pub(super) fn conversations(snap: &Snapshot) -> Vec<(PathBuf, String, Fields)> {
let mut rows: Vec<(PathBuf, String, Fields)> = Vec::new();
for ws in &snap.workspaces {
let Some(tree) = snap.trees.get(&ws.path) else {
continue;
};
rows.extend(tree.agents.iter().map(|agent| {
let mut fields = vec![(Field::Name, agent.agent_id.clone())];
fields.extend(agent.name_fact().map(|name| (Field::Name, name)));
(ws.path.clone(), agent.agent_id.clone(), fields)
}));
}
rows
}
pub(super) fn read_conversation(
workspace: &Path,
agent: &str,
unreadable: &mut Vec<String>,
) -> Fields {
let goal = crate::files_view::agent_worktree(workspace, agent).join(GOAL);
let mut fields: Fields = Vec::new();
match std::fs::read(&goal) {
Ok(bytes) => fields.push((Field::Summary, String::from_utf8_lossy(&bytes).into_owned())),
Err(e) if goal.exists() => unreadable.push(format!("{}: {e}", goal.display())),
Err(_) => {}
}
fields.extend(
crate::transcript::build(workspace, agent)
.entries
.into_iter()
.map(|entry| {
(
Field::Text,
String::from_utf8_lossy(&entry.raw).into_owned(),
)
}),
);
fields
}
pub(super) fn unreadable(snap: &Snapshot) -> Vec<String> {
let workspaces = snap
.workspaces
.iter()
.filter(|w| !snap.trees.contains_key(&w.path))
.map(|w| format!("{}: no derived tree", w.path.display()));
let projects = snap
.join_rows
.iter()
.filter(|r| r.state == JoinState::OrphanedProject)
.map(|r| format!("{}: balls unlistable", r.project));
workspaces.chain(projects).collect()
}