Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Notable changes to Agentaps are recorded here.

### Fixed

- Keep folders visible after their last session is archived and allow projects, including empty folders, to be archived and restored independently.

- The website header shows the latest release and star count for `cachix/agentaps` in compact dark badges linking to the GitHub release and repository.

### Changed
Expand Down
25 changes: 20 additions & 5 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ fn completed_file_text(
}

struct ProjectView {
archived: bool,
path: PathBuf,
ssh_host: Option<String>,
branch: String,
Expand Down Expand Up @@ -982,6 +983,7 @@ impl Workspace {
.projects
.into_iter()
.map(|project| ProjectView {
archived: project.archived,
branch: project
.ssh_host
.clone()
Expand Down Expand Up @@ -1228,11 +1230,9 @@ impl Workspace {
.composer
.update(cx, |input, cx| input.focus(window, cx));
} else {
let view = if this
.projects
.iter()
.any(|project| project.agents.iter().any(|agent| agent.config.archived))
{
let view = if this.projects.iter().any(|project| {
project.archived || project.agents.iter().any(|agent| agent.config.archived)
}) {
WorkspaceView::Empty
} else if !this.projects.is_empty() {
WorkspaceView::NewSession {
Expand Down Expand Up @@ -1308,6 +1308,7 @@ impl Workspace {
.map(|index| {
let project = &self.projects[index];
ProjectConfig {
archived: project.archived,
path: project.path.clone(),
ssh_host: project.ssh_host.clone(),
agents: project.agents.iter().map(AgentView::snapshot).collect(),
Expand Down Expand Up @@ -1678,6 +1679,7 @@ impl Workspace {
index
} else {
self.projects.push(ProjectView {
archived: false,
branch: ssh_host.clone().unwrap_or_else(|| branch(&path)),
sync_counts: None,
path: path.clone(),
Expand Down Expand Up @@ -1763,6 +1765,7 @@ impl Workspace {
terminal: bool,
cx: &mut Context<Self>,
) -> (u64, usize) {
self.projects[project_index].archived = false;
let id = self.next_agent_id;
let config = AgentConfig {
id,
Expand Down Expand Up @@ -1824,6 +1827,7 @@ impl Workspace {
window: &mut Window,
cx: &mut Context<Self>,
) {
self.projects[project_index].archived = true;
// Archive the displayed session last so its replacement is outside this project.
let current = self
.view
Expand All @@ -1838,6 +1842,14 @@ impl Workspace {
if let Some(session) = current {
self.set_archived(project_index, session.agent_index, true, window, cx);
}
self.persist();
cx.notify();
}

fn restore_project(&mut self, project_index: usize, cx: &mut Context<Self>) {
self.projects[project_index].archived = false;
self.persist();
cx.notify();
}

fn set_archived(
Expand All @@ -1848,6 +1860,9 @@ impl Workspace {
window: &mut Window,
cx: &mut Context<Self>,
) {
if !archived {
self.projects[project_index].archived = false;
}
let agent = &mut self.projects[project_index].agents[agent_index];
if agent.config.archived == archived {
return;
Expand Down
2 changes: 2 additions & 0 deletions src/app/mobile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ impl Workspace {
index
} else {
self.projects.push(ProjectView {
archived: false,
sync_counts: None,
branch: ssh_host.clone().unwrap_or_else(|| branch(&path)),
path: path.clone(),
Expand Down Expand Up @@ -451,6 +452,7 @@ pub(super) fn verify_session_switching_and_mobile_routing(
let mut second_agent = test_agent(ProtocolVersion::V2);
second_agent.config.id = 102;
this.projects = vec![ProjectView {
archived: false,
path: root.to_owned(),
ssh_host: None,
branch: String::new(),
Expand Down
38 changes: 32 additions & 6 deletions src/app/render/sidebar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,12 @@ impl Workspace {
.iter()
.flat_map(|project| &project.agents)
.filter(|agent| agent.config.archived)
.count();
.count()
+ self
.projects
.iter()
.filter(|project| project.archived)
.count();
for (index, location) in self.sidebar_results(&session_query).into_iter().enumerate() {
let SessionLocation {
project_index,
Expand Down Expand Up @@ -425,7 +430,10 @@ impl Workspace {
continue;
}
let collapsed = self.collapsed_projects.contains(&project_index);
if !has_sessions && (archive_view || active_only || !project.agents.is_empty()) {
if (!archive_view && project.archived)
|| (!has_sessions && archive_view && !project.archived)
|| (active_only && !has_sessions)
{
continue;
}
let name = project
Expand Down Expand Up @@ -519,18 +527,36 @@ impl Workspace {
})
.child(name),
)
.when(!archive_view && has_sessions, |header| {
.when(!archive_view || project.archived, |header| {
header.child(
sidebar_action(palette, false)
.id(("project-archive", project_index))
.invisible()
.group_hover(row_group.clone(), |style| style.visible())
.child(Icon::new(IconName::Inbox).size(px(14.)))
.tooltip(|window, cx| Tooltip::new("Archive project").build(window, cx))
.child(
Icon::new(if archive_view {
IconName::Undo2
} else {
IconName::Inbox
})
.size(px(14.)),
)
.tooltip(move |window, cx| {
Tooltip::new(if archive_view {
"Restore project"
} else {
"Archive project"
})
.build(window, cx)
})
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
.on_click(cx.listener(move |this, _, window, cx| {
cx.stop_propagation();
this.archive_project(project_index, window, cx);
if archive_view {
this.restore_project(project_index, cx);
} else {
this.archive_project(project_index, window, cx);
}
})),
)
})
Expand Down
44 changes: 43 additions & 1 deletion src/app/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,13 +604,13 @@ fn zoom_shortcuts_and_menu_actions_change_the_font_scale(cx: &mut gpui_kit::Test
verify_project_session_sidebar(&restored, cx);
verify_project_agent_picker(&restored, cx);
verify_inline_session_renaming(&restored, cx);
verify_project_archiving(&restored, cx);
verify_workspace_shortcuts(&restored, cx);
verify_settings_apply_and_persist(&restored, temp.path(), cx);
verify_key_bindings(&restored, cx);
verify_notifications_for_background_sessions(&restored, temp.path(), cx);
verify_repository_cloning(&restored, temp.path(), cx);
verify_split_panes(&restored, temp.path(), cx);
verify_project_archiving(&restored, cx);
verify_pane_resize_grab_areas(&restored, cx);
// SAFETY: restore the process environment modified for this test.
unsafe {
Expand Down Expand Up @@ -672,6 +672,7 @@ fn verify_cli_new_session(
);
let connection_id = source.connection.as_ref().unwrap().id;
this.projects = vec![ProjectView {
archived: false,
path: path.to_owned(),
ssh_host: None,
branch: "main".into(),
Expand Down Expand Up @@ -765,6 +766,7 @@ fn verify_resets_and_folder_moves_preserve_harness_references(
original.config.custom_title = Some("Keep my session name".into());
original.log(Role::User, "Previous conversation");
this.projects = vec![ProjectView {
archived: false,
path: path.to_owned(),
ssh_host: None,
branch: "main".into(),
Expand Down Expand Up @@ -800,6 +802,7 @@ fn verify_resets_and_folder_moves_preserve_harness_references(
this.projects[0].agents[0].controller.session_id = Some("reset-session".into());
this.projects[0].agents[0].config.session_has_activity = true;
this.projects.push(ProjectView {
archived: false,
path: path.join("other"),
ssh_host: None,
branch: "main".into(),
Expand Down Expand Up @@ -875,6 +878,7 @@ fn verify_workspace_shortcuts(workspace: &Entity<Workspace>, cx: &mut gpui_kit::
}
});
this.projects = vec![ProjectView {
archived: false,
path: PathBuf::from("."),
ssh_host: None,
branch: String::new(),
Expand Down Expand Up @@ -1006,6 +1010,7 @@ fn verify_inline_session_renaming(
agent.config.id = agent_id;
agent.config.command.clear();
this.projects = vec![ProjectView {
archived: false,
path: PathBuf::from("."),
ssh_host: None,
branch: String::new(),
Expand Down Expand Up @@ -1234,6 +1239,7 @@ fn verify_settings_apply_and_persist(
agent.config.command.clear();
agent.status = Status::Connecting;
this.projects = vec![ProjectView {
archived: false,
path: path.to_owned(),
ssh_host: None,
branch: "main".into(),
Expand Down Expand Up @@ -1551,6 +1557,7 @@ fn verify_notifications_for_background_sessions(
}
});
this.projects = vec![ProjectView {
archived: false,
path: path.to_owned(),
ssh_host: None,
branch: "main".into(),
Expand Down Expand Up @@ -1807,6 +1814,7 @@ fn verify_split_panes(
});
}
this.projects = vec![ProjectView {
archived: false,
path: path.to_owned(),
ssh_host: None,
branch: "main".into(),
Expand Down Expand Up @@ -1991,6 +1999,7 @@ fn verify_split_panes(
});
let other_path = path.join("other-checkout");
this.projects.push(ProjectView {
archived: false,
path: other_path.clone(),
ssh_host: None,
branch: "main".into(),
Expand Down Expand Up @@ -2205,13 +2214,15 @@ fn verify_project_archiving(workspace: &Entity<Workspace>, cx: &mut gpui_kit::Vi
};
this.projects = vec![
ProjectView {
archived: false,
path: PathBuf::from("/projects/archive"),
ssh_host: None,
branch: "main".into(),
sync_counts: None,
agents: vec![agent(801, false), agent(802, false), agent(803, true)],
},
ProjectView {
archived: false,
path: PathBuf::from("/projects/keep"),
ssh_host: None,
branch: "main".into(),
Expand All @@ -2229,7 +2240,21 @@ fn verify_project_archiving(workspace: &Entity<Workspace>, cx: &mut gpui_kit::Vi
agent_index: 0,
};
this.set_view(WorkspaceView::Conversation(first), window, cx);
this.set_archived(0, 0, true, window, cx);
assert!(!this.projects[0].archived);
this.set_archived(0, 1, true, window, cx);
assert!(
!this.projects[0].archived,
"archiving the last session keeps its folder active"
);
this.projects[0].agents[0].config.archived = false;
this.projects[0].agents[1].config.archived = false;
this.set_view(WorkspaceView::Conversation(first), window, cx);
this.archive_project(0, window, cx);
assert!(
this.projects[0].archived,
"explicit project archiving hides its folder"
);
assert!(
this.projects[0]
.agents
Expand All @@ -2242,6 +2267,7 @@ fn verify_project_archiving(workspace: &Entity<Workspace>, cx: &mut gpui_kit::Vi
let saved: Config =
serde_json::from_slice(&serde_json::to_vec(&this.config()).unwrap()).unwrap();
assert!(saved.projects[0].agents.iter().all(|agent| agent.archived));
assert!(saved.projects[0].archived);
assert_eq!(saved.projects[0].agents.len(), 3);

this.archive_project(0, window, cx);
Expand All @@ -2254,6 +2280,20 @@ fn verify_project_archiving(workspace: &Entity<Workspace>, cx: &mut gpui_kit::Vi
this.set_view(this.view.toggle_archive(), window, cx);
assert_eq!(this.view, WorkspaceView::Empty);
assert!(this.sidebar_results("").is_empty());
this.restore_project(0, cx);
assert!(!this.projects[0].archived);
assert!(
this.projects[0]
.agents
.iter()
.all(|agent| agent.config.archived)
);
this.projects[0].agents.clear();
this.archive_project(0, window, cx);
assert!(this.projects[0].archived, "empty folders can be archived");
assert!(this.config().projects[0].archived);
this.restore_project(0, cx);
assert!(!this.config().projects[0].archived);
});
});
}
Expand All @@ -2277,6 +2317,7 @@ fn verify_project_session_sidebar(
};
this.projects = vec![
ProjectView {
archived: false,
path: PathBuf::from("/projects/first"),
ssh_host: None,
branch: "main".into(),
Expand All @@ -2288,6 +2329,7 @@ fn verify_project_session_sidebar(
],
},
ProjectView {
archived: false,
path: PathBuf::from("/projects/second"),
ssh_host: Some("remote".into()),
branch: "topic".into(),
Expand Down
14 changes: 14 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ pub struct ForkSource {

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProjectConfig {
#[serde(default)]
pub archived: bool,
pub path: PathBuf,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ssh_host: Option<String>,
Expand Down Expand Up @@ -497,6 +499,18 @@ pub(crate) fn save_to(path: &Path, config: &Config) -> Result<(), String> {

#[cfg(test)]
mod tests {
#[test]
fn project_archive_state_defaults_to_active_and_survives_saving() {
let mut project: super::ProjectConfig =
serde_json::from_str(r#"{"path":"/project","agents":[]}"#).unwrap();
assert!(!project.archived);
project.archived = true;
let saved = serde_json::to_string(&project).unwrap();
let restored: super::ProjectConfig = serde_json::from_str(&saved).unwrap();
assert!(restored.archived);
assert!(restored.agents.is_empty());
}

use super::*;

use std::time::{SystemTime, UNIX_EPOCH};
Expand Down
1 change: 1 addition & 0 deletions src/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ mod tests {
});
let config = Config {
projects: vec![ProjectConfig {
archived: false,
path: "/project".into(),
ssh_host: None,
agents: vec![agent],
Expand Down
1 change: 1 addition & 0 deletions src/session/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,7 @@ fn harness_history_reloads_after_restart() {
let session_id = session.session_id.clone().unwrap();
let config = Config {
projects: vec![ProjectConfig {
archived: false,
path: temp.path().to_owned(),
ssh_host: None,
agents: vec![session.snapshot()],
Expand Down
Loading