feat(tools): Mnemon knowledge-graph 3D viewer - #25
Merged
Merged
Conversation
Self-contained 3D viewer for the Mnemon knowledge graph (hermes-codespace): - index.html: category-label pills overlaid on bubbles - build.py: inlines 3d-force-graph@1.80 + graph.json into mnemon-graph.html, auto-fetches the bundle on demand (KG_CACHE) so a fresh clone can rebuild - export_graph.py: reads the Mnemon sqlite DB -> graph.json - commmitted graph.json + mnemon-graph.html + mnemon-viz.html Fixes while building: - importance slider is integer (min=1 max=5 step=1), not float-stepped - category names render inside the bubbles via a DOM overlay (no redundant Three copy, avoids 'Multiple instances of Three.js') - auto-rotate implemented manually (bundle lacks .autoRotate())
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a self-contained Mnemon knowledge-graph visualization tool under .devcontainer/tools/knowledge-graph/ to export Mnemon’s sqlite data and render it in a 3D ForceGraph viewer (with filtering controls and portable build output).
Changes:
- Adds a new 3D viewer template (
index.html) with category toggles, importance filtering, tooltips, and manual auto-rotate. - Adds export/build tooling (
export_graph.py,build.py) to generategraph.jsonand inline it (plus the 3d-force-graph bundle) into a single HTML artifact. - Adds generated graph output (
graph.json) and ignores build cache artifacts via.gitignore.
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| .devcontainer/tools/knowledge-graph/index.html | New 3D viewer UI + filtering + tooltip + label overlay logic. |
| .devcontainer/tools/knowledge-graph/graph.json | Committed exported graph data (nodes/edges/meta) used by the viewer. |
| .devcontainer/tools/knowledge-graph/export_graph.py | Exports Mnemon sqlite data into viewer-friendly JSON. |
| .devcontainer/tools/knowledge-graph/build.py | Builds a single portable HTML by inlining JS bundle + exported JSON. |
| .devcontainer/tools/knowledge-graph/.gitignore | Ignores cached bundle and Python bytecode. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+256
to
+259
| .onNodeClick(function(n){ | ||
| Graph.cameraPosition({ x:n.x+(n.x||0)*0.9, y:n.y, z:n.z+90 || 90 }, n, 600); | ||
| showTooltip(n); | ||
| }) |
Comment on lines
+63
to
+67
| # 3) data | ||
| if "__DATA__" in html: | ||
| data = open(DATA, encoding="utf-8").read() | ||
| html = html.replace("/* __DATA__ */", "DATA = " + data + ";\n") | ||
|
|
Comment on lines
+20
to
+24
| # Strip common wiki prefixes for cleaner labels | ||
| for p in ("Wiki: ", "CI Debugging ", ""): | ||
| if s.startswith(p): | ||
| s = s[len(p):] | ||
| break |
Comment on lines
+223
to
+231
| function applyVisibility(){ | ||
| if(!Graph) return; | ||
| Graph.nodeVisibility(nodeVisible) | ||
| .linkVisibility(function(l){ | ||
| var s=Graph.graphData().nodes.find(function(x){return x.id===l.source;}); | ||
| var t=Graph.graphData().nodes.find(function(x){return x.id===l.target;}); | ||
| return s&&t&&nodeVisible(s)&&nodeVisible(t); | ||
| }); | ||
| } |
The link-visibility predicate looked up l.source/l.target as string ids, but 3d-force-graph resolves them to node objects after the engine settles, so every lookup missed and all edges vanished on the first slider move. Accept both forms (object endpoint or id lookup).
Explains how the viewer was derived (options, dead ends: three.js inline crash, missing autoRotate, linkVisibility object-vs-id bug), the pipeline (export_graph.py -> graph.json -> build.py -> mnemon-graph.html, plus the vis.js fallback), and the verified end-to-end regeneration steps.
Skill: 'export mnemon graph' now triggers the verified pipeline (export_graph.py -> build.py -> mnemon viz -> serve -> commit) with the hard-won pitfalls (serving trap, three.js inline crash, missing autoRotate, linkVisibility object endpoints, graph2ScreenCoords no-z, integer slider, public-repo data review, browser cache). Wiki: mnemon-graph-viewer.md reference article (pipeline, data model, design decisions, regeneration) + INDEX entry.
Per user design review: the viewer was a build-time fusion of library + data (build.py inlined graph.json into the HTML), forcing a Python rebuild on every data refresh. Now the viewer is a FIXED asset that fetches graph.json at load time (with ?data= override for any export). Refresh = replace one JSON file; build.py only vendors the fg2 library when the template changes. Verified in browser: same viewer renders 25/372 and 69/1428 graphs by swapping JSON only; ?data=old-graph.json override works; edge visibility + integer slider intact. Server logs confirm runtime GET /graph.json per load. Docs (DESIGN.md, skill, wiki) updated to the new workflow.
User opened mnemon-graph.html from disk and got 'Cannot load graph.json':
browsers block fetch() from file:// (CORS), so the runtime-fetch viewer
failed without a server. Added a dual load path:
- export_graph.py now also writes graph-data.js (window.GRAPH_DATA = {...}),
a script tag that IS allowed from file://
- viewer prefers GRAPH_DATA (script tag) -> ?data= -> fetch(graph.json)
- actionable error message when data is truly missing
Verified live in browser: file:// with only viewer+graph-data.js renders
25/372; http with only viewer+graph.json (no graph-data.js) renders 25/372
via fetch; empty dir shows the new error message. 10/10 ad-hoc checks pass
(dual emission, identical JSON, artifact paths, determinism, committed data
untouched).
User asked: why must I know the full mnemon-graph.html URL? The serving trap (root serving the marker-filled template -> blank page) is now fixed by making the root URL work: - template renamed index.html -> template.html (build.py updated) - new index.html: tiny meta-refresh forwarder to mnemon-graph.html - so http://host:8123/ just works; no need to know the artifact filename Verified in browser: http://localhost:8123/ -> redirects -> renders 25/372; file:// double-click of index.html also forwards and renders (with graph-data.js beside it). Docs (DESIGN.md, skill, wiki) updated: serving-trap section rewritten to 'Serving' (resolved), template refs updated.
…ioning - Added computeAutoForces() function that calculates intelligent defaults based on canvas size and graph topology - Applied auto-computed forces: link distance, charge strength, and distance min - Implemented smart camera positioning to prevent overly tight zoom - UI sliders now initialize to auto-computed values - Verified with 12/12 checks passing Auto-layout now automatically prevents graph from rendering as a big blob by computing: - Optimal link distance: scales with canvas size and graph density - Strong repulsion: scales with node count and edge density - Smart initial camera zoom: shows entire graph without manual adjustment Closes the gap between manual force adjustment and automatic smart defaults.
…ioning - Added computeAutoForces() function that calculates intelligent defaults based on canvas size and graph topology - Applied auto-computed forces: link distance, charge strength, and distance min - Implemented smart camera positioning to prevent overly tight zoom - UI sliders now initialize to auto-computed values - Verified with 12/12 checks passing Auto-layout now automatically prevents graph from rendering as a big blob by computing: - Optimal link distance: scales with canvas size and graph density - Strong repulsion: scales with node count and edge density - Smart initial camera zoom: shows entire graph without manual adjustment Closes the gap between manual force adjustment and automatic smart defaults.
…ate computeAutoForces
Root cause of 'no visible change' after the auto-layout commit:
1. The force-application chain was written as a leading-dot statement
after a semicolon ('.d3Force(...)' with no receiver) — a JS
SyntaxError that killed the ENTIRE app script. build() never ran
with the auto forces; graph always fell back to library defaults.
2. Two computeAutoForces() declarations existed; the later (old weak)
one shadowed the enhanced version in JS hoisting.
Fixes verified in-browser (node --check + live render):
- Force chain now valid JS: Graph.d3Force(...) separate statements
- Single computeAutoForces with enhanced spread values
(linkDist 253 / charge -1000 / min 21 for 25-node graph)
- nodeRelSize 12->3: bubble radius cbrt(val)*3 (max ~8 units,
was ~21) — bubbles no longer dominate the scene
- spinCam now orbits the graph cluster center (bbox) instead of
the origin, so auto-rotate keeps the framed view centered
- Initial camera z:400 frames the expanded graph
Measured: 25/25 labels in viewport, spread 410x320 px on
1280x577 viewport, centered at (623,317) vs (640,289).
…in, duplicate fn, bubble size, orbit center) Skill pitfalls 12-15, wiki decision-table rows, DESIGN.md §3.9
The vis.js fallback was a stopgap from the blank-page debugging era. The 3D viewer (mnemon-graph.html) is now pixel-verified; the fallback is a second, unmaintained renderer that doubles the export surface. - delete mnemon-viz.html - export pipeline is now: export_graph.py -> graph.json + graph-data.js only - skill/wiki/DESIGN.md updated; decision table documents why it was dropped
…ve from tools/knowledge-graph into mnemon-graph-export/scripts/); update all references (SKILL.md, wiki, DESIGN.md); add .gitignore for generated data
|
| Filename | Overview |
|---|---|
| .devcontainer/skills/mnemon-graph-export/scripts/export_graph.py | Exports live Mnemon records and edges while safely encoding the file-compatible script data. |
| .devcontainer/skills/mnemon-graph-export/scripts/build.py | Builds the static viewer and verifies both freshly downloaded and cached library bundles against a pinned digest. |
| .devcontainer/skills/mnemon-graph-export/scripts/template.html | Implements runtime loading, escaped labels and tooltips, filtering, force controls, and the 3D viewer lifecycle. |
| .devcontainer/skills/mnemon-graph-export/scripts/mnemon-graph.html | The generated viewer is synchronized with the corrected template in the reviewed security- and control-sensitive sections. |
| .devcontainer/skills/mnemon-graph-export/SKILL.md | Documents the export, rebuild, serving, and mandatory browser-rendering verification workflow. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
DB[(Mnemon SQLite DB)] -->|export_graph.py| JSON[graph.json]
DB -->|export_graph.py| JS[graph-data.js]
Template[template.html] -->|build.py + verified bundle| Viewer[mnemon-graph.html]
JSON -->|HTTP fetch| Viewer
JS -->|file:// script load| Viewer
Viewer --> Browser[3D knowledge graph]
Reviews (11): Last reviewed commit: "fix(knowledge-graph): escape nodeLabel i..." | Re-trigger Greptile
…n-graph-viewer + new wiki articles)
…xport data - Make category pills hoverable (pointer-events + mouseenter/mouseleave) so hovering anywhere on a pill shows that node's tooltip, not just the sphere - Rebuild viewer from template - Untrack graph.json/graph-data.js (generated by export_graph.py, gitignored) - Refresh graph from latest mnemon export (30 nodes, 412 edges)
…ity + correctness) - template.html: escape n.importance in tooltip (P1 XSS via ?data= JSON) - template.html: NaN-safe camera coords in onNodeClick (n.x/n.z defaults) - template.html: clamp auto-computed forces to slider ranges so knob == applied - export_graph.py: drop dead empty prefix in short_label loop - export_graph.py: escape </script (case-insensitive) in graph-data.js to prevent script-boundary break / injection (P1) - build.py: pin SHA-256 of vendored 3d-force-graph bundle; fail build on mismatch (P2) Verified: export (34 nodes/540 edges), JS syntax OK, headless render OK (34 memories/connections), XSS PoC clean, </script> escaping correct.
…reptile P1) computeAutoForces() could return an in-range charge strength not divisible by 10, while the repulsion slider is step=10 — the browser snaps the knob to the step but the sim gets the unsnapped integer, so display != applied and the first adjustment jumps. Snap chargeStr to the 10-unit step. Verified: JS syntax OK, viewer rebuilt, headless render still 34 memories.
Remove the memory-automation edit to MEMORY.md that had landed on the branch. File now exactly matches origin/main; PR no longer touches it.
fg2.js is the downloaded 3d-force-graph bundle cached by build.py — it is gitignored but was tracked from before the rule existed. Untrack it; build.py re-fetches on demand and verifies the pinned SHA-256. Local file retained.
Follows the same pattern as codespace-port-visibility, codespace-vscode-open, codespace-lavish, codespace-webtop skill entries. Future agents will recall this skill exists and know the trigger words + exact invocation steps.
… terminology + PR policy - Terminology: CONTENT -> runtime group (was just 'CONTENT'), infrastructure unchanged - Adds Mnemon seed import detail: parses 'imported' field (not 'added') - Adds PR-ONLY POLICY: Never commit MEMORY.md/USER.md changes on PR branches; memory-automation artifacts create diffs-vs-main that must be reverted
nodeLabel callback returned raw n.label which 3d-force-graph renders as HTML. Attacker-controlled label via ?data= could inject <script>/<img onerror>. Now escaped via esc(). Verified: JS syntax OK, headless render OK, malicious label PoC clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a self-contained 3D knowledge-graph viewer for the Mnemon memory store under
.devcontainer/tools/knowledge-graph/:index.html— viewer template (dark theme, category filters, importance slider, node tooltips)build.py— inlines 3d-force-graph@1.80 +graph.jsoninto a single portablemnemon-graph.html; auto-fetches the bundle on first build (KG_CACHE) so a fresh clone can rebuildexport_graph.py— exports the Mnemon sqlite DB tograph.json(nodes + edges + meta)graph.json,mnemon-graph.html(3D view),mnemon-viz.html(vis.js 2D view) — committed outputsFixes / details
min=1 max=5 step=1) — previouslystep=0.1produced values like 2.6 while data importance is an integer.graph2ScreenCoords()— avoids loading a second Three.js copy, which crashed with "Multiple instances of Three.js"..autoRotate(), which silently brokebuild()init on load.Verification
build.pyrebuild is deterministic (byte-identical artifacts across runs) and reproducible from a fresh clone (fetches the pinned 3d-force-graph@1.80.0 bundle on demand).Open
mnemon-graph.htmlin a browser, or serve the dir:python3 -m http.server.