← back to Norma
refactor(agent-base): hoist static dashboard CSS to a module const
47943fbff83c6bbe434022f858a3f54196beb7b0 · 2026-07-13 01:53:27 -0700 · Steve Abrams
Behavior-preserving — moves the 26-line inline <style> block out of the
per-request GET / handler into a DASHBOARD_CSS const (defined once, not
rebuilt per page load). Rendered HTML identical; verified node --check +
norma-instagram boots + GET / 200 with CSS intact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M agents/shared/agent-base.js
Diff
commit 47943fbff83c6bbe434022f858a3f54196beb7b0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 13 01:53:27 2026 -0700
refactor(agent-base): hoist static dashboard CSS to a module const
Behavior-preserving — moves the 26-line inline <style> block out of the
per-request GET / handler into a DASHBOARD_CSS const (defined once, not
rebuilt per page load). Rendered HTML identical; verified node --check +
norma-instagram boots + GET / 200 with CSS intact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
agents/shared/agent-base.js | 58 ++++++++++++++++++++++++---------------------
1 file changed, 31 insertions(+), 27 deletions(-)
diff --git a/agents/shared/agent-base.js b/agents/shared/agent-base.js
index 0f45e5f..e5ff800 100644
--- a/agents/shared/agent-base.js
+++ b/agents/shared/agent-base.js
@@ -24,6 +24,36 @@ const { CronScheduler } = require('./cron-scheduler');
const AUTH_USER = process.env.AUTH_USERNAME || 'admin';
const AUTH_PASS = process.env.AUTH_PASSWORD || '';
+// Static CSS for the read-only status dashboard (GET /) — hoisted out of the
+// per-request handler so it's defined once, not rebuilt on every page load.
+const DASHBOARD_CSS = `
+ :root{--bg:#0e1116;--card:#171b22;--line:#242a33;--fg:#e6edf3;--mut:#8b949e;--ok:#3fb950;--warn:#d29922;--bad:#f85149;--accent:#58a6ff}
+ *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
+ header{padding:20px 24px;border-bottom:1px solid var(--line);display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
+ h1{font-size:18px;margin:0;font-weight:600}.sub{color:var(--mut);font-size:13px}
+ .dot{width:9px;height:9px;border-radius:50%;display:inline-block;margin-right:6px}
+ main{padding:20px 24px;display:grid;gap:18px;max-width:1000px}
+ .card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:16px 18px}
+ .card h2{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mut);margin:0 0 12px}
+ table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:7px 10px;border-bottom:1px solid var(--line);font-size:13px}
+ th{color:var(--mut);font-weight:500}tr:last-child td{border-bottom:0}
+ .pill{padding:2px 8px;border-radius:999px;font-size:12px;font-weight:600}
+ .pill.on{background:rgba(63,185,80,.15);color:var(--ok)}.pill.off{background:rgba(139,148,158,.15);color:var(--mut)}
+ .chips{display:flex;gap:8px;flex-wrap:wrap}.chip{background:#20262e;border:1px solid var(--line);border-radius:6px;padding:3px 10px;font-size:12px}
+ .muted{color:var(--mut)}code{background:#20262e;padding:1px 6px;border-radius:4px}
+ .foot{color:var(--mut);font-size:12px;padding:0 24px 24px}
+ .badge{padding:2px 9px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.04em}
+ .badge.live{background:rgba(63,185,80,.15);color:var(--ok)}.badge.sim{background:rgba(210,153,34,.15);color:var(--warn)}
+ .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:12px}
+ .stat{background:#20262e;border:1px solid var(--line);border-radius:8px;padding:12px 14px}
+ .stat .v{font-size:22px;font-weight:700}.stat .l{color:var(--mut);font-size:12px;margin-top:2px}.stat .s{color:var(--mut);font-size:11px}
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}
+ .tile{border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#20262e;text-decoration:none;color:inherit;display:flex;flex-direction:column}
+ .tile .ph{aspect-ratio:1;width:100%;object-fit:cover;display:block;background:linear-gradient(135deg,#2a3340,#3a2a40)}
+ .tile .cap{padding:8px 10px;font-size:12px;color:var(--fg);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
+ .tile .meta{padding:0 10px 8px;font-size:11px;color:var(--mut)}
+`;
+
/**
* Create a fully configured Express agent server.
*
@@ -135,33 +165,7 @@ function createAgentServer({ name, port, skills = {}, cronJobs = [], dashboard =
res.type('html').send(`<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>${name} — status</title>
-<style>
- :root{--bg:#0e1116;--card:#171b22;--line:#242a33;--fg:#e6edf3;--mut:#8b949e;--ok:#3fb950;--warn:#d29922;--bad:#f85149;--accent:#58a6ff}
- *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
- header{padding:20px 24px;border-bottom:1px solid var(--line);display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
- h1{font-size:18px;margin:0;font-weight:600}.sub{color:var(--mut);font-size:13px}
- .dot{width:9px;height:9px;border-radius:50%;display:inline-block;margin-right:6px}
- main{padding:20px 24px;display:grid;gap:18px;max-width:1000px}
- .card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:16px 18px}
- .card h2{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mut);margin:0 0 12px}
- table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:7px 10px;border-bottom:1px solid var(--line);font-size:13px}
- th{color:var(--mut);font-weight:500}tr:last-child td{border-bottom:0}
- .pill{padding:2px 8px;border-radius:999px;font-size:12px;font-weight:600}
- .pill.on{background:rgba(63,185,80,.15);color:var(--ok)}.pill.off{background:rgba(139,148,158,.15);color:var(--mut)}
- .chips{display:flex;gap:8px;flex-wrap:wrap}.chip{background:#20262e;border:1px solid var(--line);border-radius:6px;padding:3px 10px;font-size:12px}
- .muted{color:var(--mut)}code{background:#20262e;padding:1px 6px;border-radius:4px}
- .foot{color:var(--mut);font-size:12px;padding:0 24px 24px}
- .badge{padding:2px 9px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.04em}
- .badge.live{background:rgba(63,185,80,.15);color:var(--ok)}.badge.sim{background:rgba(210,153,34,.15);color:var(--warn)}
- .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:12px}
- .stat{background:#20262e;border:1px solid var(--line);border-radius:8px;padding:12px 14px}
- .stat .v{font-size:22px;font-weight:700}.stat .l{color:var(--mut);font-size:12px;margin-top:2px}.stat .s{color:var(--mut);font-size:11px}
- .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}
- .tile{border:1px solid var(--line);border-radius:8px;overflow:hidden;background:#20262e;text-decoration:none;color:inherit;display:flex;flex-direction:column}
- .tile .ph{aspect-ratio:1;width:100%;object-fit:cover;display:block;background:linear-gradient(135deg,#2a3340,#3a2a40)}
- .tile .cap{padding:8px 10px;font-size:12px;color:var(--fg);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
- .tile .meta{padding:0 10px 8px;font-size:11px;color:var(--mut)}
-</style></head><body>
+<style>${DASHBOARD_CSS}</style></head><body>
<header>
<h1><span id="dot" class="dot" style="background:var(--mut)"></span>${name}</h1>
<span class="sub">port ${port} · read-only · auto-refresh 15s</span>
← c7157cf chore: version bump (session close — SSRF guard + security P
·
back to Norma
·
chore: v0.1.3 (dashboard CSS refactor) 5d84a60 →