From 670027fd220d0572197f1fd1042e50a6a71b45b6 Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Mon, 13 Apr 2026 14:16:59 +0530 Subject: [PATCH] fix(explorer): resolve 3 code-review bugs in GraphWorkspace, DecisionWorkspace, and index.css MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GraphWorkspace: set isRunningPredictions=true before link-prediction fetch and false in finally block; pass isRunningPredictions prop to LazyGraphInspectorPanel so the inspector button disables and shows a spinner during the request (was declared but never wired — broke noUnusedLocals TypeScript build) - DecisionWorkspace: add AbortController to the /api/decisions useEffect so the fetch is cancelled on unmount; add per-call AbortController to handleSelectDecision for /api/decisions/:id/chain; add res.ok guards before .json() on both fetches; encodeURIComponent on decision_id to prevent path-injection edge cases - index.css: add missing @keyframes skeleton-pulse rule (0%/100% opacity 0.45, 50% opacity 0.85) — KGOverviewTab skeletonBarStyle referenced this animation but it was never defined, leaving skeleton bars static Co-Authored-By: Claude Sonnet 4.6 --- explorer/src/index.css | 6 ++++++ .../DecisionWorkspace/DecisionWorkspace.tsx | 19 ++++++++++++++----- .../GraphWorkspace/GraphWorkspace.tsx | 4 ++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/explorer/src/index.css b/explorer/src/index.css index 2da58a27..c87a4c2d 100644 --- a/explorer/src/index.css +++ b/explorer/src/index.css @@ -63,3 +63,9 @@ code, pre, .mono { .animate-spin { animation: spin 1s linear infinite; } + +/* Skeleton pulse animation for loading placeholders */ +@keyframes skeleton-pulse { + 0%, 100% { opacity: 0.45; } + 50% { opacity: 0.85; } +} diff --git a/explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx b/explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx index f5e4790d..974819e3 100644 --- a/explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx +++ b/explorer/src/workspaces/DecisionWorkspace/DecisionWorkspace.tsx @@ -203,15 +203,21 @@ export function DecisionWorkspace() { const [filterQuery, setFilterQuery] = useState(""); useEffect(() => { + const controller = new AbortController(); setListLoading(true); - fetch("/api/decisions") - .then((res) => res.json()) + fetch("/api/decisions", { signal: controller.signal }) + .then((res) => { + if (!res.ok) throw new Error(`Failed to load decisions: ${res.status}`); + return res.json(); + }) .then((data) => { setDecisions(data); if (data.length > 0) void handleSelectDecision(data[0]); }) - .catch(console.error) + .catch((err) => { if (err.name !== "AbortError") console.error(err); }) .finally(() => setListLoading(false)); + return () => controller.abort(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const filteredDecisions = useMemo(() => { @@ -229,15 +235,18 @@ export function DecisionWorkspace() { setSelectedDecision(d); setLoading(true); setChain([]); + const controller = new AbortController(); try { - const res = await fetch(`/api/decisions/${d.decision_id}/chain`); + const res = await fetch(`/api/decisions/${encodeURIComponent(d.decision_id)}/chain`, { signal: controller.signal }); + if (!res.ok) throw new Error(`Failed to load chain: ${res.status}`); const data = await res.json(); setChain(data.chain || []); } catch (e) { - console.error(e); + if ((e as DOMException).name !== "AbortError") console.error(e); } finally { setLoading(false); } + return () => controller.abort(); }; return ( diff --git a/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx b/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx index b14a26e8..0c04357d 100644 --- a/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx +++ b/explorer/src/workspaces/GraphWorkspace/GraphWorkspace.tsx @@ -816,6 +816,7 @@ export function GraphWorkspace() { const handleRunPredictions = useCallback(async () => { if (!selectedNodeId) return; + setIsRunningPredictions(true); try { const response = await fetch("/api/enrich/links", { method: "POST", @@ -835,6 +836,8 @@ export function GraphWorkspace() { } catch (predictionError) { console.error("[GraphWorkspace] prediction failed", predictionError); setPredictions([]); + } finally { + setIsRunningPredictions(false); } }, [predictionType, selectedNodeId]); @@ -1609,6 +1612,7 @@ export function GraphWorkspace() { predictionType={predictionType} onPredictionTypeChange={setPredictionType} onRunPredictions={() => void handleRunPredictions()} + isRunningPredictions={isRunningPredictions} pathTargetId={pathTargetId} onPathTargetChange={setPathTargetId} onTracePath={() => void handleTracePath()}