Skip to content

feat(tools): Mnemon knowledge-graph 3D viewer - #25

Merged
gitricko merged 24 commits into
mainfrom
feat/knowledge-graph-viewer
Sep 6, 2026
Merged

gitricko merged 24 commits into
mainfrom
feat/knowledge-graph-viewer

Conversation

@gitricko

@gitricko gitricko commented Aug 3, 2026

Copy link
Copy Markdown
Owner

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.json into a single portable mnemon-graph.html; auto-fetches the bundle on first build (KG_CACHE) so a fresh clone can rebuild
  • export_graph.py — exports the Mnemon sqlite DB to graph.json (nodes + edges + meta)
  • graph.json, mnemon-graph.html (3D view), mnemon-viz.html (vis.js 2D view) — committed outputs

Fixes / details

  • Importance slider is now integer (min=1 max=5 step=1) — previously step=0.1 produced values like 2.6 while data importance is an integer.
  • Category names render on the bubbles (DECISION / CONTEXT / FACT / INSIGHT / GENERAL) via a DOM overlay positioned with graph2ScreenCoords() — avoids loading a second Three.js copy, which crashed with "Multiple instances of Three.js".
  • Auto-rotate implemented manually in a rAF loop — the vendored 3d-force-graph bundle does not expose .autoRotate(), which silently broke build() init on load.
  • Filtering (importance slider + category toggles) hides/shows both nodes and their label pills.

Verification

  • Verified by real browser rendering + PIL pixel measurement (not static greps): graph renders, 25 labels overlay the bubbles, filter reduces 25 → 6 visible at importance 5.
  • build.py rebuild 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.html in a browser, or serve the dir: python3 -m http.server.

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())
Copilot AI review requested due to automatic review settings August 3, 2026 23:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 generate graph.json and 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);
});
}
gitricko added 13 commits August 4, 2026 02:36
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
@greptile-apps

greptile-apps Bot commented Sep 6, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a self-contained Mnemon knowledge-graph export and visualization workflow.

  • Exports live Mnemon nodes and edges into JSON and a file-compatible JavaScript data artifact.
  • Builds a portable 3D viewer with filtering, labels, force controls, and runtime data loading.
  • Adds skill and wiki documentation for export, rebuild, serving, and browser verification workflows.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

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]
Loading

Reviews (11): Last reviewed commit: "fix(knowledge-graph): escape nodeLabel i..." | Re-trigger Greptile

Comment thread .devcontainer/skills/mnemon-graph-export/scripts/template.html Outdated
Comment thread .devcontainer/skills/mnemon-graph-export/scripts/template.html
Comment thread .devcontainer/skills/mnemon-graph-export/scripts/build.py
…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)
Comment thread .devcontainer/skills/mnemon-graph-export/scripts/export_graph.py Outdated
…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.
Comment thread .devcontainer/skills/mnemon-graph-export/scripts/template.html Outdated
…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
Comment thread .devcontainer/skills/mnemon-graph-export/scripts/template.html Outdated
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.
@gitricko
gitricko merged commit cd7da68 into main Sep 6, 2026
7 checks passed
@gitricko
gitricko deleted the feat/knowledge-graph-viewer branch September 6, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants