| 2086 | // time (applyState/connectEvents drive a single active studio job — overlapping |
| 2087 | // restores would fight over the studio). Caps at 30 min as a safety net. |
| 2088 | async function waitForJobTerminal(jobId) { |
| 2089 | const deadline = Date.now() + 30 * 60 * 1000; |
| 2090 | while (Date.now() < deadline) { |
| 2091 | await new Promise((r) => setTimeout(r, 1500)); |
| 2092 | try { |
| 2093 | const r = await fetch(`/api/jobs/${jobId}`, { cache: "no-store" }); |
| 2094 | if (r.status === 404) return; |
| 2095 | const s = await r.json(); |
| 2096 | if (s.status === "done" || s.status === "error" || s.status === "cancelled") return; |
| 2097 | } catch { /* transient — keep waiting */ } |
| 2098 | } |
| 2099 | } |
| 2100 | |
| 2101 | // "Sync again": reconcile the library with the backend, then auto-restore the |
| 2102 | // tracks that fell out of sync. |