feat(quick-pane): promote active runtime

This commit is contained in:
2026-08-28 12:07:50 +02:00
parent 0a318f5ab6
commit b44b1be4b7
19 changed files with 1298 additions and 126 deletions
+11
View File
@@ -228,6 +228,17 @@ pub async fn create_quick_runtime(
)
.await
}
pub async fn promote_quick_runtime(
socket_path: &str,
runtime_id: &str,
) -> Result<RuntimeResult, String> {
typed_request(
socket_path,
"promote_quick_runtime",
Some(json!({ "runtimeId": runtime_id })),
)
.await
}
pub async fn open_session_runtime(
socket_path: &str,
worktree_path: &str,
+101 -1
View File
@@ -41,6 +41,7 @@ impl ManagedWindowVisibility {
}
// One mutex serializes cross-window transitions, avoiding nested visibility-lock ordering.
struct WindowVisibility(Mutex<ManagedWindowVisibility>);
struct PendingPromotedRuntime(Mutex<Option<String>>);
struct WorkspaceSubscription {
generation: Arc<bridge::SubscriptionGeneration>,
task: Mutex<Option<JoinHandle<()>>>,
@@ -96,7 +97,10 @@ fn should_hide_close_request(label: &str) -> bool {
label == "main" || label == "quick-pane"
}
fn get_or_create_main(app: &AppHandle) -> Result<(WebviewWindow, bool), String> {
fn get_or_create_main_with_visibility(
app: &AppHandle,
visible_when_created: bool,
) -> Result<(WebviewWindow, bool), String> {
if let Some(window) = app.get_webview_window("main") {
return Ok((window, false));
}
@@ -109,11 +113,16 @@ fn get_or_create_main(app: &AppHandle) -> Result<(WebviewWindow, bool), String>
.ok_or_else(|| "Main window configuration is missing".to_owned())?;
let window = WebviewWindowBuilder::from_config(app, config)
.map_err(|error| error.to_string())?
.visible(visible_when_created)
.build()
.map_err(|error| error.to_string())?;
Ok((window, true))
}
fn get_or_create_main(app: &AppHandle) -> Result<(WebviewWindow, bool), String> {
get_or_create_main_with_visibility(app, true)
}
fn apply_main_window_action(app: &AppHandle, args: &[String]) {
let (window, created) = match get_or_create_main(app) {
Ok(result) => result,
@@ -174,6 +183,44 @@ fn get_or_create_quick_pane(app: &AppHandle) -> Result<(WebviewWindow, bool), St
Ok((window, true))
}
fn show_main_for_promoted_runtime(app: &AppHandle, runtime_id: String) -> Result<(), String> {
// A recreated Main starts hidden, so Quick is never visible with it.
let (window, created) = get_or_create_main_with_visibility(app, false)?;
let state = app
.try_state::<WindowVisibility>()
.ok_or_else(|| "Window visibility state is unavailable".to_owned())?;
let pending = app
.try_state::<PendingPromotedRuntime>()
.ok_or_else(|| "Promoted runtime state is unavailable".to_owned())?;
let mut visibility = state
.0
.lock()
.map_err(|_| "Could not update window visibility".to_owned())?;
let mut pending = pending
.0
.lock()
.map_err(|_| "Could not queue promoted runtime".to_owned())?;
if created {
visibility.main = false;
}
if let Some(quick_pane) = app.get_webview_window("quick-pane") {
quick_pane
.hide()
.map_err(|error| format!("Could not hide Quick Pane before promotion: {error}"))?;
}
visibility.hidden("quick-pane");
// Retain this on show failure; a later Main startup can consume it.
*pending = Some(runtime_id);
visibility.main = false;
window
.show()
.map_err(|error| format!("Could not show Main after hiding Quick Pane: {error}"))?;
visibility.main_shown();
let _ = window.unminimize();
let _ = window.set_focus();
Ok(())
}
fn toggle_quick_pane(app: &AppHandle) {
let (window, created) = match get_or_create_quick_pane(app) {
Ok(result) => result,
@@ -333,6 +380,45 @@ async fn create_quick_runtime(worktree_path: String) -> Result<bridge::RuntimeRe
bridge::create_quick_runtime(&socket_path()?, &worktree_path).await
}
#[tauri::command]
async fn promote_quick_runtime(
app: AppHandle,
runtime_id: String,
) -> Result<bridge::RuntimeResult, String> {
let result = bridge::promote_quick_runtime(&socket_path()?, &runtime_id).await?;
show_main_for_promoted_runtime(&app, result.runtime.runtime_id.clone())?;
let _ = app.emit("workspace-promoted-runtime", json!({ "runtimeId": result.runtime.runtime_id }));
Ok(result)
}
#[tauri::command]
fn get_pending_promoted_runtime(
pending: State<'_, PendingPromotedRuntime>,
) -> Result<Option<String>, String> {
pending
.0
.lock()
.map(|pending| pending.clone())
.map_err(|_| "Could not read promoted runtime".to_owned())
}
#[tauri::command]
fn consume_pending_promoted_runtime(
runtime_id: String,
pending: State<'_, PendingPromotedRuntime>,
) -> Result<bool, String> {
let mut pending = pending
.0
.lock()
.map_err(|_| "Could not consume promoted runtime".to_owned())?;
if pending.as_deref() == Some(runtime_id.as_str()) {
*pending = None;
Ok(true)
} else {
Ok(false)
}
}
#[tauri::command]
async fn open_session_runtime(
worktree_path: String,
@@ -582,6 +668,7 @@ pub fn run() {
main: true,
quick_pane: false,
})))
.manage(PendingPromotedRuntime(Mutex::new(None)))
.manage(WorkspaceSubscription {
generation: Arc::new(bridge::SubscriptionGeneration::default()),
task: Mutex::new(None),
@@ -628,6 +715,9 @@ pub fn run() {
dismiss_quick_pane,
create_session_runtime,
create_quick_runtime,
promote_quick_runtime,
get_pending_promoted_runtime,
consume_pending_promoted_runtime,
open_session_runtime,
close_session_runtime,
close_quick_runtime,
@@ -742,6 +832,16 @@ mod tests {
);
}
#[test]
fn promoted_runtime_pending_id_clears_only_after_matching_acknowledgement() {
let pending = PendingPromotedRuntime(Mutex::new(Some("runtime-a".to_owned())));
let mut value = pending.0.lock().unwrap();
assert_ne!(value.as_deref(), Some("runtime-b"));
assert_eq!(value.as_deref(), Some("runtime-a"));
*value = None;
assert_eq!(value.as_deref(), None);
}
#[test]
fn dismissed_quick_pane_reopens_with_one_toggle() {
let mut visibility = ManagedWindowVisibility {