mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 00:01:06 +00:00
fix: restore autofocus when creating worktrees (#3787)
* fix: restore autofocus when creating worktrees refs #3766 * fix: let the client focus newly created worktrees refs #3766
This commit is contained in:
@@ -874,10 +874,9 @@ impl ClientShellState {
|
||||
return self.handle_settings_endpoint_result(kind, result);
|
||||
}
|
||||
kind => {
|
||||
return (
|
||||
self.handle_worktree_endpoint_result(kind, result),
|
||||
Vec::new(),
|
||||
);
|
||||
let mut outcome = ClientShellInput::default();
|
||||
let repaint = self.handle_worktree_endpoint_result(kind, result, &mut outcome);
|
||||
return (repaint || outcome.repaint, outcome.actions);
|
||||
}
|
||||
}
|
||||
let repaint = match result {
|
||||
|
||||
@@ -978,7 +978,7 @@ fn worktree_create_previews_the_endpoint_owned_checkout_path() {
|
||||
if params.workspace_id.as_deref() == Some("ws_1")
|
||||
&& params.branch.as_deref() == Some("feature/client-shell")
|
||||
&& params.path.is_none()
|
||||
&& params.focus
|
||||
&& !params.focus
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,11 @@ fn pending_worktree() -> (ClientShellState, Vec<ClientShellAction>) {
|
||||
let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
|
||||
state.set_snapshot(Box::new(snapshot()));
|
||||
state.set_pane_surface(surface());
|
||||
submit_worktree(state)
|
||||
}
|
||||
|
||||
fn submit_worktree(mut state: ClientShellState) -> (ClientShellState, Vec<ClientShellAction>) {
|
||||
let boot_id = state.snapshot.as_ref().unwrap().boot_id.clone();
|
||||
let mut outcome = ClientShellInput::default();
|
||||
state.record_binding(
|
||||
crate::input::KeybindMatch::Action(crate::input::KeybindAction::NewWorktree),
|
||||
@@ -42,7 +47,7 @@ fn pending_worktree() -> (ClientShellState, Vec<ClientShellAction>) {
|
||||
let [ClientShellAction::Endpoint { request, .. }] = &outcome.actions[..] else {
|
||||
panic!("expected worktree preparation");
|
||||
};
|
||||
state.handle_endpoint_result("boot-1", &request.id, Ok(worktree_list_result(None)));
|
||||
state.handle_endpoint_result(&boot_id, &request.id, Ok(worktree_list_result(None)));
|
||||
state.handle_input_bytes(b"feature/reconnect");
|
||||
let outcome = state.handle_input_bytes(b"\r");
|
||||
assert!(matches!(
|
||||
@@ -59,6 +64,150 @@ fn request_id(actions: &[ClientShellAction]) -> &str {
|
||||
&request.id
|
||||
}
|
||||
|
||||
fn worktree_created_result() -> crate::api::schema::ResponseResult {
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"type": "worktree_created",
|
||||
"workspace": {
|
||||
"workspace_id": "ws_2", "number": 2, "label": "worktree",
|
||||
"focused": false, "pane_count": 1, "tab_count": 1,
|
||||
"active_tab_id": "tab_2", "agent_status": "idle"
|
||||
},
|
||||
"tab": {
|
||||
"tab_id": "tab_2", "workspace_id": "ws_2", "number": 1,
|
||||
"label": "worktree", "focused": false, "pane_count": 1,
|
||||
"agent_status": "idle"
|
||||
},
|
||||
"root_pane": {
|
||||
"pane_id": "pane_2", "terminal_id": "term_2", "workspace_id": "ws_2",
|
||||
"tab_id": "tab_2", "focused": false, "agent_status": "idle", "revision": 0
|
||||
},
|
||||
"worktree": {
|
||||
"path": "/repo-feature", "branch": "feature/reconnect", "is_bare": false,
|
||||
"is_detached": false, "is_prunable": false, "is_linked_worktree": true,
|
||||
"open_workspace_id": "ws_2", "label": "worktree"
|
||||
}
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn add_remote(state: &mut ClientShellState) -> ClientEndpointId {
|
||||
let profile = crate::client::endpoint::SavedSshEndpoint {
|
||||
id: crate::client::endpoint::ProfileId::parse("0123456789abcdef0123456789abcdef").unwrap(),
|
||||
label: "Build".into(),
|
||||
target: "dev@build.example".into(),
|
||||
session: "agents".into(),
|
||||
enabled: true,
|
||||
};
|
||||
let remote = ClientEndpointId::Ssh(profile.id.clone());
|
||||
state.set_endpoint_catalog(&[profile]);
|
||||
state.set_endpoint_status(&remote, ClientEndpointStatus::Online);
|
||||
let mut projection = snapshot();
|
||||
projection.boot_id = "remote-boot".into();
|
||||
state.set_endpoint_snapshot(&remote, Box::new(projection));
|
||||
remote
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worktree_create_leaves_server_focus_unchanged() {
|
||||
let (_, actions) = pending_worktree();
|
||||
let [ClientShellAction::Endpoint { request, .. }] = &actions[..] else {
|
||||
panic!("expected worktree creation");
|
||||
};
|
||||
assert!(matches!(
|
||||
&request.method,
|
||||
crate::api::schema::Method::WorktreeCreate(params) if !params.focus
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worktree_create_success_focuses_returned_tab_on_its_endpoint_after_snapshot_update() {
|
||||
for use_remote in [false, true] {
|
||||
let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));
|
||||
state.set_snapshot(Box::new(snapshot()));
|
||||
if use_remote {
|
||||
let remote = add_remote(&mut state);
|
||||
assert!(state.activate_endpoint_projection(&remote));
|
||||
}
|
||||
let (mut state, actions) = submit_worktree(state);
|
||||
let endpoint_id = state.active_endpoint_id.clone();
|
||||
let mut updated = state.snapshot.clone().unwrap();
|
||||
let boot_id = updated.boot_id.clone();
|
||||
updated.revision += 1;
|
||||
updated.workspaces[0].label = "updated while creating".into();
|
||||
state.set_endpoint_snapshot(&endpoint_id, updated);
|
||||
let (repaint, focus) = state.handle_endpoint_result(
|
||||
&boot_id,
|
||||
request_id(&actions),
|
||||
Ok(worktree_created_result()),
|
||||
);
|
||||
assert!(repaint);
|
||||
assert!(state.overlay.is_none());
|
||||
let [ClientShellAction::Endpoint {
|
||||
endpoint_id: target,
|
||||
boot_id: target_boot,
|
||||
request,
|
||||
}] = &focus[..]
|
||||
else {
|
||||
panic!("creation should request focus through normal client navigation");
|
||||
};
|
||||
assert_eq!(target, &endpoint_id);
|
||||
assert_eq!(target_boot, &boot_id);
|
||||
assert!(matches!(
|
||||
&request.method,
|
||||
crate::api::schema::Method::TabFocus(target) if target.tab_id == "tab_2"
|
||||
));
|
||||
assert!(state
|
||||
.handle_endpoint_result(
|
||||
&boot_id,
|
||||
request_id(&actions),
|
||||
Ok(worktree_created_result())
|
||||
)
|
||||
.1
|
||||
.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worktree_create_late_success_does_not_focus_after_switching_away_and_back() {
|
||||
let (mut state, actions) = pending_worktree();
|
||||
let remote = add_remote(&mut state);
|
||||
assert!(state.activate_endpoint_projection(&remote));
|
||||
assert!(state.activate_endpoint_projection(&ClientEndpointId::Local));
|
||||
assert!(state
|
||||
.handle_endpoint_result(
|
||||
"boot-1",
|
||||
request_id(&actions),
|
||||
Ok(worktree_created_result())
|
||||
)
|
||||
.1
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worktree_create_cancelled_or_failed_request_never_focuses() {
|
||||
for cancel in [false, true] {
|
||||
let (mut state, actions) = pending_worktree();
|
||||
let id = request_id(&actions);
|
||||
if cancel {
|
||||
assert!(state.cancel_endpoint_request(id));
|
||||
} else {
|
||||
let (_, follow_up) = state.handle_endpoint_result(
|
||||
"boot-1",
|
||||
id,
|
||||
Err(ClientShellEndpointError {
|
||||
code: Some("worktree_failed".into()),
|
||||
message: "creation failed".into(),
|
||||
}),
|
||||
);
|
||||
assert!(follow_up.is_empty());
|
||||
}
|
||||
assert!(state
|
||||
.handle_endpoint_result("boot-1", id, Ok(worktree_created_result()))
|
||||
.1
|
||||
.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_popup_request_unblocks_input_and_ignores_late_success() {
|
||||
let (mut state, actions) = pending_popup();
|
||||
|
||||
@@ -291,7 +291,7 @@ impl ClientShellState {
|
||||
base: Some("HEAD".to_owned()),
|
||||
path: None,
|
||||
label: None,
|
||||
focus: true,
|
||||
focus: false,
|
||||
trust_repository: false,
|
||||
}),
|
||||
PendingEndpointKind::WorktreeCreate,
|
||||
@@ -390,6 +390,7 @@ impl ClientShellState {
|
||||
&mut self,
|
||||
kind: PendingEndpointKind,
|
||||
result: Result<crate::api::schema::ResponseResult, ClientShellEndpointError>,
|
||||
outcome: &mut ClientShellInput,
|
||||
) -> bool {
|
||||
use crate::api::schema::ResponseResult;
|
||||
|
||||
@@ -481,8 +482,20 @@ impl ClientShellState {
|
||||
}
|
||||
true
|
||||
}
|
||||
(PendingEndpointKind::WorktreeCreate, Ok(ResponseResult::WorktreeCreated { .. }))
|
||||
| (PendingEndpointKind::WorktreeOpen, Ok(ResponseResult::WorktreeOpened { .. }))
|
||||
(
|
||||
PendingEndpointKind::WorktreeCreate,
|
||||
Ok(ResponseResult::WorktreeCreated { tab, .. }),
|
||||
) => {
|
||||
self.overlay = None;
|
||||
self.push_endpoint_method(
|
||||
crate::api::schema::Method::TabFocus(crate::api::schema::TabTarget {
|
||||
tab_id: tab.tab_id,
|
||||
}),
|
||||
outcome,
|
||||
);
|
||||
true
|
||||
}
|
||||
(PendingEndpointKind::WorktreeOpen, Ok(ResponseResult::WorktreeOpened { .. }))
|
||||
| (
|
||||
PendingEndpointKind::WorktreeRemove { .. },
|
||||
Ok(ResponseResult::WorktreeRemoved { .. }),
|
||||
|
||||
Reference in New Issue
Block a user