diff --git a/app/src-tauri/src/commands/project_commands.rs b/app/src-tauri/src/commands/project_commands.rs
index 46ecbf9..b30040d 100644
--- a/app/src-tauri/src/commands/project_commands.rs
+++ b/app/src-tauri/src/commands/project_commands.rs
@@ -259,16 +259,18 @@ pub async fn stop_project_container(
.get(&project_id)
.ok_or_else(|| format!("Project {} not found", project_id))?;
- if let Some(ref container_id) = project.container_id {
- state.projects_store.update_status(&project_id, ProjectStatus::Stopping)?;
+ state.projects_store.update_status(&project_id, ProjectStatus::Stopping)?;
+ if let Some(ref container_id) = project.container_id {
// Close exec sessions for this project
state.exec_manager.close_sessions_for_container(container_id).await;
- docker::stop_container(container_id).await?;
- state.projects_store.update_status(&project_id, ProjectStatus::Stopped)?;
+ if let Err(e) = docker::stop_container(container_id).await {
+ log::warn!("Docker stop failed for container {} (project {}): {} — resetting to Stopped anyway", container_id, project_id, e);
+ }
}
+ state.projects_store.update_status(&project_id, ProjectStatus::Stopped)?;
Ok(())
}
diff --git a/app/src-tauri/src/storage/projects_store.rs b/app/src-tauri/src/storage/projects_store.rs
index de6f3d5..5f930e4 100644
--- a/app/src-tauri/src/storage/projects_store.rs
+++ b/app/src-tauri/src/storage/projects_store.rs
@@ -70,17 +70,38 @@ impl ProjectsStore {
(Vec::new(), false)
};
+ // Reconcile stale transient statuses: on a cold app start no Docker
+ // operations can be in flight, so Starting/Stopping are always stale.
+ let mut projects = projects;
+ let mut needs_save = needs_save;
+ for p in projects.iter_mut() {
+ match p.status {
+ crate::models::ProjectStatus::Starting | crate::models::ProjectStatus::Stopping => {
+ log::warn!(
+ "Reconciling stale '{}' status for project '{}' ({}) → Stopped",
+ serde_json::to_string(&p.status).unwrap_or_default().trim_matches('"'),
+ p.name,
+ p.id
+ );
+ p.status = crate::models::ProjectStatus::Stopped;
+ p.updated_at = chrono::Utc::now().to_rfc3339();
+ needs_save = true;
+ }
+ _ => {}
+ }
+ }
+
let store = Self {
projects: Mutex::new(projects),
file_path,
};
- // Persist migrated format back to disk
+ // Persist migrated/reconciled format back to disk
if needs_save {
- log::info!("Migrated projects.json from single-path to multi-path format");
+ log::info!("Saving reconciled/migrated projects.json to disk");
let projects = store.lock();
if let Err(e) = store.save(&projects) {
- log::error!("Failed to save migrated projects: {}", e);
+ log::error!("Failed to save projects: {}", e);
}
}
diff --git a/app/src/components/projects/ProjectCard.tsx b/app/src/components/projects/ProjectCard.tsx
index 4c93a1d..306d990 100644
--- a/app/src/components/projects/ProjectCard.tsx
+++ b/app/src/components/projects/ProjectCard.tsx
@@ -315,9 +315,12 @@ export default function ProjectCard({ project }: Props) {
>
) : (
-
- {project.status}...
-
+ <>
+
+ {project.status}...
+
+
+ >
)}
{ e?.stopPropagation?.(); setShowConfig(!showConfig); }}