mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
test(graph-ui): add render diagnostics guardrails
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
"test:generation-context": "node tests/generation-context.mjs",
|
||||
"test:generation-recall-transactions": "node tests/generation-recall-transaction-isolation.mjs",
|
||||
"test:graph-persistence": "node tests/graph-persistence.mjs",
|
||||
"test:graph-renderer-guardrails": "node tests/graph-renderer-guardrails.mjs",
|
||||
"test:index-slicing-ratchet": "node tests/index-slicing-ratchet.mjs",
|
||||
"test:runtime-deps": "node tests/runtime-deps-completeness.mjs",
|
||||
"test:identity-resolver": "node tests/identity-resolver.mjs",
|
||||
|
||||
165
tests/graph-renderer-guardrails.mjs
Normal file
165
tests/graph-renderer-guardrails.mjs
Normal file
@@ -0,0 +1,165 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
globalThis.window = {
|
||||
devicePixelRatio: 1,
|
||||
matchMedia: () => ({ matches: false, addEventListener() {}, removeEventListener() {} }),
|
||||
};
|
||||
globalThis.performance ??= { now: () => Date.now() };
|
||||
globalThis.requestAnimationFrame = (callback) => setTimeout(() => callback(performance.now()), 0);
|
||||
globalThis.cancelAnimationFrame = (id) => clearTimeout(id);
|
||||
globalThis.ResizeObserver = class ResizeObserver {
|
||||
observe() {}
|
||||
disconnect() {}
|
||||
};
|
||||
|
||||
function createNoopContext() {
|
||||
const noop = () => {};
|
||||
return {
|
||||
setTransform: noop,
|
||||
clearRect: noop,
|
||||
save: noop,
|
||||
restore: noop,
|
||||
translate: noop,
|
||||
scale: noop,
|
||||
beginPath: noop,
|
||||
arc: noop,
|
||||
arcTo: noop,
|
||||
fill: noop,
|
||||
stroke: noop,
|
||||
moveTo: noop,
|
||||
lineTo: noop,
|
||||
quadraticCurveTo: noop,
|
||||
fillText: noop,
|
||||
closePath: noop,
|
||||
rect: noop,
|
||||
fillRect: noop,
|
||||
strokeRect: noop,
|
||||
measureText: (text = "") => ({ width: String(text).length * 6 }),
|
||||
createRadialGradient: () => ({ addColorStop: noop }),
|
||||
set fillStyle(_value) {},
|
||||
set strokeStyle(_value) {},
|
||||
set lineWidth(_value) {},
|
||||
set font(_value) {},
|
||||
set textAlign(_value) {},
|
||||
set textBaseline(_value) {},
|
||||
set globalAlpha(_value) {},
|
||||
set lineCap(_value) {},
|
||||
set lineJoin(_value) {},
|
||||
};
|
||||
}
|
||||
|
||||
function createCanvas() {
|
||||
return {
|
||||
parentElement: { clientWidth: 640, clientHeight: 360 },
|
||||
width: 0,
|
||||
height: 0,
|
||||
style: {},
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
getBoundingClientRect: () => ({ left: 0, top: 0, width: 640, height: 360 }),
|
||||
getContext: (type) => (type === "2d" ? createNoopContext() : null),
|
||||
};
|
||||
}
|
||||
|
||||
function createGraphFixture() {
|
||||
return {
|
||||
nodes: [
|
||||
{ id: "objective-1", type: "event", name: "Objective", importance: 6, scope: { layer: "objective" } },
|
||||
{ id: "user-1", type: "concept", name: "User POV", importance: 5, scope: { layer: "pov", ownerType: "user", ownerName: "Host" } },
|
||||
{ id: "char-1", type: "character", name: "Character POV", importance: 7, scope: { layer: "pov", ownerType: "character", ownerName: "Alice" } },
|
||||
{ id: "archived-1", type: "event", name: "Archived", archived: true, scope: { layer: "objective" } },
|
||||
],
|
||||
edges: [
|
||||
{ fromId: "objective-1", toId: "user-1", relation: "related", strength: 0.7 },
|
||||
{ fromId: "user-1", toId: "char-1", relation: "related", strength: 0.6 },
|
||||
{ fromId: "objective-1", toId: "missing-node", relation: "invalid-target" },
|
||||
{ fromId: "objective-1", toId: "char-1", relation: "invalidated", invalidAt: "2026-01-01T00:00:00.000Z" },
|
||||
{ fromId: "char-1", toId: "user-1", relation: "expired", expiredAt: "2026-01-02T00:00:00.000Z" },
|
||||
{ fromId: "archived-1", toId: "objective-1", relation: "archived-edge" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function assertInputUnchanged(graph, beforeJson) {
|
||||
assert.equal(JSON.stringify(graph), beforeJson);
|
||||
for (const node of graph.nodes) {
|
||||
for (const key of ["x", "y", "regionKey", "diagnostics", "highlight"]) {
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(node, key), false, `${node.id} gained ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { GraphRenderer } = await import("../ui/graph-renderer.js");
|
||||
|
||||
{
|
||||
const graph = createGraphFixture();
|
||||
const before = JSON.stringify(graph);
|
||||
const renderer = new GraphRenderer(createCanvas(), {
|
||||
runtimeConfig: { graphUseNativeLayout: false, graphNativeForceDisable: true },
|
||||
layoutConfig: { neuralIterations: 8 },
|
||||
});
|
||||
|
||||
renderer.loadGraph(graph, { userPovAliases: ["Host"] });
|
||||
assertInputUnchanged(graph, before);
|
||||
renderer.destroy();
|
||||
}
|
||||
|
||||
{
|
||||
const graph = createGraphFixture();
|
||||
const renderer = new GraphRenderer(createCanvas(), {
|
||||
runtimeConfig: { graphUseNativeLayout: false, graphNativeForceDisable: true },
|
||||
layoutConfig: { neuralIterations: 8 },
|
||||
});
|
||||
|
||||
renderer.loadGraph(graph, { userPovAliases: ["Host"] });
|
||||
const diagnostics = renderer.getLastLayoutDiagnostics();
|
||||
assert.ok(diagnostics);
|
||||
assert.equal(diagnostics.rawNodeCount, 4);
|
||||
assert.equal(diagnostics.archivedNodeCount, 1);
|
||||
assert.equal(diagnostics.activeNodeCount, 3);
|
||||
assert.equal(diagnostics.visibleNodeCount, 3);
|
||||
assert.equal(diagnostics.nodeCount, 3);
|
||||
assert.equal(diagnostics.rawEdgeCount, 6);
|
||||
assert.equal(diagnostics.skippedEdgeCount, 4);
|
||||
assert.equal(diagnostics.activeEdgeCount, 2);
|
||||
assert.equal(diagnostics.visibleEdgeCount, 2);
|
||||
assert.equal(diagnostics.edgeCount, 2);
|
||||
assert.equal(diagnostics.objectiveNodeCount, 1);
|
||||
assert.equal(diagnostics.userPovNodeCount, 1);
|
||||
assert.equal(diagnostics.characterPovNodeCount, 1);
|
||||
assert.equal(diagnostics.characterPovPanelCount, 1);
|
||||
assert.equal(diagnostics.sampled, false);
|
||||
assert.equal(diagnostics.capped, false);
|
||||
assert.equal(diagnostics.renderOnly, true);
|
||||
assert.equal(Number.isFinite(diagnostics.totalMs), true);
|
||||
assert.ok(["js-main", "skipped"].includes(diagnostics.mode));
|
||||
renderer.destroy();
|
||||
}
|
||||
|
||||
{
|
||||
const graph = createGraphFixture();
|
||||
const before = JSON.stringify(graph);
|
||||
const renderer = new GraphRenderer(createCanvas(), {
|
||||
runtimeConfig: { graphUseNativeLayout: false, graphNativeForceDisable: true },
|
||||
layoutConfig: { neuralIterations: 8 },
|
||||
});
|
||||
|
||||
renderer.setEnabled(false);
|
||||
assert.doesNotThrow(() => renderer.loadGraph(graph));
|
||||
assertInputUnchanged(graph, before);
|
||||
const diagnostics = renderer.getLastLayoutDiagnostics();
|
||||
assert.ok(diagnostics);
|
||||
assert.equal(diagnostics.enabled, false);
|
||||
assert.equal(diagnostics.renderOnly, true);
|
||||
assert.equal(diagnostics.reason, "disabled");
|
||||
assert.equal(diagnostics.mode, "skipped");
|
||||
assert.equal(diagnostics.rawNodeCount, 4);
|
||||
assert.equal(diagnostics.rawEdgeCount, 6);
|
||||
assert.equal(diagnostics.activeNodeCount, 3);
|
||||
assert.equal(diagnostics.activeEdgeCount, 2);
|
||||
assert.equal(diagnostics.archivedNodeCount, 1);
|
||||
assert.equal(diagnostics.skippedEdgeCount, 4);
|
||||
renderer.destroy();
|
||||
}
|
||||
|
||||
console.log("graph-renderer guardrail tests passed");
|
||||
@@ -216,6 +216,41 @@ function partitionNodesByScope(nodes, userPovAliasSet = null) {
|
||||
return { objective, userPov, charMap };
|
||||
}
|
||||
|
||||
function countRawNodesByScope(nodes, userPovAliasSet = null) {
|
||||
const aliasSet = userPovAliasSet instanceof Set ? userPovAliasSet : new Set();
|
||||
let objectiveNodeCount = 0;
|
||||
let userPovNodeCount = 0;
|
||||
let characterPovNodeCount = 0;
|
||||
const charKeys = new Set();
|
||||
|
||||
for (const node of Array.isArray(nodes) ? nodes : []) {
|
||||
const scope = normalizeMemoryScope(node?.scope);
|
||||
if (scope.layer !== 'pov') {
|
||||
objectiveNodeCount += 1;
|
||||
continue;
|
||||
}
|
||||
if (scopeMatchesHostUserAliases(scope, aliasSet) || scope.ownerType === 'user') {
|
||||
userPovNodeCount += 1;
|
||||
continue;
|
||||
}
|
||||
if (scope.ownerType === 'character') {
|
||||
characterPovNodeCount += 1;
|
||||
const nameKey = normalizeKeyForPartition(scope.ownerName);
|
||||
const idKey = normalizeKeyForPartition(scope.ownerId);
|
||||
charKeys.add(nameKey || idKey || '·');
|
||||
continue;
|
||||
}
|
||||
objectiveNodeCount += 1;
|
||||
}
|
||||
|
||||
return {
|
||||
objectiveNodeCount,
|
||||
userPovNodeCount,
|
||||
characterPovNodeCount,
|
||||
characterPovPanelCount: charKeys.size,
|
||||
};
|
||||
}
|
||||
|
||||
export class GraphRenderer {
|
||||
/**
|
||||
* @param {HTMLCanvasElement} canvas
|
||||
@@ -305,13 +340,60 @@ export class GraphRenderer {
|
||||
this._lastLayoutHints = layoutHints && typeof layoutHints === 'object'
|
||||
? { ...layoutHints }
|
||||
: {};
|
||||
const rawNodes = Array.isArray(graph?.nodes) ? graph.nodes : [];
|
||||
const rawEdges = Array.isArray(graph?.edges) ? graph.edges : [];
|
||||
const rawNodeCount = rawNodes.length;
|
||||
const rawEdgeCount = rawEdges.length;
|
||||
const diagnosticCanvasBase = {
|
||||
canvasCssWidth: Number(this._lastCanvasCssWidth || 0),
|
||||
canvasCssHeight: Number(this._lastCanvasCssHeight || 0),
|
||||
devicePixelRatio: Number(window.devicePixelRatio || 1),
|
||||
enabled: this.enabled !== false,
|
||||
};
|
||||
if (layoutHints && Object.prototype.hasOwnProperty.call(layoutHints, 'userPovAliases')) {
|
||||
this._userPovAliasSet = buildUserPovAliasNormalizedSet(
|
||||
layoutHints.userPovAliases,
|
||||
);
|
||||
}
|
||||
const activeRawNodes = rawNodes.filter(n => !n.archived);
|
||||
const activeRawNodeIds = new Set(activeRawNodes.map((node) => node?.id).filter(Boolean));
|
||||
const activeRawEdges = rawEdges.filter(e => (
|
||||
!e?.invalidAt
|
||||
&& !e?.expiredAt
|
||||
&& activeRawNodeIds.has(e?.fromId)
|
||||
&& activeRawNodeIds.has(e?.toId)
|
||||
));
|
||||
const rawPartitionCounts = countRawNodesByScope(activeRawNodes, this._userPovAliasSet);
|
||||
|
||||
if (!this.enabled) {
|
||||
this._setLastLayoutDiagnostics({
|
||||
mode: 'skipped',
|
||||
nodeCount: 0,
|
||||
edgeCount: 0,
|
||||
rawNodeCount,
|
||||
rawEdgeCount,
|
||||
activeNodeCount: activeRawNodes.length,
|
||||
activeEdgeCount: activeRawEdges.length,
|
||||
visibleNodeCount: 0,
|
||||
visibleEdgeCount: 0,
|
||||
archivedNodeCount: Math.max(0, rawNodeCount - activeRawNodes.length),
|
||||
skippedEdgeCount: Math.max(0, rawEdgeCount - activeRawEdges.length),
|
||||
...rawPartitionCounts,
|
||||
prepareMs: 0,
|
||||
layoutSeedMs: 0,
|
||||
solveMs: 0,
|
||||
totalMs: Math.max(0, performance.now() - loadStartedAt),
|
||||
layoutReuseCount: 0,
|
||||
layoutReuseTotal: 0,
|
||||
layoutReuseRatio: 0,
|
||||
sampled: false,
|
||||
capped: false,
|
||||
renderOnly: true,
|
||||
at: Date.now(),
|
||||
...diagnosticCanvasBase,
|
||||
enabled: false,
|
||||
reason: 'disabled',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -321,8 +403,7 @@ export class GraphRenderer {
|
||||
const W = this.canvas.width / dpr;
|
||||
const H = this.canvas.height / dpr;
|
||||
|
||||
const activeNodes = graph.nodes.filter(n => !n.archived);
|
||||
this.nodes = activeNodes.map((n) => {
|
||||
this.nodes = activeRawNodes.map((n) => {
|
||||
const node = {
|
||||
id: n.id,
|
||||
type: n.type || 'event',
|
||||
@@ -342,7 +423,7 @@ export class GraphRenderer {
|
||||
return node;
|
||||
});
|
||||
|
||||
this.edges = graph.edges
|
||||
this.edges = rawEdges
|
||||
.filter(e => !e.invalidAt && !e.expiredAt && this.nodeMap.has(e.fromId) && this.nodeMap.has(e.toId))
|
||||
.map(e => ({
|
||||
from: this.nodeMap.get(e.fromId),
|
||||
@@ -353,10 +434,32 @@ export class GraphRenderer {
|
||||
const prepareFinishedAt = performance.now();
|
||||
|
||||
const parts = partitionNodesByScope(this.nodes, this._userPovAliasSet);
|
||||
const characterPovNodeCount = [...parts.charMap.values()]
|
||||
.reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0);
|
||||
this._regionPanels = this._computeRegionPanels(W, H, parts);
|
||||
const layoutReuse = this._applyPreviousLayoutSeed(previousLayoutSeedByNodeId);
|
||||
this._layoutAllPartitions(parts);
|
||||
const layoutFinishedAt = performance.now();
|
||||
const baseLayoutDiagnostics = {
|
||||
nodeCount: this.nodes.length,
|
||||
edgeCount: this.edges.length,
|
||||
rawNodeCount,
|
||||
rawEdgeCount,
|
||||
activeNodeCount: this.nodes.length,
|
||||
activeEdgeCount: this.edges.length,
|
||||
visibleNodeCount: this.nodes.length,
|
||||
visibleEdgeCount: this.edges.length,
|
||||
archivedNodeCount: Math.max(0, rawNodeCount - this.nodes.length),
|
||||
skippedEdgeCount: Math.max(0, rawEdgeCount - this.edges.length),
|
||||
objectiveNodeCount: parts.objective.length,
|
||||
userPovNodeCount: parts.userPov.length,
|
||||
characterPovNodeCount,
|
||||
characterPovPanelCount: parts.charMap.size,
|
||||
sampled: false,
|
||||
capped: false,
|
||||
renderOnly: true,
|
||||
...diagnosticCanvasBase,
|
||||
};
|
||||
const neuralPlan = this._resolveNeuralSimulationPlan();
|
||||
const shouldTryNativeLayout = this._shouldTryNativeLayout(
|
||||
this.nodes.length,
|
||||
@@ -378,6 +481,7 @@ export class GraphRenderer {
|
||||
prepareFinishedAt,
|
||||
layoutFinishedAt,
|
||||
layoutReuse,
|
||||
baseLayoutDiagnostics,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
@@ -397,8 +501,7 @@ export class GraphRenderer {
|
||||
if (!nativeSolvePromise) {
|
||||
this._setLastLayoutDiagnostics({
|
||||
mode: solvePath,
|
||||
nodeCount: this.nodes.length,
|
||||
edgeCount: this.edges.length,
|
||||
...baseLayoutDiagnostics,
|
||||
prepareMs: Math.max(0, prepareFinishedAt - loadStartedAt),
|
||||
layoutSeedMs: Math.max(0, layoutFinishedAt - prepareFinishedAt),
|
||||
solveMs,
|
||||
@@ -945,6 +1048,9 @@ export class GraphRenderer {
|
||||
const layoutReuse = timings.layoutReuse && typeof timings.layoutReuse === 'object'
|
||||
? timings.layoutReuse
|
||||
: this._lastLayoutReuseStats;
|
||||
const baseLayoutDiagnostics = timings.baseLayoutDiagnostics && typeof timings.baseLayoutDiagnostics === 'object'
|
||||
? timings.baseLayoutDiagnostics
|
||||
: {};
|
||||
|
||||
const bridge = this._ensureNativeLayoutBridge();
|
||||
const solveStartedAt = performance.now();
|
||||
@@ -968,8 +1074,7 @@ export class GraphRenderer {
|
||||
applied: false,
|
||||
diagnostics: {
|
||||
mode: 'native-stale',
|
||||
nodeCount: this.nodes.length,
|
||||
edgeCount: this.edges.length,
|
||||
...baseLayoutDiagnostics,
|
||||
prepareMs: Math.max(0, prepareFinishedAt - loadStartedAt),
|
||||
layoutSeedMs: Math.max(0, layoutFinishedAt - prepareFinishedAt),
|
||||
solveMs: Math.max(0, performance.now() - solveStartedAt),
|
||||
@@ -988,8 +1093,7 @@ export class GraphRenderer {
|
||||
applied: true,
|
||||
diagnostics: {
|
||||
mode: nativeResult.usedNative ? 'rust-wasm-worker' : 'js-worker',
|
||||
nodeCount: this.nodes.length,
|
||||
edgeCount: this.edges.length,
|
||||
...baseLayoutDiagnostics,
|
||||
prepareMs: Math.max(0, prepareFinishedAt - loadStartedAt),
|
||||
layoutSeedMs: Math.max(0, layoutFinishedAt - prepareFinishedAt),
|
||||
solveMs: Math.max(0, performance.now() - solveStartedAt),
|
||||
@@ -1010,8 +1114,7 @@ export class GraphRenderer {
|
||||
applied: false,
|
||||
diagnostics: {
|
||||
mode: 'native-failed-hard',
|
||||
nodeCount: this.nodes.length,
|
||||
edgeCount: this.edges.length,
|
||||
...baseLayoutDiagnostics,
|
||||
prepareMs: Math.max(0, prepareFinishedAt - loadStartedAt),
|
||||
layoutSeedMs: Math.max(0, layoutFinishedAt - prepareFinishedAt),
|
||||
solveMs: Math.max(0, performance.now() - solveStartedAt),
|
||||
@@ -1031,8 +1134,7 @@ export class GraphRenderer {
|
||||
applied: true,
|
||||
diagnostics: {
|
||||
mode: 'js-fallback',
|
||||
nodeCount: this.nodes.length,
|
||||
edgeCount: this.edges.length,
|
||||
...baseLayoutDiagnostics,
|
||||
prepareMs: Math.max(0, prepareFinishedAt - loadStartedAt),
|
||||
layoutSeedMs: Math.max(0, layoutFinishedAt - prepareFinishedAt),
|
||||
solveMs: Math.max(0, performance.now() - solveStartedAt) + fallbackSolveMs,
|
||||
|
||||
70
ui/panel.js
70
ui/panel.js
@@ -5621,18 +5621,32 @@ function _formatGraphLayoutDiagnosticsText(diagnostics = null) {
|
||||
const totalMs = Number(
|
||||
diagnostics.totalMs ?? diagnostics.solveMs ?? diagnostics.workerSolveMs,
|
||||
);
|
||||
const nodeCount = Number(diagnostics.nodeCount);
|
||||
const edgeCount = Number(diagnostics.edgeCount);
|
||||
const visibleNodeCount = Number(
|
||||
diagnostics.visibleNodeCount ?? diagnostics.nodeCount,
|
||||
);
|
||||
const visibleEdgeCount = Number(
|
||||
diagnostics.visibleEdgeCount ?? diagnostics.edgeCount,
|
||||
);
|
||||
const rawNodeCount = Number(diagnostics.rawNodeCount);
|
||||
const rawEdgeCount = Number(diagnostics.rawEdgeCount);
|
||||
|
||||
const parts = [`LAYOUT: ${modeLabel}`];
|
||||
if (Number.isFinite(totalMs)) {
|
||||
parts.push(`${Math.max(0, Math.round(totalMs))}ms`);
|
||||
}
|
||||
if (Number.isFinite(nodeCount) && Number.isFinite(edgeCount)) {
|
||||
if (Number.isFinite(visibleNodeCount) && Number.isFinite(visibleEdgeCount)) {
|
||||
parts.push(
|
||||
`${Math.max(0, Math.floor(nodeCount))}/${Math.max(
|
||||
`v${Math.max(0, Math.floor(visibleNodeCount))}/${Math.max(
|
||||
0,
|
||||
Math.floor(edgeCount),
|
||||
Math.floor(visibleEdgeCount),
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
if (Number.isFinite(rawNodeCount) && Number.isFinite(rawEdgeCount)) {
|
||||
parts.push(
|
||||
`raw${Math.max(0, Math.floor(rawNodeCount))}/${Math.max(
|
||||
0,
|
||||
Math.floor(rawEdgeCount),
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
@@ -5640,6 +5654,48 @@ function _formatGraphLayoutDiagnosticsText(diagnostics = null) {
|
||||
return parts.join(" · ");
|
||||
}
|
||||
|
||||
function _formatGraphLayoutDiagnosticsTitle(diagnostics = null) {
|
||||
if (!diagnostics || typeof diagnostics !== "object") return "";
|
||||
const formatCountPair = (left, right) => {
|
||||
const a = Number(left);
|
||||
const b = Number(right);
|
||||
if (!Number.isFinite(a) || !Number.isFinite(b)) return "--";
|
||||
return `${Math.max(0, Math.floor(a))}/${Math.max(0, Math.floor(b))}`;
|
||||
};
|
||||
const formatBool = (value) => (value === true ? "true" : value === false ? "false" : "--");
|
||||
const reuseCount = Number(diagnostics.layoutReuseCount);
|
||||
const reuseTotal = Number(diagnostics.layoutReuseTotal);
|
||||
const reuseRatio = Number(diagnostics.layoutReuseRatio);
|
||||
const reuseParts = [];
|
||||
if (Number.isFinite(reuseCount) && Number.isFinite(reuseTotal)) {
|
||||
reuseParts.push(`${Math.max(0, Math.floor(reuseCount))}/${Math.max(0, Math.floor(reuseTotal))}`);
|
||||
}
|
||||
if (Number.isFinite(reuseRatio)) {
|
||||
reuseParts.push(`${Math.round(Math.max(0, reuseRatio) * 100)}%`);
|
||||
}
|
||||
|
||||
const lines = [
|
||||
`visible nodes/edges: ${formatCountPair(diagnostics.visibleNodeCount ?? diagnostics.nodeCount, diagnostics.visibleEdgeCount ?? diagnostics.edgeCount)}`,
|
||||
`raw nodes/edges: ${formatCountPair(diagnostics.rawNodeCount, diagnostics.rawEdgeCount)}`,
|
||||
`active nodes/edges: ${formatCountPair(diagnostics.activeNodeCount, diagnostics.activeEdgeCount)}`,
|
||||
`archived/skipped: ${formatCountPair(diagnostics.archivedNodeCount, diagnostics.skippedEdgeCount)}`,
|
||||
`partitions objective/userPOV/characterPOV/panels: ${[
|
||||
diagnostics.objectiveNodeCount,
|
||||
diagnostics.userPovNodeCount,
|
||||
diagnostics.characterPovNodeCount,
|
||||
diagnostics.characterPovPanelCount,
|
||||
].map((value) => {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? String(Math.max(0, Math.floor(n))) : "--";
|
||||
}).join("/")}`,
|
||||
`reuse count/ratio: ${reuseParts.join(" · ") || "--"}`,
|
||||
`sampled/capped/renderOnly: ${formatBool(diagnostics.sampled)}/${formatBool(diagnostics.capped)}/${formatBool(diagnostics.renderOnly)}`,
|
||||
];
|
||||
const reason = String(diagnostics.reason || "").trim();
|
||||
if (reason) lines.push(`reason: ${reason}`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function _refreshGraphLayoutDiagnosticsUi() {
|
||||
const desktopMeta = document.getElementById("bme-graph-layout-meta");
|
||||
const mobileMeta = document.getElementById("bme-mobile-graph-layout-meta");
|
||||
@@ -5648,9 +5704,7 @@ function _refreshGraphLayoutDiagnosticsUi() {
|
||||
const renderer = _resolveVisibleGraphRenderer();
|
||||
const diagnostics = renderer?.getLastLayoutDiagnostics?.() || null;
|
||||
const text = _formatGraphLayoutDiagnosticsText(diagnostics);
|
||||
const title = diagnostics?.reason
|
||||
? `layout reason: ${String(diagnostics.reason).trim()}`
|
||||
: "";
|
||||
const title = _formatGraphLayoutDiagnosticsTitle(diagnostics);
|
||||
|
||||
if (desktopMeta) {
|
||||
desktopMeta.textContent = text;
|
||||
|
||||
Reference in New Issue
Block a user