Commit Graph

10 Commits

Author SHA1 Message Date
Svrnty
01825190a3 feat(adwright panel): wire GetCycleMetrics into Overview KPIs
Frontend now drives Impressions / CTR / Spend from real per-variant
metrics (cycle_metric) rather than hardcoded zeros + fake +12% / -0.3%
deltas. Adds state.data.metricsByCycle keyed by cycle id; populated
via _fetchCycleMetrics which hits the panel-update endpoint directly
(no CMO chat dispatch needed — the route calls gRPC live per request).

After every cycles load (refresh-cycles or list-cycles ingest), fans
out one metrics fetch per cycle. _deriveKpis flattens all metrics
arrays, sums impressions/clicks/spend, computes real CTR from
clicks/impressions. Spend bar now reflects $spend / $budget_total
from real values instead of falling back to the $6,000 default floor.

Plugin route _real_payload_for gains a new branch:
adwright_get_cycle_metrics?cycle_id=N → core.get_cycle_metrics_tool(N).

Fake deltas dropped — real deltas need a previous-window snapshot
the backend doesn't supply yet.

Verified live: metricsByCycle.1 populated with backend's [{variant_id:1,
impressions:0, ...}] record. All metric values still zero in this
sandbox because no ads have dispatched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:57:10 -04:00
Svrnty
9ce6bfc08f fix(adwright + plugin): font 404s + status humanization + compound refresh + poll deadline
Bundles four Tranche-A polish fixes that ship together:

