diff --git a/railiance_fabric/graph_explorer_ui.py b/railiance_fabric/graph_explorer_ui.py index 6ca6e8a..9a36c2d 100644 --- a/railiance_fabric/graph_explorer_ui.py +++ b/railiance_fabric/graph_explorer_ui.py @@ -67,6 +67,42 @@ def graph_explorer_page() -> str: } button.primary { background: var(--accent); border-color: var(--accent); color: #ffffff; } button:disabled { color: #98a2b3; cursor: default; } + .field-label { + display: inline-flex; + align-items: center; + gap: 5px; + } + .help-tip { + width: 18px; + height: 18px; + min-height: 18px; + padding: 0; + border-radius: 50%; + color: var(--muted); + font-size: 11px; + line-height: 1; + background: #f8fafc; + } + .help-tip:hover, .help-tip:focus-visible { + border-color: var(--accent); + color: var(--accent); + outline: none; + } + .help-popup { + position: fixed; + z-index: 30; + display: none; + max-width: 300px; + border: 1px solid var(--line); + border-radius: 8px; + background: #ffffff; + box-shadow: 0 16px 40px rgba(23, 32, 51, .16); + color: var(--text); + padding: 10px; + pointer-events: none; + } + .help-popup strong { display: block; margin-bottom: 4px; } + .help-popup p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.35; } .filter-menu { position: relative; min-width: 0; @@ -122,7 +158,10 @@ def graph_explorer_page() -> str: z-index: 3; display: flex; align-items: end; + flex-wrap: wrap; + justify-content: flex-end; gap: 6px; + max-width: min(460px, calc(100% - 24px)); padding: 6px; border: 1px solid var(--line); border-radius: 8px; @@ -220,48 +259,76 @@ def graph_explorer_page() -> str: } .popup strong { display: block; margin-bottom: 4px; } .notice { color: var(--warn); padding: 14px; } + .canvas-notice { + position: absolute; + inset: 0; + display: grid; + place-items: center; + color: var(--muted); + padding: 24px; + text-align: center; + } @media (max-width: 900px) { .shell { min-height: 760px; } .toolbar { grid-template-columns: 1fr 1fr; } .main { grid-template-columns: 1fr; grid-template-rows: minmax(420px, 1fr) 330px; } .side { border-left: 0; border-top: 1px solid var(--line); } + .map-controls { top: 8px; right: 8px; max-width: calc(100% - 16px); } }
- - -
+
+ Mode + +
+
+ Layout + -
Node Types + +
+
Node Types
All node types
-
Edge Types +
Edge Types
All edge types
- - - +
+ Review + +
+
+ Unresolved + +
+
+ Profile + +
- +
-
+
@@ -321,6 +392,7 @@ def graph_explorer_page() -> str: const graphUrl = "/exports/graph-explorer"; const canvas = document.getElementById("graph-canvas"); const popup = document.getElementById("popup"); + const helpPopup = document.getElementById("help-popup"); const selectionAnchor = document.getElementById("selection-anchor"); const selectionAnchorLabel = document.getElementById("selection-anchor-label"); const searchInput = document.getElementById("search"); @@ -365,6 +437,26 @@ def graph_explorer_page() -> str: .replaceAll("&", "&").replaceAll("<", "<") .replaceAll(">", ">").replaceAll('"', """); + const showHelp = (target) => { + if (!helpPopup || !target?.dataset?.help) return; + const bounds = target.getBoundingClientRect(); + const title = target.dataset.helpTitle || target.textContent || "Help"; + helpPopup.innerHTML = `${escapeHtml(title)}

${escapeHtml(target.dataset.help)}

`; + helpPopup.style.display = "block"; + const width = Math.min(helpPopup.offsetWidth || 300, 300); + const left = Math.min(Math.max(12, bounds.left), window.innerWidth - width - 12); + const top = bounds.bottom + 8 < window.innerHeight - 80 + ? bounds.bottom + 8 + : Math.max(12, bounds.top - helpPopup.offsetHeight - 8); + helpPopup.style.left = `${left}px`; + helpPopup.style.top = `${top}px`; + }; + + const hideHelp = () => { + if (!helpPopup) return; + helpPopup.style.display = "none"; + }; + const elementText = (data) => [ data.id, data.stableKey, data.label, data.name, data.description, data.repo, data.domain, data.kind, data.layer, data.edgeType @@ -960,6 +1052,13 @@ def graph_explorer_page() -> str: ...element, data: {...element.data, color: layerColors[element.data.layer] || "#667085"} })); + if (elements.length === 0) { + canvas.innerHTML = "

No graph entities were returned by the registry.

"; + counts.textContent = "0 nodes / 0 edges"; + hiddenSummary.textContent = "Hidden 0"; + return; + } + canvas.innerHTML = ""; cy = cytoscape({ container: canvas, elements, @@ -1158,12 +1257,21 @@ def graph_explorer_page() -> str: } }); }); + document.querySelectorAll("[data-help]").forEach((target) => { + target.addEventListener("mouseenter", () => showHelp(target)); + target.addEventListener("focus", () => showHelp(target)); + target.addEventListener("mouseleave", hideHelp); + target.addEventListener("blur", hideHelp); + }); + document.addEventListener("keydown", (event) => { + if (event.key === "Escape") hideHelp(); + }); if (!window.cytoscape) { canvas.innerHTML = "

Cytoscape.js could not be loaded.

"; return; } boot().catch((error) => { - canvas.innerHTML = `

${escapeHtml(error.message)}

`; + canvas.innerHTML = `

Could not load graph explorer data: ${escapeHtml(error.message)}

`; }); })(); diff --git a/tests/test_graph_explorer.py b/tests/test_graph_explorer.py index ffc62a1..02e85f5 100644 --- a/tests/test_graph_explorer.py +++ b/tests/test_graph_explorer.py @@ -210,11 +210,19 @@ def test_registry_serves_graph_explorer_exports(tmp_path: Path) -> None: assert 'id="node-type-filter"' in page assert 'id="edge-type-filter"' in page assert 'id="selection-anchor"' in page + assert 'id="help-popup"' in page assert "updateSelectionAnchor" in page assert "updateLabelVisibility" in page + assert "showHelp" in page assert "label-hidden" in page + assert "Loading graph..." in page + assert "No graph entities were returned by the registry." in page + assert "Could not load graph explorer data" in page assert "Node Types" in page assert "Edge Types" in page + assert 'data-help-title="Node Types"' in page + assert 'data-help-title="Saved Views"' in page + assert "Remove will redraw" in page assert "Registered only" in page assert 'id="layer-filter"' not in page assert '"border-width": 4' not in page diff --git a/workplans/RAIL-FAB-WP-0009-graph-explorer-ui-refinement.md b/workplans/RAIL-FAB-WP-0009-graph-explorer-ui-refinement.md index 14c74ac..7112e79 100644 --- a/workplans/RAIL-FAB-WP-0009-graph-explorer-ui-refinement.md +++ b/workplans/RAIL-FAB-WP-0009-graph-explorer-ui-refinement.md @@ -123,7 +123,7 @@ Acceptance notes: ```task id: RAIL-FAB-WP-0009-T03 -status: in_progress +status: done priority: high state_hub_task_id: "3e60397c-c833-4bd7-ba1b-b754b203dade" ``` @@ -176,7 +176,7 @@ Acceptance notes: ```task id: RAIL-FAB-WP-0009-T05 -status: todo +status: done priority: medium state_hub_task_id: "67a9cbc9-ebaa-4cb1-bec9-46bf250faf41" ```