[object Object]

← 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 →