fix(web): cap tree layout spring iterations and remove unused variable

Finding A (blocker): calculateTreeLayout runs 14 synchronous spring
iterations over all edges and nodes — O(N×E×14) + O(N log N) per layer
per iteration — with no size guard.  At 10K+ nodes this freezes the
main thread for several seconds.

Fix: make SPRING_ITERATIONS adaptive:
  - N > 10 000 → 0 iterations (proportional initial layout only)
  - N >  3 000 → 4 iterations
  - otherwise  → 14 iterations (unchanged behaviour for small graphs)

Also removes the unused `const r` at useSigma.ts:1314, which was a
leftover after the radial-resistance decomposition was removed.
This clears the CodeQL "unused variable" warning (Finding G).

Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-6
This commit is contained in:
Hugo Gu 2026-05-25 21:23:04 +08:00
parent 3471e9086c
commit bbae989e79
2 changed files with 5 additions and 2 deletions

View file

@ -1311,7 +1311,6 @@ export const useSigma = (options: UseSigmaOptions = {}): UseSigmaReturn => {
const targetR = ringTargetR[Math.min(ring, CIRCLES_RING_COUNT - 1)];
const x = attrs.x;
const y = attrs.y;
const r = Math.sqrt(x * x + y * y) || 1;
// Soft-wall gravity (force 1) already handles radial boundary
// enforcement — no separate resistance decomposition needed.

View file

@ -501,7 +501,11 @@ export function calculateTreeLayout(graph: KnowledgeGraph): Map<string, TreeNode
// 6. Relax the graph like a constrained spring system. Only X is allowed
// to move, so node types stay on their original Y layers.
const SPRING_ITERATIONS = 14;
// For large graphs the spring phase is O(N×E×iterations) and would freeze
// the main thread — scale it down proportionally so the initial proportional
// layout (already good at large N) is kept without expensive refinement.
const nodeCount = graph.nodes.length;
const SPRING_ITERATIONS = nodeCount > 10000 ? 0 : nodeCount > 3000 ? 4 : 14;
for (let iter = 0; iter < SPRING_ITERATIONS; iter++) {
const deltaXByNode = new Map<string, number>();