Connect-host wizard: delete the minted pending node on cancel (no orphan 'waiting for daemon' card)
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 7s
ci / sandbox-k8s (push) Failing after 27s
ci / frontend (push) Failing after 36s
ci / e2e (push) Has been skipped

The wizard mints a pending node on open (POST /api/nodes/pair). Cancelling left it
behind as a 'waiting for daemon to dial home' card. handleClose now DELETEs the node
on close unless it actually connected (everConnected) — so cancel leaves no card,
while a node that already dialed home is kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-26 15:30:54 -07:00
co-authored by Claude Opus 4.8
parent fadb1a084c
commit 447bf04932
@@ -37,6 +37,8 @@ export function ConnectHostWizard({ onClose }: { onClose: () => void }) {
const [node, setNode] = useState<FleetNode | null>(null); const [node, setNode] = useState<FleetNode | null>(null);
const [exec, setExec] = useState<{ ok: boolean; output: string } | null>(null); const [exec, setExec] = useState<{ ok: boolean; output: string } | null>(null);
const [execing, setExecing] = useState(false); const [execing, setExecing] = useState(false);
// Once the daemon has dialed home, the node is real — keep it even on cancel.
const [everConnected, setEverConnected] = useState(false);
const origin = typeof window !== "undefined" ? window.location.origin : ""; const origin = typeof window !== "undefined" ? window.location.origin : "";
@@ -82,6 +84,19 @@ export function ConnectHostWizard({ onClose }: { onClose: () => void }) {
}; };
}, [step, nodeId]); }, [step, nodeId]);
useEffect(() => {
if (node?.status === "online") setEverConnected(true);
}, [node]);
// Cancelling before the daemon ever connected → drop the orphan pending node so
// we don't leave a "waiting for daemon" card behind.
const handleClose = useCallback(() => {
if (nodeId && !everConnected) {
fetch(`/api/nodes/${nodeId}`, { method: "DELETE" }).catch(() => {});
}
onClose();
}, [nodeId, everConnected, onClose]);
const runExec = useCallback(() => { const runExec = useCallback(() => {
if (!nodeId) return; if (!nodeId) return;
setExecing(true); setExecing(true);
@@ -105,7 +120,7 @@ export function ConnectHostWizard({ onClose }: { onClose: () => void }) {
const idx = STEPS.findIndex((s) => s.key === step); const idx = STEPS.findIndex((s) => s.key === step);
return ( return (
<div style={{ position: "fixed", inset: 0, zIndex: 300, background: "rgba(0,0,0,.6)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }} onClick={onClose}> <div style={{ position: "fixed", inset: 0, zIndex: 300, background: "rgba(0,0,0,.6)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }} onClick={handleClose}>
<div onClick={(e) => e.stopPropagation()} style={{ width: 560, maxWidth: "100%", maxHeight: "90vh", overflow: "auto", borderRadius: 18, background: "#141417", border: "1px solid rgba(255,255,255,.1)", boxShadow: "0 24px 70px rgba(0,0,0,.6)" }}> <div onClick={(e) => e.stopPropagation()} style={{ width: 560, maxWidth: "100%", maxHeight: "90vh", overflow: "auto", borderRadius: 18, background: "#141417", border: "1px solid rgba(255,255,255,.1)", boxShadow: "0 24px 70px rgba(0,0,0,.6)" }}>
<div style={{ display: "flex", alignItems: "center", gap: 12, padding: "18px 20px 14px", borderBottom: "1px solid rgba(255,255,255,.07)" }}> <div style={{ display: "flex", alignItems: "center", gap: 12, padding: "18px 20px 14px", borderBottom: "1px solid rgba(255,255,255,.07)" }}>
<span style={{ width: 34, height: 34, borderRadius: 9, background: "rgba(94,200,216,.12)", border: "1px solid rgba(94,200,216,.3)", display: "flex", alignItems: "center", justifyContent: "center", color: "#5ec8d8" }}><Terminal size={17} /></span> <span style={{ width: 34, height: 34, borderRadius: 9, background: "rgba(94,200,216,.12)", border: "1px solid rgba(94,200,216,.3)", display: "flex", alignItems: "center", justifyContent: "center", color: "#5ec8d8" }}><Terminal size={17} /></span>
@@ -117,7 +132,7 @@ export function ConnectHostWizard({ onClose }: { onClose: () => void }) {
))} ))}
</div> </div>
</div> </div>
<button type="button" onClick={onClose} aria-label="Close" style={{ width: 30, height: 30, borderRadius: 8, border: "1px solid rgba(255,255,255,.12)", background: "transparent", color: "#9a9aa2", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><X size={16} /></button> <button type="button" onClick={handleClose} aria-label="Close" style={{ width: 30, height: 30, borderRadius: 8, border: "1px solid rgba(255,255,255,.12)", background: "transparent", color: "#9a9aa2", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><X size={16} /></button>
</div> </div>
<div style={{ padding: 20 }}> <div style={{ padding: 20 }}>
@@ -165,13 +180,13 @@ export function ConnectHostWizard({ onClose }: { onClose: () => void }) {
</div> </div>
<div style={{ display: "flex", justifyContent: "space-between", gap: 10, padding: "14px 20px", borderTop: "1px solid rgba(255,255,255,.07)" }}> <div style={{ display: "flex", justifyContent: "space-between", gap: 10, padding: "14px 20px", borderTop: "1px solid rgba(255,255,255,.07)" }}>
<button type="button" onClick={onClose} style={{ padding: "9px 16px", borderRadius: 9, border: "1px solid rgba(255,255,255,.14)", background: "transparent", color: "#9a9aa2", fontSize: 13, fontWeight: 600, cursor: "pointer" }}>{step === "exec" ? "Done" : "Cancel"}</button> <button type="button" onClick={handleClose} style={{ padding: "9px 16px", borderRadius: 9, border: "1px solid rgba(255,255,255,.14)", background: "transparent", color: "#9a9aa2", fontSize: 13, fontWeight: 600, cursor: "pointer" }}>{step === "exec" ? "Done" : "Cancel"}</button>
{step === "install" ? ( {step === "install" ? (
<button type="button" disabled={!token} onClick={() => setStep("verify")} style={{ padding: "9px 18px", borderRadius: 9, border: 0, background: token ? "#5ec8d8" : "rgba(94,200,216,.3)", color: "#04222a", fontSize: 13, fontWeight: 700, cursor: token ? "pointer" : "default" }}>I’ve run it →</button> <button type="button" disabled={!token} onClick={() => setStep("verify")} style={{ padding: "9px 18px", borderRadius: 9, border: 0, background: token ? "#5ec8d8" : "rgba(94,200,216,.3)", color: "#04222a", fontSize: 13, fontWeight: 700, cursor: token ? "pointer" : "default" }}>I’ve run it →</button>
) : step === "verify" ? ( ) : step === "verify" ? (
<button type="button" disabled={!online} onClick={() => setStep("exec")} style={{ padding: "9px 18px", borderRadius: 9, border: 0, background: online ? "#5ec8d8" : "rgba(94,200,216,.3)", color: "#04222a", fontSize: 13, fontWeight: 700, cursor: online ? "pointer" : "default" }}>Next →</button> <button type="button" disabled={!online} onClick={() => setStep("exec")} style={{ padding: "9px 18px", borderRadius: 9, border: 0, background: online ? "#5ec8d8" : "rgba(94,200,216,.3)", color: "#04222a", fontSize: 13, fontWeight: 700, cursor: online ? "pointer" : "default" }}>Next →</button>
) : ( ) : (
<button type="button" onClick={onClose} style={{ padding: "9px 18px", borderRadius: 9, border: 0, background: "#5fd08a", color: "#04220f", fontSize: 13, fontWeight: 700, cursor: "pointer" }}>Finish</button> <button type="button" onClick={handleClose} style={{ padding: "9px 18px", borderRadius: 9, border: 0, background: "#5fd08a", color: "#04220f", fontSize: 13, fontWeight: 700, cursor: "pointer" }}>Finish</button>
)} )}
</div> </div>
</div> </div>