From 3ba6d7dfa7ce14a23ed8271ac5dcb57d3fea3c26 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:12:11 +0530 Subject: [PATCH 1/5] feat: refactor reactflow get-api loader to generic + migrate Handle Refactors the reactflow get-api corpus loader from a hardcoded ReactFlow-only lookup to a generic per-API lookup keyed by normalized API name. The loader now reads `apis[].file` from the namespace index, caches per-API, and supports arbitrary API slices without further code changes. Adds corpus/frontend/reactflow/handle.yaml with the Handle component contract (props, usage, multi-handle example, tips). Test asserts corpus source for Handle; fallback check moves to Background. Co-Authored-By: Claude Sonnet 4.6 --- corpus/frontend/reactflow/handle.yaml | 71 +++++++++++++++++++ corpus/frontend/reactflow/index.yaml | 2 + src/plugins/reactflow/tools/get-api.ts | 66 ++++++++--------- ...flow-corpus-backed-tools-behaviour.test.ts | 11 ++- 4 files changed, 111 insertions(+), 39 deletions(-) create mode 100644 corpus/frontend/reactflow/handle.yaml diff --git a/corpus/frontend/reactflow/handle.yaml b/corpus/frontend/reactflow/handle.yaml new file mode 100644 index 0000000..b2b57f9 --- /dev/null +++ b/corpus/frontend/reactflow/handle.yaml @@ -0,0 +1,71 @@ +name: Handle +kind: component +description: >- + Used in custom nodes to define connection points. Handles can be sources + (outgoing) or targets (incoming). +importPath: "import { Handle, Position } from '@xyflow/react'" +props: + - name: type + type: "'source' | 'target'" + description: Whether this is an input or output handle. + default: "'source'" + - name: position + type: Position + description: "Side of the node: Position.Top, Bottom, Left, Right." + default: Position.Top + - name: id + type: string + description: Handle ID, needed when a node has multiple handles of the same type. + - name: isConnectable + type: boolean + description: Whether connections can be made to/from this handle. + default: "true" + - name: isConnectableStart + type: boolean + description: Whether a connection can start from this handle. + default: "true" + - name: isConnectableEnd + type: boolean + description: Whether a connection can end on this handle. + default: "true" + - name: isValidConnection + type: IsValidConnection + description: Custom validation logic for connections to this handle. + - name: onConnect + type: OnConnect + description: Callback when connection is made to this handle. +usage: | + import { Handle, Position } from '@xyflow/react'; + + function CustomNode({ data }) { + return ( + <> + +
{data.label}
+ + + ); + } +examples: + - title: Multiple handles + category: custom-nodes + code: | + function MultiHandleNode({ data }) { + return ( +
+ + +
{data.label}
+ + +
+ ); + } +tips: + - Use the id prop when a node has multiple source or target handles. + - Prefer isValidConnection on over per-handle validation for performance. + - Add 'nodrag' class to interactive elements inside a node to prevent drag when clicking them. +relatedApis: + - ReactFlow + - NodeResizer + - NodeToolbar diff --git a/corpus/frontend/reactflow/index.yaml b/corpus/frontend/reactflow/index.yaml index b005965..5f8d294 100644 --- a/corpus/frontend/reactflow/index.yaml +++ b/corpus/frontend/reactflow/index.yaml @@ -2,3 +2,5 @@ namespace: frontend.reactflow apis: reactflow: file: reactflow.yaml + handle: + file: handle.yaml diff --git a/src/plugins/reactflow/tools/get-api.ts b/src/plugins/reactflow/tools/get-api.ts index 0bc15ec..8c99119 100644 --- a/src/plugins/reactflow/tools/get-api.ts +++ b/src/plugins/reactflow/tools/get-api.ts @@ -16,11 +16,7 @@ type CorpusIndex = { type CorpusNamespaceIndex = { namespace?: string; - apis?: { - reactflow?: { - file?: string; - }; - }; + apis?: Record; }; type CorpusApiEntry = { @@ -36,21 +32,27 @@ type CorpusApiEntry = { relatedApis?: string[]; }; +type LoadedCorpusApi = { + api: Parameters[0]; + source: string; +}; + +type ReactFlowExample = Parameters[0]["examples"][number]; + const moduleDir = dirname(fileURLToPath(import.meta.url)); const corpusRoot = join(moduleDir, "../../../../corpus"); const corpusNamespace = "frontend.reactflow"; -const corpusApiName = "ReactFlow"; -type ReactFlowExample = Parameters[0]["examples"][number]; -let cachedCorpusReactFlowApi: { api: Parameters[0]; source: string } | null | undefined; +const cachedCorpusApis = new Map(); function normalizeApiName(name: string): string { return name.toLowerCase().replace(/[<>/()]/g, "").trim(); } -function loadCorpusReactFlowApi(): { api: Parameters[0]; source: string } | null { - if (cachedCorpusReactFlowApi !== undefined) { - return cachedCorpusReactFlowApi; +function loadCorpusApi(name: string): LoadedCorpusApi | null { + const key = normalizeApiName(name); + if (cachedCorpusApis.has(key)) { + return cachedCorpusApis.get(key) ?? null; } try { @@ -58,35 +60,35 @@ function loadCorpusReactFlowApi(): { api: Parameters[ const index = YAML.parse(indexRaw) as CorpusIndex | null; const namespaceIndexPath = index?.namespaces?.[corpusNamespace]?.index; if (!namespaceIndexPath) { - cachedCorpusReactFlowApi = null; - return cachedCorpusReactFlowApi; + cachedCorpusApis.set(key, null); + return null; } const namespaceRaw = readFileSync(join(corpusRoot, namespaceIndexPath), "utf8"); const namespaceIndex = YAML.parse(namespaceRaw) as CorpusNamespaceIndex | null; - const apiPath = namespaceIndex?.apis?.reactflow?.file; + const apiPath = namespaceIndex?.apis?.[key]?.file; if (!apiPath) { - cachedCorpusReactFlowApi = null; - return cachedCorpusReactFlowApi; + cachedCorpusApis.set(key, null); + return null; } const apiRaw = readFileSync(join(corpusRoot, "frontend/reactflow", apiPath), "utf8"); const api = YAML.parse(apiRaw) as CorpusApiEntry | null; if ( !api || - normalizeApiName(api.name ?? "") !== normalizeApiName(corpusApiName) || + normalizeApiName(api.name ?? "") !== key || !api.usage || !api.importPath || !api.description || !api.kind ) { - cachedCorpusReactFlowApi = null; - return cachedCorpusReactFlowApi; + cachedCorpusApis.set(key, null); + return null; } - cachedCorpusReactFlowApi = { + const loaded: LoadedCorpusApi = { api: { - name: api.name ?? corpusApiName, + name: api.name ?? name, kind: api.kind as Parameters[0]["kind"], description: api.description, importPath: api.importPath, @@ -108,26 +110,14 @@ function loadCorpusReactFlowApi(): { api: Parameters[ }, source: corpusNamespace, }; - return cachedCorpusReactFlowApi ?? null; + cachedCorpusApis.set(key, loaded); + return loaded; } catch { - cachedCorpusReactFlowApi = null; + cachedCorpusApis.set(key, null); return null; } } -function getReactFlowApi(name: string) { - if (normalizeApiName(name) !== normalizeApiName(corpusApiName)) { - return getApiByName(name, ALL_APIS); - } - - const corpusEntry = loadCorpusReactFlowApi(); - if (corpusEntry) { - return corpusEntry.api; - } - - return getApiByName(name, ALL_APIS); -} - export function register(server: McpServer): void { server.tool( "reactflow_get_api", @@ -140,8 +130,8 @@ export function register(server: McpServer): void { ), }, async ({ name }) => { - const corpusEntry = normalizeApiName(name) === normalizeApiName(corpusApiName) ? loadCorpusReactFlowApi() : null; - const api = getReactFlowApi(name); + const corpusEntry = loadCorpusApi(name); + const api = corpusEntry?.api ?? getApiByName(name, ALL_APIS); if (!api) { const suggestions = searchApis(name, ALL_APIS) .slice(0, 5) diff --git a/tests/reactflow-corpus-backed-tools-behaviour.test.ts b/tests/reactflow-corpus-backed-tools-behaviour.test.ts index 491d417..14c8ff7 100644 --- a/tests/reactflow-corpus-backed-tools-behaviour.test.ts +++ b/tests/reactflow-corpus-backed-tools-behaviour.test.ts @@ -13,11 +13,20 @@ test("reactflow_get_api prefers corpus metadata for ReactFlow", async () => { expect(text).toContain("import { ReactFlow } from '@xyflow/react'"); }); -test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { +test("reactflow_get_api prefers corpus metadata for Handle", async () => { const result = await reactFlowGetApi.invoke({ name: "Handle" }); const text = extractTextContent(result); expect(text).toContain("# Handle (component)"); + expect(text).toContain("**Corpus Source:** frontend.reactflow"); expect(text).toContain("import { Handle, Position } from '@xyflow/react'"); +}); + +test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { + const result = await reactFlowGetApi.invoke({ name: "Background" }); + const text = extractTextContent(result); + + expect(text).toContain("# Background (component)"); + expect(text).toContain("import { Background, BackgroundVariant } from '@xyflow/react'"); expect(text).not.toContain("**Corpus Source:** frontend.reactflow"); }); From fcdbb5a374fc759415f6f0daf3f81705723297ec Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:12:50 +0530 Subject: [PATCH 2/5] feat: migrate reactflow Background API to corpus-backed slice Adds corpus/frontend/reactflow/background.yaml with Background component contract (variants, gap, size, cross pattern example). Registers background in namespace index. Test asserts corpus source for Background; fallback check moves to Controls. Co-Authored-By: Claude Sonnet 4.6 --- corpus/frontend/reactflow/background.yaml | 41 +++++++++++++++++++ corpus/frontend/reactflow/index.yaml | 2 + ...flow-corpus-backed-tools-behaviour.test.ts | 11 ++++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 corpus/frontend/reactflow/background.yaml diff --git a/corpus/frontend/reactflow/background.yaml b/corpus/frontend/reactflow/background.yaml new file mode 100644 index 0000000..9bd785a --- /dev/null +++ b/corpus/frontend/reactflow/background.yaml @@ -0,0 +1,41 @@ +name: Background +kind: component +description: >- + Renders different background types common in node-based UIs. Comes with three + variants: lines, dots, and cross. +importPath: "import { Background, BackgroundVariant } from '@xyflow/react'" +props: + - name: variant + type: BackgroundVariant + description: Background pattern type. + default: BackgroundVariant.Dots + - name: gap + type: "number | [number, number]" + description: Gap between pattern elements. + default: "20" + - name: size + type: number + description: Size of pattern dots/lines. + default: "1" + - name: color + type: string + description: Pattern color. + - name: lineWidth + type: number + description: Stroke width for lines/cross variant. + default: "1" +usage: | + + + +examples: + - title: Cross pattern background + category: styling + code: | + import { Background, BackgroundVariant } from '@xyflow/react'; + + +relatedApis: + - ReactFlow + - MiniMap + - Controls diff --git a/corpus/frontend/reactflow/index.yaml b/corpus/frontend/reactflow/index.yaml index 5f8d294..8aaa0c3 100644 --- a/corpus/frontend/reactflow/index.yaml +++ b/corpus/frontend/reactflow/index.yaml @@ -4,3 +4,5 @@ apis: file: reactflow.yaml handle: file: handle.yaml + background: + file: background.yaml diff --git a/tests/reactflow-corpus-backed-tools-behaviour.test.ts b/tests/reactflow-corpus-backed-tools-behaviour.test.ts index 14c8ff7..632f567 100644 --- a/tests/reactflow-corpus-backed-tools-behaviour.test.ts +++ b/tests/reactflow-corpus-backed-tools-behaviour.test.ts @@ -22,11 +22,20 @@ test("reactflow_get_api prefers corpus metadata for Handle", async () => { expect(text).toContain("import { Handle, Position } from '@xyflow/react'"); }); -test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { +test("reactflow_get_api prefers corpus metadata for Background", async () => { const result = await reactFlowGetApi.invoke({ name: "Background" }); const text = extractTextContent(result); expect(text).toContain("# Background (component)"); + expect(text).toContain("**Corpus Source:** frontend.reactflow"); expect(text).toContain("import { Background, BackgroundVariant } from '@xyflow/react'"); +}); + +test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { + const result = await reactFlowGetApi.invoke({ name: "Controls" }); + const text = extractTextContent(result); + + expect(text).toContain("# Controls (component)"); + expect(text).toContain("import { Controls, ControlButton } from '@xyflow/react'"); expect(text).not.toContain("**Corpus Source:** frontend.reactflow"); }); From fedde33e7ca644b5aef03e6ba62c6e9a484e303d Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:13:27 +0530 Subject: [PATCH 3/5] feat: migrate reactflow Controls API to corpus-backed slice Adds corpus/frontend/reactflow/controls.yaml with the viewport controls panel contract and custom ControlButton example. Registers controls in namespace index. Test asserts corpus source for Controls; fallback check moves to MiniMap. Co-Authored-By: Claude Sonnet 4.6 --- corpus/frontend/reactflow/controls.yaml | 46 +++++++++++++++++++ corpus/frontend/reactflow/index.yaml | 2 + ...flow-corpus-backed-tools-behaviour.test.ts | 11 ++++- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 corpus/frontend/reactflow/controls.yaml diff --git a/corpus/frontend/reactflow/controls.yaml b/corpus/frontend/reactflow/controls.yaml new file mode 100644 index 0000000..aed07c1 --- /dev/null +++ b/corpus/frontend/reactflow/controls.yaml @@ -0,0 +1,46 @@ +name: Controls +kind: component +description: >- + Renders a small panel with zoom in, zoom out, fit view, and lock viewport + buttons. +importPath: "import { Controls, ControlButton } from '@xyflow/react'" +props: + - name: showZoom + type: boolean + description: Show zoom buttons. + default: "true" + - name: showFitView + type: boolean + description: Show fit view button. + default: "true" + - name: showInteractive + type: boolean + description: Show lock button. + default: "true" + - name: position + type: PanelPosition + description: Corner position. + default: "'bottom-left'" + - name: orientation + type: "'horizontal' | 'vertical'" + description: Layout direction. + default: "'vertical'" +usage: | + + + +examples: + - title: Custom control button + category: interaction + code: | + import { Controls, ControlButton } from '@xyflow/react'; + + + console.log('custom action')}> + + + +relatedApis: + - ReactFlow + - ControlButton + - Panel diff --git a/corpus/frontend/reactflow/index.yaml b/corpus/frontend/reactflow/index.yaml index 8aaa0c3..3102744 100644 --- a/corpus/frontend/reactflow/index.yaml +++ b/corpus/frontend/reactflow/index.yaml @@ -6,3 +6,5 @@ apis: file: handle.yaml background: file: background.yaml + controls: + file: controls.yaml diff --git a/tests/reactflow-corpus-backed-tools-behaviour.test.ts b/tests/reactflow-corpus-backed-tools-behaviour.test.ts index 632f567..8c325ef 100644 --- a/tests/reactflow-corpus-backed-tools-behaviour.test.ts +++ b/tests/reactflow-corpus-backed-tools-behaviour.test.ts @@ -31,11 +31,20 @@ test("reactflow_get_api prefers corpus metadata for Background", async () => { expect(text).toContain("import { Background, BackgroundVariant } from '@xyflow/react'"); }); -test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { +test("reactflow_get_api prefers corpus metadata for Controls", async () => { const result = await reactFlowGetApi.invoke({ name: "Controls" }); const text = extractTextContent(result); expect(text).toContain("# Controls (component)"); + expect(text).toContain("**Corpus Source:** frontend.reactflow"); expect(text).toContain("import { Controls, ControlButton } from '@xyflow/react'"); +}); + +test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { + const result = await reactFlowGetApi.invoke({ name: "MiniMap" }); + const text = extractTextContent(result); + + expect(text).toContain("# MiniMap (component)"); + expect(text).toContain("import { MiniMap } from '@xyflow/react'"); expect(text).not.toContain("**Corpus Source:** frontend.reactflow"); }); From 36d303a5efc23b5ae56a36534fd8372cc5728b33 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:14:01 +0530 Subject: [PATCH 4/5] feat: migrate reactflow MiniMap API to corpus-backed slice Adds corpus/frontend/reactflow/minimap.yaml with the minimap overview component contract and pannable/zoomable usage. Registers minimap in namespace index. Test asserts corpus source for MiniMap; fallback check moves to useReactFlow. Co-Authored-By: Claude Sonnet 4.6 --- corpus/frontend/reactflow/index.yaml | 2 + corpus/frontend/reactflow/minimap.yaml | 41 +++++++++++++++++++ ...flow-corpus-backed-tools-behaviour.test.ts | 10 ++++- 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 corpus/frontend/reactflow/minimap.yaml diff --git a/corpus/frontend/reactflow/index.yaml b/corpus/frontend/reactflow/index.yaml index 3102744..cc4014b 100644 --- a/corpus/frontend/reactflow/index.yaml +++ b/corpus/frontend/reactflow/index.yaml @@ -8,3 +8,5 @@ apis: file: background.yaml controls: file: controls.yaml + minimap: + file: minimap.yaml diff --git a/corpus/frontend/reactflow/minimap.yaml b/corpus/frontend/reactflow/minimap.yaml new file mode 100644 index 0000000..9c9d435 --- /dev/null +++ b/corpus/frontend/reactflow/minimap.yaml @@ -0,0 +1,41 @@ +name: MiniMap +kind: component +description: >- + Renders an overview of your flow. Each node appears as an SVG element showing + the current viewport position relative to the full flow. +importPath: "import { MiniMap } from '@xyflow/react'" +props: + - name: nodeColor + type: "string | ((node: Node) => string)" + description: Color of minimap nodes. + - name: nodeStrokeColor + type: "string | ((node: Node) => string)" + description: Stroke color of minimap nodes. + - name: nodeStrokeWidth + type: number + description: Stroke width. + default: "2" + - name: maskColor + type: string + description: Color of the area outside the viewport. + - name: position + type: PanelPosition + description: Corner position. + default: "'bottom-right'" + - name: pannable + type: boolean + description: Allow panning via minimap. + default: "false" + - name: zoomable + type: boolean + description: Allow zooming via minimap. + default: "false" +usage: | + + n.type === 'input' ? '#6366f1' : '#94a3b8'} pannable zoomable /> + +examples: [] +relatedApis: + - ReactFlow + - Background + - Controls diff --git a/tests/reactflow-corpus-backed-tools-behaviour.test.ts b/tests/reactflow-corpus-backed-tools-behaviour.test.ts index 8c325ef..6f4b28c 100644 --- a/tests/reactflow-corpus-backed-tools-behaviour.test.ts +++ b/tests/reactflow-corpus-backed-tools-behaviour.test.ts @@ -40,11 +40,19 @@ test("reactflow_get_api prefers corpus metadata for Controls", async () => { expect(text).toContain("import { Controls, ControlButton } from '@xyflow/react'"); }); -test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { +test("reactflow_get_api prefers corpus metadata for MiniMap", async () => { const result = await reactFlowGetApi.invoke({ name: "MiniMap" }); const text = extractTextContent(result); expect(text).toContain("# MiniMap (component)"); + expect(text).toContain("**Corpus Source:** frontend.reactflow"); expect(text).toContain("import { MiniMap } from '@xyflow/react'"); +}); + +test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { + const result = await reactFlowGetApi.invoke({ name: "useReactFlow" }); + const text = extractTextContent(result); + + expect(text).toContain("# useReactFlow (hook)"); expect(text).not.toContain("**Corpus Source:** frontend.reactflow"); }); From bbd242433f7e08b188b3c642924758d16b5e16c8 Mon Sep 17 00:00:00 2001 From: Kailas Mahavarkar <66670953+KailasMahavarkar@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:14:59 +0530 Subject: [PATCH 5/5] feat: migrate reactflow useReactFlow hook to corpus-backed slice Adds corpus/frontend/reactflow/usereactflow.yaml with the ReactFlowInstance hook contract, destructure example, addNodes/deleteElements examples, and non-reactivity tips. Registers usereactflow in namespace index. Test asserts corpus source for useReactFlow; fallback check moves to NodeResizer. Co-Authored-By: Claude Sonnet 4.6 --- corpus/frontend/reactflow/index.yaml | 2 + corpus/frontend/reactflow/usereactflow.yaml | 48 +++++++++++++++++++ ...flow-corpus-backed-tools-behaviour.test.ts | 11 ++++- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 corpus/frontend/reactflow/usereactflow.yaml diff --git a/corpus/frontend/reactflow/index.yaml b/corpus/frontend/reactflow/index.yaml index cc4014b..d3bb54e 100644 --- a/corpus/frontend/reactflow/index.yaml +++ b/corpus/frontend/reactflow/index.yaml @@ -10,3 +10,5 @@ apis: file: controls.yaml minimap: file: minimap.yaml + usereactflow: + file: usereactflow.yaml diff --git a/corpus/frontend/reactflow/usereactflow.yaml b/corpus/frontend/reactflow/usereactflow.yaml new file mode 100644 index 0000000..f1f7a29 --- /dev/null +++ b/corpus/frontend/reactflow/usereactflow.yaml @@ -0,0 +1,48 @@ +name: useReactFlow +kind: hook +description: >- + Returns a ReactFlowInstance to update nodes/edges, manipulate the viewport, + or query flow state. Does NOT cause re-renders on state changes. +importPath: "import { useReactFlow } from '@xyflow/react'" +returns: ReactFlowInstance +usage: | + const { getNodes, setNodes, addNodes, getEdges, setEdges, addEdges, + fitView, zoomIn, zoomOut, getViewport, setViewport, + screenToFlowPosition, deleteElements, updateNode, updateNodeData, + getIntersectingNodes, toObject } = useReactFlow(); +examples: + - title: Add node on button click + category: interaction + code: | + function AddNodeButton() { + const { addNodes, screenToFlowPosition } = useReactFlow(); + const onClick = () => { + addNodes({ + id: crypto.randomUUID(), + position: screenToFlowPosition({ x: 200, y: 200 }), + data: { label: 'New Node' }, + }); + }; + return ; + } + - title: Delete selected elements + category: interaction + code: | + function DeleteButton() { + const { deleteElements, getNodes, getEdges } = useReactFlow(); + const onClick = async () => { + const selectedNodes = getNodes().filter((n) => n.selected); + const selectedEdges = getEdges().filter((e) => e.selected); + await deleteElements({ nodes: selectedNodes, edges: selectedEdges }); + }; + return ; + } +tips: + - Must be used inside or . + - Unlike useNodes/useEdges, this hook won't cause re-renders on state changes. Query state on demand. + - "Pass useReactFlow() as dependency to useCallback/useEffect - it's not initialized on first render." +relatedApis: + - ReactFlowProvider + - ReactFlowInstance + - useNodes + - useEdges diff --git a/tests/reactflow-corpus-backed-tools-behaviour.test.ts b/tests/reactflow-corpus-backed-tools-behaviour.test.ts index 6f4b28c..4e104ad 100644 --- a/tests/reactflow-corpus-backed-tools-behaviour.test.ts +++ b/tests/reactflow-corpus-backed-tools-behaviour.test.ts @@ -49,10 +49,19 @@ test("reactflow_get_api prefers corpus metadata for MiniMap", async () => { expect(text).toContain("import { MiniMap } from '@xyflow/react'"); }); -test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { +test("reactflow_get_api prefers corpus metadata for useReactFlow", async () => { const result = await reactFlowGetApi.invoke({ name: "useReactFlow" }); const text = extractTextContent(result); expect(text).toContain("# useReactFlow (hook)"); + expect(text).toContain("**Corpus Source:** frontend.reactflow"); + expect(text).toContain("fitView, zoomIn, zoomOut"); +}); + +test("reactflow_get_api falls back to in-file data for non-corpus APIs", async () => { + const result = await reactFlowGetApi.invoke({ name: "NodeResizer" }); + const text = extractTextContent(result); + + expect(text).toContain("# NodeResizer (component)"); expect(text).not.toContain("**Corpus Source:** frontend.reactflow"); });