From 7782bda6772b79cb32b7e928e37d1ae6a9cd908d Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Mon, 2 Mar 2026 11:29:58 +0100 Subject: [PATCH] Fix potential timeline hangs from cycles and refresh cascades Add cycle detection and O(n) path building to getAllBranches, and a concurrency guard on timeline refresh to drop overlapping calls. Co-Authored-By: Claude Opus 4.6 --- js/snapshot_manager.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/js/snapshot_manager.js b/js/snapshot_manager.js index a449237..67b9208 100644 --- a/js/snapshot_manager.js +++ b/js/snapshot_manager.js @@ -986,18 +986,22 @@ function getAncestorIds(snapshotId, parentOf) { function getAllBranches(tree) { const branches = []; + const visited = new Set(); function walk(nodeId, path) { + if (visited.has(nodeId)) return; // cycle detection + visited.add(nodeId); const record = tree.byId.get(nodeId); if (!record) return; - const currentPath = [...path, record]; + path.push(record); const children = tree.childrenOf.get(nodeId); if (!children || children.length === 0) { - branches.push(currentPath); + branches.push([...path]); } else { for (const child of children) { - walk(child.id, currentPath); + walk(child.id, path); } } + path.pop(); } for (const root of tree.roots) { walk(root.id, []); @@ -3720,9 +3724,14 @@ function buildTimeline() { return marker; } + let refreshPending = false; async function refresh() { if (!showTimeline) return; - + if (refreshPending) return; // drop overlapping calls + refreshPending = true; + try { await _refreshInner(); } finally { refreshPending = false; } + } + async function _refreshInner() { const wfKey = getWorkflowKey(); // Hide/show expand button based on branching