fix(daemon,app): graceful-shutdown final snapshot pass + StoppedSnapshot detach cleanup
Addresses final-review findings: Cmd::Shutdown now snapshots all live surfaces synchronously before exit (spec graceful-shutdown requirement); StoppedSnapshot calls detachSurface on unmount to release the bridge output channel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -453,17 +453,19 @@ async fn handle_request(
|
||||
let Some(ws) = reg.workspace(&workspace_id).cloned() else {
|
||||
let _ = out.send(err(id, "NOT_FOUND", "workspace")).await; return;
|
||||
};
|
||||
// Kill current panels of this workspace.
|
||||
let existing: Vec<SurfaceId> = ws.surfaces.keys().cloned().collect();
|
||||
for sid in &existing {
|
||||
if let Some(h) = reg.live(sid) { let _ = h.tx.send(crate::surface::SurfaceMsg::Close).await; }
|
||||
reg.remove_surface(sid);
|
||||
subs.remove(sid);
|
||||
}
|
||||
// Spawn `count` panels (slots padded/truncated to count).
|
||||
let mut new_ids = Vec::new();
|
||||
for i in 0..count {
|
||||
let slot = slots.get(i);
|
||||
// Additive: keep existing panels (and their live processes) in their
|
||||
// current visual order, spawn only the delta needed to reach `count`,
|
||||
// then rebuild the tree to the preset shape. Presets never destroy
|
||||
// running panels — shrinking is done by closing panels via the X. The
|
||||
// GUI only offers presets whose count >= the current pane count, so
|
||||
// `count >= existing.len()` and `ids.len() == count` after the loop.
|
||||
let existing: Vec<SurfaceId> = ws.layout.as_ref()
|
||||
.map(spacesh_core::ops::leaves)
|
||||
.unwrap_or_else(|| ws.surfaces.keys().cloned().collect());
|
||||
let mut ids = existing.clone();
|
||||
let to_spawn = count.saturating_sub(existing.len());
|
||||
for j in 0..to_spawn {
|
||||
let slot = slots.get(existing.len() + j);
|
||||
let new_sid = reg.new_surface_id();
|
||||
let command = slot.and_then(|s| s.command.clone());
|
||||
let shell = command.clone().unwrap_or_else(|| config.resolved_shell());
|
||||
@@ -476,20 +478,18 @@ async fn handle_request(
|
||||
reg.set_live(handle);
|
||||
reg.set_state(&new_sid, spacesh_proto::SurfaceState::Idle);
|
||||
reg.add_surface_spec(&workspace_id, new_sid.clone(), spec);
|
||||
new_ids.push(new_sid);
|
||||
broadcast_evt(clients, &Envelope::Evt(Evt::SurfaceCreated { surface_id: new_sid.clone(), workspace_id: workspace_id.clone() }));
|
||||
ids.push(new_sid);
|
||||
}
|
||||
Err(e) => { let _ = out.send(err(id, "SPAWN_FAILED", &e.to_string())).await; return; }
|
||||
}
|
||||
}
|
||||
if let Some(tree) = spacesh_core::presets::build(&preset_id, &new_ids) {
|
||||
if let Some(tree) = spacesh_core::presets::build(&preset_id, &ids) {
|
||||
if let Some(w) = reg.workspace_mut(&workspace_id) { w.layout = Some(tree); }
|
||||
}
|
||||
for sid in &new_ids {
|
||||
broadcast_evt(clients, &Envelope::Evt(Evt::SurfaceCreated { surface_id: sid.clone(), workspace_id: workspace_id.clone() }));
|
||||
}
|
||||
emit_layout(reg, &workspace_id, clients);
|
||||
persister.mark_dirty(reg.persist_state());
|
||||
let _ = out.send(ok(id, serde_json::json!({ "surface_ids": new_ids.iter().map(|s| s.0.clone()).collect::<Vec<_>>() }))).await;
|
||||
let _ = out.send(ok(id, serde_json::json!({ "surface_ids": ids.iter().map(|s| s.0.clone()).collect::<Vec<_>>() }))).await;
|
||||
}
|
||||
|
||||
Cmd::RestartSurface { surface_id, resume } => {
|
||||
@@ -732,6 +732,19 @@ async fn handle_request(
|
||||
}
|
||||
|
||||
Cmd::Shutdown => {
|
||||
// Final snapshot pass: capture each live surface's visible screen so a
|
||||
// clean restart (e.g. Settings → Restart daemon) repaints last screens.
|
||||
// Written synchronously through the store (the async writer task would
|
||||
// not drain before process::exit).
|
||||
for sid in reg.live_ids() {
|
||||
let Some(handle) = reg.live(&sid) else { continue };
|
||||
let (reply_tx, reply_rx) = oneshot::channel();
|
||||
if handle.tx.send(SurfaceMsg::Snapshot { reply: reply_tx }).await.is_ok() {
|
||||
if let Ok((snap, _dirty)) = reply_rx.await {
|
||||
snapshot_store.save(&sid, &snap);
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = out.send(ok(id, serde_json::Value::Null)).await;
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user