Reconcile project statuses against Docker on startup, update docs and CI
All checks were successful
Build App / build-macos (push) Successful in 2m40s
Build App / build-windows (push) Successful in 4m12s
Build App / build-linux (push) Successful in 5m4s
Build Container / build-container (push) Successful in 2m41s
Build App / sync-to-github (push) Successful in 10s
All checks were successful
Build App / build-macos (push) Successful in 2m40s
Build App / build-windows (push) Successful in 4m12s
Build App / build-linux (push) Successful in 5m4s
Build Container / build-container (push) Successful in 2m41s
Build App / sync-to-github (push) Successful in 10s
- Add reconcile_project_statuses command that checks actual Docker container state on startup, preserving Running status for containers that are genuinely still running and resetting stale statuses to Stopped - Add is_container_running helper using Docker inspect API - Frontend calls reconciliation after Docker is confirmed available - Update TECHNICAL.md project structure, auth modes, and file listings to match current codebase - Update README.md and HOW-TO-USE.md with MCP servers, Mission Control, file manager, bash shells, clipboard/audio shims, and progress modal docs - Add workflow file self-triggers to CI path filters for build-app.yml and build.yml - Install Mission Control skills to ~/.claude/skills/ in entrypoint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -386,6 +386,46 @@ pub async fn rebuild_project_container(
|
||||
start_project_container(project_id, app_handle, state).await
|
||||
}
|
||||
|
||||
/// Reconcile project statuses against actual Docker container state.
|
||||
/// Called by the frontend after Docker is confirmed available. Projects
|
||||
/// marked as Running whose containers are no longer running get reset
|
||||
/// to Stopped.
|
||||
#[tauri::command]
|
||||
pub async fn reconcile_project_statuses(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<Project>, String> {
|
||||
let projects = state.projects_store.list();
|
||||
|
||||
for project in &projects {
|
||||
if project.status != ProjectStatus::Running && project.status != ProjectStatus::Error {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_running = if let Some(ref container_id) = project.container_id {
|
||||
docker::is_container_running(container_id).await.unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_running {
|
||||
log::info!(
|
||||
"Project '{}' ({}) container is still running — keeping Running status",
|
||||
project.name,
|
||||
project.id
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"Project '{}' ({}) container is not running — setting to Stopped",
|
||||
project.name,
|
||||
project.id
|
||||
);
|
||||
let _ = state.projects_store.update_status(&project.id, ProjectStatus::Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(state.projects_store.list())
|
||||
}
|
||||
|
||||
fn default_docker_socket() -> String {
|
||||
if cfg!(target_os = "windows") {
|
||||
"//./pipe/docker_engine".to_string()
|
||||
|
||||
@@ -47,8 +47,8 @@ The `/workspace/mission-control/` directory contains **Flight Control** — an A
|
||||
### How It Works
|
||||
|
||||
- **Mission Control is a tool, not a project.** It provides skills and methodology for managing other projects.
|
||||
- All Flight Control skills live in `/workspace/mission-control/.claude/skills/`
|
||||
- The projects registry at `/workspace/mission-control/projects.md` lists all active projects
|
||||
- All Flight Control skills are installed as personal skills in `~/.claude/skills/` and are automatically available as `/slash-commands`
|
||||
- The methodology docs and project registry live in `/workspace/mission-control/`
|
||||
|
||||
### When to Use
|
||||
|
||||
@@ -1031,6 +1031,16 @@ pub async fn get_container_info(project: &Project) -> Result<Option<ContainerInf
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether a Docker container is currently running.
|
||||
/// Returns false if the container doesn't exist or Docker is unavailable.
|
||||
pub async fn is_container_running(container_id: &str) -> Result<bool, String> {
|
||||
let docker = get_docker()?;
|
||||
match docker.inspect_container(container_id, None).await {
|
||||
Ok(info) => Ok(info.state.and_then(|s| s.running).unwrap_or(false)),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_sibling_containers() -> Result<Vec<ContainerSummary>, String> {
|
||||
let docker = get_docker()?;
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ pub fn run() {
|
||||
commands::project_commands::start_project_container,
|
||||
commands::project_commands::stop_project_container,
|
||||
commands::project_commands::rebuild_project_container,
|
||||
commands::project_commands::reconcile_project_statuses,
|
||||
// Settings
|
||||
commands::settings_commands::get_settings,
|
||||
commands::settings_commands::update_settings,
|
||||
|
||||
@@ -72,6 +72,8 @@ impl ProjectsStore {
|
||||
|
||||
// Reconcile stale transient statuses: on a cold app start no Docker
|
||||
// operations can be in flight, so Starting/Stopping are always stale.
|
||||
// Running/Error are left as-is and reconciled against Docker later
|
||||
// via the reconcile_project_statuses command.
|
||||
let mut projects = projects;
|
||||
let mut needs_save = needs_save;
|
||||
for p in projects.iter_mut() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useProjects } from "./hooks/useProjects";
|
||||
import { useMcpServers } from "./hooks/useMcpServers";
|
||||
import { useUpdates } from "./hooks/useUpdates";
|
||||
import { useAppState } from "./store/appState";
|
||||
import { reconcileProjectStatuses } from "./lib/tauri-commands";
|
||||
|
||||
export default function App() {
|
||||
const { checkDocker, checkImage, startDockerPolling } = useDocker();
|
||||
@@ -17,8 +18,8 @@ export default function App() {
|
||||
const { refresh } = useProjects();
|
||||
const { refresh: refreshMcp } = useMcpServers();
|
||||
const { loadVersion, checkForUpdates, startPeriodicCheck } = useUpdates();
|
||||
const { sessions, activeSessionId } = useAppState(
|
||||
useShallow(s => ({ sessions: s.sessions, activeSessionId: s.activeSessionId }))
|
||||
const { sessions, activeSessionId, setProjects } = useAppState(
|
||||
useShallow(s => ({ sessions: s.sessions, activeSessionId: s.activeSessionId, setProjects: s.setProjects }))
|
||||
);
|
||||
|
||||
// Initialize on mount
|
||||
@@ -28,6 +29,14 @@ export default function App() {
|
||||
checkDocker().then((available) => {
|
||||
if (available) {
|
||||
checkImage();
|
||||
// Reconcile project statuses against actual Docker container state,
|
||||
// then refresh the project list so the UI reflects reality.
|
||||
reconcileProjectStatuses().then((projects) => {
|
||||
setProjects(projects);
|
||||
}).catch(() => {
|
||||
// If reconciliation fails (e.g. Docker hiccup), just load from store
|
||||
refresh();
|
||||
});
|
||||
} else {
|
||||
stopPolling = startDockerPolling();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ export const stopProjectContainer = (projectId: string) =>
|
||||
invoke<void>("stop_project_container", { projectId });
|
||||
export const rebuildProjectContainer = (projectId: string) =>
|
||||
invoke<Project>("rebuild_project_container", { projectId });
|
||||
export const reconcileProjectStatuses = () =>
|
||||
invoke<Project[]>("reconcile_project_statuses");
|
||||
|
||||
// Settings
|
||||
export const getSettings = () => invoke<AppSettings>("get_settings");
|
||||
|
||||
Reference in New Issue
Block a user