Some checks failed
plugin-tests / test (push) Failing after 6s
Per sot/03-PROTOCOLS/CORTEX-OS-UMBRELLA-VIZ-PRD.md. Living-graph panel
inside hermes-webui consuming curator-maintained graph/umbrella.json
(UI-stable v1.0 schema emitted by curator-planb sweep.py v0.2).
routes/umbrella.py — 3 endpoints:
GET /umbrella → redirect to static panel HTML
GET /api/umbrella → graph/umbrella.json contents
GET /api/umbrella/doc?path=X → markdown body for a node's source_path
(path-traversal guarded; 50KB cap)
static/umbrella.{html,css,js} — Cytoscape.js v3.30.2 (CDN) render:
- 8 node types: doc/profile/skill/mcp_server/sovereign_api/cortex_tool/
external_dep/credential — each gets distinct color + shape
- 5 edge types: depends_on/governs/consumes/produces/supersedes
- filter chips (toggle node type visibility)
- layout switcher (force/tier/concentric)
- search (dim non-matching nodes)
- click node → side panel w/ frontmatter + markdown body + in/out edges
- edge-list buttons jump between connected nodes
Plan B brand-aligned dark theme; DESIGN.md 8-property subset
(backgroundColor/textColor/typography/rounded/padding/size/height/width).
svrnty_nav.js bundled (sidebar nav integration — non-umbrella scope).
CONNECTION-MAP.md regenerated via scripts/ast-connection-map.py.
plugin.py route-list extended w/ "umbrella" + injects umbrella assets via
the standard static dir registration.
Module import: ✓ (python3 -c "import routes.umbrella" clean).
Graph artifact verified: 81 nodes / 120 edges live in graph/umbrella.json.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
117 lines
4.2 KiB
JavaScript
117 lines
4.2 KiB
JavaScript
// svrnty_nav.js — injects Adwright + BTE sidebar buttons into hermes-webui's
|
|
// .sidebar-nav and wraps switchPanel so our panels participate in the existing
|
|
// main-view show/hide system (showing-<name> on <main>).
|
|
//
|
|
// Each panel module (adwright.js, bte.js) mounts its content inside <main>
|
|
// and CSS keys visibility off main.showing-adwright / main.showing-bte.
|
|
//
|
|
// IIFE, idempotent — guarded by window.__svrntyNavLoaded.
|
|
|
|
(function () {
|
|
"use strict";
|
|
if (window.__svrntyNavLoaded) return;
|
|
window.__svrntyNavLoaded = true;
|
|
|
|
const TABS = [
|
|
{
|
|
id: "adwright",
|
|
label: "Adwright",
|
|
tooltip: "Adwright — marketing intelligence",
|
|
// Bullseye / target icon — marketing focus
|
|
svg: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg>',
|
|
},
|
|
{
|
|
id: "bte",
|
|
label: "BTE",
|
|
tooltip: "BTE — brand creative studio",
|
|
// Palette/sparkle icon — creative
|
|
svg: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2l1.8 5.6L19.4 9.4l-4.5 3.3 1.7 5.7L12 15l-4.6 3.4 1.7-5.7L4.6 9.4l5.6-1.8L12 2z"/></svg>',
|
|
},
|
|
];
|
|
|
|
function _injectButtons() {
|
|
const nav = document.querySelector(".sidebar-nav");
|
|
if (!nav) return false;
|
|
TABS.forEach((t) => {
|
|
if (nav.querySelector('[data-panel="' + t.id + '"]')) return;
|
|
const btn = document.createElement("button");
|
|
btn.className = "nav-tab has-tooltip has-tooltip--bottom svrnty-nav-tab";
|
|
btn.setAttribute("data-panel", t.id);
|
|
btn.setAttribute("data-label", t.label);
|
|
btn.setAttribute("data-tooltip", t.tooltip);
|
|
btn.setAttribute("aria-label", t.label);
|
|
btn.innerHTML = t.svg;
|
|
btn.addEventListener("click", () => {
|
|
if (typeof window.switchPanel === "function") {
|
|
window.switchPanel(t.id, { fromRailClick: true });
|
|
}
|
|
});
|
|
nav.appendChild(btn);
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// Wrap switchPanel to add showing-<id> class on <main> for our IDs.
|
|
// We chain the original so all upstream behavior (collapse rail, hide other
|
|
// panels, toggle data-panel active) keeps working unchanged.
|
|
function _wrapSwitchPanel() {
|
|
if (typeof window.switchPanel !== "function") return false;
|
|
if (window.switchPanel.__svrntyWrapped) return true;
|
|
const original = window.switchPanel;
|
|
const OUR_IDS = TABS.map((t) => t.id);
|
|
|
|
async function wrapped(name, opts) {
|
|
const result = await original(name, opts);
|
|
const main = document.querySelector("main.main");
|
|
if (main) {
|
|
OUR_IDS.forEach((id) => {
|
|
main.classList.toggle("svrnty-showing-" + id, name === id);
|
|
});
|
|
}
|
|
// Notify panel modules so they can lazy-init/refresh.
|
|
try {
|
|
window.dispatchEvent(
|
|
new CustomEvent("svrnty:panel-switch", { detail: { name } }),
|
|
);
|
|
} catch (_) {}
|
|
return result;
|
|
}
|
|
wrapped.__svrntyWrapped = true;
|
|
window.switchPanel = wrapped;
|
|
return true;
|
|
}
|
|
|
|
function _init() {
|
|
const buttonsOk = _injectButtons();
|
|
const wrapOk = _wrapSwitchPanel();
|
|
if (!buttonsOk || !wrapOk) {
|
|
// DOM not ready yet — retry on next paint
|
|
requestAnimationFrame(_init);
|
|
}
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", _init);
|
|
} else {
|
|
_init();
|
|
}
|
|
|
|
// Re-inject buttons if something re-renders the sidebar (defensive).
|
|
const obs = new MutationObserver(() => {
|
|
const nav = document.querySelector(".sidebar-nav");
|
|
if (!nav) return;
|
|
const missing = TABS.some((t) => !nav.querySelector('[data-panel="' + t.id + '"]'));
|
|
if (missing) _injectButtons();
|
|
});
|
|
if (document.body) {
|
|
obs.observe(document.body, { childList: true, subtree: true });
|
|
} else {
|
|
document.addEventListener("DOMContentLoaded", () =>
|
|
obs.observe(document.body, { childList: true, subtree: true }),
|
|
);
|
|
}
|
|
|
|
// Expose namespace
|
|
window.SvrntyNav = { TABS, _injectButtons, _wrapSwitchPanel };
|
|
})();
|