mnemosyne-core 2.1.8 → 2.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1532,10 +1532,16 @@
1532
1532
  background: var(--bg-panel);
1533
1533
  backdrop-filter: var(--glass);
1534
1534
  border-left: 1px solid var(--border-subtle);
1535
- overflow-y: auto;
1535
+ display: flex;
1536
+ flex-direction: column;
1536
1537
  z-index: 100;
1537
1538
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1538
1539
  }
1540
+ #sidebar-content {
1541
+ flex: 1;
1542
+ overflow-y: auto;
1543
+ min-height: 0;
1544
+ }
1539
1545
  #sidebar.collapsed {
1540
1546
  transform: translateX(100%);
1541
1547
  }
@@ -4041,6 +4047,7 @@
4041
4047
  const API = `${window.location.protocol}//${window.location.host}`;
4042
4048
  const WS = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`;
4043
4049
  let nodes = [], links = [];
4050
+ const MAX_GRAPH_NODES = 300; // cap initial render for performance
4044
4051
  let selected = null;
4045
4052
  let currentMemory = "";
4046
4053
  let focusMode = false;
@@ -4809,7 +4816,8 @@ async function load() {
4809
4816
  }
4810
4817
 
4811
4818
  function updateStats({ memories, blocks, bonds: bondCount, agents }) {
4812
- document.getElementById("stat-memories").innerHTML = `Memories: <strong>${memories}</strong>`;
4819
+ const capped = nodes.length > MAX_GRAPH_NODES;
4820
+ document.getElementById("stat-memories").innerHTML = `Memories: <strong>${memories}${capped ? '/' + nodes.length : ''}</strong>${capped ? ' <span style="color:var(--accent-light); font-size:11px;">(capped)</span>' : ''}`;
4813
4821
  document.getElementById("stat-blocks").innerHTML = `Blocks: <strong>${blocks}</strong>`;
4814
4822
  document.getElementById("stat-bonds").innerHTML = `Bonds: <strong>${bondCount}</strong>`;
4815
4823
  }
@@ -5599,6 +5607,33 @@ function initGraph() {
5599
5607
  if (width === 0 || height === 0) { console.error("[initGraph] svg has zero size"); return; }
5600
5608
  if (!nodes.length) { console.log("[initGraph] no nodes"); return; }
5601
5609
 
5610
+ // Performance: cap nodes for huge datasets
5611
+ let displayNodes = nodes;
5612
+ let displayLinks = links;
5613
+ let capped = false;
5614
+ if (nodes.length > MAX_GRAPH_NODES) {
5615
+ capped = true;
5616
+ // Keep root, then highest-degree nodes
5617
+ const degree = new Map();
5618
+ links.forEach(l => {
5619
+ const s = l.source?.id || l.source;
5620
+ const t = l.target?.id || l.target;
5621
+ degree.set(s, (degree.get(s) || 0) + 1);
5622
+ degree.set(t, (degree.get(t) || 0) + 1);
5623
+ });
5624
+ const sorted = [...nodes].sort((a, b) => (degree.get(b.id) || 0) - (degree.get(a.id) || 0));
5625
+ const rootIdx = sorted.findIndex(n => n.type === "root");
5626
+ const root = rootIdx >= 0 ? sorted.splice(rootIdx, 1)[0] : null;
5627
+ const kept = new Set((root ? [root.id] : []).concat(sorted.slice(0, MAX_GRAPH_NODES - (root ? 1 : 0)).map(n => n.id)));
5628
+ displayNodes = nodes.filter(n => kept.has(n.id));
5629
+ displayLinks = links.filter(l => {
5630
+ const s = l.source?.id || l.source;
5631
+ const t = l.target?.id || l.target;
5632
+ return kept.has(s) && kept.has(t);
5633
+ });
5634
+ toast("Large dataset", `Showing ${displayNodes.length} of ${nodes.length} memories. Use search or focus mode to explore.`, "info");
5635
+ }
5636
+
5602
5637
  svg.selectAll("*").remove();
5603
5638
  graphG = svg.append("g");
5604
5639
  zoomBehavior = d3.zoom().on("zoom", (e) => {
@@ -5613,7 +5648,7 @@ function initGraph() {
5613
5648
  svg.call(zoomBehavior);
5614
5649
 
5615
5650
  const savedLayout = loadGraphLayout(currentMemory);
5616
- simNodes = nodes.map(n => {
5651
+ simNodes = displayNodes.map(n => {
5617
5652
  const saved = savedLayout[n.id];
5618
5653
  if (saved) {
5619
5654
  const pinned = saved.pinned ? { fx: saved.x, fy: saved.y } : {};
@@ -5621,7 +5656,7 @@ function initGraph() {
5621
5656
  }
5622
5657
  return { ...n, x: width / 2 + (Math.random() - 0.5) * 40, y: height / 2 + (Math.random() - 0.5) * 40 };
5623
5658
  });
5624
- simLinks = links.map(l => ({ ...l }));
5659
+ simLinks = displayLinks.map(l => ({ ...l }));
5625
5660
 
5626
5661
  graphG.append("g").attr("class", "tree-links");
5627
5662
  graphG.append("g").attr("class", "bond-links");
@@ -5780,19 +5815,21 @@ function updateGraph({ reheat = 0.3, newNodeIds = new Set() } = {}) {
5780
5815
 
5781
5816
  linkBondSel = bondEnter.merge(linkBondSel);
5782
5817
 
5783
- // Bond labels
5818
+ // Bond labels — hidden by default for large graphs, shown on hover
5819
+ const showBondLabels = simLinks.filter(l => l.type === "bond").length <= 60;
5784
5820
  bondLabelSel = bondLabelsGroup.selectAll("g").data(simLinks.filter(l => l.type === "bond"), d => {
5785
5821
  const s = typeof d.source === "object" ? d.source.id : d.source;
5786
5822
  const t = typeof d.target === "object" ? d.target.id : d.target;
5787
5823
  return s + "-" + t + "-bond";
5788
5824
  });
5789
5825
  bondLabelSel.exit().transition().duration(300).attr("opacity", 0).remove();
5790
- const labelEnter = bondLabelSel.enter().append("g").attr("class", "bond-label-group").attr("opacity", 0);
5826
+ const labelEnter = bondLabelSel.enter().append("g").attr("class", "bond-label-group").attr("opacity", showBondLabels ? 0 : 0);
5791
5827
  labelEnter.append("rect").attr("class", "bond-label-bg").attr("width", 60).attr("height", 18).attr("x", -30).attr("y", -9);
5792
5828
  labelEnter.append("text").attr("class", "bond-label-text").attr("dy", "0.35em");
5793
- labelEnter.transition().duration(400).attr("opacity", 1);
5829
+ labelEnter.transition().duration(400).attr("opacity", showBondLabels ? 1 : 0);
5794
5830
  bondLabelSel = labelEnter.merge(bondLabelSel);
5795
5831
  bondLabelSel.select("text").text(d => d.label).attr("fill", d => getBondColor(d.label));
5832
+ bondLabelSel.style("pointer-events", "none");
5796
5833
 
5797
5834
  // Atoms
5798
5835
  nodeSel = atomsGroup.selectAll("g").data(simNodes, d => d.id);
@@ -6040,16 +6077,36 @@ function bondLinkMouseOver(e, d) {
6040
6077
  tt.style.top = (e.pageY - 10) + "px";
6041
6078
  tt.style.display = "block";
6042
6079
  d3.select(e.currentTarget).select("path").attr("stroke-width", 3.5);
6080
+ // Show bond label on hover when labels are hidden by default
6081
+ if (bondLabelSel) {
6082
+ const s = typeof d.source === "object" ? d.source.id : d.source;
6083
+ const t = typeof d.target === "object" ? d.target.id : d.target;
6084
+ bondLabelSel.filter(l => {
6085
+ const ls = typeof l.source === "object" ? l.source.id : l.source;
6086
+ const lt = typeof l.target === "object" ? l.target.id : l.target;
6087
+ return ls === s && lt === t;
6088
+ }).transition().duration(150).attr("opacity", 1);
6089
+ }
6043
6090
  }
6044
6091
  function linkMouseMove(e) {
6045
6092
  const tt = document.getElementById("link-tooltip");
6046
6093
  if (tt) { tt.style.left = (e.pageX + 10) + "px"; tt.style.top = (e.pageY - 10) + "px"; }
6047
6094
  }
6048
- function bondLinkMouseOut(e) {
6095
+ function bondLinkMouseOut(e, d) {
6049
6096
  const tt = document.getElementById("link-tooltip");
6050
6097
  if (tt) tt.style.display = "none";
6051
6098
  d3.select(e.currentTarget).select("path").attr("stroke-width", 2);
6052
- }
6099
+ // Hide bond label again if labels are hidden by default
6100
+ const showBondLabels = simLinks.filter(l => l.type === "bond").length <= 60;
6101
+ if (!showBondLabels && bondLabelSel && d) {
6102
+ const s = typeof d.source === "object" ? d.source.id : d.source;
6103
+ const t = typeof d.target === "object" ? d.target.id : d.target;
6104
+ bondLabelSel.filter(l => {
6105
+ const ls = typeof l.source === "object" ? l.source.id : l.source;
6106
+ const lt = typeof l.target === "object" ? l.target.id : l.target;
6107
+ return ls === s && lt === t;
6108
+ }).transition().duration(150).attr("opacity", 0);
6109
+ }
6053
6110
  function bondLinkClick(e, d) {
6054
6111
  e.stopPropagation();
6055
6112
  showBondToolbar(e, d);
package/dist/index.js CHANGED
@@ -201,7 +201,7 @@ function getVersion() {
201
201
  const pkg = JSON.parse(readFileSync6(resolve9(process.cwd(), "package.json"), "utf-8"));
202
202
  return pkg.version;
203
203
  } catch {
204
- return "2.1.8";
204
+ return "2.1.9";
205
205
  }
206
206
  }
207
207
  }
@@ -5391,7 +5391,7 @@ function getVersion2() {
5391
5391
  const pkg = JSON.parse(readFileSync6(resolve9(process.cwd(), "package.json"), "utf-8"));
5392
5392
  return pkg.version;
5393
5393
  } catch {
5394
- return "2.1.8";
5394
+ return "2.1.9";
5395
5395
  }
5396
5396
  }
5397
5397
  }
@@ -5978,7 +5978,7 @@ function getVersion3() {
5978
5978
  const pkg = JSON.parse(readFileSync6(resolve9(process.cwd(), "package.json"), "utf-8"));
5979
5979
  return pkg.version;
5980
5980
  } catch {
5981
- return "2.1.8";
5981
+ return "2.1.9";
5982
5982
  }
5983
5983
  }
5984
5984
  }
@@ -7144,7 +7144,7 @@ var PKG_VERSION = (() => {
7144
7144
  const pkg = JSON.parse(require("fs").readFileSync(require("path").resolve(__dirname, "../../package.json"), "utf-8"));
7145
7145
  return pkg.version;
7146
7146
  } catch {
7147
- return "2.1.8";
7147
+ return "2.1.9";
7148
7148
  }
7149
7149
  })();
7150
7150
  function handleHealth(store, pathname, method, res) {
@@ -7509,7 +7509,7 @@ var MnemosyneServer = class {
7509
7509
  const wss = new import_websocket_server.default({ server: this.httpServer });
7510
7510
  this.wsHandler = new WebSocketHandler(wss, this.store);
7511
7511
  }
7512
- const version = cfg?.server?.version || "2.1.8";
7512
+ const version = cfg?.server?.version || "2.1.9";
7513
7513
  this.httpServer.listen(port, () => {
7514
7514
  console.log(`Mnemosyne v${version} \u2014 port ${port}`);
7515
7515
  console.log(`Dashboard: http://${host}:${port}/dashboard`);