fix(oracle-r2): same-origin baseUrl + refresh actually re-fetches
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled

Oracle round-2 review caught two real bugs:

1. Production baseUrl bypassed the nginx /api proxy
   - api.ts defaulted to https://demo.flow-master.ai in prod
   - Browser would hit cross-origin and CORS-fail
   - Now: baseUrl='' everywhere; nginx.conf already reverse-proxies /api/*
   - vite.config.ts proxy still handles dev

2. Refresh button didn't refresh
   - setMode('live') early-returned when already in live mode
   - Now: setMode() and refreshLive() share runLiveFetch(); refreshLive
     ignores the same-mode guard and always re-runs
   - 4 new vitest regressions in state/store.test.ts cover the contract
   - Smoke now asserts /api/ea2/work-items is called twice after Refresh

Also:
- buildScenarios.ts parallelized: cap N=6 candidates, Promise.all per-
  candidate fetches → live mode now ~3s instead of 30s
- CommandBar + LeftRail preview toasts now name the exact endpoint
  (/api/runtime/transactions/{id}/actions) in the visible text
- Landing 'Go live' button rebound to refreshLive() when already live;
  copy changed to 'Live · refresh'
- README: scenario table now renders (added separator row); deploy
  section points at the real ops PR + the actual overlay path
  (overlays/demo, not overlays/mc.flow-master.ai); CORS doc clarifies
  same-origin requirement

Constraint: browsers reject cross-origin → same-origin /api/* required
Rejected: dev/prod baseUrl divergence | created production bug
Confidence: high
Scope-risk: narrow
Not-tested: production image actually built + served by ops PR (gated by trusted updater + DNS)
This commit is contained in:
2026-06-14 00:26:38 +04:00
parent 2b83e3ad0e
commit e3b4ed62c0
10 changed files with 237 additions and 81 deletions
+35 -1
View File
@@ -114,7 +114,11 @@ await page.waitForTimeout(450);
logOk("tour advances", (await page.locator(".tour-title").first().innerText()).length > 0);
await page.keyboard.press("Escape");
// Live-mode toggle (real network call to demo.flow-master.ai)
// Live-mode toggle (real network call to demo.flow-master.ai via vite proxy)
const networkCalls = [];
page.on("request", (req) => {
if (req.url().includes("/api/ea2/work-items")) networkCalls.push(req.url());
});
const toggleBtn = page.locator(".mode-toggle").first();
await toggleBtn.click();
const liveOk = await page.waitForFunction(
@@ -126,6 +130,36 @@ if (liveOk) {
await page.waitForTimeout(400);
await page.screenshot({ path: `${OUT}/09-live-mode.png` });
logOk("Refresh button appears in live mode", await page.locator(".link-btn", { hasText: "Refresh" }).isVisible());
// Refresh must trigger ANOTHER /api/ea2/work-items request (Oracle round 2 fix).
// Wait for the Refresh button to become enabled (initial fetch may still be settling).
await page.locator(".link-btn", { hasText: "Refresh" }).waitFor({ state: "visible" });
await page.waitForFunction(
() => {
const btns = Array.from(document.querySelectorAll(".link-btn"));
const btn = btns.find((b) => (b.textContent || "").includes("Refresh"));
return btn && !btn.disabled;
},
{ timeout: 10000 },
);
const before = networkCalls.length;
await page.locator(".link-btn", { hasText: "Refresh" }).click();
await page.waitForTimeout(2500);
logOk("Refresh re-fetches /api/ea2/work-items", networkCalls.length > before,
`before=${before} after=${networkCalls.length}`);
}
// LeftRail Confirm button: toast must name /api/runtime/transactions/{id}/actions
await page.locator(".mc-tab", { hasText: "Accounts Receivable" }).click();
await page.waitForTimeout(400);
const confirmBtn = page.locator(".agent-acts .btn-primary").first();
if (await confirmBtn.count() > 0) {
await confirmBtn.click();
await page.waitForTimeout(300);
const lastToast = (await page.locator(".toast-msg").last().innerText()).trim();
logOk("LeftRail Confirm toast names /api/runtime/transactions endpoint",
/\/api\/runtime\/transactions\/\{id\}\/actions/.test(lastToast),
`toast="${lastToast.slice(0, 80)}"`);
}
// Telemetry honesty (check before leaving MC because RH has no telemetry strip)