diff --git a/engraphis/dashboard_assets/ledger.js b/engraphis/dashboard_assets/ledger.js index a6dad820..18faed65 100644 --- a/engraphis/dashboard_assets/ledger.js +++ b/engraphis/dashboard_assets/ledger.js @@ -139,7 +139,11 @@ const GRAPH_ALL_NODE_LIMIT = 20_000; const GRAPH_ALL_EDGE_LIMIT = 200_000; const GRAPH_LOAD_TIMEOUT_MS = 60_000; - const GRAPH_FULL_LOAD_TIMEOUT_MS = 30_000; + // Cold complete scenes can legitimately spend ~25s in the bounded server projection + // before the renderer receives its payload. Keep enough headroom for the response body, + // graph assets, and renderer readiness instead of turning a healthy cold request into a + // generic client timeout. + const GRAPH_FULL_LOAD_TIMEOUT_MS = 90_000; const GRAPH_CONNECTION_MEMORIES_TIMEOUT_MS = 8_000; const GRAPH_PREFERENCES_KEY = 'engraphis-ledger-graph-preferences-v1'; const GRAPH_PHYSICS_VERSION = 5; @@ -3719,7 +3723,10 @@ const memoryProjection = fullGraph ? '&include_memory_nodes=false' : ''; const [payload] = await Promise.race([ Promise.all([ - api(`/graph/scene?${query(targetWorkspace)}&level=${level}${presentation}${limits}${connectedOnly}${includeCode}${scopedRepo}${asOf}${history}${memoryProjection}`, { signal: controller.signal }), + api(`/graph/scene?${query(targetWorkspace)}&level=${level}${presentation}${limits}${connectedOnly}${includeCode}${scopedRepo}${asOf}${history}${memoryProjection}`, { + signal: controller.signal, + timeoutMs: deadline, + }), assets, ]), timeoutPromise, diff --git a/tests/e2e/ledger.spec.js b/tests/e2e/ledger.spec.js index f10256d5..86a77856 100644 --- a/tests/e2e/ledger.spec.js +++ b/tests/e2e/ledger.spec.js @@ -2305,7 +2305,7 @@ test('overview→Every-node readiness failure preserves the committed overview r await page.addInitScript(() => { const nativeSetTimeout = window.setTimeout.bind(window); window.setTimeout = (callback, delay, ...args) => { - if (delay === 30_000) return nativeSetTimeout(callback, 120, ...args); + if (delay === 90_000) return nativeSetTimeout(callback, 120, ...args); return nativeSetTimeout(callback, delay, ...args); }; }); @@ -2386,7 +2386,7 @@ test('successful retry after readiness failure commits exactly one new renderer' await page.addInitScript(() => { const nativeSetTimeout = window.setTimeout.bind(window); window.setTimeout = (callback, delay, ...args) => { - if (delay === 30_000) return nativeSetTimeout(callback, 120, ...args); + if (delay === 90_000) return nativeSetTimeout(callback, 120, ...args); return nativeSetTimeout(callback, delay, ...args); }; }); diff --git a/tests/test_dashboard_v2.py b/tests/test_dashboard_v2.py index 3811570e..f6a0f0d1 100644 --- a/tests/test_dashboard_v2.py +++ b/tests/test_dashboard_v2.py @@ -1044,6 +1044,7 @@ def test_graph_load_is_bounded_single_flight_and_retryable(monkeypatch, tmp_path assert "const GRAPH_ALL_NODE_LIMIT = 20_000;" in script.text assert "const GRAPH_ALL_EDGE_LIMIT = 200_000;" in script.text assert "const GRAPH_LOAD_TIMEOUT_MS = 60_000;" in script.text + assert "const GRAPH_FULL_LOAD_TIMEOUT_MS = 90_000;" in script.text assert "AbortController" in script.text assert "state.graphLoadPromise" in script.text assert "graphLoadRepo: ''" in script.text @@ -1061,6 +1062,7 @@ def test_graph_load_is_bounded_single_flight_and_retryable(monkeypatch, tmp_path assert "previousController.abort()" in script.text assert "Promise.race([" in script.text assert "timeoutPromise" in script.text + assert "timeoutMs: deadline" in script.text assert "/graph/scene?" in script.text assert "&level=${level}" in script.text assert "&include_memory_nodes=false" in script.text