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
+4 -3
View File
@@ -12,6 +12,7 @@ export default function Landing() {
const scenarios = useApp((s) => s.scenarios);
const mode = useApp((s) => s.mode);
const setMode = useApp((s) => s.setMode);
const refreshLive = useApp((s) => s.refreshLive);
const liveLoading = useApp((s) => s.liveLoading);
const liveTotals = useApp((s) => s.liveTotals);
@@ -61,12 +62,12 @@ export default function Landing() {
</button>
<button
className={`btn btn-ghost btn-lg${mode === "live" ? " is-on" : ""}`}
onClick={() => setMode(mode === "live" ? "snapshot" : "live")}
onClick={() => mode === "live" ? refreshLive() : setMode("live")}
disabled={liveLoading}
title="Toggle between bundled snapshot and a live in-browser fetch from demo.flow-master.ai"
title={mode === "live" ? "Re-fetch live scenarios from demo.flow-master.ai" : "Toggle between bundled snapshot and a live in-browser fetch"}
>
{liveLoading ? <span className="spin" /> : <Pulse size={13} />}
{mode === "live" ? "Live mode · on" : "Go live"}
{mode === "live" ? "Live · refresh" : "Go live"}
</button>
</div>