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 name = snap.project_name(project);
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(&name, ball)));
}
rows.extend(snap.workspaces.iter().map(|w| {
let name = leaf_name(&w.path);
(
Address::Workspace { name: name.clone() },
vec![(Field::Name, name)],
)
}));
rows
}
fn ball_row(project: &str, ball: &Ball) -> (Address, Fields) {
(
Address::Ball {
project: project.to_owned(),
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!("{}/{agent}: goal.md: {e}", leaf_name(workspace)));
}
Err(_) => {}
}
let transcript = crate::transcript::build(workspace, agent);
for entry in &transcript.entries {
if let crate::transcript::EntryKind::Compacted { first, last, .. } = entry.kind {
unreadable.push(format!(
"{}/{agent}: entries {first:03}\u{2013}{last:03} compacted away — searched the \
surviving record and the compaction summary",
leaf_name(workspace)
));
}
}
fields.extend(transcript.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", leaf_name(&w.path)));
let projects = snap
.join_rows
.iter()
.filter(|r| r.state == JoinState::OrphanedProject)
.map(|r| format!("{}: balls unlistable", r.project));
workspaces.chain(projects).collect()
}