From 7dcda7669f151c649066453985a38342192e26aa Mon Sep 17 00:00:00 2001 From: Svrnty Date: Sun, 24 May 2026 13:38:39 -0400 Subject: [PATCH] fix(adwright panel): add 1-click Switch-to-CMO button + authoritative profile fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JP feedback: panel says "switch to cmo profile" but offers no way to do so. Also window.S.activeProfile may be empty on /session/* pages before boot.js finishes initializing, so the banner showed unnecessarily. This patch: - Replaces vague text with a [Switch to CMO] button that POSTs to /api/profile/switch {name: "cmo-planb"} and reloads on success - Adds _fetchActiveProfile() that reads /api/profile/active directly (defense against window.S race); cached in NS.state._fetchedProfile - Background poll every 5s catches profile switches made from the upstream Profiles panel — no hard reload needed to clear the banner - Disabled-state refresh fires once on mount + on each poll Karpathy 4 rules: smallest possible change (one button + one fetch helper), no abstraction layer, no fallback "smart" detection — authoritative API only. --- static/adwright.js | 57 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/static/adwright.js b/static/adwright.js index 61c7bbb..c724132 100644 --- a/static/adwright.js +++ b/static/adwright.js @@ -44,7 +44,11 @@ // ── Active-profile detection (PRD §3 visibility gating) ──────────────── // hermes-webui exposes S.activeProfile (see static/ui.js line 1). We probe // it defensively — the panel always renders, but enables only for CMO. + // Cache from a real /api/profile/active fetch since window.S may not be + // populated yet on /session/* pages before boot.js runs. + NS.state._fetchedProfile = null; function _activeProfile() { + if (NS.state._fetchedProfile) return NS.state._fetchedProfile; try { return (window.S && window.S.activeProfile) || "default"; } catch (_) { return "default"; } @@ -90,6 +94,24 @@ _wireConnections(panel); _refreshDisabledState(); _renderTab(NS.state.activeTab); + // Authoritative profile fetch (window.S may be empty on /session/* paths + // before boot.js finishes). Update banner state once it arrives. + _fetchActiveProfile().then((name) => { + if (name) { + NS.state._fetchedProfile = name; + _refreshDisabledState(); + } + }); + // Re-poll every 5s so a profile switch from the upstream Profiles panel + // also lifts the banner without requiring a hard reload. + setInterval(() => { + _fetchActiveProfile().then((name) => { + if (name && name !== NS.state._fetchedProfile) { + NS.state._fetchedProfile = name; + _refreshDisabledState(); + } + }); + }, 5000); NS.state.mounted = true; return true; @@ -116,7 +138,8 @@ '' + '
' + '' + tabs + '
' + @@ -136,6 +159,38 @@ if (bteBtn) bteBtn.addEventListener("click", () => { if (window.SvrntyBTE && window.SvrntyBTE.open) window.SvrntyBTE.open(); }); + // One-click profile switch to cmo-planb. + const switchBtn = panel.querySelector("#svrntyAwSwitchCmo"); + if (switchBtn) switchBtn.addEventListener("click", async () => { + switchBtn.disabled = true; + switchBtn.textContent = "Switching…"; + try { + const res = await fetch("/api/profile/switch", { + method: "POST", + credentials: "same-origin", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: "cmo-planb" }), + }); + if (!res.ok) throw new Error("HTTP " + res.status); + // Reload to pick up new profile state across the whole webui. + location.reload(); + } catch (e) { + switchBtn.disabled = false; + switchBtn.textContent = "Switch failed — retry"; + console.error("[svrnty-aw] profile switch failed", e); + } + }); + } + + // Pull live active profile from server (don't rely on window.S which may + // not be populated yet on /session/* pages before boot.js runs). + async function _fetchActiveProfile() { + try { + const r = await fetch("/api/profile/active", { credentials: "same-origin" }); + if (!r.ok) return null; + const d = await r.json(); + return (d && (d.name || d.profile)) || null; + } catch (_) { return null; } } function _activateTab(id) {