Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ab503e4f5 | ||
|
|
eaf11a7696 | ||
|
|
fffd2a55f3 | ||
|
|
9dead5652e |
@@ -777,7 +777,7 @@ async fn unpushed_commits_block_kill_and_force_keeps_the_tree() {
|
||||
other => panic!("expected a kill_guarded refusal, got {other:?}"),
|
||||
}
|
||||
|
||||
// Force overrides the refusal and nothing else: session gone, tree kept.
|
||||
// Force overrides the refusal: session gone, tree removed.
|
||||
client.kill_session_forced(s.id).await.expect("forced kill");
|
||||
|
||||
assert!(
|
||||
@@ -789,12 +789,12 @@ async fn unpushed_commits_block_kill_and_force_keeps_the_tree() {
|
||||
.any(|x| x.id == s.id),
|
||||
"the forced kill ends the session"
|
||||
);
|
||||
assert!(wt.is_dir(), "a guarded worktree must stay on disk");
|
||||
assert!(!wt.is_dir(), "a forced kill removes the worktree");
|
||||
assert!(
|
||||
git_worktree_list(base.path())
|
||||
!git_worktree_list(base.path())
|
||||
.iter()
|
||||
.any(|l| l.contains("feature/local")),
|
||||
"the kept tree stays registered in git"
|
||||
"the removed tree is unregistered from git"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -196,12 +196,24 @@ export function useStructuredSession(
|
||||
connect();
|
||||
};
|
||||
|
||||
// Mobile browsers suspend timers and silently kill WebSockets in the
|
||||
// background. Kick a fresh connect when the page becomes visible again.
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState !== "visible" || disposed) return;
|
||||
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) return;
|
||||
cancelTimer();
|
||||
setStatus("reconnecting");
|
||||
connect();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisibility);
|
||||
|
||||
setStatus("connecting");
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
reconnectRef.current = () => {};
|
||||
document.removeEventListener("visibilitychange", onVisibility);
|
||||
cancelTimer();
|
||||
closeSocket();
|
||||
};
|
||||
|
||||
@@ -302,6 +302,24 @@ export const TerminalPane = forwardRef<TerminalPaneHandle, TerminalPaneProps>(
|
||||
ta?.addEventListener("focus", onTaFocus);
|
||||
ta?.addEventListener("blur", onTaBlur);
|
||||
|
||||
// Mobile browsers (Android especially) suspend timers and silently kill
|
||||
// WebSockets when the app goes to background. The backoff timer set by
|
||||
// onclose is frozen too, so recovery stalls until visibility returns. Kick
|
||||
// a fresh connect immediately when the page becomes visible again.
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState !== "visible" || disposed) return;
|
||||
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) return;
|
||||
// Cancel any pending backoff — we're reconnecting right now.
|
||||
if (reconnectTimer !== null) {
|
||||
window.clearTimeout(reconnectTimer);
|
||||
reconnectTimer = null;
|
||||
}
|
||||
reconnectMs = INITIAL_RECONNECT_MS;
|
||||
onStatusChange?.("reconnecting");
|
||||
connect();
|
||||
};
|
||||
document.addEventListener("visibilitychange", onVisibility);
|
||||
|
||||
window.addEventListener("resize", onResize);
|
||||
const visual = window.visualViewport ?? null;
|
||||
visual?.addEventListener("resize", onResize);
|
||||
@@ -326,6 +344,7 @@ export const TerminalPane = forwardRef<TerminalPaneHandle, TerminalPaneProps>(
|
||||
reconnectTimer = null;
|
||||
}
|
||||
ro.disconnect();
|
||||
document.removeEventListener("visibilitychange", onVisibility);
|
||||
window.removeEventListener("resize", onResize);
|
||||
visual?.removeEventListener("resize", onResize);
|
||||
ta?.removeEventListener("focus", onTaFocus);
|
||||
|
||||
Reference in New Issue
Block a user