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>