← back to Norma Sdcc Pitch
fix: demo mode short-circuit — when NORMA_BASE=demo, /api/run-example serves canned mocks instantly (no live-fetch attempt). always returns status+ms+source so frontend never shows undefined
76506bb383be879195089346c7e7306ea3d8deb3 · 2026-05-20 09:15:07 -0700 · SteveStudio2
Files touched
Diff
commit 76506bb383be879195089346c7e7306ea3d8deb3
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 20 09:15:07 2026 -0700
fix: demo mode short-circuit — when NORMA_BASE=demo, /api/run-example serves canned mocks instantly (no live-fetch attempt). always returns status+ms+source so frontend never shows undefined
---
server.js | 35 +++++++++++++++++++++++------------
1 file changed, 23 insertions(+), 12 deletions(-)
diff --git a/server.js b/server.js
index 2023d42..b73ff01 100644
--- a/server.js
+++ b/server.js
@@ -280,8 +280,25 @@ app.post('/api/run-example', async (req, res) => {
if (!route || typeof route !== 'string' || !route.startsWith('/api/')) {
return res.status(400).json({ error: 'route must be a /api/* path on Norma' });
}
+ const m = (method || (payload ? 'POST' : 'GET')).toUpperCase();
+
+ // If a canned mock exists AND Norma is set to "demo" mode, short-circuit straight to mock.
+ // Otherwise try to reach Norma first, fall back to mock on any failure.
+ const tryLive = NORMA_BASE && NORMA_BASE !== 'demo';
+
+ // helper to send mock
+ async function sendMock() {
+ const mock = maybeMockResponse(route, m);
+ if (mock) {
+ await new Promise((r) => setTimeout(r, 250 + Math.random() * 400));
+ return res.json({ ok: true, status: 200, ms: 320 + Math.floor(Math.random() * 200), body: mock, source: 'demo-mock', _note: 'Live Norma backend not deployed on this host — showing realistic demo data with production response shape.' });
+ }
+ return res.status(502).json({ ok: false, status: 502, ms: 0, body: { error: 'no mock + Norma unreachable for ' + route }, source: 'error' });
+ }
+
+ if (!tryLive) return sendMock();
- // Light cookie cache — fetch one admin session, reuse across calls.
+ // Light cookie cache for live mode
const cookieFile = path.join(PITCH_DIR, '.norma-cookie');
let cookie = null;
if (fs.existsSync(cookieFile)) {
@@ -296,6 +313,7 @@ app.post('/api/run-example', async (req, res) => {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'admin', password: process.env.NORMA_ADMIN_PASSWORD || 'DWSecure2024!' }),
+ signal: AbortSignal.timeout(2500),
});
if (lr.ok) {
const setCookie = lr.headers.get('set-cookie');
@@ -304,12 +322,11 @@ app.post('/api/run-example', async (req, res) => {
fs.writeFileSync(cookieFile, cookie);
}
}
- } catch (err) {
- return res.status(502).json({ error: 'Norma unreachable at ' + NORMA_BASE, detail: err.message });
+ } catch {
+ return sendMock();
}
}
- const m = (method || (payload ? 'POST' : 'GET')).toUpperCase();
try {
const url = NORMA_BASE + route;
const headers = { 'Content-Type': 'application/json' };
@@ -324,14 +341,8 @@ app.post('/api/run-example', async (req, res) => {
try { body = JSON.parse(text); } catch { body = { _raw: text.slice(0, 4000) }; }
res.json({ ok: r.ok, status: r.status, ms, body, source: 'live' });
} catch (err) {
- // Norma not reachable — serve canned mock if available so the demo still works
- const mock = maybeMockResponse(route, m);
- if (mock) {
- // Add small artificial delay to feel realistic
- await new Promise((r) => setTimeout(r, 250 + Math.random() * 400));
- return res.json({ ok: true, status: 200, ms: 320 + Math.floor(Math.random() * 200), body: mock, source: 'demo-mock', _note: 'Live Norma backend not deployed on this host — showing realistic demo data from the schema. Identical to production response shape.' });
- }
- res.status(502).json({ ok: false, status: 502, ms: 0, body: { error: 'Norma backend unreachable + no mock for ' + route, detail: err.message }, source: 'error' });
+ // live failed — fall back to mock
+ return sendMock();
}
});
← c03fe2c fix: live demo mode — canned realistic mock responses for /a
·
back to Norma Sdcc Pitch
·
add: browser-playable explainer video banner at top of pitch 51ce968 →