fix(adwright panel): add 1-click Switch-to-CMO button + authoritative profile fetch
Some checks failed
plugin-tests / test (push) Failing after 5s

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.
This commit is contained in:
Svrnty 2026-05-24 13:38:39 -04:00
parent c6d94462c4
commit 7dcda7669f

View File

@ -44,7 +44,11 @@
// ── Active-profile detection (PRD §3 visibility gating) ──────────────── // ── Active-profile detection (PRD §3 visibility gating) ────────────────
// hermes-webui exposes S.activeProfile (see static/ui.js line 1). We probe // hermes-webui exposes S.activeProfile (see static/ui.js line 1). We probe
// it defensively — the panel always renders, but enables only for CMO. // 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() { function _activeProfile() {
if (NS.state._fetchedProfile) return NS.state._fetchedProfile;
try { try {
return (window.S && window.S.activeProfile) || "default"; return (window.S && window.S.activeProfile) || "default";
} catch (_) { return "default"; } } catch (_) { return "default"; }
@ -90,6 +94,24 @@
_wireConnections(panel); _wireConnections(panel);
_refreshDisabledState(); _refreshDisabledState();
_renderTab(NS.state.activeTab); _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; NS.state.mounted = true;
return true; return true;
@ -116,7 +138,8 @@
'<nav class="svrnty-aw-nav">' + nav + '</nav>' + '<nav class="svrnty-aw-nav">' + nav + '</nav>' +
'<div class="svrnty-aw-content">' + '<div class="svrnty-aw-content">' +
'<div class="svrnty-aw-disabled-banner" id="svrntyAwDisabledBanner" style="display:none">' + '<div class="svrnty-aw-disabled-banner" id="svrntyAwDisabledBanner" style="display:none">' +
'Adwright is read-only — switch to the <strong>cmo</strong> profile to run actions.' + 'Adwright needs the <strong>cmo-planb</strong> profile active. ' +
'<button class="svrnty-aw-btn svrnty-aw-btn-primary" id="svrntyAwSwitchCmo">Switch to CMO</button>' +
'</div>' + '</div>' +
tabs + tabs +
'</div>' + '</div>' +
@ -136,6 +159,38 @@
if (bteBtn) bteBtn.addEventListener("click", () => { if (bteBtn) bteBtn.addEventListener("click", () => {
if (window.SvrntyBTE && window.SvrntyBTE.open) window.SvrntyBTE.open(); 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) { function _activateTab(id) {