import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { type GraphData, type GraphTypeFilters, type PinMap, type SelectionInput, type ThemeId } from "../src"; import { createGraphFacadeFromRenderer, createGraphFacadeRouteManager, createGraphOfflineCapabilities, createGraphStandaloneCapabilities, createGraphWorkbenchCapabilities, selectionInputForSigmaHit, type GraphFacadeRenderer, type GraphFacadeRouteRendererFactoryInput, type GraphFacadeState } from "../src/facade"; const DATA: GraphData = { meta: { build_date: "2026-06-16", wiki_title: "Facade test graph", total_nodes: 2, total_edges: 1 }, nodes: [ { id: "a", label: "Alpha", type: "topic", community: "c1", source_path: "wiki/a.md", content: "Alpha content" }, { id: "b", label: "Beta", type: "source", community: "c1", source_path: "wiki/b.md", content: "Beta content" } ], edges: [ { id: "a->b", from: "a", to: "b", type: "EXTRACTED", confidence: "EXTRACTED", relation_type: "实现", weight: 1 } ] }; describe("GraphFacade", () => { it("owns the public engine lifecycle around a renderer", async () => { const container = { dataset: {} as Record }; const renderer = createFakeRenderer(); const engine = createGraphFacadeFromRenderer(container, renderer, { data: DATA, theme: "shan-shui" }); assert.equal(container.dataset.llmWikiGraphEngine, "mounted"); assert.equal(container.dataset.llmWikiGraphTheme, "shan-shui"); engine.setTheme("mo-ye"); assert.equal(container.dataset.llmWikiGraphTheme, "mo-ye"); assert.deepEqual(renderer.calls.at(-1), ["setTheme", "mo-ye"]); engine.setEdgeStyle({ semanticEmphasis: true, focusHighlight: false }); assert.deepEqual(renderer.calls.at(-1), ["setEdgeStyle", { semanticEmphasis: true, focusHighlight: false }]); engine.focusNode("wiki/a.md"); assert.equal(container.dataset.llmWikiGraphFocus, "wiki/a.md"); assert.deepEqual(renderer.calls.at(-1), ["focusNode", "wiki/a.md"]); engine.clearInteraction(); assert.equal(container.dataset.llmWikiGraphFocus, undefined); assert.deepEqual(renderer.calls.at(-1), ["clearInteraction"]); assert.equal(engine.setNodeFixed("a", "fix"), true); assert.deepEqual(renderer.calls.at(-1), ["setNodeFixed", "a", "fix"]); await engine.applyDiff({ addedNodes: ["c"] }); assert.deepEqual(renderer.calls.at(-1), ["applyDiff", { addedNodes: ["c"] }, undefined]); engine.destroy(); assert.equal(container.dataset.llmWikiGraphEngine, undefined); assert.equal(container.dataset.llmWikiGraphTheme, undefined); assert.equal(renderer.calls.at(-1)?.[0], "destroy"); assert.throws(() => engine.resetView(), /Graph engine has been destroyed/); }); it("resolves selections against refreshed data", () => { const container = { dataset: {} as Record }; const renderer = createFakeRenderer(); const nextData: GraphData = { ...DATA, nodes: DATA.nodes.map((node) => node.id === "a" ? { ...node, community: "c2" } : node) }; const engine = createGraphFacadeFromRenderer(container, renderer, { data: DATA, theme: "shan-shui" }); assert.deepEqual(engine.select({ kind: "node", id: "a" }).communityIds, ["c1"]); engine.setData(nextData); const selection = engine.select({ kind: "node", id: "a" }); assert.deepEqual(selection.communityIds, ["c2"]); assert.deepEqual(renderer.calls.at(-2), ["setData", nextData, undefined]); assert.deepEqual(renderer.calls.at(-1), ["select", { kind: "node", id: "a" }]); }); it("keeps return global and reset layout as separate facade commands", () => { const container = { dataset: {} as Record }; const renderer = createFakeRenderer(); const viewResets: number[] = []; const selectionClears: number[] = []; const facadeState: GraphFacadeState = { data: DATA, pins: {}, selection: null, temporaryObject: null }; const engine = createGraphFacadeFromRenderer(container, renderer, { data: DATA, theme: "shan-shui", capabilities: { onSelectionClear: () => selectionClears.push(1), onViewReset: () => viewResets.push(1) } }, facadeState); const focused = engine.focusCommunity("c1"); assert.equal(container.dataset.llmWikiGraphFocus, "community:c1"); assert.equal(facadeState.selection, null); assert.deepEqual(focused.communityIds, ["c1"]); assert.deepEqual(focused.nodeIds, ["a", "b"]); engine.resetLayout(); assert.equal(container.dataset.llmWikiGraphFocus, "community:c1"); assert.deepEqual(renderer.calls.at(-1), ["resetLayout"]); assert.deepEqual(viewResets, []); assert.deepEqual(selectionClears, []); engine.resetView(); assert.equal(container.dataset.llmWikiGraphFocus, undefined); assert.equal(facadeState.selection, null); assert.equal(facadeState.temporaryObject, null); assert.deepEqual(renderer.calls.at(-1), ["resetView"]); assert.deepEqual(viewResets, [1]); assert.deepEqual(selectionClears, []); }); it("exposes shared summary payloads from current facade data and pins", () => { const container = { dataset: {} as Record }; const renderer = createFakeRenderer(); const engine = createGraphFacadeFromRenderer(container, renderer, { data: DATA, theme: "shan-shui", pins: { "wiki/a.md": { x: 10, y: 20, coordinateSpace: "world" } } }); const node = engine.summarizeNode("a", { selection: { kind: "node", id: "a" }, searchResultIds: ["a"] }); const community = engine.summarizeCommunity("c1", { selection: { kind: "community", id: "c1" } }); const global = engine.summarizeGlobal({ searchResultIds: ["b"] }); const search = engine.summarizeSearchResults("beta", ["b", "missing"]); const excluded = engine.summarizeExcludedObject({ kind: "node", nodeId: "a" }, "filter", { searchResultIds: ["a"] }); assert.equal(node.kind, "node-summary"); assert.equal(node.nodeId, "a"); assert.equal(node.pinHint.pinned, true); assert.equal(node.selection.containsCurrentObject, true); assert.deepEqual(node.commands.map((command) => command.kind), ["open-detail-read", "select-neighbors", "set-fixed-position", "enter-community"]); assert.equal(community.kind, "community-summary"); assert.equal(community.communityId, "c1"); assert.deepEqual(community.selection.selectedNodeIds, ["a", "b"]); assert.equal(global.kind, "global-overview"); assert.deepEqual(global.searchResultIds, ["b"]); assert.equal(search.kind, "search-results"); assert.deepEqual(search.visibleResultIds, ["b"]); assert.deepEqual(search.unavailableResultIds, ["missing"]); assert.equal(excluded.kind, "excluded-object"); assert.deepEqual(excluded.commands.map((command) => command.kind), ["show-this-object", "clear-temporary-object-display"]); engine.setPins({ "wiki/b.md": { x: 1, y: 2, coordinateSpace: "world" } }); const beta = engine.summarizeNode("b"); assert.equal(beta.kind, "node-summary"); assert.equal(beta.pinHint.nodeId, "b"); assert.equal(beta.pinHint.pinned, true); engine.setData(DATA); const betaAfterRefresh = engine.summarizeNode("b"); assert.equal(betaAfterRefresh.kind, "node-summary"); assert.equal(betaAfterRefresh.pinHint.pinned, true); }); it("declares separate workbench, offline, and standalone capability contracts", async () => { const persistPins = async (_pins: PinMap) => {}; const workbench = createGraphWorkbenchCapabilities({ onOpenPage: () => {}, onSelectionChange: () => {}, onSelectionClear: () => {}, onAsk: () => {}, persistPins, onDragStateChange: () => {} }); const offline = createGraphOfflineCapabilities({ persistPins }); const standalone = createGraphStandaloneCapabilities(); assert.equal(workbench.mode, "workbench"); assert.deepEqual(Object.keys(workbench.capabilities || {}).sort(), [ "onAsk", "onDragStateChange", "onOpenPage", "onSelectionChange", "onSelectionClear", "onViewReset", "onVisibilityStateChange", "persistPins" ]); assert.equal(offline.mode, "offline"); assert.deepEqual(Object.keys(offline.capabilities || {}), ["persistPins"]); assert.equal(offline.capabilities?.onOpenPage, undefined); assert.equal(offline.capabilities?.onSelectionChange, undefined); assert.equal(offline.capabilities?.onAsk, undefined); assert.equal(standalone.mode, "standalone"); assert.equal(standalone.capabilities, undefined); await offline.capabilities?.persistPins?.({}); }); it("routes global Sigma to DOM/SVG community reading and back to global Sigma with facade state", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: { "wiki/a.md": { x: 10, y: 20, coordinateSpace: "world" } }, theme: "shan-shui", focus: null, typeFilters: { topic: true, source: true }, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; const sigmaInputs: GraphFacadeRouteRendererFactoryInput[] = []; const communityInputs: GraphFacadeRouteRendererFactoryInput[] = []; const smallFallbackInputs: GraphFacadeRouteRendererFactoryInput[] = []; let overLimitNoticeCount = 0; const renderers: Array = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: (input) => { sigmaInputs.push(input); return trackRenderer(renderers, "sigma"); }, createDomSvgCommunity: (input) => { communityInputs.push(input); return trackRenderer(renderers, "dom-community"); }, createDomSvgSmallFallback: (input) => { smallFallbackInputs.push(input); return trackRenderer(renderers, "small-fallback"); }, createOverLimitNotice: () => { overLimitNoticeCount += 1; return trackRenderer(renderers, "over-limit-notice"); } } }); assert.equal(manager.routeId, "sigma-global"); assert.equal(sigmaInputs.length, 1); assert.equal(communityInputs.length, 0); assert.equal(smallFallbackInputs.length, 0); assert.equal(overLimitNoticeCount, 0); manager.select({ kind: "node", id: "a" }); manager.setTypeFilters({ topic: true, source: false }); manager.setPins({ "wiki/b.md": { x: 30, y: 40, coordinateSpace: "world" } }); manager.focusCommunity("c1"); assert.equal(manager.routeId, "dom-svg-community"); assert.equal(communityInputs.length, 1); assert.deepEqual(communityInputs[0].options.focus, { kind: "community", id: "c1" }); assert.deepEqual(communityInputs[0].options.selection, { kind: "node", id: "a" }); assert.deepEqual(communityInputs[0].options.typeFilters, { topic: true, source: false }); assert.deepEqual(Object.keys(communityInputs[0].options.pins), ["wiki/b.md"]); communityInputs[0].options.callbacks.onVisibilityStateChange?.({ searchQuery: "Alpha", searchResultIds: ["a"], typeFilters: { topic: true, source: false }, temporaryObject: null }); manager.resetView(); assert.equal(manager.routeId, "sigma-global"); assert.equal(sigmaInputs.length, 2); assert.equal(smallFallbackInputs.length, 0); assert.equal(overLimitNoticeCount, 0); assert.deepEqual(sigmaInputs[1].options.focus, null); assert.deepEqual(sigmaInputs[1].options.selection, { kind: "node", id: "a" }); assert.deepEqual(sigmaInputs[1].options.searchResultIds, ["a"]); assert.deepEqual(sigmaInputs[1].options.typeFilters, { topic: true, source: false }); assert.deepEqual(Object.keys(sigmaInputs[1].options.pins), ["wiki/b.md"]); assert.deepEqual(renderers.map((renderer) => renderer.calls.find((call) => call[0] === "destroy")?.[0]).filter(Boolean), [ "destroy", "destroy" ]); }); it("keeps edge style on the Sigma route and ignores it on DOM/SVG community routes", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: { topic: true, source: true }, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; const sigmaInputs: GraphFacadeRouteRendererFactoryInput[] = []; const renderers: Array = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: (input) => { sigmaInputs.push(input); return trackRenderer(renderers, "sigma"); }, createDomSvgCommunity: () => trackRenderer(renderers, "dom-community"), createDomSvgSmallFallback: () => trackRenderer(renderers, "small-fallback"), createOverLimitNotice: () => trackRenderer(renderers, "over-limit-notice") } }); const baseStyle = { semanticEmphasis: true, focusHighlight: false }; const focusStyle = { semanticEmphasis: true, focusHighlight: true }; manager.setEdgeStyle(baseStyle); assert.equal(manager.routeId, "sigma-global"); assert.deepEqual(renderers[0].calls.at(-1), ["setEdgeStyle", baseStyle]); manager.focusCommunity("c1"); const communityRenderer = renderers.at(-1); const communityCallCount = communityRenderer?.calls.length ?? 0; manager.setEdgeStyle(focusStyle); assert.equal(manager.routeId, "dom-svg-community"); assert.equal(communityRenderer?.calls.length, communityCallCount); manager.resetView(); assert.equal(manager.routeId, "sigma-global"); assert.equal(sigmaInputs.length, 2); assert.deepEqual(sigmaInputs[1].options.edgeStyle, focusStyle); }); it("marks route continuity on the stable facade host and clears transition markers", async () => { const route = createRouteMarkerHarness(); route.expect("sigma-global"); route.expectActiveRendererCount(1); route.manager.focusCommunity("c1"); route.expect("dom-svg-community", "sigma-global->dom-svg-community"); route.expectActiveRendererCount(1); await route.expectTransitionCleared(); route.manager.resetView(); route.expect("sigma-global", "dom-svg-community->sigma-global"); route.expectActiveRendererCount(1); route.manager.destroy(); route.expectDestroyed(); await route.expectTransitionCleared(); route.expectDestroyed(); }); it("marks fallback and over-limit route continuity on the stable facade host", async () => { const route = createRouteMarkerHarness(); route.expect("sigma-global"); route.sigmaInputs[0].onSigmaUnavailable?.(new Error("WebGL unavailable")); route.expect("dom-svg-small-fallback", "sigma-global->dom-svg-small-fallback"); route.expectActiveRendererCount(1); await route.expectTransitionCleared(); route.manager.setData(largeGraphData(2001, 1, 1)); route.expect("over-limit-notice", "dom-svg-small-fallback->over-limit-notice"); route.expectActiveRendererCount(1); await route.expectTransitionCleared(); route.manager.destroy(); }); it("keeps public resetView active when already on the Sigma global route", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: { kind: "node", id: "a" }, searchResultIds: [], temporaryObject: null }; const renderers: Array = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => trackRenderer(renderers, "sigma"), createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => createFakeRenderer() } }); assert.equal(manager.routeId, "sigma-global"); manager.resetView(); assert.deepEqual(renderers[0].calls.at(-1), ["resetView"]); assert.deepEqual(state.selection, { kind: "node", id: "a" }); }); it("uses global reset to exit Sigma community spotlight without changing routes", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: { "wiki/a.md": { x: 10, y: 20, coordinateSpace: "world" } }, theme: "shan-shui", focus: null, typeFilters: { topic: true, source: true }, aggregationMarkers: [], selection: { kind: "community", id: "c1" }, searchResultIds: ["a"], temporaryObject: { kind: "community", communityId: "c1" } }; const renderers: Array = []; const selectionClears: number[] = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, callbacks: { onSelectionClearRequested: () => selectionClears.push(1) }, factories: { createSigmaGlobal: () => trackRenderer(renderers, "sigma"), createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => createFakeRenderer() } }); manager.resetView(); assert.equal(manager.routeId, "sigma-global"); assert.equal(state.focus, null); assert.equal(state.selection, null); assert.equal(state.temporaryObject, null); assert.deepEqual(state.searchResultIds, ["a"]); assert.deepEqual(Object.keys(state.pins), ["wiki/a.md"]); assert.deepEqual(renderers[0].calls.slice(-1), [["resetView"]]); assert.deepEqual(selectionClears, [1]); }); it("lets the Sigma toolbar global reset clear the active renderer spotlight", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: { topic: true, source: true }, aggregationMarkers: [], selection: { kind: "community", id: "c1" }, searchResultIds: [], temporaryObject: { kind: "community", communityId: "c1" } }; const sigmaInputs: GraphFacadeRouteRendererFactoryInput[] = []; const renderers: Array = []; const selectionClears: number[] = []; const viewResets: number[] = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, callbacks: { onSelectionClearRequested: () => selectionClears.push(1), onViewReset: () => viewResets.push(1) }, factories: { createSigmaGlobal: (input) => { sigmaInputs.push(input); return trackRenderer(renderers, "sigma"); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => createFakeRenderer() } }); sigmaInputs[0].options.callbacks.onGlobalResetRequested?.(); assert.equal(manager.routeId, "sigma-global"); assert.equal(state.selection, null); assert.equal(state.temporaryObject, null); assert.deepEqual(selectionClears, [1]); assert.deepEqual(viewResets, [1]); assert.deepEqual(renderers[0].calls.slice(-1), [["resetView"]]); }); it("returns from DOM/SVG community reading to a plain global map after community selection", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: { "wiki/a.md": { x: 10, y: 20, coordinateSpace: "world" } }, theme: "shan-shui", focus: null, typeFilters: { topic: true, source: true }, aggregationMarkers: [], selection: { kind: "community", id: "c1" }, searchResultIds: ["a"], temporaryObject: { kind: "community", communityId: "c1" } }; const sigmaInputs: GraphFacadeRouteRendererFactoryInput[] = []; const communityInputs: GraphFacadeRouteRendererFactoryInput[] = []; const selectionClears: number[] = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, callbacks: { onSelectionClearRequested: () => selectionClears.push(1) }, factories: { createSigmaGlobal: (input) => { sigmaInputs.push(input); return createFakeRenderer(); }, createDomSvgCommunity: (input) => { communityInputs.push(input); return createFakeRenderer(); }, createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => createFakeRenderer() } }); manager.focusCommunity("c1"); manager.resetView(); assert.equal(manager.routeId, "sigma-global"); assert.equal(state.focus, null); assert.equal(state.selection, null); assert.equal(state.temporaryObject, null); assert.equal(sigmaInputs.length, 2); assert.deepEqual(sigmaInputs[1].options.selection, null); assert.deepEqual(sigmaInputs[1].options.searchResultIds, ["a"]); assert.deepEqual(Object.keys(sigmaInputs[1].options.pins), ["wiki/a.md"]); assert.deepEqual(selectionClears, [1]); }); it("lets a DOM/SVG community toolbar request the facade-level global route", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: { "wiki/a.md": { x: 10, y: 20, coordinateSpace: "world" } }, theme: "shan-shui", focus: null, typeFilters: { topic: true, source: true }, aggregationMarkers: [], selection: { kind: "node", id: "a" }, searchResultIds: ["a"], temporaryObject: null }; const sigmaInputs: GraphFacadeRouteRendererFactoryInput[] = []; const communityInputs: GraphFacadeRouteRendererFactoryInput[] = []; const viewResets: number[] = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, callbacks: { onViewReset: () => viewResets.push(1) }, factories: { createSigmaGlobal: (input) => { sigmaInputs.push(input); return createFakeRenderer(); }, createDomSvgCommunity: (input) => { communityInputs.push(input); return createFakeRenderer(); }, createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => createFakeRenderer() } }); manager.focusCommunity("c1"); assert.equal(manager.routeId, "dom-svg-community"); assert.equal(communityInputs.length, 1); (communityInputs[0].options.callbacks as { onGlobalResetRequested?: () => void }).onGlobalResetRequested?.(); assert.equal(manager.routeId, "sigma-global"); assert.equal(sigmaInputs.length, 2); assert.equal(state.focus, null); assert.deepEqual(sigmaInputs[1].options.focus, null); assert.deepEqual(sigmaInputs[1].options.selection, { kind: "node", id: "a" }); assert.deepEqual(sigmaInputs[1].options.searchResultIds, ["a"]); assert.deepEqual(sigmaInputs[1].options.typeFilters, { topic: true, source: true }); assert.deepEqual(Object.keys(sigmaInputs[1].options.pins), ["wiki/a.md"]); assert.deepEqual(viewResets, [1]); }); it("returns global to DOM/SVG small fallback without retrying a known unavailable Sigma instance", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let sigmaCreateCount = 0; const smallFallbackInputs: GraphFacadeRouteRendererFactoryInput[] = []; let overLimitNoticeCount = 0; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { sigmaCreateCount += 1; throw new Error("WebGL unavailable"); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: (input) => { smallFallbackInputs.push(input); assert.ok(input.options.data.nodes.length <= 2000); return createFakeRenderer(); }, createOverLimitNotice: () => { overLimitNoticeCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "dom-svg-small-fallback"); assert.equal(manager.sigmaKnownUnavailable, true); assert.equal(manager.sigmaAttemptCount, 1); assert.equal(sigmaCreateCount, 1); assert.equal(smallFallbackInputs.length, 1); assert.equal(overLimitNoticeCount, 0); manager.focusCommunity("c1"); assert.equal(manager.routeId, "dom-svg-community"); manager.resetView(); assert.equal(manager.routeId, "dom-svg-small-fallback"); assert.equal(manager.sigmaKnownUnavailable, true); assert.equal(manager.sigmaAttemptCount, 1); assert.equal(sigmaCreateCount, 1); assert.equal(smallFallbackInputs.length, 2); assert.equal(overLimitNoticeCount, 0); }); it("returns global to DOM/SVG small fallback from a DOM request with one view reset callback", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; const viewResets: number[] = []; const communityInputs: GraphFacadeRouteRendererFactoryInput[] = []; const smallFallbackRenderers: Array = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, callbacks: { onViewReset: () => viewResets.push(1) }, factories: { createSigmaGlobal: () => { throw new Error("WebGL unavailable"); }, createDomSvgCommunity: (input) => { communityInputs.push(input); return createFakeRenderer(); }, createDomSvgSmallFallback: () => { const renderer = trackRenderer(smallFallbackRenderers, "small-fallback"); renderer.resetView = () => { renderer.calls.push(["resetView"]); viewResets.push(1); }; return renderer; }, createOverLimitNotice: () => createFakeRenderer() } }); manager.focusCommunity("c1"); assert.equal(manager.routeId, "dom-svg-community"); (communityInputs[0].options.callbacks as { onGlobalResetRequested?: () => void }).onGlobalResetRequested?.(); assert.equal(manager.routeId, "dom-svg-small-fallback"); assert.equal(state.focus, null); assert.deepEqual(viewResets, [1]); assert.deepEqual(smallFallbackRenderers.at(-1)?.calls.filter((call) => call[0] === "resetView"), [["resetView"]]); }); it("updates the current fallback renderer when Sigma is known unavailable and the route stays the same", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; const smallFallbackRenderers: Array = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { throw new Error("WebGL unavailable"); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => trackRenderer(smallFallbackRenderers, "small-fallback"), createOverLimitNotice: () => createFakeRenderer() } }); const nextData = { ...DATA, meta: { ...DATA.meta, wiki_title: "Facade test graph refreshed" } }; assert.equal(manager.routeId, "dom-svg-small-fallback"); manager.setData(nextData); assert.equal(manager.routeId, "dom-svg-small-fallback"); assert.equal(smallFallbackRenderers.length, 1); assert.deepEqual(smallFallbackRenderers[0].calls.at(-1), ["setData", nextData, undefined]); }); it("keeps a 2000-node graph eligible for Sigma global rendering", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: largeGraphData(2000, 4000, 500), pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let sigmaCount = 0; let overLimitCount = 0; let domFallbackCount = 0; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { sigmaCount += 1; return createFakeRenderer(); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => { domFallbackCount += 1; return createFakeRenderer(); }, createOverLimitNotice: () => { overLimitCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "sigma-global"); assert.equal(sigmaCount, 1); assert.equal(overLimitCount, 0); assert.equal(domFallbackCount, 0); }); it("routes a 2001-node graph directly to the over-limit notice before Sigma", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: largeGraphData(2001, 1, 1), pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let sigmaCount = 0; let overLimitCount = 0; let domFallbackCount = 0; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { sigmaCount += 1; return createFakeRenderer(); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => { domFallbackCount += 1; return createFakeRenderer(); }, createOverLimitNotice: () => { overLimitCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "over-limit-notice"); assert.equal(sigmaCount, 0); assert.equal(domFallbackCount, 0); assert.equal(overLimitCount, 1); }); it("keeps over-limit reset on the over-limit route", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: largeGraphData(2001, 1, 1), pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: { kind: "community", id: "large-community-0" }, searchResultIds: [], temporaryObject: null }; const overLimitRenderers: Array = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => createFakeRenderer(), createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => trackRenderer(overLimitRenderers, "over-limit-notice") } }); assert.equal(manager.routeId, "over-limit-notice"); manager.resetView(); assert.equal(manager.routeId, "over-limit-notice"); assert.deepEqual(overLimitRenderers[0].calls.filter((call) => call[0] === "resetView"), [["resetView"]]); }); it("routes stale small metadata to over-limit notice using actual node array length", () => { const container = { dataset: {} as Record }; const staleLargeData = largeGraphData(2001, 1, 1); staleLargeData.meta.total_nodes = 1; staleLargeData.meta.total_edges = 1; const state: GraphFacadeState = { data: staleLargeData, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let sigmaCount = 0; let overLimitCount = 0; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { sigmaCount += 1; return createFakeRenderer(); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => { overLimitCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "over-limit-notice"); assert.equal(sigmaCount, 0); assert.equal(overLimitCount, 1); }); it("re-routes normal global data to over-limit notice when refreshed over the node cap", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let sigmaCount = 0; let overLimitCount = 0; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { sigmaCount += 1; return createFakeRenderer(); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => { overLimitCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "sigma-global"); manager.setData(largeGraphData(2001, 1, 1)); assert.equal(manager.routeId, "over-limit-notice"); assert.equal(container.dataset.llmWikiGraphRoute, "over-limit-notice"); assert.equal(container.dataset.llmWikiGraphRouteTransition, "sigma-global->over-limit-notice"); assert.equal(sigmaCount, 1); assert.equal(overLimitCount, 1); }); it("returns from over-limit notice to Sigma global when refreshed back under the node cap", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: largeGraphData(2001, 1, 1), pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let sigmaCount = 0; let overLimitCount = 0; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { sigmaCount += 1; return createFakeRenderer(); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => { overLimitCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "over-limit-notice"); manager.setData(largeGraphData(2000, 1, 1)); assert.equal(manager.routeId, "sigma-global"); assert.equal(overLimitCount, 1); assert.equal(sigmaCount, 1); }); it("re-routes known-unavailable small fallback data to over-limit notice when refreshed over the node cap", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let smallFallbackCount = 0; let overLimitCount = 0; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { throw new Error("WebGL unavailable"); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => { smallFallbackCount += 1; return createFakeRenderer(); }, createOverLimitNotice: () => { overLimitCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "dom-svg-small-fallback"); manager.setData(largeGraphData(2001, 1, 1)); assert.equal(manager.routeId, "over-limit-notice"); assert.equal(smallFallbackCount, 1); assert.equal(overLimitCount, 1); }); it("keeps route manager selection state synchronized with renderer callbacks", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: { kind: "node", nodeId: "a" } }; const selections: SelectionInput[] = []; const sigmaInputs: GraphFacadeRouteRendererFactoryInput[] = []; createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, callbacks: { onSelectionInput: (selection) => selections.push(selection) }, factories: { createSigmaGlobal: (input) => { sigmaInputs.push(input); return createFakeRenderer(); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => createFakeRenderer() } }); sigmaInputs[0].options.callbacks.onSelectionInput?.({ kind: "node", id: "a" }); assert.deepEqual(state.selection, { kind: "node", id: "a" }); assert.deepEqual(selections, [{ kind: "node", id: "a" }]); sigmaInputs[0].options.callbacks.onSelectionClearRequested?.(); assert.equal(state.selection, null); assert.equal(state.temporaryObject, null); }); it("routes over-limit Sigma retry back to the static notice before creating Sigma", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: largeGraphData(2001, 1, 1), pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let sigmaCount = 0; let overLimitCount = 0; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: () => { sigmaCount += 1; return createFakeRenderer(); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => createFakeRenderer(), createOverLimitNotice: () => { overLimitCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "over-limit-notice"); assert.equal(container.dataset.llmWikiGraphRoute, "over-limit-notice"); manager.retrySigma(); assert.equal(manager.routeId, "over-limit-notice"); assert.equal(container.dataset.llmWikiGraphRoute, "over-limit-notice"); assert.equal(container.dataset.llmWikiGraphRouteTransition, undefined); assert.equal(sigmaCount, 0); assert.equal(overLimitCount, 2); }); it("routes abnormal Sigma runtime failures to DOM/SVG small fallback and retries Sigma only on request", () => { const container = { dataset: {} as Record }; const state: GraphFacadeState = { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }; let sigmaCreateCount = 0; let smallFallbackCount = 0; let overLimitNoticeCount = 0; const sigmaInputs: GraphFacadeRouteRendererFactoryInput[] = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state, factories: { createSigmaGlobal: (input) => { sigmaCreateCount += 1; sigmaInputs.push(input); return createFakeRenderer(); }, createDomSvgCommunity: () => createFakeRenderer(), createDomSvgSmallFallback: () => { smallFallbackCount += 1; return createFakeRenderer(); }, createOverLimitNotice: () => { overLimitNoticeCount += 1; return createFakeRenderer(); } } }); assert.equal(manager.routeId, "sigma-global"); sigmaInputs[0].onSigmaUnavailable?.(new Error("canvas runtime abnormal failure")); assert.equal(manager.routeId, "dom-svg-small-fallback"); assert.equal(manager.sigmaKnownUnavailable, true); assert.equal(smallFallbackCount, 1); assert.equal(overLimitNoticeCount, 0); assert.equal(sigmaCreateCount, 1); manager.resetView(); assert.equal(manager.routeId, "dom-svg-small-fallback"); assert.equal(sigmaCreateCount, 1); assert.equal(smallFallbackCount, 1); assert.equal(overLimitNoticeCount, 0); manager.retrySigma(); assert.equal(manager.routeId, "sigma-global"); assert.equal(manager.sigmaKnownUnavailable, false); assert.equal(sigmaCreateCount, 2); }); }); describe("selectionInputForSigmaHit", () => { it("converts additive Sigma node hits into manual multi-node selections", () => { const data = sigmaHitGraph(); assert.deepEqual(selectionInputForSigmaHit(data, { kind: "node", id: "a1" }, { kind: "node", id: "a2" }, { additive: true }), { kind: "nodes", ids: ["a1", "a2"] }); assert.deepEqual(selectionInputForSigmaHit(data, { kind: "nodes", ids: ["a1", "a2"] }, { kind: "node", id: "a1" }, { additive: true }), { kind: "node", id: "a2" }); assert.equal(selectionInputForSigmaHit(data, { kind: "node", id: "a1" }, { kind: "node", id: "a1" }, { additive: true }), null); assert.deepEqual(selectionInputForSigmaHit(data, { kind: "node", id: "a1" }, { kind: "node", id: "b1" }, { additive: false }), { kind: "node", id: "b1" }); }); }); function sigmaHitGraph(): GraphData { return { meta: { build_date: "2026-06-12", wiki_title: "Sigma hit graph", total_nodes: 4, total_edges: 0 }, nodes: [ { id: "a1", label: "Alpha 1", type: "topic", community: "alpha", source_path: "wiki/a1.md" }, { id: "a2", label: "Alpha 2", type: "entity", community: "alpha", source_path: "wiki/a2.md" }, { id: "a3", label: "Alpha 3", type: "entity", community: "alpha", source_path: "wiki/a3.md" }, { id: "b1", label: "Beta 1", type: "entity", community: "beta", source_path: "wiki/b1.md" } ], edges: [] }; } function createFakeRenderer(): GraphFacadeRenderer & { calls: unknown[][] } { const calls: unknown[][] = []; return { calls, async applyDiff(diff, options) { calls.push(["applyDiff", diff, options]); }, isDragging() { calls.push(["isDragging"]); return false; }, setData(data: GraphData, pins?: PinMap) { calls.push(["setData", data, pins]); }, setEdgeStyle(style) { calls.push(["setEdgeStyle", style]); }, setAggregationMarkers(markers) { calls.push(["setAggregationMarkers", markers]); }, focusNode(path: string) { calls.push(["focusNode", path]); }, focusCommunity(id: string) { calls.push(["focusCommunity", id]); }, previewNode(id: string | null) { calls.push(["previewNode", id]); }, setTypeFilters(filters: GraphTypeFilters) { calls.push(["setTypeFilters", filters]); }, showTemporaryObject(object) { calls.push(["showTemporaryObject", object]); }, clearTemporaryObjectDisplay() { calls.push(["clearTemporaryObjectDisplay"]); }, resetView() { calls.push(["resetView"]); }, select(selection: SelectionInput) { calls.push(["select", selection]); }, clearSelection() { calls.push(["clearSelection"]); }, clearInteraction() { calls.push(["clearInteraction"]); }, setNodeFixed(id: string, mode: "fix" | "unfix") { calls.push(["setNodeFixed", id, mode]); return true; }, setTheme(theme: ThemeId) { calls.push(["setTheme", theme]); }, setPins(pins: PinMap) { calls.push(["setPins", pins]); }, resetLayout() { calls.push(["resetLayout"]); }, destroy() { calls.push(["destroy"]); } }; } function trackRenderer( renderers: Array, route: string ): GraphFacadeRenderer & { calls: unknown[][] } { const renderer = createFakeRenderer(); renderer.calls.push(["create", route]); renderers.push(renderer); return renderer; } function assertActiveRendererCount(renderers: Array, expected: number): void { const activeCount = renderers.filter((renderer) => !renderer.calls.some((call) => call[0] === "destroy")).length; assert.equal(activeCount, expected); } function createRouteMarkerHarness(): { container: { dataset: Record }; manager: GraphFacadeRenderer & { readonly routeId: string; readonly sigmaKnownUnavailable: boolean; readonly sigmaAttemptCount: number; retrySigma(): void; }; sigmaInputs: GraphFacadeRouteRendererFactoryInput[]; expect(routeId: string, transition?: string): void; expectActiveRendererCount(expected: number): void; expectDestroyed(): void; expectTransitionCleared(): Promise; } { const container = { dataset: {} as Record }; const renderers: Array = []; const sigmaInputs: GraphFacadeRouteRendererFactoryInput[] = []; const manager = createGraphFacadeRouteManager(container as unknown as HTMLElement, { state: { data: DATA, pins: {}, theme: "shan-shui", focus: null, typeFilters: {}, aggregationMarkers: [], selection: null, searchResultIds: [], temporaryObject: null }, factories: { createSigmaGlobal: (input) => { sigmaInputs.push(input); return trackRenderer(renderers, "sigma"); }, createDomSvgCommunity: () => trackRenderer(renderers, "community"), createDomSvgSmallFallback: () => trackRenderer(renderers, "fallback"), createOverLimitNotice: () => trackRenderer(renderers, "over-limit") } }); return { container, manager, sigmaInputs, expect(routeId, transition) { assert.equal(container.dataset.llmWikiGraphRoute, routeId); assert.equal(container.dataset.llmWikiGraphRouteTransition, transition); }, expectActiveRendererCount(expected) { assertActiveRendererCount(renderers, expected); }, expectDestroyed() { assert.equal(container.dataset.llmWikiGraphRoute, undefined); assert.equal(container.dataset.llmWikiGraphRouteTransition, undefined); }, expectTransitionCleared() { return waitForRouteTransitionClear(container); } }; } async function waitForRouteTransitionClear(container: { dataset: Record }): Promise { await new Promise((resolve) => setTimeout(resolve, 190)); assert.equal(container.dataset.llmWikiGraphRouteTransition, undefined); } function largeGraphData(nodeCount: number, edgeCount: number, communitySize: number): GraphData { const nodes = Array.from({ length: nodeCount }, (_, index) => ({ id: `large-${index}`, label: `Large ${index}`, type: "topic", community: index < communitySize ? "large-community" : `community-${index}`, source_path: `wiki/large/${index}.md` })); const edges = Array.from({ length: edgeCount }, (_, index) => ({ id: `large-edge-${index}`, from: nodes[index % nodes.length].id, to: nodes[(index + 1) % nodes.length].id, type: "EXTRACTED" })); return { meta: { build_date: "2026-06-19", wiki_title: "Large graph", total_nodes: nodeCount, total_edges: edgeCount }, nodes, edges }; }