← back to Marketing Command Center
Collapse 8 Preview panels into a single Insights hub (tabbed, single-pane swap); nav now 6 groups (v1.9.1)
7014784021a561f0b79e23a287ae796ba110b73c · 2026-08-12 14:01:31 -0700 · Steve
Files touched
A modules/insights/index.jsM modules/registry.jsM package.jsonM public/app.jsA public/panels/insights.htmlA public/panels/insights.js
Diff
commit 7014784021a561f0b79e23a287ae796ba110b73c
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 12 14:01:31 2026 -0700
Collapse 8 Preview panels into a single Insights hub (tabbed, single-pane swap); nav now 6 groups (v1.9.1)
---
modules/insights/index.js | 11 ++++++++++
modules/registry.js | 1 +
package.json | 2 +-
public/app.js | 8 +++++--
public/panels/insights.html | 21 ++++++++++++++++++
public/panels/insights.js | 52 +++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 92 insertions(+), 3 deletions(-)
diff --git a/modules/insights/index.js b/modules/insights/index.js
new file mode 100644
index 0000000..44d41ba
--- /dev/null
+++ b/modules/insights/index.js
@@ -0,0 +1,11 @@
+// Insights (Preview host) — collapses the 8 not-yet-live analytics panels into
+// one tabbed nav entry. No-op mount: it owns no API. insights.js loads the
+// selected panel's html+js into a single content pane and calls that panel's own
+// init(), so those 8 panels stay authoritative and their /api/* routers stay
+// mounted (only their nav entries are hidden via the app.js denylist).
+module.exports = {
+ id: 'insights',
+ title: 'Insights',
+ icon: '📊',
+ mount(_router) { /* intentionally empty — no /api/insights/* routes */ },
+};
diff --git a/modules/registry.js b/modules/registry.js
index a3c357e..2691ebf 100644
--- a/modules/registry.js
+++ b/modules/registry.js
@@ -37,4 +37,5 @@ module.exports = [
'browse-abandon', // browse-abandonment drip: Day1 storytelling → Day14 room-setting → Day30 suppress, mock events + dry-run preview (STAGED)
'playbook', // monthly playbook: 4-6 suggested campaigns/actions for next 4 weeks from calendar + segment health + perf gaps (Gemini or template)
'engine', // Engine: daily AI-suggested social posts with an approve → schedule → post flow (queue via store.js, live via channels.postLive)
+ 'insights', // Insights (Preview host): tabbed host over the 8 not-yet-live analytics panels (performance, segment-perf, send-times, segments, journeys, profiles, follow-counts, ab-tests). No-op mount; those modules stay mounted, only their nav entries are hidden.
];
diff --git a/package.json b/package.json
index d557b82..dd0dcf7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "marketing-command-center",
- "version": "1.9.0",
+ "version": "1.9.1",
"description": "DW Marketing Command Center — Constant Contact, marketing calendar, suggested copy, on-demand layouts",
"main": "server.js",
"scripts": {
diff --git a/public/app.js b/public/app.js
index 4d1e2ab..5e3db1a 100644
--- a/public/app.js
+++ b/public/app.js
@@ -17,14 +17,18 @@ const GROUPS = [
{ name: 'Compose', ids: ['compose', 'linkedin', 'board', 'social'] },
{ name: 'Library', ids: ['assets', 'sounds', 'reels', 'copy', 'layouts', 'templates'] },
{ name: 'Accounts & Email', ids: ['accounts', 'channels', 'clients', 'constant-contact', 'browse-abandon'] },
- { name: 'Preview · not live yet', ids: ['performance', 'segment-perf', 'send-times', 'segments', 'journeys', 'profiles', 'follow-counts', 'ab-tests'] },
+ { name: 'Insights · preview', ids: ['insights'] },
];
// Panels whose ROUTERS stay mounted (their APIs are still called) but whose NAV
// entries are hidden — because they were merged into a host panel above. Removing
// them from registry.js would unmount their APIs and break the host, so we hide
// them here instead. calendars/calendar → calendarhub; engine/composer → compose.
-const HIDDEN = new Set(['calendars', 'calendar', 'engine', 'composer']);
+const HIDDEN = new Set([
+ 'calendars', 'calendar', 'engine', 'composer', // → calendarhub / compose
+ 'performance', 'segment-perf', 'send-times', 'segments', // → insights (preview)
+ 'journeys', 'profiles', 'follow-counts', 'ab-tests',
+]);
// Always fetch against the bare origin. If the page itself was opened with
// embedded credentials (http://user:pass@host), relative URLs resolve against
diff --git a/public/panels/insights.html b/public/panels/insights.html
new file mode 100644
index 0000000..5fd8e04
--- /dev/null
+++ b/public/panels/insights.html
@@ -0,0 +1,21 @@
+<!-- Insights — one nav entry over the 8 preview/not-yet-live analytics panels.
+ A wrapping tab bar swaps a single content pane: insights.js loads the picked
+ panel's html+js into #ins-content and runs that panel's own init(). Only one
+ is mounted at a time (no cross-panel id collisions). Backends stay mounted;
+ the 8 panels' nav entries are hidden via the app.js denylist. -->
+<div class="ins">
+ <div class="ins-note">📊 <b>Preview</b> — these views aren't wired to live data yet. Grouped here so they don't clutter the main nav.</div>
+ <div class="ins-tabbar" id="ins-tabs"></div>
+ <div class="ins-content" id="ins-content"><div class="loading">Loading…</div></div>
+</div>
+
+<style>
+ .ins-note{background:var(--cream);border:1px solid var(--line);border-radius:10px;padding:9px 13px;
+ font-size:12.5px;color:var(--ink);margin-bottom:12px}
+ .ins-tabbar{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}
+ .ins-tab{border:1px solid var(--line);background:#fff;border-radius:9px;cursor:pointer;
+ font:600 12.5px/1 Inter;color:var(--ink);padding:8px 13px}
+ .ins-tab:hover{background:var(--cream)}
+ .ins-tab.active{background:var(--ink);color:#fff;border-color:var(--ink)}
+ .ins-content{min-height:200px}
+</style>
diff --git a/public/panels/insights.js b/public/panels/insights.js
new file mode 100644
index 0000000..50c1929
--- /dev/null
+++ b/public/panels/insights.js
@@ -0,0 +1,52 @@
+// Insights hub — merges the 8 preview analytics panels under one nav entry
+// WITHOUT duplicating their code. A wrapping tab bar swaps a SINGLE content pane:
+// clicking a tab loads that panel's html into #ins-content and calls its own
+// MCC_PANELS[id].init(pane). Single-pane (not 8 panes) so only one panel's DOM
+// exists at a time — no cross-panel id collisions, lighter than mounting all 8.
+window.MCC_PANELS = window.MCC_PANELS || {};
+window.MCC_PANELS['insights'] = {
+ async init(root) {
+ const ORIGIN = location.origin;
+ const TABS = [
+ { id: 'performance', label: 'Performance' },
+ { id: 'segment-perf', label: 'Segment Perf' },
+ { id: 'send-times', label: 'Send Times' },
+ { id: 'segments', label: 'Segments' },
+ { id: 'journeys', label: 'Journeys' },
+ { id: 'profiles', label: 'Profiles' },
+ { id: 'follow-counts', label: 'Follow Counts' },
+ { id: 'ab-tests', label: 'A/B Tests' },
+ ];
+ const bar = root.querySelector('#ins-tabs');
+ const content = root.querySelector('#ins-content');
+ let current = null;
+
+ async function ensureScript(id) {
+ if (window.MCC_PANELS[id]) return;
+ await new Promise(res => {
+ const s = document.createElement('script');
+ s.src = ORIGIN + '/panels/' + id + '.js';
+ s.onload = res; s.onerror = res;
+ document.body.appendChild(s);
+ });
+ }
+ async function show(id) {
+ if (current === id) return;
+ current = id;
+ bar.querySelectorAll('.ins-tab').forEach(b => b.classList.toggle('active', b.dataset.id === id));
+ content.innerHTML = '<div class="loading">Loading…</div>';
+ try {
+ const r = await fetch(ORIGIN + '/panels/' + id + '.html', { credentials: 'same-origin' });
+ content.innerHTML = r.ok ? await r.text() : `<div class="muted">Could not load ${id}.</div>`;
+ } catch { content.innerHTML = `<div class="muted">Could not load ${id}.</div>`; return; }
+ await ensureScript(id);
+ try {
+ if (window.MCC_PANELS[id] && window.MCC_PANELS[id].init) await window.MCC_PANELS[id].init(content);
+ } catch (e) { console.error('[insights] ' + id + ' init failed', e); }
+ }
+
+ bar.innerHTML = TABS.map(t => `<button type="button" class="ins-tab" data-id="${t.id}">${t.label}</button>`).join('');
+ bar.querySelectorAll('.ins-tab').forEach(btn => btn.onclick = () => show(btn.dataset.id));
+ show(TABS[0].id); // Performance is the default view
+ },
+};
← 39e86ec auto-data-snapshot: 2026-08-12T14:00:30 (1 data files) — pac
·
back to Marketing Command Center
·
deploy: Insights hub live on Kamatera (v1.9.1) 454644c →