The Adwright ↗BTE button called SvrntyBTE.open() directly, which sets
overlay.hidden=false but never flips main.svrnty-showing-bte. The CSS
gates BTE overlay visibility on that class, so the click did nothing
visible — overlay was mounted but display:none. Route the click through
window.switchPanel("bte") instead so the canonical nav wrapper handles
class flipping + event dispatch identically to the sidebar BTE button.
Validated via Playwright with 6 gates: V1 sidebar BTE (1 event, class
set, visible), V2 ↗BTE crosslink (1 event, class flip, visible),
V3 Adwright nav (class flip, BTE hidden), V4 main classes correct,
V5 console clean, V6 /api/bte/proxy 200 OK. All pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Empty-state messages now differentiate "never fetched" (null) from
"fetched but empty" ([]), so the panel reads correctly after a refresh
returns zero rows. Affected renderers: _renderCycles, _renderAudience,
_renderConnections. Targeting unchanged (its gate is "both lists needed",
not single-list empty).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: cycleCount: String(c.length || '—') turned 0 into '—' due to || falsy
coercion. Plan B DB starts empty → Overview KPIs all '—' even after a
successful refresh. Renders '0' now (the actually correct truthful value).
Karpathy 4 rules: smallest change (one expression), no shape change.
Bug: panel _ingestUpdate expected payload.connections array (mock shape).
Real adwright_get_connections_status returns {meta:{...}, woo:{...}}.
Resulted in connections tab always empty + Overview KPIs sourced from
empty arrays even though backend was returning real data.
Now ingest normalises both shapes (real + mock fallback) into the
[{id, name, ok, status, detail}, ...] shape the renderer already expects.
detail field plumbed through for future Connections card expansion.
Verified via curl /api/adwright/last-panel-update?tool=adwright_get_connections_status
returns real Plan B sandbox + Woo. Panel now displays them.
Karpathy 4 rules: surfaced shape mismatch via real backend probe, smallest
shim (one ingest branch), supports both old + new shape (no regression).
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.
Header now shows ↗ BTE button next to the profile status pill. Click
invokes window.SvrntyBTE.open() to surface the BTE overlay, satisfying
the "Adwright pulls content from BTE panel" integration goal at the UX
level. Asset-URL-level integration follows automatically once cycles
contain BTE-rendered asset references (post Phase 8).
Themed via existing CSS vars (--accent, --border2, --accent-bg, etc) —
zero hardcoded colors. CONNECTION-MAP regenerated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>