// Shared async-button primitive. Used by both the dashboard and the // per-agent UI for any button that triggers a network action. // // `asyncBtn(btn, fn)` — the single reusable component: // 1. Guards double-click: returns immediately if `btn` is already // disabled (prevents a second identical request from firing). // 2. Saves `btn.innerHTML` and replaces it with a spinner during the // async operation. // 3. Re-enables the button and restores the original content when `fn` // resolves or rejects (via `finally`), so callers don't need // save/restore boilerplate. // // Usage: // btn.addEventListener('click', () => asyncBtn(btn, async () => { // const resp = await fetch('/api/...'); // if (!resp.ok) throw new Error(await resp.text()); // // handle success // })); // // Error handling: `asyncBtn` restores the button on any thrown error / // rejected promise but does NOT surface the error — callers must catch // and display it themselves (via `themedToast`, `alert`, a status span, // etc.) inside `fn` without re-throwing. `fn` must not let errors escape // unhandled: `asyncBtn` returns the `fn().finally(...)` promise so // callers can optionally chain `.catch` or `await`, but does not add its // own catch — an unhandled rejection from `fn` will propagate normally. export function asyncBtn(btn, fn) { if (btn.disabled) return; // double-click guard const orig = btn.innerHTML; btn.disabled = true; btn.innerHTML = ''; return fn().finally(() => { btn.disabled = false; btn.innerHTML = orig; }); }