()
| 198 | `; |
| 199 | |
| 200 | function clientMain() { |
| 201 | const boot = window.__APPMOD__ || {}; |
| 202 | // The host embeds a per-instance token in the iframe URL; echo it back on every |
| 203 | // request so the loopback server accepts us. Empty in unit harnesses (no guard). |
| 204 | const apiToken = boot.token || ""; |
| 205 | const tokenQuery = apiToken ? "?token=" + encodeURIComponent(apiToken) : ""; |
| 206 | let state = null; |
| 207 | let activeTab = boot.initialTab || "overview"; |
| 208 | let pending = null; |
| 209 | let autopilotRunning = false; |
| 210 | |
| 211 | const $ = (sel) => document.querySelector(sel); |
| 212 | const esc = (s) => |
| 213 | String(s == null ? "" : s).replace(/[&<>"']/g, (c) => |
| 214 | ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]) |
| 215 | ); |
| 216 | |
| 217 | function toast(msg) { |
| 218 | const t = $("#toast"); |
| 219 | t.textContent = msg; |
| 220 | t.classList.add("show"); |
| 221 | clearTimeout(toast._t); |
| 222 | toast._t = setTimeout(() => t.classList.remove("show"), 2600); |
| 223 | } |
| 224 | |
| 225 | function statusBadge(status) { |
| 226 | const map = { |
| 227 | completed: ["b-green", "Completed"], |
| 228 | in_progress: ["b-amber", "In progress"], |
| 229 | not_started: ["b-gray", "Not started"], |
| 230 | done: ["b-green", "Done"], |
| 231 | pending: ["b-gray", "Pending"], |
| 232 | failed: ["b-red", "Failed"], |
| 233 | not_run: ["b-gray", "Not run"], |
| 234 | pass: ["b-green", "Pass"], |
| 235 | }; |
| 236 | const [cls, label] = map[status] || ["b-gray", status || "—"]; |
| 237 | return '<span class="badge ' + cls + '">' + esc(label) + "</span>"; |
| 238 | } |
| 239 | |
| 240 | function btn(label, kind, payload, opts) { |
| 241 | opts = opts || {}; |
| 242 | const cls = "btn" + (opts.primary ? " primary" : ""); |
| 243 | // While Autopilot runs, freeze manual actions so a stray click can't inject |
| 244 | // a competing prompt into the same session — except Stop and tab navigation. |
| 245 | const frozen = autopilotRunning && kind !== "autopilot_stop" && kind !== "autopilot_dismiss" && String(kind).indexOf("goto:") !== 0; |
| 246 | const dis = pending || frozen ? " disabled" : ""; |
| 247 | return ( |
| 248 | '<button class="' + cls + '" data-kind="' + esc(kind) + '" data-payload="' + |
| 249 | esc(JSON.stringify(payload || {})) + '"' + dis + ">" + esc(label) + "</button>" |
| 250 | ); |
| 251 | } |
| 252 | |
| 253 | // ---- tab renderers ------------------------------------------------------- |
| 254 | |
| 255 | function overviewTab(s) { |
| 256 | let html = ""; |
| 257 |
nothing calls this directly
no test coverage detected