1. **Font 404s** — CSS @font-face URLs were /extensions/fonts/* but the
   plugin loader serves static under /plugins/svrnty/*. Updated paths
   to match register_static prefix.

2. **Humanized status enum** — _humanStatus() maps proto canonical strings
   (CYCLE_STATUS_PREVIEW_READY, VARIANT_STATUS_ACTIVE, etc.) to
   user-facing labels ("Preview ready", "Active"). Applied to Cycles
   row, Overview timeline, and variant detail.

3. **Compound Overview refresh** — refresh-overview now dispatches both
   refresh-cycles AND list-recipes via _COMPOUND_ACTIONS map. Without
   this, KPI Recipes stayed at 0 until user visited Targeting tab.
   Added per-action user-facing toast labels (_ACTION_LABELS) so the
   user sees "Loading recipes…" instead of just hermes-webui's raw
   "Queued: /adwright list-recipes" banner.

4. **Poll deadline race** — compound actions fired two sub-actions
   back-to-back; sub1's poll response was nulling pendingTool before
   sub2 could be ingested. _pollOnce now (a) resets deadline on each
   successful update (sign of progress) and (b) only stops polling
   when the response matches the CURRENT pendingTool (not the captured
   local var from earlier in the recursive fire).

Verified live: Overview now shows Cycles=1, Recipes=10, Spend=$0/$200,
timeline=Cycle #1 — Preview ready. Console clean (0 errors, was 3
font 404s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:47:56 -04:00
Svrnty
91c134c309 fix(adwright): read budget_total from backend (was c.budget, undefined)
Some checks failed
plugin-tests / test (push) Failing after 5s
Backend AdwrightCore.list_cycles preserves proto snake_case via
MessageToDict(preserving_proto_field_name=True), so cycle JSON carries
`budget_total` (decimal string like "200.00"), not `budget`. Frontend
read c.budget which was always undefined → fell back to 0 in both the
Cycles row meta and the Overview spend bar (which then defaulted to
$6,000 budget per the `|| 6000` floor in _deriveKpis).

Added _cycleBudget + _cycleSpend helpers that parseFloat the decimal
strings, with c.budget fallback so any future renamed field still works.
Spend stays 0 until GetCycleMetrics is wired in — it doesn't live in
ListCycles.

Verified live: Cycles row now shows $0 / $200 (was $0 / $0). Overview
spend now shows $0 / $200 (was $0 / $6,000).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:24:56 -04:00
Svrnty
6c88bf8899 fix(adwright): route ↗BTE crosslink through canonical switchPanel
Some checks failed
plugin-tests / test (push) Failing after 6s
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>
2026-05-24 17:05:41 -04:00
Svrnty
db1d4e0cdb fix(adwright): distinguish null vs [] in cycles/audience/connections empty-states
Some checks failed
plugin-tests / test (push) Failing after 6s
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>
2026-05-24 14:54:44 -04:00
Svrnty
ef6a123c06 fix(adwright overview): show 0 (not —) when data empty; was treating zero as 'missing'
Some checks failed
plugin-tests / test (push) Failing after 6s
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.
2026-05-24 14:45:20 -04:00
Svrnty
058d67b459 fix(adwright panel): map real {meta,woo} shape from adwright-mcp; connections tab now shows live status
Some checks failed
plugin-tests / test (push) Failing after 6s
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).
2026-05-24 14:24:31 -04:00
Svrnty
7dcda7669f 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.
2026-05-24 13:38:39 -04:00
Svrnty
849dd27119 feat(adwright panel): add cross-link button to BTE Command Center
Some checks failed
plugin-tests / test (push) Failing after 6s
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>
2026-05-24 12:19:17 -04:00
Svrnty
0b19fdd7d0 feat(plugin): Adwright + BTE Command Center panels (v0.4.0)
Some checks failed
plugin-tests / test (push) Failing after 5s
Two new tool panels surfaced inside hermes-webui via inject_script/
inject_stylesheet. Both vanilla JS + CSS, no frameworks, WebUI CSS-vars
only (no hardcoded colors), light/dark inherits free.

## Adwright panel (static/adwright.{js,css} + routes/adwright.py)

5 tabs: Overview · Cycles · Audience · Targeting · Connections.
Layout: 60/40 panel/chat split via CSS :has() selector.
Always-visible, soft-disabled when active profile isn't `cmo*`.

Action wiring (READ path — agent-mediated per governance):
1. Panel button → fires custom event
2. Handler synthesizes /adwright <cmd> chat message
3. Posts via existing btnSend pathway → message visible in chat
4. CMO sees + calls mcp_adwright_<tool>
5. Panel polls /api/adwright/last-panel-update for structured payload
6. Mock payload returned v1; real session-DB reader plugs in when
   adwright-mcp gains writer

Connections WRITE path (governance exception, NO secrets in chat):
- POST /api/adwright/provision-creds with form fields
- Plugin invokes credctl set <key> via stdin (value never on argv)
- Allowlist enforced (defense-in-depth on key names)
- Auth-gated by WebUI session cookie

Skin: .svrnty-aw-* class prefix, window.SvrntyAdwright JS namespace,
guard against double-load, scoped MutationObserver.

## BTE Command Center panel (static/bte.{js,css} + routes/bte_proxy.py)

Content-mode pills (Polished/UGC/Photorealistic/Artistic) × media toggle
(Image/Video — Video disabled v1 pending Phase 4e) × recipe family picker
(Hero Shot/Lifestyle Shot/Photoshoot/Recipe Sheet/Montage Catalog) per
canonical PLANB-RECIPE-TAXONOMY. SKU picker, variant stepper 1-12,
single/batch toggle, [Generate] button.

Asset grid with streaming thumbnails, asset detail (full-res + rate +
comment + "Use in Adwright cycle" deep link). Embedded CMO chat right rail
for re-orienting generations ("make next batch warmer / less white space").

BTE proxy route (/api/bte/proxy) with whitelisted paths
(requestPhotoshoot, assetGrid, recipeStats, assets/{id}/thumb, etc.)
prevents browser-side CORS to BTE :6001.

Skin: .svrnty-bte-* class prefix, window.SvrntyBTE JS namespace.

## Wiring

manifest_version: 0.2.0 → 0.4.0
assets registered:
- /plugins/svrnty/adwright.{js,css} + static/adwright/
- /plugins/svrnty/bte.{js,css} + static/bte/
routes registered:
- GET /api/adwright/last-panel-update (panel update channel)
- POST /api/adwright/provision-creds (governance-exception write)
- GET/POST /api/bte/proxy (BTE REST proxy with allowlist)

Karpathy 4 rules: agents reported every deviation with rationale (Python
venv interp for hermes mcp add, missing aggregate connections-status RPC
composed from two verifies, mock panel-update v1 with locked frontend
protocol so real session-DB reader is a drop-in swap), verified asset
serving + plugin route registration before claiming complete, surfaced
open questions instead of silently choosing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:12:27 -04:00