From 78816dec4a2f60924407bcdce603d7d8f1575d51 Mon Sep 17 00:00:00 2001 From: Avi Avni Date: Tue, 8 Jul 2025 02:12:17 -0700 Subject: [PATCH 01/75] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index edd18fcb..2a712b33 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,8 @@ npm run dev curl -X POST http://127.0.0.1:5000/analyze_folder -H "Content-Type: application/json" -d '{"path": "", "ignore": ["./.github", "./sbin", "./.git","./deps", "./bin", "./build"]}' -H "Authorization: " ``` -Note: At the moment code-graph can analyze both the C & Python source files. -Support for additional languages e.g. JavaScript, Go, Java is planned to be added +Note: At the moment code-graph can analyze both the Java & Python source files. +Support for additional languages e.g. C, JavaScript, Go is planned to be added in the future. Browse to [http://localhost:3000](http://localhost:3000) From 3cd8513577c8d676c8cc4ede760d618a9e66aaa3 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 19:22:07 +0300 Subject: [PATCH 02/75] fix tests --- app/components/elementMenu.tsx | 1 + e2e/config/constants.ts | 4 +- e2e/config/testData.ts | 2 +- e2e/logic/POM/codeGraph.ts | 147 ++++++++++++++++++++++------- e2e/logic/utils.ts | 6 +- e2e/tests/canvas.spec.ts | 98 ++++++++----------- e2e/tests/chat.spec.ts | 37 ++++---- e2e/tests/nodeDetailsPanel.spec.ts | 78 +++++++-------- e2e/tests/searchBar.spec.ts | 14 +-- 9 files changed, 230 insertions(+), 157 deletions(-) diff --git a/app/components/elementMenu.tsx b/app/components/elementMenu.tsx index d6fd8d99..4d8919da 100644 --- a/app/components/elementMenu.tsx +++ b/app/components/elementMenu.tsx @@ -41,6 +41,7 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit setContainerWidth(ref.clientWidth) }} className="absolute z-10 bg-black rounded-lg shadow-lg flex divide-x divide-[#434343]" + id="elementMenu" style={{ left: Math.max(-34, Math.min(position.x - 33 - containerWidth / 2, (parentRef?.current?.clientWidth || 0) + 32 - containerWidth)), top: Math.min(position.y - 153, (parentRef?.current?.clientHeight || 0) - 9), diff --git a/e2e/config/constants.ts b/e2e/config/constants.ts index 63bcec56..98d3cf31 100644 --- a/e2e/config/constants.ts +++ b/e2e/config/constants.ts @@ -1,5 +1,5 @@ -export const GRAPH_ID = "GraphRAG-SDK"; -export const PROJECT_NAME = "GraphRAG-SDK"; +export const GRAPHRAG_SDK = "GraphRAG-SDK"; +export const FLASK_GRAPH = "flask"; export const CHAT_OPTTIONS_COUNT = 1; export const Node_Question = "how many nodes do we have?"; export const Edge_Question = "how many edges do we have?"; \ No newline at end of file diff --git a/e2e/config/testData.ts b/e2e/config/testData.ts index dab5ebf7..db9551ff 100644 --- a/e2e/config/testData.ts +++ b/e2e/config/testData.ts @@ -1,7 +1,7 @@ export const searchData: { searchInput: string; completedSearchInput?: string; }[] = [ { searchInput: "test"}, { searchInput: "set"}, - { searchInput: "low", completedSearchInput: "lower" }, + { searchInput: "low", completedSearchInput: "lower_items" }, { searchInput: "as", completedSearchInput: "ask"}, ]; diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 9910ded3..d2120e49 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -120,6 +120,10 @@ export default class CodeGraph extends BasePage { return this.page.locator("//main[@data-name='main-chat']/*[last()-2]//img[@alt='Waiting for response']") } + private get waitingForResponseImage(): Locator { + return this.page.locator("//img[@alt='Waiting for response']") + } + private get selectInputForShowPath(): (inputNum: string) => Locator { return (inputNum: string) => this.page.locator(`(//main[@data-name='main-chat']//input)[${inputNum}]`); } @@ -189,6 +193,10 @@ export default class CodeGraph extends BasePage { private get nodeDetailsPanel(): Locator { return this.page.locator("//div[@data-name='node-details-panel']"); } + + private get elementMenu(): Locator { + return this.page.locator("//div[@id='elementMenu']"); + } private get nodedetailsPanelHeader(): Locator { return this.page.locator("//div[@data-name='node-details-panel']/header/p"); @@ -277,7 +285,12 @@ export default class CodeGraph extends BasePage { } async getTextInLastChatElement(): Promise{ - await delay(2500); + // Wait for loading indicator to disappear + await this.waitingForResponseImage.waitFor({ state: 'hidden', timeout: 15000 }); + + // Short delay to ensure text is fully rendered + await delay(1000); + return (await this.lastElementInChat.textContent())!; } @@ -416,8 +429,18 @@ export default class CodeGraph extends BasePage { } async nodeClick(x: number, y: number): Promise { - await this.canvasElement.hover({ position: { x, y } }); - await this.canvasElement.click({ position: { x, y } }); + await this.waitForCanvasAnimationToEnd(); + for (let attempt = 1; attempt <= 3; attempt++) { + await this.canvasElement.hover({ position: { x, y } }); + await this.page.waitForTimeout(500); + await this.canvasElement.click({ position: { x, y }, button: 'left' }); + if (await this.elementMenu.isVisible()) { + return; + } + await this.page.waitForTimeout(1000); + } + + throw new Error(`Failed to click, elementMenu not visible after multiple attempts.`); } async selectCodeGraphCheckbox(checkbox: string): Promise { @@ -493,42 +516,39 @@ export default class CodeGraph extends BasePage { return Promise.all(elements.map(element => element.innerHTML())); } - async getGraphDetails(): Promise { - await this.canvasElementBeforeGraphSelection.waitFor({ state: 'detached' }); - await delay(2000) - await this.page.waitForFunction(() => !!window.graph); + async getGraphNodes(): Promise { + await this.waitForCanvasAnimationToEnd(); const graphData = await this.page.evaluate(() => { - return window.graph; - }); - - return graphData; - } - - async transformNodeCoordinates(graphData: any): Promise { - const { canvasLeft, canvasTop, canvasWidth, canvasHeight, transform } = await this.canvasElement.evaluate((canvas: HTMLCanvasElement) => { - const rect = canvas.getBoundingClientRect(); - const ctx = canvas.getContext('2d'); - const transform = ctx?.getTransform()!; - return { - canvasLeft: rect.left, - canvasTop: rect.top, - canvasWidth: rect.width, - canvasHeight: rect.height, - transform, - }; + return (window as any).graph; }); - - const screenCoordinates = graphData.elements.nodes.map((node: any) => { - const adjustedX = node.x * transform.a + transform.e; - const adjustedY = node.y * transform.d + transform.f; - const screenX = canvasLeft + adjustedX - 35; - const screenY = canvasTop + adjustedY - 190; - return {...node, screenX, screenY,}; - }); + let transformData: any = null; + for (let attempt = 0; attempt < 3; attempt++) { + await this.page.waitForTimeout(1000); + + transformData = await this.canvasElement.evaluate((canvas: HTMLCanvasElement) => { + const rect = canvas.getBoundingClientRect(); + const ctx = canvas.getContext('2d'); + return { + left: rect.left, + top: rect.top, + transform: ctx?.getTransform() || null, + }; + }); - return screenCoordinates; + if (transformData.transform) break; + console.warn(`Attempt ${attempt + 1}: Transform data not available, retrying...`); + } + + if (!transformData?.transform) throw new Error("Canvas transform data not available!"); + + const { a, e, d, f } = transformData.transform; + return graphData.elements.nodes.map((node: any) => ({ + ...node, + screenX: transformData.left + node.x * a + e - 35, + screenY: transformData.top + node.y * d + f - 190, + })); } async getCanvasScaling(): Promise<{ scaleX: number; scaleY: number }> { @@ -543,4 +563,63 @@ export default class CodeGraph extends BasePage { return { scaleX, scaleY }; } + async getGraphDetails(): Promise { + await this.canvasElementBeforeGraphSelection.waitFor({ state: 'detached' }); + await this.waitForCanvasAnimationToEnd(); + await this.page.waitForFunction(() => !!window.graph); + + const graphData = await this.page.evaluate(() => { + return window.graph; + }); + + return graphData; + } + + async waitForCanvasAnimationToEnd(timeout = 15000, checkInterval = 500): Promise { + const canvasHandle = await this.canvasElement.elementHandle(); + + if (!canvasHandle) { + throw new Error("Canvas element not found!"); + } + + await this.page.waitForFunction( + async ({ canvas, checkInterval, timeout }) => { + const ctx = canvas.getContext('2d'); + if (!ctx) return false; + + const width = canvas.width; + const height = canvas.height; + + let previousData = ctx.getImageData(0, 0, width, height).data; + const startTime = Date.now(); + + return new Promise((resolve) => { + const checkCanvas = () => { + if (Date.now() - startTime > timeout) { + resolve(true); + return; + } + + setTimeout(() => { + const currentData = ctx.getImageData(0, 0, width, height).data; + if (JSON.stringify(previousData) === JSON.stringify(currentData)) { + resolve(true); + } else { + previousData = currentData; + checkCanvas(); + } + }, checkInterval); + }; + checkCanvas(); + }); + }, + { + canvas: await canvasHandle.evaluateHandle((el) => el as HTMLCanvasElement), + checkInterval, + timeout + }, + { timeout } + ); + } + } diff --git a/e2e/logic/utils.ts b/e2e/logic/utils.ts index 6ba3475d..47833e70 100644 --- a/e2e/logic/utils.ts +++ b/e2e/logic/utils.ts @@ -17,4 +17,8 @@ export const waitToBeEnabled = async (locator: Locator, timeout: number = 5000): export function findNodeByName(nodes: { name: string }[], nodeName: string): any { return nodes.find((node) => node.name === nodeName); - } \ No newline at end of file +} + +export function findFirstNodeWithSrc(nodes: { src?: string }[]): any { + return nodes.find((node) => node.src !== undefined); +} \ No newline at end of file diff --git a/e2e/tests/canvas.spec.ts b/e2e/tests/canvas.spec.ts index 3701ac86..8869ec2c 100644 --- a/e2e/tests/canvas.spec.ts +++ b/e2e/tests/canvas.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from "@playwright/test"; import BrowserWrapper from "../infra/ui/browserWrapper"; import CodeGraph from "../logic/POM/codeGraph"; import urls from "../config/urls.json"; -import { GRAPH_ID, PROJECT_NAME } from "../config/constants"; +import { GRAPHRAG_SDK } from "../config/constants"; import { findNodeByName } from "../logic/utils"; import { nodesPath, categories, nodes } from "../config/testData"; import { ApiCalls } from "../logic/api/apiCalls"; @@ -20,7 +20,7 @@ test.describe("Canvas tests", () => { test(`Verify zoom in functionality on canvas`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); const initialGraph = await codeGraph.getCanvasScaling(); await codeGraph.clickZoomIn(); await codeGraph.clickZoomIn(); @@ -31,7 +31,7 @@ test.describe("Canvas tests", () => { test(`Verify zoom out functionality on canvas`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); const initialGraph = await codeGraph.getCanvasScaling(); await codeGraph.clickZoomOut(); await codeGraph.clickZoomOut(); @@ -42,29 +42,29 @@ test.describe("Canvas tests", () => { test(`Verify center graph button centers nodes in canvas`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); + await codeGraph.clickCenter(); const initialGraph = await codeGraph.getCanvasScaling(); await codeGraph.clickZoomOut(); await codeGraph.clickZoomOut(); await codeGraph.clickCenter(); const updatedGraph = await codeGraph.getCanvasScaling(); - expect(Math.abs(initialGraph.scaleX - updatedGraph.scaleX)).toBeLessThanOrEqual(0.1); - expect(Math.abs(initialGraph.scaleY - updatedGraph.scaleY)).toBeLessThanOrEqual(0.1); + expect(Math.abs(initialGraph.scaleX - updatedGraph.scaleX)).toBeLessThanOrEqual(0.2); + expect(Math.abs(initialGraph.scaleY - updatedGraph.scaleY)).toBeLessThanOrEqual(0.2); }) nodes.slice(0,2).forEach((node) => { test(`Validate node hide functionality via element menu in canvas for ${node.nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const initialGraph = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(initialGraph); - const targetNode = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.selectGraph(GRAPHRAG_SDK); + const initialGraph = await codeGraph.getGraphNodes(); + const targetNode = findNodeByName(initialGraph, node.nodeName); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnRemoveNodeViaElementMenu(); - const updatedGraph = await codeGraph.getGraphDetails(); - const targetNodeForUpdateGraph = findNodeByName(updatedGraph.elements.nodes, node.nodeName); + const updatedGraph = await codeGraph.getGraphNodes(); + const targetNodeForUpdateGraph = findNodeByName(updatedGraph, node.nodeName); expect(targetNodeForUpdateGraph.visible).toBe(false); }); }) @@ -72,15 +72,14 @@ test.describe("Canvas tests", () => { nodes.slice(0,2).forEach((node) => { test(`Validate unhide node functionality after hiding a node in canvas for ${node.nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const initialGraph = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(initialGraph); - const targetNode = findNodeByName(convertCoordinates, node.nodeName); + await codeGraph.selectGraph(GRAPHRAG_SDK); + const initialGraph = await codeGraph.getGraphNodes(); + const targetNode = findNodeByName(initialGraph, node.nodeName); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnRemoveNodeViaElementMenu(); await codeGraph.clickOnUnhideNodesBtn(); - const updatedGraph = await codeGraph.getGraphDetails(); - const targetNodeForUpdateGraph = findNodeByName(updatedGraph.elements.nodes, node.nodeName); + const updatedGraph = await codeGraph.getGraphNodes(); + const targetNodeForUpdateGraph = findNodeByName(updatedGraph, node.nodeName); expect(targetNodeForUpdateGraph.visible).toBe(true); }); }) @@ -89,30 +88,30 @@ test.describe("Canvas tests", () => { const checkboxIndex = index + 1; test(`Verify that unchecking the ${category} checkbox hides ${category} nodes on the canvas`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.selectCodeGraphCheckbox(checkboxIndex.toString()); - const result = await codeGraph.getGraphDetails(); - const findItem = result.categories.find((item: { name: string; }) => item.name === category) - expect(findItem.show).toBe(false) + const result = await codeGraph.getGraphNodes(); + const findItem = result.find((item: { category: string; }) => item.category === category); + expect(findItem?.visible).toBe(false); }); }) nodesPath.forEach((path) => { test(`Verify "Clear graph" button resets canvas view for path ${path.firstNode} and ${path.secondNode}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.clickOnshowPathBtn(); await codeGraph.insertInputForShowPath("1", path.firstNode); await codeGraph.insertInputForShowPath("2", path.secondNode); - const initialGraph = await codeGraph.getGraphDetails(); - const firstNode = findNodeByName(initialGraph.elements.nodes, path.firstNode); - const secondNode = findNodeByName(initialGraph.elements.nodes, path.secondNode); + const initialGraph = await codeGraph.getGraphNodes(); + const firstNode = findNodeByName(initialGraph, path.firstNode); + const secondNode = findNodeByName(initialGraph, path.secondNode); expect(firstNode.isPath).toBe(true); expect(secondNode.isPath).toBe(true); await codeGraph.clickOnClearGraphBtn(); - const updateGraph = await codeGraph.getGraphDetails(); - const firstNode1 = findNodeByName(updateGraph.elements.nodes, path.firstNode); - const secondNode1 = findNodeByName(updateGraph.elements.nodes, path.secondNode); + const updateGraph = await codeGraph.getGraphNodes(); + const firstNode1 = findNodeByName(updateGraph, path.firstNode); + const secondNode1 = findNodeByName(updateGraph, path.secondNode); expect(firstNode1.isPath).toBe(false); expect(secondNode1.isPath).toBe(false); }); @@ -133,22 +132,21 @@ test.describe("Canvas tests", () => { const nodeIndex: number = index + 1; test(`Validate canvas node dragging for node: ${index}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const initialGraph = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(initialGraph); - await codeGraph.changeNodePosition(convertCoordinates[nodeIndex].screenX, convertCoordinates[nodeIndex].screenY); + await codeGraph.selectGraph(GRAPHRAG_SDK); + const initialGraph = await codeGraph.getGraphNodes(); + await codeGraph.changeNodePosition(initialGraph[nodeIndex].screenX, initialGraph[nodeIndex].screenY); const updateGraph = await codeGraph.getGraphDetails(); - expect(updateGraph.elements.nodes[nodeIndex].x).not.toBe(initialGraph.elements.nodes[nodeIndex].x); - expect(updateGraph.elements.nodes[nodeIndex].y).not.toBe(initialGraph.elements.nodes[nodeIndex].y); + expect(updateGraph.elements.nodes[nodeIndex].x).not.toBe(initialGraph[nodeIndex].x); + expect(updateGraph.elements.nodes[nodeIndex].y).not.toBe(initialGraph[nodeIndex].y); }); } test(`Validate node and edge counts in canvas match API data`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); const { nodes, edges } = await codeGraph.getMetricsPanelInfo(); const api = new ApiCalls(); - const response = await api.projectInfo(PROJECT_NAME); + const response = await api.projectInfo(GRAPHRAG_SDK); expect(response.result.info.node_count).toEqual(parseInt(nodes)); expect(response.result.info.edge_count).toEqual(parseInt(edges)); }); @@ -156,36 +154,20 @@ test.describe("Canvas tests", () => { test(`Validate displayed nodes match API response after selecting a graph via UI`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); const graphData = await codeGraph.getGraphDetails(); const api = new ApiCalls(); - const response = await api.getProject(PROJECT_NAME); + const response = await api.getProject(GRAPHRAG_SDK); const isMatching = graphData.elements.nodes.slice(0, 2).every( (node: any, index: number) => node.name === response.result.entities.nodes[index].properties.name ); expect(isMatching).toBe(true) }); - nodes.slice(0,2).forEach((node) => { - test(`Validate copy to clipboard functionality for node and verify with api for ${node.nodeName}`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const graphData = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); - const targetNode = findNodeByName(convertCoordinates, node.nodeName); - await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); - const result = await codeGraph.clickOnCopyToClipboard(); - const api = new ApiCalls(); - const response = await api.getProject(PROJECT_NAME); - const matchingNode = response.result.entities.nodes.find(nod => nod.properties?.name === node.nodeName); - expect(matchingNode?.properties.src).toBe(result); - }); - }) - nodesPath.forEach(({firstNode, secondNode}) => { test(`Verify successful node path connection in canvas between ${firstNode} and ${secondNode} via UI`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.clickOnshowPathBtn(); await codeGraph.insertInputForShowPath("1", firstNode); await codeGraph.insertInputForShowPath("2", secondNode); @@ -200,7 +182,7 @@ test.describe("Canvas tests", () => { nodesPath.forEach((path) => { test(`Validate node path connection in canvas ui and confirm via api for path ${path.firstNode} and ${path.secondNode}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.clickOnshowPathBtn(); await codeGraph.insertInputForShowPath("1", path.firstNode); await codeGraph.insertInputForShowPath("2", path.secondNode); @@ -209,7 +191,7 @@ test.describe("Canvas tests", () => { const secondNodeRes = findNodeByName(result.elements.nodes, path.secondNode); const api = new ApiCalls(); - const response = await api.showPath(PROJECT_NAME ,firstNodeRes.id, secondNodeRes.id); + const response = await api.showPath(GRAPHRAG_SDK ,firstNodeRes.id, secondNodeRes.id); const callsRelationObject = response.result.paths[0].find(item => item.relation === "CALLS") expect(callsRelationObject?.src_node).toBe(firstNodeRes.id); expect(callsRelationObject?.dest_node).toBe(secondNodeRes.id); diff --git a/e2e/tests/chat.spec.ts b/e2e/tests/chat.spec.ts index 1f245f11..6244def4 100644 --- a/e2e/tests/chat.spec.ts +++ b/e2e/tests/chat.spec.ts @@ -3,7 +3,7 @@ import BrowserWrapper from "../infra/ui/browserWrapper"; import urls from "../config/urls.json"; import { ApiCalls } from "../logic/api/apiCalls"; import CodeGraph from "../logic/POM/codeGraph"; -import { CHAT_OPTTIONS_COUNT, GRAPH_ID, Node_Question, PROJECT_NAME } from "../config/constants"; +import { CHAT_OPTTIONS_COUNT, GRAPHRAG_SDK, Node_Question } from "../config/constants"; import { delay } from "../logic/utils"; import { nodesPath } from "../config/testData"; @@ -20,7 +20,7 @@ test.describe("Chat tests", () => { test(`Validate clicking the lightbulb button displays the correct options at the end of the chat`, async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); + await chat.selectGraph(GRAPHRAG_SDK); await chat.clickOnLightBulbBtn(); const count = await chat.getLastChatElementButtonCount(); expect(count).toBe(CHAT_OPTTIONS_COUNT); @@ -28,10 +28,10 @@ test.describe("Chat tests", () => { test(`Validate that multiple consecutive questions receive individual answers`, async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); + await chat.selectGraph(GRAPHRAG_SDK); const isLoadingArray: boolean[] = []; - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { await chat.sendMessage(Node_Question); const isLoading: boolean = await chat.getpreviousQuestionLoadingImage(); isLoadingArray.push(isLoading); @@ -39,20 +39,22 @@ test.describe("Chat tests", () => { const prevIsLoading = isLoadingArray[i - 1]; expect(prevIsLoading).toBe(false); } + await delay(3000); } }); test("Verify auto-scroll and manual scroll in chat", async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); - for (let i = 0; i < 5; i++) { + await chat.selectGraph(GRAPHRAG_SDK); + for (let i = 0; i < 3; i++) { await chat.sendMessage(Node_Question); + await delay(3000); } - await delay(500); + await delay(500); // delay for scroll await chat.scrollToTop(); const { scrollTop } = await chat.getScrollMetrics(); expect(scrollTop).toBeLessThanOrEqual(1); - await chat.sendMessage("Latest Message"); + await chat.sendMessage(Node_Question); await delay(500); expect(await chat.isAtBottom()).toBe(true); }); @@ -60,7 +62,7 @@ test.describe("Chat tests", () => { nodesPath.forEach((path) => { test(`Verify successful node path connection between two nodes in chat for ${path.firstNode} and ${path.secondNode}`, async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); + await chat.selectGraph(GRAPHRAG_SDK); await chat.clickOnshowPathBtn(); await chat.insertInputForShowPath("1", path.firstNode); await chat.insertInputForShowPath("2", path.secondNode); @@ -72,7 +74,7 @@ test.describe("Chat tests", () => { nodesPath.forEach((path) => { test(`Verify unsuccessful node path connection between two nodes in chat for ${path.firstNode} and ${path.secondNode}`, async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); + await chat.selectGraph(GRAPHRAG_SDK); await chat.clickOnshowPathBtn(); await chat.insertInputForShowPath("1", path.secondNode); await chat.insertInputForShowPath("2", path.firstNode); @@ -83,7 +85,7 @@ test.describe("Chat tests", () => { test("Validate error notification and its closure when sending an empty question in chat", async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); + await chat.selectGraph(GRAPHRAG_SDK); await chat.clickAskquestionBtn(); expect(await chat.isNotificationError()).toBe(true); await chat.clickOnNotificationErrorCloseBtn(); @@ -94,7 +96,7 @@ test.describe("Chat tests", () => { const questionNumber = index + 1; test(`Validate displaying question ${index} in chat after selection from options menu`, async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); + await chat.selectGraph(GRAPHRAG_SDK); await chat.clickOnQuestionOptionsMenu(); const selectedQuestion = await chat.selectAndGetQuestionInOptionsMenu(questionNumber.toString()); expect(selectedQuestion).toEqual(await chat.getLastQuestionInChat()) @@ -103,28 +105,31 @@ test.describe("Chat tests", () => { test(`Validate consistent UI responses for repeated questions in chat`, async () => { const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); + await chat.selectGraph(GRAPHRAG_SDK); const responses: string[] = []; for (let i = 0; i < 3; i++) { await chat.sendMessage(Node_Question); const result = await chat.getTextInLastChatElement(); const number = result.match(/\d+/g)?.[0]!; responses.push(number); + await delay(3000); //delay before asking next question } const identicalResponses = responses.every((value) => value === responses[0]); expect(identicalResponses).toBe(true); }); + test(`Validate UI response matches API response for a given question in chat`, async () => { + const api = new ApiCalls(); + const apiResponse = await api.askQuestion(GRAPHRAG_SDK, Node_Question); + const chat = await browser.createNewPage(CodeGraph, urls.baseUrl); - await chat.selectGraph(GRAPH_ID); + await chat.selectGraph(GRAPHRAG_SDK); await chat.sendMessage(Node_Question); const uiResponse = await chat.getTextInLastChatElement(); const number = uiResponse.match(/\d+/g)?.[0]!; - const api = new ApiCalls(); - const apiResponse = await api.askQuestion(PROJECT_NAME, Node_Question); expect(number).toEqual(apiResponse.result.response.match(/\d+/g)?.[0]); }); }); diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts index aa59a6fe..358363ff 100644 --- a/e2e/tests/nodeDetailsPanel.spec.ts +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -2,10 +2,10 @@ import { test, expect } from "@playwright/test"; import BrowserWrapper from "../infra/ui/browserWrapper"; import CodeGraph from "../logic/POM/codeGraph"; import urls from "../config/urls.json"; -import { GRAPH_ID, PROJECT_NAME } from "../config/constants"; +import { FLASK_GRAPH, GRAPHRAG_SDK, } from "../config/constants"; import { nodes } from "../config/testData"; import { ApiCalls } from "../logic/api/apiCalls"; -import { findNodeByName } from "../logic/utils"; +import { findFirstNodeWithSrc, findNodeByName } from "../logic/utils"; test.describe("Node details panel tests", () => { let browser: BrowserWrapper; @@ -21,10 +21,10 @@ test.describe("Node details panel tests", () => { nodes.slice(0,2).forEach((node) => { test(`Validate node details panel displayed on node click for ${node.nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const graphData = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); - const targetNode = findNodeByName(convertCoordinates, node.nodeName); + await browser.setPageToFullScreen(); + await codeGraph.selectGraph(GRAPHRAG_SDK); + const graphData = await codeGraph.getGraphNodes(); + const targetNode = findNodeByName(graphData, node.nodeName); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnViewNode(); expect(await codeGraph.isNodeDetailsPanel()).toBe(true) @@ -34,11 +34,11 @@ test.describe("Node details panel tests", () => { nodes.slice(0,2).forEach((node) => { test(`Validate node details panel is not displayed after close interaction for ${node.nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const graphData = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); - const node1 = findNodeByName(convertCoordinates, node.nodeName); - await codeGraph.nodeClick(node1.screenX, node1.screenY); + await browser.setPageToFullScreen(); + await codeGraph.selectGraph(GRAPHRAG_SDK); + const graphData = await codeGraph.getGraphNodes(); + const targetNode = findNodeByName(graphData, node.nodeName); + await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnViewNode(); await codeGraph.clickOnNodeDetailsCloseBtn(); expect(await codeGraph.isNodeDetailsPanel()).toBe(false) @@ -48,11 +48,11 @@ test.describe("Node details panel tests", () => { nodes.forEach((node) => { test(`Validate node details panel header displays correct node name: ${node.nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const graphData = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); - const node1 = findNodeByName(convertCoordinates, node.nodeName); - await codeGraph.nodeClick(node1.screenX, node1.screenY); + await browser.setPageToFullScreen(); + await codeGraph.selectGraph(GRAPHRAG_SDK); + const graphData = await codeGraph.getGraphNodes(); + const targetNode = findNodeByName(graphData, node.nodeName); + await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); expect(await codeGraph.getNodeDetailsHeader()).toContain(node.nodeName.toUpperCase()) }) }) @@ -60,42 +60,44 @@ test.describe("Node details panel tests", () => { nodes.slice(0,2).forEach((node) => { test(`Validate copy functionality for node inside node details panel and verify with api for ${node.nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const graphData = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); - const nodeData = findNodeByName(convertCoordinates, node.nodeName); - await codeGraph.nodeClick(nodeData.screenX, nodeData.screenY); + await browser.setPageToFullScreen(); + await codeGraph.selectGraph(FLASK_GRAPH); + const graphData = await codeGraph.getGraphNodes(); + const targetNode = findFirstNodeWithSrc(graphData); + await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnViewNode(); - const result = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); + const result = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); const api = new ApiCalls(); - const response = await api.getProject(PROJECT_NAME); - const foundNode = response.result.entities.nodes.find(nod => nod.properties?.name === node.nodeName); + const response = await api.getProject(FLASK_GRAPH); + const foundNode = response.result.entities.nodes.find((nod) => nod.properties?.name === targetNode.name); expect(foundNode?.properties.src).toBe(result); }); }) - nodes.slice(0,2).forEach((node) => { + nodes.slice(0, 2).forEach((node) => { test(`Validate view node panel keys via api for ${node.nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); - const graphData = await codeGraph.getGraphDetails(); - const convertCoordinates = await codeGraph.transformNodeCoordinates(graphData); - const node1 = findNodeByName(convertCoordinates, node.nodeName); + await browser.setPageToFullScreen(); + await codeGraph.selectGraph(GRAPHRAG_SDK); + const graphData = await codeGraph.getGraphNodes(); + const node1 = findNodeByName(graphData, node.nodeName); const api = new ApiCalls(); - const response = await api.getProject(PROJECT_NAME); + const response = await api.getProject(GRAPHRAG_SDK); const data: any = response.result.entities.nodes; const findNode = data.find((nod: any) => nod.properties.name === node.nodeName); - await codeGraph.nodeClick(node1.screenX, node1.screenY); let elements = await codeGraph.getNodeDetailsPanelElements(); - elements.splice(2,1) - const apiFields = [...Object.keys(findNode), ...Object.keys(findNode.properties || {})]; - + elements.splice(2, 1); + const apiFields = [ + ...Object.keys(findNode), + ...Object.keys(findNode.properties || {}), + ]; + const isValid = elements.every((field) => { - const cleanedField = field.replace(':', '').trim(); - return apiFields.includes(cleanedField); + const cleanedField = field.replace(":", "").trim(); + return apiFields.includes(cleanedField); }); - expect(isValid).toBe(true) + expect(isValid).toBe(true); }); - }) + }); }); \ No newline at end of file diff --git a/e2e/tests/searchBar.spec.ts b/e2e/tests/searchBar.spec.ts index a7427d3d..cb1074a7 100644 --- a/e2e/tests/searchBar.spec.ts +++ b/e2e/tests/searchBar.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from "@playwright/test"; import BrowserWrapper from "../infra/ui/browserWrapper"; import CodeGraph from "../logic/POM/codeGraph"; import urls from "../config/urls.json"; -import { GRAPH_ID, PROJECT_NAME } from "../config/constants"; +import { GRAPHRAG_SDK } from "../config/constants"; import { delay } from "../logic/utils"; import { searchData, specialCharacters } from "../config/testData"; import { ApiCalls } from "../logic/api/apiCalls"; @@ -21,7 +21,7 @@ test.describe("search bar tests", () => { searchData.slice(0, 2).forEach(({ searchInput }) => { test(`Verify search bar auto-complete behavior for input: ${searchInput} via UI`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.fillSearchBar(searchInput); await delay(1000); const textList = await codeGraph.getSearchBarElementsText(); @@ -34,7 +34,7 @@ test.describe("search bar tests", () => { searchData.slice(2, 4).forEach(({ searchInput, completedSearchInput }) => { test(`Validate search bar updates with selected element: ${searchInput}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.fillSearchBar(searchInput); await codeGraph.selectSearchBarOptionBtn("1"); expect(await codeGraph.getSearchBarInputValue()).toBe( @@ -46,7 +46,7 @@ test.describe("search bar tests", () => { searchData.slice(0, 2).forEach(({ searchInput }) => { test(`Verify auto-scroll scroll in search bar list for: ${searchInput}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.fillSearchBar(searchInput); await codeGraph.scrollToBottomInSearchBarList(); expect(await codeGraph.isScrolledToBottomInSearchBarList()).toBe(true); @@ -56,7 +56,7 @@ test.describe("search bar tests", () => { specialCharacters.forEach(({ character, expectedRes }) => { test(`Verify entering special characters behavior in search bar for: ${character}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.fillSearchBar(character); await delay(1000); expect((await codeGraph.getSearchBarInputValue()).includes(character)).toBe(expectedRes); @@ -66,11 +66,11 @@ test.describe("search bar tests", () => { searchData.slice(0, 2).forEach(({ searchInput}) => { test(`search bar auto complete via ui and validating via api for: ${searchInput}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await codeGraph.selectGraph(GRAPH_ID); + await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.fillSearchBar(searchInput); const count = await codeGraph.getSearchAutoCompleteCount(); const api = new ApiCalls(); - const response = await api.searchAutoComplete(PROJECT_NAME, searchInput); + const response = await api.searchAutoComplete(GRAPHRAG_SDK, searchInput); expect(count).toBe(response.result.completions.length); }); }) From 9377bd7c583aefc647e33f90fab123bb70364c36 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 19:23:56 +0300 Subject: [PATCH 03/75] implement sharding in CI --- .github/workflows/playwright.yml | 59 ++++++++++++++++++++++++++++++-- playwright.config.ts | 7 +++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 3893ea77..a27108c7 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -8,6 +8,10 @@ jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shard: [1, 2] services: falkordb: image: falkordb/falkordb:latest @@ -32,10 +36,61 @@ jobs: run: | npm install npm run build - NEXTAUTH_SECRET=SECRET npm start & npx playwright test --reporter=dot,list + NEXTAUTH_SECRET=SECRET npm start & + npx playwright test --shard=${{ matrix.shard }}/2 --reporter=dot,list,json - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: - name: playwright-report + name: playwright-report-shard-${{ matrix.shard }} path: playwright-report/ retention-days: 30 + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: test-results-shard-${{ matrix.shard }} + path: test-results/ + retention-days: 30 + + merge-reports: + if: ${{ !cancelled() }} + needs: [test] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Download all test results + uses: actions/download-artifact@v4 + with: + path: all-test-results + pattern: test-results-shard-* + - name: Download all reports + uses: actions/download-artifact@v4 + with: + path: all-reports + pattern: playwright-report-shard-* + - name: Merge test results + run: | + # Create combined results directory + mkdir -p combined-results + + # Merge JSON results + npx playwright merge-reports --reporter=html,json all-reports/playwright-report-shard-*/ + + # Move merged report to combined directory + mv playwright-report combined-results/ + + # Create a summary of all test results + echo "## Test Results Summary" > combined-results/summary.md + echo "Total shards: 2" >> combined-results/summary.md + echo "Workers per shard: 2" >> combined-results/summary.md + echo "Generated on: $(date)" >> combined-results/summary.md + - name: Upload combined report + uses: actions/upload-artifact@v4 + with: + name: combined-playwright-report + path: combined-results/ + retention-days: 30 diff --git a/playwright.config.ts b/playwright.config.ts index 7a5292d5..64751558 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -22,7 +22,12 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 2 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: process.env.CI ? [ + ['dot'], + ['list'], + ['json', { outputFile: 'test-results/results.json' }], + ['html', { open: 'never', outputFolder: 'playwright-report' }] + ] : 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ From 0e4d9a2a43e5a53cc3b2fc5751330b7d34fbd59a Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 19:42:57 +0300 Subject: [PATCH 04/75] fix nodeDetailsPanel tests --- e2e/tests/nodeDetailsPanel.spec.ts | 31 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts index 358363ff..6443da66 100644 --- a/e2e/tests/nodeDetailsPanel.spec.ts +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -57,22 +57,21 @@ test.describe("Node details panel tests", () => { }) }) - nodes.slice(0,2).forEach((node) => { - test(`Validate copy functionality for node inside node details panel and verify with api for ${node.nodeName}`, async () => { - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); - await browser.setPageToFullScreen(); - await codeGraph.selectGraph(FLASK_GRAPH); - const graphData = await codeGraph.getGraphNodes(); - const targetNode = findFirstNodeWithSrc(graphData); - await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); - await codeGraph.clickOnViewNode(); - const result = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); - const api = new ApiCalls(); - const response = await api.getProject(FLASK_GRAPH); - const foundNode = response.result.entities.nodes.find((nod) => nod.properties?.name === targetNode.name); - expect(foundNode?.properties.src).toBe(result); - }); - }) + + test(`Validate copy functionality for node inside node details panel and verify with api`, async () => { + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await browser.setPageToFullScreen(); + await codeGraph.selectGraph(FLASK_GRAPH); + const graphData = await codeGraph.getGraphNodes(); + const targetNode = findFirstNodeWithSrc(graphData); + await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); + await codeGraph.clickOnViewNode(); + const result = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); + const api = new ApiCalls(); + const response = await api.getProject(FLASK_GRAPH); + const foundNode = response.result.entities.nodes.find((nod) => nod.properties?.name === targetNode.name); + expect(foundNode?.properties.src).toBe(result); + }); nodes.slice(0, 2).forEach((node) => { test(`Validate view node panel keys via api for ${node.nodeName}`, async () => { From a9ef1986d7ba8a30bf4935dbeedb984af0a0d3dd Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:29:17 +0300 Subject: [PATCH 05/75] fix tests --- e2e/logic/utils.ts | 4 ++++ e2e/tests/nodeDetailsPanel.spec.ts | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/e2e/logic/utils.ts b/e2e/logic/utils.ts index 47833e70..744851bf 100644 --- a/e2e/logic/utils.ts +++ b/e2e/logic/utils.ts @@ -21,4 +21,8 @@ export function findNodeByName(nodes: { name: string }[], nodeName: string): any export function findFirstNodeWithSrc(nodes: { src?: string }[]): any { return nodes.find((node) => node.src !== undefined); +} + +export function findNodeWithSpecificSrc(nodes: { src?: string }[], srcContent: string): any { + return nodes.find((node) => node.src && node.src.includes(srcContent)); } \ No newline at end of file diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts index 6443da66..5b6d26d0 100644 --- a/e2e/tests/nodeDetailsPanel.spec.ts +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -2,10 +2,10 @@ import { test, expect } from "@playwright/test"; import BrowserWrapper from "../infra/ui/browserWrapper"; import CodeGraph from "../logic/POM/codeGraph"; import urls from "../config/urls.json"; -import { FLASK_GRAPH, GRAPHRAG_SDK, } from "../config/constants"; +import { FLASK_GRAPH, GRAPHRAG_SDK } from "../config/constants"; +import { findNodeByName, findFirstNodeWithSrc, findNodeWithSpecificSrc } from "../logic/utils"; import { nodes } from "../config/testData"; import { ApiCalls } from "../logic/api/apiCalls"; -import { findFirstNodeWithSrc, findNodeByName } from "../logic/utils"; test.describe("Node details panel tests", () => { let browser: BrowserWrapper; @@ -58,17 +58,21 @@ test.describe("Node details panel tests", () => { }) - test(`Validate copy functionality for node inside node details panel and verify with api`, async () => { + test.only(`Validate copy functionality for node inside node details panel and verify with api`, async () => { + const api = new ApiCalls(); + const response = await api.getProject(FLASK_GRAPH); + const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); await codeGraph.selectGraph(FLASK_GRAPH); const graphData = await codeGraph.getGraphNodes(); - const targetNode = findFirstNodeWithSrc(graphData); + const targetNode = findNodeWithSpecificSrc(graphData, "test_options_work"); + + await new Promise(resolve => setTimeout(resolve, 2000)); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnViewNode(); const result = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); - const api = new ApiCalls(); - const response = await api.getProject(FLASK_GRAPH); + const foundNode = response.result.entities.nodes.find((nod) => nod.properties?.name === targetNode.name); expect(foundNode?.properties.src).toBe(result); }); From c5011ff8ead45c425287bb5ac88f82c1dbf63673 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:33:06 +0300 Subject: [PATCH 06/75] Update nodeDetailsPanel.spec.ts --- e2e/tests/nodeDetailsPanel.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts index 5b6d26d0..ad7afd80 100644 --- a/e2e/tests/nodeDetailsPanel.spec.ts +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -58,7 +58,7 @@ test.describe("Node details panel tests", () => { }) - test.only(`Validate copy functionality for node inside node details panel and verify with api`, async () => { + test(`Validate copy functionality for node inside node details panel and verify with api`, async () => { const api = new ApiCalls(); const response = await api.getProject(FLASK_GRAPH); From abde36540156e6a86a0cd6b0f207b0c1a008c280 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 22:18:08 +0300 Subject: [PATCH 07/75] Update nodeDetailsPanel.spec.ts --- e2e/tests/nodeDetailsPanel.spec.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts index ad7afd80..7ac30cdd 100644 --- a/e2e/tests/nodeDetailsPanel.spec.ts +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -58,7 +58,7 @@ test.describe("Node details panel tests", () => { }) - test(`Validate copy functionality for node inside node details panel and verify with api`, async () => { + test.only(`Validate copy functionality for node inside node details panel and verify with api`, async () => { const api = new ApiCalls(); const response = await api.getProject(FLASK_GRAPH); @@ -66,14 +66,18 @@ test.describe("Node details panel tests", () => { await browser.setPageToFullScreen(); await codeGraph.selectGraph(FLASK_GRAPH); const graphData = await codeGraph.getGraphNodes(); - const targetNode = findNodeWithSpecificSrc(graphData, "test_options_work"); - + + const targetNode = findFirstNodeWithSrc(graphData); + await new Promise(resolve => setTimeout(resolve, 2000)); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnViewNode(); const result = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); - + await new Promise(resolve => setTimeout(resolve, 2000)); const foundNode = response.result.entities.nodes.find((nod) => nod.properties?.name === targetNode.name); + console.log("API Node:", foundNode); + console.log("Copied Result:", result); + expect(foundNode?.properties.src).toBe(result); }); From 11b8e910696733796164daa07baa141e1b1f0241 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 22:20:38 +0300 Subject: [PATCH 08/75] Update nodeDetailsPanel.spec.ts --- e2e/tests/nodeDetailsPanel.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts index 7ac30cdd..c902d839 100644 --- a/e2e/tests/nodeDetailsPanel.spec.ts +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -58,7 +58,7 @@ test.describe("Node details panel tests", () => { }) - test.only(`Validate copy functionality for node inside node details panel and verify with api`, async () => { + test(`Validate copy functionality for node inside node details panel and verify with api`, async () => { const api = new ApiCalls(); const response = await api.getProject(FLASK_GRAPH); From 5656d2b051e0a7167cfeb2e557725c01e709a951 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 23:14:18 +0300 Subject: [PATCH 09/75] Update nodeDetailsPanel.spec.ts --- e2e/tests/nodeDetailsPanel.spec.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts index c902d839..75901439 100644 --- a/e2e/tests/nodeDetailsPanel.spec.ts +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -59,27 +59,26 @@ test.describe("Node details panel tests", () => { test(`Validate copy functionality for node inside node details panel and verify with api`, async () => { - const api = new ApiCalls(); - const response = await api.getProject(FLASK_GRAPH); - const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); await codeGraph.selectGraph(FLASK_GRAPH); const graphData = await codeGraph.getGraphNodes(); + const targetNode = graphData.find(node => + node.name === "root" && node.src !== undefined + ) || findFirstNodeWithSrc(graphData); - const targetNode = findFirstNodeWithSrc(graphData); await new Promise(resolve => setTimeout(resolve, 2000)); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnViewNode(); const result = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); - await new Promise(resolve => setTimeout(resolve, 2000)); + + const api = new ApiCalls(); + const response = await api.getProject(FLASK_GRAPH); const foundNode = response.result.entities.nodes.find((nod) => nod.properties?.name === targetNode.name); - console.log("API Node:", foundNode); - console.log("Copied Result:", result); expect(foundNode?.properties.src).toBe(result); - }); +}); nodes.slice(0, 2).forEach((node) => { test(`Validate view node panel keys via api for ${node.nodeName}`, async () => { From f7a2de22789803086250be21c8ef975dfc0cf5a6 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Tue, 8 Jul 2025 23:24:49 +0300 Subject: [PATCH 10/75] update CI --- .github/workflows/playwright.yml | 46 +------------------------------- 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a27108c7..e72babaa 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -37,7 +37,7 @@ jobs: npm install npm run build NEXTAUTH_SECRET=SECRET npm start & - npx playwright test --shard=${{ matrix.shard }}/2 --reporter=dot,list,json + npx playwright test --shard=${{ matrix.shard }}/2 --reporter=dot,list - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: @@ -50,47 +50,3 @@ jobs: name: test-results-shard-${{ matrix.shard }} path: test-results/ retention-days: 30 - - merge-reports: - if: ${{ !cancelled() }} - needs: [test] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: npm ci - - name: Download all test results - uses: actions/download-artifact@v4 - with: - path: all-test-results - pattern: test-results-shard-* - - name: Download all reports - uses: actions/download-artifact@v4 - with: - path: all-reports - pattern: playwright-report-shard-* - - name: Merge test results - run: | - # Create combined results directory - mkdir -p combined-results - - # Merge JSON results - npx playwright merge-reports --reporter=html,json all-reports/playwright-report-shard-*/ - - # Move merged report to combined directory - mv playwright-report combined-results/ - - # Create a summary of all test results - echo "## Test Results Summary" > combined-results/summary.md - echo "Total shards: 2" >> combined-results/summary.md - echo "Workers per shard: 2" >> combined-results/summary.md - echo "Generated on: $(date)" >> combined-results/summary.md - - name: Upload combined report - uses: actions/upload-artifact@v4 - with: - name: combined-playwright-report - path: combined-results/ - retention-days: 30 From 4eee80f11b861ec1b95b3404569ec657ddbdcad8 Mon Sep 17 00:00:00 2001 From: Dandan7 <182233217+danshalev7@users.noreply.github.com> Date: Sun, 3 Aug 2025 11:04:08 +0300 Subject: [PATCH 11/75] Update page.tsx Replaced the multiple H1 tags with H2 tags, keeping 'CODE GRAPH' as sole H1 tag --- app/page.tsx | 58 ++++++++++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index a58f1a56..7938501e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -180,35 +180,35 @@ export default function Home() {

Github

- - - - -
- HOW TO USE THE PRODUCT - -
- { - TIPS.map((tip, index) => ( -
-
-

{tip.title}

-

{tip.keyboardCommand}

-
-

{tip.description}

-
- )) - } -
-
+ + + + +
+ HOW TO USE THE PRODUCT + +
+ { + TIPS.map((tip, index) => ( +
+
+

{tip.title}

+

{tip.keyboardCommand}

+
+

{tip.description}

+
+ )) + } +
+ {/* - avoidOverlap(data.nodes as Position[])} - nodeVisibility="visible" - linkVisibility="visible" - linkCurvature="curve" - linkDirectionalArrowRelPos={1} - linkDirectionalArrowColor={(link) => (link.isPath || link.isPathSelected) ? PATH_COLOR : link.color} - linkDirectionalArrowLength={(link) => link.source.id === link.target.id ? 0 : (link.id === selectedObj?.id || link.isPathSelected) ? 3 : 2} - nodeRelSize={NODE_SIZE} - linkLineDash={(link) => (link.isPath && !link.isPathSelected) ? [5, 5] : []} - linkColor={(link) => (link.isPath || link.isPathSelected) ? PATH_COLOR : link.color} - linkWidth={(link) => (link.id === selectedObj?.id || link.isPathSelected) ? 2 : 1} - nodeCanvasObjectMode={() => 'after'} - linkCanvasObjectMode={() => 'after'} - nodeCanvasObject={(node, ctx) => { - if (!node.x || !node.y) return - - if (isPathResponse) { - if (node.isPathSelected) { - ctx.fillStyle = node.color; - ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = 1 - } else if (node.isPath) { - ctx.fillStyle = node.color; - ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = 0.5 - } else { - ctx.fillStyle = '#E5E5E5'; - ctx.strokeStyle = 'gray'; - ctx.lineWidth = 0.5 - } - } else if (isPathResponse === undefined) { - if (node.isPathSelected) { - ctx.fillStyle = node.color; - ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = 1 - } else if (node.isPath) { - ctx.fillStyle = node.color; - ctx.strokeStyle = PATH_COLOR; - ctx.lineWidth = 0.5 - } else { - ctx.fillStyle = node.color; - ctx.strokeStyle = 'black'; - ctx.lineWidth = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id ? 1 : 0.5 - } - } else { - ctx.fillStyle = node.color; - ctx.strokeStyle = 'black'; - ctx.lineWidth = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id ? 1 : 0.5 - } - - ctx.beginPath(); - ctx.arc(node.x, node.y, NODE_SIZE, 0, 2 * Math.PI, false); - ctx.stroke(); - ctx.fill(); - - ctx.fillStyle = 'black'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.font = '2px Arial'; - const textWidth = ctx.measureText(node.name).width; - const ellipsis = '...'; - const ellipsisWidth = ctx.measureText(ellipsis).width; - const nodeSize = NODE_SIZE * 2 - PADDING; - let { name } = { ...node } - - // truncate text if it's too long - if (textWidth > nodeSize) { - while (name.length > 0 && ctx.measureText(name).width + ellipsisWidth > nodeSize) { - name = name.slice(0, -1); - } - name += ellipsis; - } - - // add label - ctx.fillText(name, node.x, node.y); - }} - linkCanvasObject={(link, ctx) => { - const start = link.source; - const end = link.target; - - if (!start.x || !start.y || !end.x || !end.y) return - - let textX, textY, angle; - - if (start.id === end.id) { - const radius = NODE_SIZE * link.curve * 6.2; - const angleOffset = -Math.PI / 4; // 45 degrees offset for text alignment - textX = start.x + radius * Math.cos(angleOffset); - textY = start.y + radius * Math.sin(angleOffset); - angle = -angleOffset; - } else { - const midX = (start.x + end.x) / 2; - const midY = (start.y + end.y) / 2; - const offset = link.curve / 2; - - angle = Math.atan2(end.y - start.y, end.x - start.x); - - // maintain label vertical orientation for legibility - if (angle > Math.PI / 2) angle = -(Math.PI - angle); - if (angle < -Math.PI / 2) angle = -(-Math.PI - angle); - - // Calculate perpendicular offset - const perpX = -Math.sin(angle) * offset; - const perpY = Math.cos(angle) * offset; - - // Adjust position to compensate for rotation around origin - const cos = Math.cos(angle); - const sin = Math.sin(angle); - textX = midX + perpX; - textY = midY + perpY; - const rotatedX = textX * cos + textY * sin; - const rotatedY = -textX * sin + textY * cos; - textX = rotatedX; - textY = rotatedY; - } - - // Setup text properties to measure background size - ctx.font = '2px Arial'; - const padding = 0.5; - // Get text width and height - const label = graph.LabelsMap.get(link.label)! - let { textWidth, textHeight } = label - - if (!textWidth || !textHeight) { - const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } = ctx.measureText(link.label) - textWidth = width - textHeight = actualBoundingBoxAscent + actualBoundingBoxDescent - graph.LabelsMap.set(link.label, { ...label, textWidth, textHeight }) - } - - // Save the current context state - ctx.save(); - - // add label with background and rotation - ctx.rotate(angle); - - // Draw background - ctx.fillStyle = 'white'; - ctx.fillRect( - textX - textWidth / 2 - padding, - textY - textHeight / 2 - padding, - textWidth + padding * 2, - textHeight + padding * 2 - ); - - // Draw text - ctx.globalAlpha = 1; - ctx.fillStyle = 'black'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(link.label, textX, textY); - - ctx.restore(); // reset rotation - }} - onNodeClick={screenSize > Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) || isShowPath ? handleNodeClick : handleRightClick} + Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) || isShowPath ? (node: Node, _evt: MouseEvent) => handleNodeClick(node) : (node: Node, evt: MouseEvent) => handleRightClick(node, evt)} onNodeRightClick={handleRightClick} - onNodeDragEnd={(n, translate) => setPosition(prev => { - return prev && { x: prev.x + translate.x * (chartRef.current?.zoom() ?? 1), y: prev.y + translate.y * (chartRef.current?.zoom() ?? 1) } - })} onLinkClick={screenSize > Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) && isPathResponse ? handleLinkClick : handleRightClick} onLinkRightClick={handleRightClick} - onBackgroundRightClick={unsetSelectedObjects} onBackgroundClick={unsetSelectedObjects} + onBackgroundRightClick={unsetSelectedObjects} onZoom={() => unsetSelectedObjects()} - onEngineStop={() => { - setCooldownTicks(0) - debugger - handleZoomToFit(chartRef, zoomedNodes.length === 1 ? 4 : 1, (n: NodeObject) => zoomedNodes.some(node => node.id === n.id)) - setZoomedNodes([]) - }} + onEngineStop={handleEngineStop} cooldownTicks={cooldownTicks} - cooldownTime={6000} /> ) diff --git a/app/components/model.ts b/app/components/model.ts index 130e60a7..415877bd 100644 --- a/app/components/model.ts +++ b/app/components/model.ts @@ -1,4 +1,3 @@ -import { LinkObject, NodeObject } from 'react-force-graph-2d' import { Path } from '@/lib/utils' export interface GraphData { @@ -13,34 +12,37 @@ export interface Category { export interface Label { name: string, - textWidth: number, - textHeight: number, } -export type Node = NodeObject<{ +export interface Node { id: number, name: string, category: string, color: string, + visible: boolean, collapsed: boolean, expand: boolean, - visible: boolean, isPathSelected: boolean, isPath: boolean, + x?: number, + y?: number, + vx?: number, + vy?: number, [key: string]: any, -}> +} -export type Link = LinkObject +} const COLORS_ORDER_NAME = [ "blue", @@ -226,18 +228,19 @@ export class Graph { let label = this.labelsMap.get(edgeData.relation) if (!label) { - label = { name: edgeData.relation, textWidth: 0, textHeight: 0 } + label = { name: edgeData.relation } this.labelsMap.set(edgeData.relation, label) this.labels.push(label) } link = { id: edgeData.id, - source, - target, + source: edgeData.src_node, + target: edgeData.dest_node, label: edgeData.relation, visible: true, expand: false, + color: "#999999", collapsed, isPathSelected: false, isPath: !!path, @@ -251,12 +254,12 @@ export class Graph { newElements.links.forEach(link => { const start = link.source const end = link.target - const sameNodesLinks = this.Elements.links.filter(l => (l.source.id === start.id && l.target.id === end.id) || (l.target.id === start.id && l.source.id === end.id)) + const sameNodesLinks = this.elements.links.filter(l => (l.source === start && l.target === end) || (l.target === start && l.source === end)) const index = sameNodesLinks.findIndex(l => l.id === link.id) ?? 0 const even = index % 2 === 0 let curve - if (start.id === end.id) { + if (start === end) { if (even) { curve = Math.floor(-(index / 2)) - 3 } else { @@ -282,7 +285,7 @@ export class Graph { this.elements = { nodes: this.elements.nodes, links: this.elements.links.map(link => { - if (this.elements.nodes.map(n => n.id).includes(link.source.id) && this.elements.nodes.map(n => n.id).includes(link.target.id)) { + if (this.elements.nodes.map(n => n.id).includes(link.source) && this.elements.nodes.map(n => n.id).includes(link.target)) { return link } this.linksMap.delete(link.id) @@ -291,15 +294,15 @@ export class Graph { } public visibleLinks(visible: boolean, ids?: number[]) { - const elements = ids ? this.elements.links.filter(link => ids.includes(link.source.id) || ids.includes(link.target.id)) : this.elements.links + const elements = ids ? this.elements.links.filter(link => ids.includes(link.source) || ids.includes(link.target)) : this.elements.links elements.forEach(link => { - if (visible && this.elements.nodes.map(n => n.id).includes(link.source.id) && link.source.visible && this.elements.nodes.map(n => n.id).includes(link.target.id) && link.target.visible) { + if (visible && this.elements.nodes.find(n => n.id === link.source)?.visible && this.elements.nodes.find(n => n.id === link.target)?.visible) { // eslint-disable-next-line no-param-reassign link.visible = true } - if (!visible && ((this.elements.nodes.map(n => n.id).includes(link.source.id) && !link.source.visible) || (this.elements.nodes.map(n => n.id).includes(link.target.id) && !link.target.visible))) { + if (!visible && (this.elements.nodes.find(n => n.id === link.source)?.visible === false || this.elements.nodes.find(n => n.id === link.target)?.visible === false)) { // eslint-disable-next-line no-param-reassign link.visible = false } diff --git a/app/page.tsx b/app/page.tsx index c78b4901..9d4144a9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,8 +1,8 @@ 'use client' -import { MutableRefObject, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Chat } from './components/chat'; -import { Graph, GraphData, Link as LinkType, Node } from './components/model'; +import { Graph, GraphData, Node } from './components/model'; import { AlignRight, BookOpen, BoomBox, Download, Github, HomeIcon, Search, X } from 'lucide-react'; import Link from 'next/link'; import { ImperativePanelHandle, Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; @@ -17,10 +17,9 @@ import { Progress } from '@/components/ui/progress'; import { Carousel, CarouselApi, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/components/ui/carousel'; import { Drawer, DrawerContent, DrawerDescription, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'; import Input from './components/Input'; -import { ForceGraphMethods, NodeObject } from 'react-force-graph-2d'; import { Labels } from './components/labels'; import { Toolbar } from './components/toolbar'; -import { cn, handleZoomToFit, Message, Path, PathData, PathNode } from '@/lib/utils'; +import { cn, GraphRef, handleZoomToFit, Message, Path, PathData, PathNode } from '@/lib/utils'; import { GraphContext } from './components/provider'; type Tip = { @@ -67,8 +66,8 @@ export default function Home() { const [options, setOptions] = useState([]); const [path, setPath] = useState(); const [isSubmit, setIsSubmit] = useState(false); - const desktopChartRef = useRef>() - const mobileChartRef = useRef>() + const desktopChartRef = useRef() + const mobileChartRef = useRef() const [menuOpen, setMenuOpen] = useState(false) const [chatOpen, setChatOpen] = useState(false) const [searchNode, setSearchNode] = useState({}); @@ -190,7 +189,7 @@ export default function Home() { return graph.extend(json.result.neighbors, true) } - const handleSearchSubmit = (node: any, chartRef: MutableRefObject | undefined>) => { + const handleSearchSubmit = (node: any, chartRef: GraphRef) => { const chart = chartRef.current if (chart) { @@ -211,7 +210,7 @@ export default function Home() { } setTimeout(() => { - handleZoomToFit(chartRef, 4, (n: NodeObject) => n.id === chartNode!.id) + handleZoomToFit(chartRef, 4, (n: GraphNode) => n.id === chartNode!.id) }, 0) setSearchNode(chartNode) setOptionsOpen(false) diff --git a/lib/utils.ts b/lib/utils.ts index 2946411b..2dcb8c2d 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -2,7 +2,6 @@ import { Link, Node } from "@/app/components/model" import { type ClassValue, clsx } from "clsx" import { MutableRefObject } from "react" import { twMerge } from "tailwind-merge" -import { ForceGraphMethods, NodeObject } from "react-force-graph-2d" export type PathData = { nodes: any[] @@ -35,13 +34,13 @@ export interface Message { graphName?: string; } -export type GraphRef = MutableRefObject | undefined> +export type GraphRef = MutableRefObject export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } -export function handleZoomToFit(chartRef: GraphRef, paddingMultiplier = 1, filter?: (node: NodeObject) => boolean) { +export function handleZoomToFit(chartRef: GraphRef, paddingMultiplier = 1, filter?: (node: NodeGraph) => boolean) { const chart = chartRef.current if (chart) { // Find the currently visible canvas by checking display property diff --git a/package-lock.json b/package-lock.json index 1eeadbdb..bb1f5672 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "code-graph", "version": "0.2.0", "dependencies": { + "@falkordb/canvas": "^0.0.6", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -30,7 +31,6 @@ "playwright": "^1.49.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-force-graph-2d": "^1.27.0", "react-gtm-module": "^2.0.11", "react-json-tree": "^0.19.0", "react-resizable-panels": "^2.1.7", @@ -216,6 +216,26 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@falkordb/canvas": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.6.tgz", + "integrity": "sha512-5r2dEt3O05dz5drfzHUxEShjqAi1kkDMUadvgBV3rq2YOlZmU9vAOYneU0mpAU1UJOprc+yc78xjTEGMict3Vw==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + } + }, + "node_modules/@falkordb/canvas/node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@floating-ui/core": { "version": "1.6.9", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", @@ -874,9 +894,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz", - "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz", + "integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1895,7 +1915,8 @@ "node_modules/@tweenjs/tween.js": { "version": "25.0.0", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", - "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==" + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.6", @@ -2181,9 +2202,10 @@ "license": "ISC" }, "node_modules/accessor-fn": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.1.tgz", - "integrity": "sha512-zZpFYBqIL1Aqg+f2qmYHJ8+yIZF7/tP6PUGx2/QM0uGPSO5UegpinmkNwDohxWtOj586BpMPVRUjce2HI6xB3A==", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", + "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", + "license": "MIT", "engines": { "node": ">=12" } @@ -2562,6 +2584,7 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", + "license": "MIT", "funding": { "type": "individual", "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" @@ -2722,9 +2745,10 @@ "license": "CC-BY-4.0" }, "node_modules/canvas-color-tracker": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.1.tgz", - "integrity": "sha512-eNycxGS7oQ3IS/9QQY41f/aQjiO9Y/MtedhCgSdsbLSxC9EyUD8L3ehl/Q3Kfmvt8um79S45PBV+5Rxm5ztdSw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", + "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", + "license": "MIT", "dependencies": { "tinycolor2": "^1.6.0" }, @@ -2940,9 +2964,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/d3": { @@ -3007,7 +3031,8 @@ "node_modules/d3-binarytree": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", - "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==" + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" }, "node_modules/d3-brush": { "version": "3.0.0", @@ -3150,9 +3175,10 @@ } }, "node_modules/d3-force-3d": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.5.tgz", - "integrity": "sha512-tdwhAhoTYZY/a6eo9nR7HP3xSW/C6XvJTbeRpR92nlPzH6OiE+4MliN9feuSFd0tPtEUo+191qOhCTWx3NYifg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", "dependencies": { "d3-binarytree": "1", "d3-dispatch": "1 - 3", @@ -3205,7 +3231,8 @@ "node_modules/d3-octree": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", - "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==" + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" }, "node_modules/d3-path": { "version": "3.1.0", @@ -4340,9 +4367,10 @@ "license": "ISC" }, "node_modules/float-tooltip": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.4.tgz", - "integrity": "sha512-UUcH+5MHMnHf7a3qF2ZJ7J5PTtTKHRqdaoC3VAHZuX8ooEegNWxpmmHk192lABXw0+O+FzGB4anpEiqe6iv+WA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", + "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", + "license": "MIT", "dependencies": { "d3-selection": "2 - 3", "kapsule": "^1.16", @@ -4363,9 +4391,10 @@ } }, "node_modules/force-graph": { - "version": "1.49.3", - "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.49.3.tgz", - "integrity": "sha512-blBqeFq3vdIzqGgvWrML9xA2R0nS5nvjHsEt9lcWVZ29IcdWQ6wa4G0CG/Uv8bP9olwpsJPZSJe3W8vNhiMCnQ==", + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.0.tgz", + "integrity": "sha512-aTnihCmiMA0ItLJLCbrQYS9mzriopW24goFPgUnKAAmAlPogTSmFWqoBPMXzIfPb7bs04Hur5zEI4WYgLW3Sig==", + "license": "MIT", "dependencies": { "@tweenjs/tween.js": "18 - 25", "accessor-fn": "1", @@ -4378,7 +4407,7 @@ "d3-scale-chromatic": "1 - 3", "d3-selection": "2 - 3", "d3-zoom": "2 - 3", - "float-tooltip": "^1.6", + "float-tooltip": "^1.7", "index-array-by": "1", "kapsule": "^1.16", "lodash-es": "4" @@ -4843,6 +4872,7 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", + "license": "MIT", "engines": { "node": ">=12" } @@ -5343,14 +5373,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jerrypick": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jerrypick/-/jerrypick-1.1.1.tgz", - "integrity": "sha512-XTtedPYEyVp4t6hJrXuRKr/jHj8SC4z+4K0b396PMkov6muL+i8IIamJIvZWe3jUspgIJak0P+BaWKawMYNBLg==", - "engines": { - "node": ">=12" - } - }, "node_modules/jiti": { "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", @@ -5430,9 +5452,10 @@ } }, "node_modules/kapsule": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.0.tgz", - "integrity": "sha512-4f/z/Luu0cEXmagCwaFyzvfZai2HKgB4CQLwmsMUA+jlUbW94HfFSX+TWZxzWoMSO6b6aR+FD2Xd5z88VYZJTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", + "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", + "license": "MIT", "dependencies": { "lodash-es": "4" }, @@ -5673,12 +5696,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", - "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", + "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", "license": "MIT", "dependencies": { - "@next/env": "15.5.7", + "@next/env": "15.5.9", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -6228,9 +6251,10 @@ "license": "MIT" }, "node_modules/preact": { - "version": "10.26.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.4.tgz", - "integrity": "sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w==", + "version": "10.28.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.0.tgz", + "integrity": "sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -6343,22 +6367,6 @@ "react": "^18.3.1" } }, - "node_modules/react-force-graph-2d": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/react-force-graph-2d/-/react-force-graph-2d-1.27.0.tgz", - "integrity": "sha512-NJAc7lWvY8PxBMn2uFXDDaeLcgJp37IYF6r0geOJlmMs5Lsf0IDmr35T7KNszHV3OUmTrZuPlyxrrzwGwyKhRg==", - "dependencies": { - "force-graph": "^1.49", - "prop-types": "15", - "react-kapsule": "^2.5" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": "*" - } - }, "node_modules/react-gtm-module": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz", @@ -6383,20 +6391,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-kapsule": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/react-kapsule/-/react-kapsule-2.5.6.tgz", - "integrity": "sha512-aE4Nq7dDG8R/LdNmvOL6Azjr97I2E7ycFDJRkoHJSp9OQgTJDT3MHTJtJDrOTwzCl6sllYSqrtcndaCzizyAjQ==", - "dependencies": { - "jerrypick": "^1.1.1" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react": ">=16.13.1" - } - }, "node_modules/react-remove-scroll": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", @@ -7332,7 +7326,8 @@ "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", diff --git a/package.json b/package.json index a24819ed..b30ce343 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@falkordb/canvas": "^0.0.6", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -31,7 +32,6 @@ "playwright": "^1.49.1", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-force-graph-2d": "^1.27.0", "react-gtm-module": "^2.0.11", "react-json-tree": "^0.19.0", "react-resizable-panels": "^2.1.7", From 15859f744a206b3d072260577bb5cb2aa52ea012 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 18 Dec 2025 16:56:38 +0200 Subject: [PATCH 22/75] Update Next.js dependency to version 15.5.8 in package.json and package-lock.json --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1eeadbdb..87cb3095 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "embla-carousel-react": "^8.5.2", "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", - "next": "^15.5.5", + "next": "^15.5.8", "playwright": "^1.49.1", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -874,9 +874,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz", - "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz", + "integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -5673,12 +5673,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", - "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", + "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", "license": "MIT", "dependencies": { - "@next/env": "15.5.7", + "@next/env": "15.5.9", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", diff --git a/package.json b/package.json index a24819ed..bb052ea7 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "embla-carousel-react": "^8.5.2", "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", - "next": "^15.5.5", + "next": "^15.5.8", "playwright": "^1.49.1", "react": "^18.3.1", "react-dom": "^18.3.1", From da9522f72378e5b711748322652b04f217270b43 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 18 Dec 2025 17:45:26 +0200 Subject: [PATCH 23/75] Refactor components to use canvasRef instead of chartRef for improved consistency and functionality --- app/components/ForceGraph.tsx | 9 +++++---- app/components/chat.tsx | 20 +++++++++---------- app/components/code-graph.tsx | 9 ++++----- app/components/graphView.tsx | 13 ++++++------- app/components/toolbar.tsx | 18 ++++++++++-------- app/page.tsx | 26 ++++++++++++------------- lib/utils.ts | 36 ++--------------------------------- 7 files changed, 50 insertions(+), 81 deletions(-) diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 1312b28e..53109bbb 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -8,7 +8,7 @@ import { GraphData, Link, Node } from "./model" interface Props { data: GraphData canvasRef: GraphRef - onNodeClick?: (node: Node) => void + onNodeClick?: (node: Node, event: MouseEvent) => void onNodeRightClick?: (node: Node, event: MouseEvent) => void onLinkClick?: (link: Link, event: MouseEvent) => void onLinkRightClick?: (link: Link, event: MouseEvent) => void @@ -113,7 +113,7 @@ export default function ForceGraph({ const handleNodeClick = useCallback((node: any, event: MouseEvent) => { if (onNodeClick) { const originalNode = data.nodes.find(n => n.id === node.id) - if (originalNode) onNodeClick(originalNode) + if (originalNode) onNodeClick(originalNode, event) } }, [onNodeClick, data.nodes]) @@ -144,15 +144,16 @@ export default function ForceGraph({ // Update event handlers useEffect(() => { if (!canvasRef.current || !canvasLoaded) return + canvasRef.current.setConfig({ onNodeClick: handleNodeClick, onNodeRightClick: handleNodeRightClick, onLinkClick: handleLinkClick, onLinkRightClick: handleLinkRightClick, onBackgroundClick, - onBackgroundRightClick, + // onBackgroundRightClick, onEngineStop, - onZoom + // onZoom }) }, [ handleNodeClick, diff --git a/app/components/chat.tsx b/app/components/chat.tsx index f344244f..ef5e822d 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -2,9 +2,9 @@ import { toast } from "@/components/ui/use-toast"; import { Dispatch, FormEvent, MutableRefObject, SetStateAction, useEffect, useRef, useState } from "react"; import Image from "next/image"; import { AlignLeft, ArrowRight, ChevronDown, Lightbulb, Undo2 } from "lucide-react"; -import { handleZoomToFit, Message, MessageTypes, Path, PathData } from "@/lib/utils"; +import { Message, MessageTypes, Path, PathData } from "@/lib/utils"; import Input from "./Input"; -import { Graph, GraphData, Link, Node } from "./model"; +import { Graph, GraphData } from "./model"; import { cn, GraphRef } from "@/lib/utils"; import { TypeAnimation } from "react-type-animation"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; @@ -20,7 +20,7 @@ interface Props { isPathResponse: boolean | undefined setIsPathResponse: (isPathResponse: boolean | undefined) => void setData: Dispatch> - chartRef: GraphRef + canvasRef: GraphRef messages: Message[] setMessages: Dispatch> query: string @@ -51,7 +51,7 @@ const RemoveLastPath = (messages: Message[]) => { return messages } -export function Chat({ messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setData, chartRef, paths, setPaths }: Props) { +export function Chat({ messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setData, canvasRef, paths, setPaths }: Props) { const [sugOpen, setSugOpen] = useState(false); @@ -91,9 +91,9 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set }, [isPathResponse]) const handleSetSelectedPath = (p: PathData) => { - const chart = chartRef.current + const canvas = canvasRef.current - if (!chart) return + if (!canvas) return setSelectedPath(prev => { if (prev) { if (isPathResponse && paths.some((path) => [...path.nodes, ...path.links].every((e: any) => [...prev.nodes, ...prev.links].some((e: any) => e.id === e.id)))) { @@ -147,7 +147,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set } setData({ ...graph.Elements }) setTimeout(() => { - handleZoomToFit(chartRef, 2, (n: GraphNode) => p.nodes.some(node => node.id === n.id)); + canvas.zoomToFit(2, (n: GraphNode) => p.nodes.some(node => node.id === n.id)); }, 0) setChatOpen && setChatOpen(false) } @@ -206,9 +206,9 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set } const handleSubmit = async () => { - const chart = chartRef.current + const canvas = canvasRef.current - if (!chart) return + if (!canvas) return setSelectedPath(undefined) @@ -247,7 +247,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set setIsPathResponse(true) setData({ ...graph.Elements }) setTimeout(() => { - handleZoomToFit(chartRef, 2, (n: GraphNode) => formattedPaths.some(p => p.nodes.some(node => node.id === n.id))); + canvas.zoomToFit(2, (n: GraphNode) => formattedPaths.some(p => p.nodes.some(node => node.id === n.id))); }, 0) } diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index fdf31e5f..6d0c7503 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -27,7 +27,7 @@ interface Props { setOptions: Dispatch> isShowPath: boolean setPath: Dispatch> - chartRef: GraphRef + canvasRef: GraphRef selectedValue: string selectedPathId: number | undefined setSelectedPathId: (selectedPathId: number) => void @@ -54,7 +54,7 @@ export function CodeGraph({ setOptions, isShowPath, setPath, - chartRef, + canvasRef: chartRef, selectedValue, setSelectedPathId, isPathResponse, @@ -177,7 +177,7 @@ export function CodeGraph({ nodes: graph.Elements.nodes.filter(node => { if (!node.collapsed) return true - const isTarget = graph.Elements.links.some(link => link.target.id === node.id && nodes.some(n => n.id === link.source.id)); + const isTarget = graph.Elements.links.some(link => link.target === node.id && nodes.some(n => n.id === link.source)); if (!isTarget) return true @@ -332,7 +332,6 @@ export function CodeGraph({ selectedPathId={selectedPathId} setSelectedPathId={setSelectedPathId} cooldownTicks={cooldownTicks} - setCooldownTicks={setCooldownTicks} setZoomedNodes={setZoomedNodes} zoomedNodes={zoomedNodes} /> @@ -364,7 +363,7 @@ export function CodeGraph({ } diff --git a/app/components/graphView.tsx b/app/components/graphView.tsx index ae13e38a..fe15c9ff 100644 --- a/app/components/graphView.tsx +++ b/app/components/graphView.tsx @@ -4,8 +4,9 @@ import { Graph, GraphData, Link, Node } from './model'; import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'; import { Path } from '@/lib/utils'; import { Fullscreen } from 'lucide-react'; -import { GraphRef, handleZoomToFit } from '@/lib/utils'; +import { GraphRef } from '@/lib/utils'; import ForceGraph from './ForceGraph'; +import { GraphNode } from '@falkordb/canvas'; export interface Position { x: number, @@ -29,7 +30,6 @@ interface Props { selectedPathId: number | undefined setSelectedPathId: (selectedPathId: number) => void cooldownTicks: number | undefined - setCooldownTicks: Dispatch> setZoomedNodes: Dispatch> zoomedNodes: Node[] } @@ -37,7 +37,7 @@ interface Props { export default function GraphView({ data, graph, - chartRef, + chartRef: canvasRef, selectedObj, setSelectedObj, selectedObjects, @@ -50,7 +50,6 @@ export default function GraphView({ selectedPathId, setSelectedPathId, cooldownTicks, - setCooldownTicks, zoomedNodes, setZoomedNodes }: Props) { @@ -122,20 +121,20 @@ export default function GraphView({ } const handleEngineStop = () => { - handleZoomToFit(chartRef, zoomedNodes.length === 1 ? 4 : 1, (n: any) => zoomedNodes.some(node => node.id === n.id)) + canvasRef.current?.zoomToFit(zoomedNodes.length === 1 ? 4 : 1, (n: GraphNode) => zoomedNodes.some(node => node.id === n.id)) setZoomedNodes([]) } return (
-
Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) || isShowPath ? (node: Node, _evt: MouseEvent) => handleNodeClick(node) : (node: Node, evt: MouseEvent) => handleRightClick(node, evt)} onNodeRightClick={handleRightClick} onLinkClick={screenSize > Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) && isPathResponse ? handleLinkClick : handleRightClick} diff --git a/app/components/toolbar.tsx b/app/components/toolbar.tsx index 9769f25e..10b3404b 100644 --- a/app/components/toolbar.tsx +++ b/app/components/toolbar.tsx @@ -3,24 +3,26 @@ import { cn } from "@/lib/utils" import { GraphRef } from "@/lib/utils"; interface Props { - chartRef: GraphRef + canvasRef: GraphRef className?: string handleDownloadImage?: () => void } -export function Toolbar({ chartRef, className, handleDownloadImage }: Props) { +export function Toolbar({ canvasRef, className, handleDownloadImage }: Props) { const handleZoomClick = (changefactor: number) => { - const chart = chartRef.current - if (chart) { - chart.zoom(chart.zoom() * changefactor) + const canvas = canvasRef.current + + if (canvas) { + canvas.zoom(canvas.getZoom() * changefactor) } } const handleCenterClick = () => { - const chart = chartRef.current - if (chart) { - chart.zoomToFit(1000, 40) + const canvas = canvasRef.current + + if (canvas) { + canvas.zoomToFit() } } diff --git a/app/page.tsx b/app/page.tsx index 9d4144a9..cfbef292 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -19,8 +19,8 @@ import { Drawer, DrawerContent, DrawerDescription, DrawerTitle, DrawerTrigger } import Input from './components/Input'; import { Labels } from './components/labels'; import { Toolbar } from './components/toolbar'; -import { cn, GraphRef, handleZoomToFit, Message, Path, PathData, PathNode } from '@/lib/utils'; -import { GraphContext } from './components/provider'; +import { cn, GraphRef, Message, Path, PathData, PathNode } from '@/lib/utils'; +import { GraphNode } from '@falkordb/canvas'; type Tip = { title: string @@ -66,8 +66,8 @@ export default function Home() { const [options, setOptions] = useState([]); const [path, setPath] = useState(); const [isSubmit, setIsSubmit] = useState(false); - const desktopChartRef = useRef() - const mobileChartRef = useRef() + const desktopChartRef = useRef(null) + const mobileChartRef = useRef(null) const [menuOpen, setMenuOpen] = useState(false) const [chatOpen, setChatOpen] = useState(false) const [searchNode, setSearchNode] = useState({}); @@ -189,10 +189,10 @@ export default function Home() { return graph.extend(json.result.neighbors, true) } - const handleSearchSubmit = (node: any, chartRef: GraphRef) => { - const chart = chartRef.current + const handleSearchSubmit = (node: any, canvasRef: GraphRef) => { + const canvas = canvasRef.current - if (chart) { + if (canvas) { let chartNode = graph.Elements.nodes.find(n => n.id == node.id) if (!chartNode?.visible) { @@ -210,7 +210,7 @@ export default function Home() { } setTimeout(() => { - handleZoomToFit(chartRef, 4, (n: GraphNode) => n.id === chartNode!.id) + canvas.zoomToFit(4, (n: GraphNode) => n.id === chartNode!.id) }, 0) setSearchNode(chartNode) setOptionsOpen(false) @@ -389,7 +389,7 @@ export default function Home() { graph={graph} data={data} setData={setData} - chartRef={desktopChartRef} + canvasRef={desktopChartRef} options={options} setOptions={setOptions} onFetchGraph={onFetchGraph} @@ -428,7 +428,7 @@ export default function Home() { setQuery={setQuery} selectedPath={selectedPath} setSelectedPath={setSelectedPath} - chartRef={desktopChartRef} + canvasRef={desktopChartRef} setPath={setPath} path={path} repo={graph.Id} @@ -507,7 +507,7 @@ export default function Home() { graph={graph} data={data} setData={setData} - chartRef={mobileChartRef} + canvasRef={mobileChartRef} options={options} setOptions={setOptions} onFetchGraph={onFetchGraph} @@ -549,7 +549,7 @@ export default function Home() { setQuery={setQuery} selectedPath={selectedPath} setSelectedPath={setSelectedPath} - chartRef={mobileChartRef} + canvasRef={mobileChartRef} setPath={setPath} path={path} repo={graph.Id} @@ -577,7 +577,7 @@ export default function Home() { +export type GraphRef = MutableRefObject export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } - -export function handleZoomToFit(chartRef: GraphRef, paddingMultiplier = 1, filter?: (node: NodeGraph) => boolean) { - const chart = chartRef.current - if (chart) { - // Find the currently visible canvas by checking display property - const canvases = document.querySelectorAll('.force-graph-container canvas') as NodeListOf; - const container = Array.from(canvases).find(canvas => { - const container = canvas.parentElement; - - if (!container) return false; - - // Check if element is actually in viewport - const rect = container.getBoundingClientRect(); - const isInViewport = rect.width > 0 && - rect.height > 0 && - rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= window.innerHeight && - rect.right <= window.innerWidth; - - return isInViewport; - })?.parentElement; - - if (!container) return; - - // Calculate padding as 10% of the smallest canvas dimension - const minDimension = Math.min(container.clientWidth, container.clientHeight); - - const padding = minDimension * 0.1 * paddingMultiplier; - - chart.zoomToFit(1000, padding, filter); - } -} \ No newline at end of file From 39e074fbfac04ec9892eaf94207a166b94090ee3 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 18 Dec 2025 17:47:46 +0200 Subject: [PATCH 24/75] Update FalkorDB canvas dependency to version 0.0.7 and enable background right-click and zoom functionalities in ForceGraph component --- app/components/ForceGraph.tsx | 4 ++-- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 53109bbb..297c4eab 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -151,9 +151,9 @@ export default function ForceGraph({ onLinkClick: handleLinkClick, onLinkRightClick: handleLinkRightClick, onBackgroundClick, - // onBackgroundRightClick, + onBackgroundRightClick, onEngineStop, - // onZoom + onZoom }) }, [ handleNodeClick, diff --git a/package-lock.json b/package-lock.json index bb1f5672..6d13c2c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.2.0", "dependencies": { - "@falkordb/canvas": "^0.0.6", + "@falkordb/canvas": "^0.0.7", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -217,9 +217,9 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.6.tgz", - "integrity": "sha512-5r2dEt3O05dz5drfzHUxEShjqAi1kkDMUadvgBV3rq2YOlZmU9vAOYneU0mpAU1UJOprc+yc78xjTEGMict3Vw==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.7.tgz", + "integrity": "sha512-EGUz7xUO17+LL89dbO4Uy3ySeHmNxBN8x78b5tbHVNrQJ5kpRyTFGtKH3KqgYSkk5N4PxCxLs2AMK1GBujFjpQ==", "license": "MIT", "dependencies": { "d3": "^7.9.0", diff --git a/package.json b/package.json index b30ce343..9f7c91d7 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@falkordb/canvas": "^0.0.6", + "@falkordb/canvas": "^0.0.7", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", From 77d26952e295914d708dd42ccb26115e42da8e45 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 07:52:12 +0000 Subject: [PATCH 25/75] Initial plan From 95ee131e8002eef1bbe28c9a862a39d092f187ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 07:57:08 +0000 Subject: [PATCH 26/75] Upgrade Next.js to 15.5.9 and eslint-config-next to 15.5.9 Co-authored-by: gkorland <753206+gkorland@users.noreply.github.com> --- package-lock.json | 36 ++++++++++++++++++++---------------- package.json | 4 ++-- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 18543659..7dbc4ca0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.0", "lucide-react": "^0.441.0", - "next": "^15.5.7", + "next": "^15.5.9", "playwright": "^1.49.1", "react": "^18", "react-dom": "^18", @@ -41,7 +41,7 @@ "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9", - "eslint-config-next": "^15.1.2", + "eslint-config-next": "^15.5.9", "tailwindcss": "^3.4.3", "typescript": "^5" } @@ -850,16 +850,17 @@ } }, "node_modules/@next/env": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz", - "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz", + "integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.1.2.tgz", - "integrity": "sha512-sgfw3+WdaYOGPKCvM1L+UucBmRfh8V2Ygefp7ELON0+0vY7uohQwXXnVWg3rY7mXDKharQR3o7uedpfvnU2hlQ==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.9.tgz", + "integrity": "sha512-kUzXx0iFiXw27cQAViE1yKWnz/nF8JzRmwgMRTMh8qMY90crNsdXJRh2e+R0vBpFR3kk1yvAR7wev7+fCCb79Q==", "dev": true, + "license": "MIT", "dependencies": { "fast-glob": "3.3.1" } @@ -869,6 +870,7 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -885,6 +887,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -4404,12 +4407,13 @@ } }, "node_modules/eslint-config-next": { - "version": "15.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.1.2.tgz", - "integrity": "sha512-PrMm1/4zWSJ689wd/ypWIR5ZF1uvmp3EkgpgBV1Yu6PhEobBjXMGgT8bVNelwl17LXojO8D5ePFRiI4qXjsPRA==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.9.tgz", + "integrity": "sha512-852JYI3NkFNzW8CqsMhI0K2CDRxTObdZ2jQJj5CtpEaOkYHn13107tHpNuD/h0WRpU4FAbCdUaxQsrfBtNK9Kw==", "dev": true, + "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.1.2", + "@next/eslint-plugin-next": "15.5.9", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -6207,12 +6211,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", - "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", + "version": "15.5.9", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", + "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", "license": "MIT", "dependencies": { - "@next/env": "15.5.7", + "@next/env": "15.5.9", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", diff --git a/package.json b/package.json index 6ed6c16a..8608f2b9 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.0", "lucide-react": "^0.441.0", - "next": "^15.5.7", + "next": "^15.5.9", "playwright": "^1.49.1", "react": "^18", "react-dom": "^18", @@ -42,7 +42,7 @@ "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.13", "eslint": "^9", - "eslint-config-next": "^15.1.2", + "eslint-config-next": "^15.5.9", "tailwindcss": "^3.4.3", "typescript": "^5" } From b0fb8f5475536b64ee44d6bc2816fcdb97895cbd Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 24 Dec 2025 14:12:31 +0200 Subject: [PATCH 27/75] Update FalkorDB canvas dependency to version 0.0.8 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d13c2c4..de1d59b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.2.0", "dependencies": { - "@falkordb/canvas": "^0.0.7", + "@falkordb/canvas": "^0.0.8", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -217,9 +217,9 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.7.tgz", - "integrity": "sha512-EGUz7xUO17+LL89dbO4Uy3ySeHmNxBN8x78b5tbHVNrQJ5kpRyTFGtKH3KqgYSkk5N4PxCxLs2AMK1GBujFjpQ==", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.8.tgz", + "integrity": "sha512-vPHEpwr7D06qUrxSbSovnH8lTdbRahlQ+wi0Hv0e7++xqvNGapMZLICrjblh/oyO2H+Ce823nTh3qZtarjdkeQ==", "license": "MIT", "dependencies": { "d3": "^7.9.0", diff --git a/package.json b/package.json index 9f7c91d7..8b6b8233 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@falkordb/canvas": "^0.0.7", + "@falkordb/canvas": "^0.0.8", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", From 3f68425f8076a2353f5e836d253e39b7b62953d0 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 30 Dec 2025 12:48:57 +0200 Subject: [PATCH 28/75] Update FalkorDB canvas dependency to version 0.0.11 and improve error handling in API routes --- app/api/repo/route.ts | 4 +-- app/components/ForceGraph.tsx | 50 +++++++---------------------------- package-lock.json | 16 +++++------ package.json | 4 +-- 4 files changed, 21 insertions(+), 53 deletions(-) diff --git a/app/api/repo/route.ts b/app/api/repo/route.ts index 421be274..f4f667f1 100644 --- a/app/api/repo/route.ts +++ b/app/api/repo/route.ts @@ -22,7 +22,7 @@ export async function GET() { return NextResponse.json({ result: repositories }, { status: 200 }) } catch (err) { console.error(err) - return NextResponse.json((err as Error).message, { status: 400 }) + return NextResponse.json(err instanceof Error ? err.message : err, { status: 400 }) } } @@ -57,6 +57,6 @@ export async function POST(request: NextRequest) { return NextResponse.json({ message: "success" }, { status: 200 }); } catch (err) { console.error(err) - return NextResponse.json((err as Error).message, { status: 400 }); + return NextResponse.json(err instanceof Error ? err.message : err, { status: 400 }); } } \ No newline at end of file diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 297c4eab..50b39d2b 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -57,10 +57,7 @@ export default function ForceGraph({ backgroundColor = "#FFFFFF", foregroundColor = "#000000" }: Props) { - const parentRef = useRef(null) const [canvasLoaded, setCanvasLoaded] = useState(false) - const [parentWidth, setParentWidth] = useState(0) - const [parentHeight, setParentHeight] = useState(0) // Load falkordb-canvas dynamically (client-only) useEffect(() => { @@ -69,33 +66,6 @@ export default function ForceGraph({ }) }, []) - // Handle parent resize - useEffect(() => { - const handleResize = () => { - if (!parentRef.current) return - setParentWidth(parentRef.current.clientWidth) - setParentHeight(parentRef.current.clientHeight) - } - - handleResize() - - const observer = new ResizeObserver(handleResize) - if (parentRef.current) { - observer.observe(parentRef.current) - } - - return () => { - observer.disconnect() - } - }, []) - - // Update canvas dimensions - useEffect(() => { - if (!canvasRef.current || !canvasLoaded) return - canvasRef.current.setWidth(parentWidth) - canvasRef.current.setHeight(parentHeight) - }, [canvasRef, parentWidth, parentHeight, canvasLoaded]) - // Update canvas colors useEffect(() => { if (!canvasRef.current || !canvasLoaded) return @@ -144,7 +114,7 @@ export default function ForceGraph({ // Update event handlers useEffect(() => { if (!canvasRef.current || !canvasLoaded) return - + canvasRef.current.setConfig({ onNodeClick: handleNodeClick, onNodeRightClick: handleNodeRightClick, @@ -156,15 +126,15 @@ export default function ForceGraph({ onZoom }) }, [ - handleNodeClick, - handleNodeRightClick, - handleLinkClick, - handleLinkRightClick, - onBackgroundClick, - onBackgroundRightClick, + handleNodeClick, + handleNodeRightClick, + handleLinkClick, + handleLinkRightClick, + onBackgroundClick, + onBackgroundRightClick, onEngineStop, onZoom, - canvasRef, + canvasRef, canvasLoaded ]) @@ -178,8 +148,6 @@ export default function ForceGraph({ }, [canvasRef, data, canvasLoaded]) return ( -
- -
+ ) } diff --git a/package-lock.json b/package-lock.json index de1d59b9..0065a0c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.2.0", "dependencies": { - "@falkordb/canvas": "^0.0.8", + "@falkordb/canvas": "^0.0.11", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -42,7 +42,7 @@ }, "devDependencies": { "@playwright/test": "^1.50.1", - "@types/node": "^22.15.26", + "@types/node": "^20.19.4", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@types/react-syntax-highlighter": "^15.5.13", @@ -217,9 +217,9 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.8.tgz", - "integrity": "sha512-vPHEpwr7D06qUrxSbSovnH8lTdbRahlQ+wi0Hv0e7++xqvNGapMZLICrjblh/oyO2H+Ce823nTh3qZtarjdkeQ==", + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.11.tgz", + "integrity": "sha512-Wc+p2+u24+OKBZ3i8BRBJdArEpuay4n9EDoeLnrf1tcWV4oIW7amRatc5a+eIuPF3vrsGJdzWbpWC2/X7MLVyQ==", "license": "MIT", "dependencies": { "d3": "^7.9.0", @@ -1953,9 +1953,9 @@ "integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==" }, "node_modules/@types/node": { - "version": "22.19.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", - "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 8b6b8233..6414a751 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@falkordb/canvas": "^0.0.8", + "@falkordb/canvas": "^0.0.11", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -43,7 +43,7 @@ }, "devDependencies": { "@playwright/test": "^1.50.1", - "@types/node": "^22.15.26", + "@types/node": "^20.19.4", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@types/react-syntax-highlighter": "^15.5.13", From f9d16ad277effeaa0da0a54128e96c25f5e81618 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 30 Dec 2025 13:52:26 +0200 Subject: [PATCH 29/75] Refactor ForceGraph and CodeGraph components to integrate cooldown ticks state management and update FalkorDB canvas dependency to version 0.0.12 --- app/components/ForceGraph.tsx | 1 - app/components/code-graph.tsx | 3 + app/components/graphView.tsx | 6 +- app/components/toolbar.tsx | 18 +- app/page.tsx | 2 + components/ui/switch.tsx | 29 +++ package-lock.json | 390 ++++++++++++++++++++-------------- package.json | 3 +- 8 files changed, 284 insertions(+), 168 deletions(-) create mode 100644 components/ui/switch.tsx diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 50b39d2b..522e47f6 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -52,7 +52,6 @@ export default function ForceGraph({ onBackgroundRightClick, onZoom, onEngineStop, - onNodeDragEnd, cooldownTicks, backgroundColor = "#FFFFFF", foregroundColor = "#000000" diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 6d0c7503..f7f1f9b7 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -332,6 +332,7 @@ export function CodeGraph({ selectedPathId={selectedPathId} setSelectedPathId={setSelectedPathId} cooldownTicks={cooldownTicks} + setCooldownTicks={setCooldownTicks} setZoomedNodes={setZoomedNodes} zoomedNodes={zoomedNodes} /> @@ -365,6 +366,8 @@ export function CodeGraph({ className="gap-4" canvasRef={chartRef} handleDownloadImage={handleDownloadImage} + setCooldownTicks={setCooldownTicks} + cooldownTicks={cooldownTicks} />
diff --git a/app/components/graphView.tsx b/app/components/graphView.tsx index fe15c9ff..4ca440e2 100644 --- a/app/components/graphView.tsx +++ b/app/components/graphView.tsx @@ -30,13 +30,13 @@ interface Props { selectedPathId: number | undefined setSelectedPathId: (selectedPathId: number) => void cooldownTicks: number | undefined + setCooldownTicks: Dispatch> setZoomedNodes: Dispatch> zoomedNodes: Node[] } export default function GraphView({ data, - graph, chartRef: canvasRef, selectedObj, setSelectedObj, @@ -50,6 +50,7 @@ export default function GraphView({ selectedPathId, setSelectedPathId, cooldownTicks, + setCooldownTicks, zoomedNodes, setZoomedNodes }: Props) { @@ -121,8 +122,11 @@ export default function GraphView({ } const handleEngineStop = () => { + if (cooldownTicks === 0) return + canvasRef.current?.zoomToFit(zoomedNodes.length === 1 ? 4 : 1, (n: GraphNode) => zoomedNodes.some(node => node.id === n.id)) setZoomedNodes([]) + setCooldownTicks(0) } return ( diff --git a/app/components/toolbar.tsx b/app/components/toolbar.tsx index 10b3404b..1327febd 100644 --- a/app/components/toolbar.tsx +++ b/app/components/toolbar.tsx @@ -1,18 +1,21 @@ import { Download, Fullscreen, ZoomIn, ZoomOut } from "lucide-react"; import { cn } from "@/lib/utils" import { GraphRef } from "@/lib/utils"; +import { Switch } from "@/components/ui/switch"; interface Props { canvasRef: GraphRef className?: string handleDownloadImage?: () => void + setCooldownTicks: (ticks?: 0) => void + cooldownTicks: number | undefined } -export function Toolbar({ canvasRef, className, handleDownloadImage }: Props) { +export function Toolbar({ canvasRef, className, handleDownloadImage, setCooldownTicks, cooldownTicks }: Props) { const handleZoomClick = (changefactor: number) => { const canvas = canvasRef.current - + if (canvas) { canvas.zoom(canvas.getZoom() * changefactor) } @@ -20,14 +23,21 @@ export function Toolbar({ canvasRef, className, handleDownloadImage }: Props) { const handleCenterClick = () => { const canvas = canvasRef.current - + if (canvas) { canvas.zoomToFit() } } return ( -
+
+ { + setCooldownTicks(cooldownTicks === undefined ? 0 : undefined) + }} + /> @@ -89,11 +90,11 @@ export default function ElementMenu({ obj, objects, setPath, handleRemove, posit title="Copy src to clipboard" onClick={async () => { try { - await navigator.clipboard.writeText(obj.src || ""); + await navigator.clipboard.writeText(obj.data.src || ""); } catch (err) { // Fallback for older browsers const textArea = document.createElement('textarea'); - textArea.value = obj.src || ""; + textArea.value = obj.data.src || ""; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; document.body.appendChild(textArea); diff --git a/app/components/graphView.tsx b/app/components/graphView.tsx index 4ca440e2..fba164ad 100644 --- a/app/components/graphView.tsx +++ b/app/components/graphView.tsx @@ -1,12 +1,12 @@ 'use client' import { Graph, GraphData, Link, Node } from './model'; -import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'; -import { Path } from '@/lib/utils'; +import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react'; +import { Path, PATH_COLOR } from '@/lib/utils'; import { Fullscreen } from 'lucide-react'; import { GraphRef } from '@/lib/utils'; import ForceGraph from './ForceGraph'; -import { GraphNode } from '@falkordb/canvas'; +import { GraphLink, GraphNode } from '@falkordb/canvas'; export interface Position { x: number, @@ -23,7 +23,7 @@ interface Props { selectedObjects: Node[] setSelectedObjects: Dispatch> setPosition: Dispatch> - handleExpand: (nodes: Node[], expand: boolean) => void + handleExpand: (nodes: Node[], expand: boolean) => void isShowPath: boolean setPath: Dispatch> isPathResponse: boolean | undefined @@ -35,8 +35,12 @@ interface Props { zoomedNodes: Node[] } +const NODE_SIZE = 6; +const PADDING = 2; + export default function GraphView({ data, + graph, chartRef: canvasRef, selectedObj, setSelectedObj, @@ -72,13 +76,13 @@ export default function GraphView({ } }, []) - const unsetSelectedObjects = (evt?: MouseEvent) => { + const unsetSelectedObjects = useCallback((evt?: MouseEvent) => { if (evt?.ctrlKey || (!selectedObj && selectedObjects.length === 0)) return setSelectedObj(undefined) setSelectedObjects([]) - } + }, [selectedObj, selectedObjects, setSelectedObj, setSelectedObjects]) - const handleRightClick = (element: Node | Link, evt: MouseEvent) => { + const handleRightClick = useCallback((element: Node | Link, evt: MouseEvent) => { if (evt.ctrlKey && "category" in element) { if (selectedObjects.some(obj => obj.id === element.id)) { setSelectedObjects(selectedObjects.filter(obj => obj.id !== element.id)) @@ -92,7 +96,7 @@ export default function GraphView({ setSelectedObj(element) setPosition({ x: evt.clientX, y: evt.clientY }) - } + }, [selectedObjects, setSelectedObjects, setSelectedObj, setPosition]) const handleLinkClick = (link: Link, evt: MouseEvent) => { unsetSelectedObjects(evt) @@ -100,34 +104,220 @@ export default function GraphView({ setSelectedPathId(link.id) } - const handleNodeClick = async (node: Node) => { + const handleNodeClick = useCallback(async (node: Node) => { const now = new Date() const { date, name } = lastClick.current - const isDoubleClick = now.getTime() - date.getTime() < 1000 && name === node.name - lastClick.current = { date: now, name: node.name } + const isDoubleClick = now.getTime() - date.getTime() < 1000 && name === node.data.name + lastClick.current = { date: now, name: node.data.name } if (isDoubleClick) { handleExpand([node], !node.expand) } else if (isShowPath) { setPath(prev => { if (!prev?.start?.name || (prev.end?.name && prev.end?.name !== "")) { - return ({ start: { id: Number(node.id), name: node.name } }) + return ({ start: { id: Number(node.id), name: node.data.name } }) } else { - return ({ end: { id: Number(node.id), name: node.name }, start: prev.start }) + return ({ end: { id: Number(node.id), name: node.data.name }, start: prev.start }) } }) return } - } + }, [handleExpand, isShowPath, setPath]) - const handleEngineStop = () => { - if (cooldownTicks === 0) return + const handleEngineStop = useCallback(() => { + if (zoomedNodes.length > 0) { + canvasRef.current?.zoomToFit(zoomedNodes.length === 1 ? 4 : 1, (n: GraphNode) => zoomedNodes.some(node => node.id === n.id)) + setZoomedNodes([]) + } + + if (cooldownTicks !== -1) return - canvasRef.current?.zoomToFit(zoomedNodes.length === 1 ? 4 : 1, (n: GraphNode) => zoomedNodes.some(node => node.id === n.id)) - setZoomedNodes([]) setCooldownTicks(0) - } + }, [zoomedNodes, cooldownTicks, canvasRef]) + + const nodeCanvasObject = useCallback((node: GraphNode, ctx: CanvasRenderingContext2D) => { + if (!node.x || !node.y) return + + if (isPathResponse) { + if (node.data.isPathSelected) { + ctx.fillStyle = node.color; + ctx.strokeStyle = PATH_COLOR; + ctx.lineWidth = 1.5 + } else if (node.data.isPath) { + ctx.fillStyle = node.color; + ctx.strokeStyle = PATH_COLOR; + ctx.lineWidth = 1 + } else { + ctx.fillStyle = '#E5E5E5'; + ctx.strokeStyle = 'gray'; + ctx.lineWidth = 1 + } + } else if (isPathResponse === undefined) { + if (node.data.isPathSelected) { + ctx.fillStyle = node.color; + ctx.strokeStyle = PATH_COLOR; + ctx.lineWidth = 1.5 + } else if (node.data.isPath) { + ctx.fillStyle = node.color; + ctx.strokeStyle = PATH_COLOR; + ctx.lineWidth = 1 + } else { + ctx.fillStyle = node.color; + ctx.strokeStyle = 'black'; + ctx.lineWidth = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id ? 1.5 : 1 + } + } else { + ctx.fillStyle = node.color; + ctx.strokeStyle = 'black'; + ctx.lineWidth = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id ? 1.5 : 1 + } + + ctx.beginPath(); + ctx.arc(node.x, node.y, NODE_SIZE + ctx.lineWidth / 2, 0, 2 * Math.PI, false); + ctx.stroke(); + ctx.fill(); + + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = '4px Arial'; + let name = node.data.name || ""; + const textWidth = ctx.measureText(name).width; + const ellipsis = '...'; + const ellipsisWidth = ctx.measureText(ellipsis).width; + const nodeSize = (NODE_SIZE + ctx.lineWidth / 2) * 2 - PADDING; + + // truncate text if it's too long + if (textWidth > nodeSize) { + while (name.length > 0 && ctx.measureText(name).width + ellipsisWidth > nodeSize) { + name = name.slice(0, -1); + } + name += ellipsis; + } + + // add label + ctx.fillText(name, node.x, node.y); + }, [selectedObj, selectedObjects, isPathResponse]) + + const nodePointerAreaPaint = useCallback((node: GraphNode, color: string, ctx: CanvasRenderingContext2D) => { + if (!node.x || !node.y) return + + ctx.fillStyle = color; + ctx.beginPath(); + ctx.arc(node.x, node.y, NODE_SIZE + 4 + ctx.lineWidth / 2, 0, 2 * Math.PI, false); + ctx.fill(); + }, []) + + const linkCanvasObject = useCallback((link: GraphLink, ctx: CanvasRenderingContext2D) => { + const start = link.source; + const end = link.target; + + if (!start.x || !start.y || !end.x || !end.y) return + + const sameNodesLinks = graph.Elements.links.filter(l => (l.source === start.id && l.target === end.id) || (l.target === start.id && l.source === end.id)) + const index = sameNodesLinks.findIndex(l => l.id === link.id) || 0 + const even = index % 2 === 0 + let curve + + if (start.id === end.id) { + if (even) { + curve = Math.floor(-(index / 2)) - 3 + } else { + curve = Math.floor((index + 1) / 2) + 2 + } + + link.curve = curve * 0.1 + + const radius = NODE_SIZE * link.curve * 6.2; + const angleOffset = -Math.PI / 4; // 45 degrees offset for text alignment + const textX = start.x + radius * Math.cos(angleOffset); + const textY = start.y + radius * Math.sin(angleOffset); + + ctx.save(); + ctx.translate(textX, textY); + ctx.rotate(-angleOffset); + } else { + if (even) { + curve = Math.floor(-(index / 2)) + } else { + curve = Math.floor((index + 1) / 2) + } + + link.curve = curve * 0.1 + + const midX = (start.x + end.x) / 2 + (end.y - start.y) * (link.curve / 2); + const midY = (start.y + end.y) / 2 + (start.x - end.x) * (link.curve / 2); + + let textAngle = Math.atan2(end.y - start.y, end.x - start.x) + + // maintain label vertical orientation for legibility + if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle); + if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle); + + ctx.save(); + ctx.translate(midX, midY); + ctx.rotate(textAngle); + } + + // add label + ctx.globalAlpha = 1; + ctx.fillStyle = 'black'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = '2px Arial'; + ctx.fillText(link.relationship, 0, 0); + ctx.restore() + }, [graph.Elements.links]) + + const linkPointerAreaPaint = useCallback((link: GraphLink, color: string, ctx: CanvasRenderingContext2D) => { + const start = link.source; + const end = link.target; + + if (!start.x || !start.y || !end.x || !end.y) return + + ctx.fillStyle = color; + + // Calculate curve the same way as linkCanvasObject + const sameNodesLinks = graph.Elements.links.filter(l => (l.source === start.id && l.target === end.id) || (l.target === start.id && l.source === end.id)) + const index = sameNodesLinks.findIndex(l => l.id === link.id) || 0 + const even = index % 2 === 0 + let curve + + if (start.id === end.id) { + // Self-loop: draw clickable area at the loop position + if (even) { + curve = Math.floor(-(index / 2)) - 3 + } else { + curve = Math.floor((index + 1) / 2) + 2 + } + + const curvature = curve * 0.1 + const radius = NODE_SIZE * curvature * 6.2; + const angleOffset = -Math.PI / 4; + const textX = start.x + radius * Math.cos(angleOffset); + const textY = start.y + radius * Math.sin(angleOffset); + + ctx.beginPath(); + ctx.arc(textX, textY, 5, 0, 2 * Math.PI, false); + ctx.fill(); + } else { + // Regular link: draw clickable area at the curve midpoint + if (even) { + curve = Math.floor(-(index / 2)) + } else { + curve = Math.floor((index + 1) / 2) + } + + const curvature = curve * 0.1 + const midX = (start.x + end.x) / 2 + (end.y - start.y) * (curvature / 2); + const midY = (start.y + end.y) / 2 + (start.x - end.x) * (curvature / 2); + + ctx.beginPath(); + ctx.arc(midX, midY, 5, 0, 2 * Math.PI, false); + ctx.fill(); + } + }, [graph.Elements.links]) return (
@@ -147,6 +337,10 @@ export default function GraphView({ onBackgroundRightClick={unsetSelectedObjects} onZoom={() => unsetSelectedObjects()} onEngineStop={handleEngineStop} + nodeCanvasObject={nodeCanvasObject} + nodePointerAreaPaint={nodePointerAreaPaint} + linkCanvasObject={linkCanvasObject} + linkPointerAreaPaint={linkPointerAreaPaint} cooldownTicks={cooldownTicks} />
diff --git a/app/components/model.ts b/app/components/model.ts index 7a9274bd..6875f941 100644 --- a/app/components/model.ts +++ b/app/components/model.ts @@ -16,7 +16,6 @@ export interface Label { export interface Node { id: number, - name: string, category: string, color: string, visible: boolean, @@ -24,9 +23,11 @@ export interface Node { expand: boolean, isPathSelected: boolean, isPath: boolean, - [key: string]: any, + data: { + name: string, + [key: string]: any, + } } - export interface Link { id: number, source: number, @@ -35,9 +36,10 @@ export interface Link { visible: boolean, isPathSelected: boolean, isPath: boolean, - curve: number, color: string, - [key: string]: any, + data: { + [key: string]: any, + }, } const COLORS_ORDER_NAME = [ @@ -161,18 +163,18 @@ export class Graph { node = { id: nodeData.id, - name: nodeData.name, color: getCategoryColorValue(category.index), category: category.name, expand: false, visible: true, collapsed, isPath: !!path, - isPathSelected: path?.start?.id === nodeData.id || path?.end?.id === nodeData.id + isPathSelected: path?.start?.id === nodeData.id || path?.end?.id === nodeData.id, + data: { + ...nodeData.properties, + } } - Object.entries(nodeData.properties).forEach(([key, value]) => { - node[key] = value - }) + this.nodesMap.set(nodeData.id, node) this.elements.nodes.push(node) newElements.nodes.push(node) @@ -195,14 +197,17 @@ export class Graph { if (!source) { source = { id: edgeData.src_node, - name: edgeData.src_node, color: getCategoryColorValue(), category: "", expand: false, visible: true, collapsed, isPath: !!path, - isPathSelected: path?.start?.id === edgeData.src_node || path?.end?.id === edgeData.src_node + isPathSelected: path?.start?.id === edgeData.src_node || path?.end?.id === edgeData.src_node, + data: { + name: edgeData.src_node + } + } this.nodesMap.set(edgeData.src_node, source) } @@ -210,14 +215,16 @@ export class Graph { if (!target) { target = { id: edgeData.dest_node, - name: edgeData.dest_node, color: getCategoryColorValue(), category: "", expand: false, visible: true, collapsed, isPath: !!path, - isPathSelected: path?.start?.id === edgeData.dest_node || path?.end?.id === edgeData.dest_node + isPathSelected: path?.start?.id === edgeData.dest_node || path?.end?.id === edgeData.dest_node, + data: { + name: edgeData.dest_node + } } this.nodesMap.set(edgeData.dest_node, target) } @@ -235,45 +242,17 @@ export class Graph { target: edgeData.dest_node, label: edgeData.relation, visible: true, - expand: false, color: "#999999", - collapsed, isPathSelected: false, isPath: !!path, - curve: 0 + data: { ...edgeData.properties } } + this.linksMap.set(edgeData.id, link) this.elements.links.push(link) newElements.links.push(link) }) - newElements.links.forEach(link => { - const start = link.source - const end = link.target - const sameNodesLinks = this.elements.links.filter(l => (l.source === start && l.target === end) || (l.target === start && l.source === end)) - const index = sameNodesLinks.findIndex(l => l.id === link.id) ?? 0 - const even = index % 2 === 0 - let curve - - if (start === end) { - if (even) { - curve = Math.floor(-(index / 2)) - 3 - } else { - curve = Math.floor((index + 1) / 2) + 2 - } - } else { - if (even) { - curve = Math.floor(-(index / 2)) - } else { - curve = Math.floor((index + 1) / 2) - } - - } - - link.curve = curve * 0.1 - }) - - return newElements } diff --git a/app/components/toolbar.tsx b/app/components/toolbar.tsx index 1327febd..b4d11258 100644 --- a/app/components/toolbar.tsx +++ b/app/components/toolbar.tsx @@ -33,9 +33,9 @@ export function Toolbar({ canvasRef, className, handleDownloadImage, setCooldown
{ - setCooldownTicks(cooldownTicks === undefined ? 0 : undefined) + setCooldownTicks(cooldownTicks !== 0 ? 0 : undefined) }} /> } { - (graph.getElements().some(e => !e.visible)) && + hasHiddenElements &&
Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) || isShowPath ? (node: Node, _evt: MouseEvent) => handleNodeClick(node) : (node: Node, evt: MouseEvent) => handleRightClick(node, evt)} diff --git a/app/page.tsx b/app/page.tsx index 49b4fb05..edc5b978 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -142,32 +142,37 @@ export default function Home() { } async function onFetchGraph(graphName: string) { + try { + const result = await fetch(`/api/repo/${prepareArg(graphName)}`, { + method: 'GET' + }) + + if (!result.ok) { + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: await result.text(), + }) + return + } - setGraph(Graph.empty()) + const json = await result.json() + const g = Graph.create(json.result.entities, graphName) + setGraph(g) - const result = await fetch(`/api/repo/${prepareArg(graphName)}`, { - method: 'GET' - }) + if (cooldownTicks === 0) setCooldownTicks(-1) - if (!result.ok) { + setIsPathResponse(false) + chatPanel.current?.expand() + // @ts-ignore + window.graph = g + } catch (error) { toast({ variant: "destructive", title: "Uh oh! Something went wrong.", - description: await result.text(), + description: "Failed to load repository graph. Please try again.", }) - return } - - const json = await result.json() - const g = Graph.create(json.result.entities, graphName) - setGraph(g) - - if (cooldownTicks === 0) setCooldownTicks(-1) - - setIsPathResponse(false) - chatPanel.current?.expand() - // @ts-ignore - window.graph = g } // Send the user query to the server to expand a node @@ -416,8 +421,9 @@ export default function Home() {
- + Date: Tue, 24 Feb 2026 11:49:22 +0200 Subject: [PATCH 56/75] Remove test.only from node path connection test to ensure all tests run --- e2e/tests/canvas.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e/tests/canvas.spec.ts b/e2e/tests/canvas.spec.ts index ef53a439..75e8c514 100644 --- a/e2e/tests/canvas.spec.ts +++ b/e2e/tests/canvas.spec.ts @@ -176,8 +176,7 @@ test.describe("Canvas tests", () => { }); nodesPath.forEach(({firstNode, secondNode}) => { - // BUG: canvas.getGraphData() returns stale data - isPath property not syncing after path selection - test.only(`Verify successful node path connection in canvas between ${firstNode} and ${secondNode} via UI`, async () => { + test(`Verify successful node path connection in canvas between ${firstNode} and ${secondNode} via UI`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.clickOnShowPathBtn("Show the path"); From 0d98edaf9a8877956fc255cf8e5a68e64bd51661 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 24 Feb 2026 11:51:23 +0200 Subject: [PATCH 57/75] Update graph data retrieval to use graphDesktop function for improved compatibility --- e2e/logic/POM/codeGraph.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 612b2d68..40de1472 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -590,7 +590,7 @@ export default class CodeGraph extends BasePage { await this.waitForCanvasAnimationToEnd(); // Wait for the graph data to be available on window object (set by handleEngineStop) await this.page.waitForFunction(() => { - const data = (window as any).graph?.(); + const data = (window as any).graphDesktop(); // Check both possible structures: { nodes } or { elements: { nodes } } return data && ((Array.isArray(data.nodes) && data.nodes.length > 0) || (data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0)); @@ -598,7 +598,7 @@ export default class CodeGraph extends BasePage { { timeout: 5000 }); const graphData = await this.page.evaluate(() => { - return (window as any).graph?.(); + return (window as any).graphDesktop(); }); let transformData: any = null; From 8462d3b56003875dd63ea4834afcc99ff4e481f8 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 24 Feb 2026 12:01:01 +0200 Subject: [PATCH 58/75] Fix canvas selection logic in handleDownloadImage for improved reliability --- app/page.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index edc5b978..d755ea8b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -269,8 +269,9 @@ export default function Home() { const handleDownloadImage = async () => { try { - const canvases = document.querySelectorAll('.force-graph-container canvas') as NodeListOf; - if (!canvases) { + const canvases = Array.from(document.querySelectorAll('falkordb-canvas').values()).map(canvas => canvas.shadowRoot?.querySelector('canvas')).filter((c): c is HTMLCanvasElement => !!c); + + if (canvases.length === 0) { toast({ title: "Error", description: "Canvas not found", From d764425b6c43590bd216c0a081dcad112dd37988 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 24 Feb 2026 12:23:27 +0200 Subject: [PATCH 59/75] Remove debug console logs from canvas tests for cleaner output --- e2e/tests/canvas.spec.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/e2e/tests/canvas.spec.ts b/e2e/tests/canvas.spec.ts index 75e8c514..7ca9cb18 100644 --- a/e2e/tests/canvas.spec.ts +++ b/e2e/tests/canvas.spec.ts @@ -105,17 +105,13 @@ test.describe("Canvas tests", () => { await codeGraph.insertInputForShowPath("2", path.secondNode); const initialGraph = await codeGraph.getGraphNodes(); const firstNode = findNodeByName(initialGraph, path.firstNode); - console.log("firstNode: ", firstNode); const secondNode = findNodeByName(initialGraph, path.secondNode); - console.log("secondNode: ", secondNode); expect(firstNode.isPath).toBe(true); expect(secondNode.isPath).toBe(true); await codeGraph.clickOnClearGraphBtn(); const updateGraph = await codeGraph.getGraphNodes(); const firstNode1 = findNodeByName(updateGraph, path.firstNode); const secondNode1 = findNodeByName(updateGraph, path.secondNode); - console.log("firstNode1: ", firstNode1); - console.log("secondNode1: ", secondNode1); expect(firstNode1.isPath).toBe(false); expect(secondNode1.isPath).toBe(false); }); @@ -184,12 +180,8 @@ test.describe("Canvas tests", () => { await codeGraph.insertInputForShowPath("2", secondNode); const result = await codeGraph.getGraphDetails(); const firstNodeRes = findNodeByName(result?.nodes, firstNode); - console.log("firstNodeRes: ", firstNodeRes); const secondnodeRes = findNodeByName(result?.nodes, secondNode); - console.log("secondNodeRes: ", secondnodeRes); - console.log("firstNodeRes: ", firstNodeRes.isPath); - console.log("secondnodeRes: ", secondnodeRes.isPath); expect(firstNodeRes).toBeDefined(); expect(secondnodeRes).toBeDefined(); From a2049969c1a0aec076f74ea21ed4811a614d6ddb Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 24 Feb 2026 12:24:33 +0200 Subject: [PATCH 60/75] Remove stale data bug comments from canvas tests for clarity --- e2e/tests/canvas.spec.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/e2e/tests/canvas.spec.ts b/e2e/tests/canvas.spec.ts index 7ca9cb18..eac29290 100644 --- a/e2e/tests/canvas.spec.ts +++ b/e2e/tests/canvas.spec.ts @@ -55,7 +55,6 @@ test.describe("Canvas tests", () => { }) - // BUG: canvas.getGraphData() returns stale data - visibility state not syncing after hide test(`Validate node hide functionality via element menu in canvas for ${nodes[0].nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); @@ -83,7 +82,6 @@ test.describe("Canvas tests", () => { categories.forEach((category, index) => { const checkboxIndex = index + 1; - // BUG: canvas.getGraphData() returns stale data - visibility state not syncing after checkbox uncheck test(`Verify that unchecking the ${category} checkbox hides ${category} nodes on the canvas`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); @@ -95,7 +93,6 @@ test.describe("Canvas tests", () => { }) nodesPath.forEach((path) => { - // BUG: canvas.getGraphData() returns stale data - isPath property not syncing after path selection test(`Verify "Clear graph" button resets canvas view for path ${path.firstNode} and ${path.secondNode}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); @@ -131,7 +128,6 @@ test.describe("Canvas tests", () => { for (let index = 0; index < 3; index++) { const nodeIndex: number = index + 1; - // BUG: canvas.getGraphData() returns stale data - node x/y positions not updating after drag test(`Validate canvas node dragging for node: ${index}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); @@ -213,7 +209,6 @@ test.describe("Canvas tests", () => { }); }) - // BUG: download button click does not trigger a file download test(`Verify file download is triggered and saved after clicking download`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); From 80d7c4fe42dbce8b1d38adb4350a24a9c0a373c7 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 24 Feb 2026 12:54:44 +0200 Subject: [PATCH 61/75] Update Playwright imports to use @playwright/test and remove outdated dependencies --- e2e/infra/ui/basePage.ts | 2 +- e2e/infra/ui/browserWrapper.ts | 2 +- e2e/logic/POM/codeGraph.ts | 10 ++-- package-lock.json | 91 +++++++++++++++++----------------- package.json | 6 +-- 5 files changed, 56 insertions(+), 55 deletions(-) diff --git a/e2e/infra/ui/basePage.ts b/e2e/infra/ui/basePage.ts index 69baf604..b3aaa253 100644 --- a/e2e/infra/ui/basePage.ts +++ b/e2e/infra/ui/basePage.ts @@ -1,4 +1,4 @@ -import { Page } from 'playwright'; +import { Page } from '@playwright/test'; export default class BasePage { protected page: Page; diff --git a/e2e/infra/ui/browserWrapper.ts b/e2e/infra/ui/browserWrapper.ts index 985bf25c..77fa5d07 100644 --- a/e2e/infra/ui/browserWrapper.ts +++ b/e2e/infra/ui/browserWrapper.ts @@ -1,4 +1,4 @@ -import { chromium, Browser, BrowserContext, Page } from 'playwright'; +import { chromium, Browser, BrowserContext, Page } from '@playwright/test'; import BasePage from './basePage'; export default class BrowserWrapper { diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 40de1472..99b02355 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -1,4 +1,4 @@ -import { Download, Locator, Page } from "playwright"; +import { Download, Locator, Page } from "@playwright/test"; import BasePage from "../../infra/ui/basePage"; import { interactWhenVisible, waitForElementToBeVisible, waitForStableText, waitToBeEnabled } from "../utils"; @@ -499,10 +499,10 @@ export default class CodeGraph extends BasePage { await this.page.mouse.click(10, 10); await this.clearGraphBtn.click(); } - + async clickOnUnhideNodesBtn(): Promise { await interactWhenVisible( - this.unhideNodesBtn, (el) => el.click(),`Unhide Nodes Button` + this.unhideNodesBtn, (el) => el.click(), `Unhide Nodes Button` ); } @@ -593,7 +593,7 @@ export default class CodeGraph extends BasePage { const data = (window as any).graphDesktop(); // Check both possible structures: { nodes } or { elements: { nodes } } return data && ((Array.isArray(data.nodes) && data.nodes.length > 0) || - (data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0)); + (data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0)); }, { timeout: 5000 }); @@ -693,7 +693,7 @@ export default class CodeGraph extends BasePage { const data = (window as any).graph?.(); // Check both possible structures: { nodes } or { elements: { nodes } } return data && ((Array.isArray(data.nodes) && data.nodes.length > 0) || - (data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0)); + (data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0)); }, { timeout: 5000 }); const graphData = await this.page.evaluate(() => { diff --git a/package-lock.json b/package-lock.json index 691e885a..e6346077 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,6 @@ "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", "next": "^15.5.8", - "playwright": "^1.49.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-gtm-module": "^2.0.11", @@ -1080,6 +1079,52 @@ "node": ">=18" } }, + "node_modules/@playwright/test/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/@playwright/test/node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/@playwright/test/node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -6651,50 +6696,6 @@ "node": ">= 6" } }, - "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", diff --git a/package.json b/package.json index 40da1a8b..d75081f9 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "HOST=0.0.0.0 PORT=3000 next dev", "build": "next build", "start": "HOST=0.0.0.0 PORT=3000 next start", - "lint": "next lint" + "lint": "next lint", + "typecheck": "tsc --noEmit" }, "dependencies": { "@falkordb/canvas": "^0.0.37", @@ -30,7 +31,6 @@ "html2canvas": "^1.4.1", "lucide-react": "^0.486.0", "next": "^15.5.8", - "playwright": "^1.49.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-gtm-module": "^2.0.11", @@ -53,4 +53,4 @@ "tailwindcss": "^3.4.17", "typescript": "^5.7.3" } -} +} \ No newline at end of file From 40eba8f1f4ceaea37e27793918517f200c14bd12 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 24 Feb 2026 15:57:08 +0200 Subject: [PATCH 62/75] Enhance graph components with new link handling and update dependencies --- app/components/ForceGraph.tsx | 3 ++ app/components/chat.tsx | 87 +++++++++++++++++++++++++++++------ app/components/code-graph.tsx | 9 ++-- app/components/graphView.tsx | 81 +++++++++++++++++++++++--------- app/page.tsx | 8 ++-- package-lock.json | 8 ++-- package.json | 2 +- 7 files changed, 150 insertions(+), 48 deletions(-) diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 6ba3196c..80c7f2fa 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -19,6 +19,7 @@ interface Props { nodePointerAreaPaint: (node: GraphNode, color: string, ctx: CanvasRenderingContext2D) => void linkCanvasObject: (link: any, ctx: CanvasRenderingContext2D) => void linkPointerAreaPaint: (link: any, color: string, ctx: CanvasRenderingContext2D) => void + linkLineDash: (link: any) => number[] | null onZoom: () => void onEngineStop: () => void cooldownTicks: number | undefined @@ -61,6 +62,7 @@ export default function ForceGraph({ nodePointerAreaPaint, linkCanvasObject, linkPointerAreaPaint, + linkLineDash, cooldownTicks, backgroundColor = "#FFFFFF", foregroundColor = "#000000" @@ -140,6 +142,7 @@ export default function ForceGraph({ onEngineStop: handleEngineStop, node: { nodeCanvasObject, nodePointerAreaPaint }, link: { linkCanvasObject, linkPointerAreaPaint }, + linkLineDash, onZoom }) }, [ diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 28107c75..ff99e169 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -9,7 +9,7 @@ import { cn, GraphRef } from "@/lib/utils"; import { TypeAnimation } from "react-type-animation"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { prepareArg } from "../utils"; -import { GraphNode } from "@falkordb/canvas"; +import { dataToGraphData, GraphLink, GraphNode } from "@falkordb/canvas"; interface Props { repo: string @@ -19,7 +19,6 @@ interface Props { selectedPathId: number | undefined isPathResponse: boolean | undefined setIsPathResponse: (isPathResponse: boolean | undefined) => void - setData: Dispatch> canvasRef: GraphRef messages: Message[] setMessages: Dispatch> @@ -27,6 +26,7 @@ interface Props { setQuery: Dispatch> selectedPath: PathData | undefined setSelectedPath: Dispatch> + setCooldownTicks: Dispatch> setChatOpen?: Dispatch> paths: PathData[] setPaths: Dispatch> @@ -52,7 +52,7 @@ const RemoveLastPath = (messages: Message[]) => { return messages } -export function Chat({ messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setData, canvasRef, paths, setPaths }: Props) { +export function Chat({ messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setCooldownTicks, canvasRef, paths, setPaths }: Props) { const [sugOpen, setSugOpen] = useState(false); @@ -273,27 +273,86 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set return } - const formattedPaths: PathData[] = json.result.paths.map((p: any) => ({ nodes: p.filter((n: any, i: number) => i % 2 === 0), links: p.filter((l: any, i: number) => i % 2 !== 0) })) - formattedPaths.forEach((p: any) => graph.extend(p, false, path)) - + const formattedPaths: PathData[] = json.result.paths.map((p: any) => ({ nodes: p.filter((_n: any, i: number) => i % 2 === 0), links: p.filter((l: any, i: number) => i % 2 !== 0) })) + const elements = formattedPaths.reduce( + (acc, p) => { + const el = graph.extend(p, false, path) + acc.nodes.push(...el.nodes) + acc.links.push(...el.links) + return acc + }, + { nodes: [], links: [] } + ) setPaths(formattedPaths) setMessages((prev) => [...prev.slice(0, -1), { type: MessageTypes.PathResponse, paths: formattedPaths, graphName: graph.Id }]); setIsPathResponse(true) const currentData = canvas.getGraphData(); - const nodesSet = new Set(formattedPaths.flatMap(p => p.nodes.map((n: Node) => n.id))); - const linksSet = new Set(formattedPaths.flatMap(p => p.links.map((l: any) => l.id))); + const nodesMap = new Map(currentData.nodes.map(n => [n.id, n])) + const linksMap = new Map(currentData.links.map(l => [l.id, l])) - currentData.nodes.forEach(n => { - n.data.isPath = nodesSet.has(n.id); + formattedPaths.flatMap(p => p.nodes).forEach(n => { + const node = nodesMap.get(n.id); + if (node) { + node.data.isPath = true; + } }); - currentData.links.forEach(l => { - l.data.isPath = linksSet.has(l.id); - l.color = linksSet.has(l.id) ? PATH_COLOR : l.color; + formattedPaths.flatMap(p => p.links).forEach(l => { + const link = linksMap.get(l.id); + + if (link) { + link.data.isPath = true; + link.color = PATH_COLOR; + } }); - canvas.setGraphData(currentData) + // Filter for only new elements + const newDataElements = { + nodes: elements.nodes.filter(n => !nodesMap.has(n.id)) + .map(({ category, color, data, id, isPath, isPathSelected, visible }) => ({ + color, + id, + labels: [category], + data: { + ...data, + isPath, + isPathSelected + }, + visible, + })), + links: elements.links.filter(l => !linksMap.has(l.id)) + .map(({ color, id, source, target, data, isPath, isPathSelected, visible, label }) => ({ + color: isPath ? PATH_COLOR : color, + id, + source, + target, + data: { + ...data, + isPath, + isPathSelected + }, + visible, + relationship: label, + })) + } + + // Convert only new data to GraphData format + const newGraphData = dataToGraphData( + newDataElements, + undefined, + new Map(currentData.nodes.map(n => [n.id, n])) + ) + + // Merge with existing data + canvasRef.current?.setGraphData({ + nodes: [...currentData.nodes, ...newGraphData.nodes], + links: [...currentData.links, ...newGraphData.links] + }) + + if (elements.nodes.length !== 0 || elements.links.length !== 0) { + setCooldownTicks(-1) + } setTimeout(() => { const nodesMap = new Map(formattedPaths.flatMap(p => p.nodes.map((n: Node) => [n.id, n]))) diff --git a/app/components/code-graph.tsx b/app/components/code-graph.tsx index 0f591502..b75f9ef1 100644 --- a/app/components/code-graph.tsx +++ b/app/components/code-graph.tsx @@ -182,7 +182,7 @@ export function CodeGraph({ if (nodes.length === 0) return; const expandedNodes: Node[] = [] - const deleteIdsMap = new Map() + const deleteIdsMap = new Set() graph.Elements = { nodes: graph.Elements.nodes.filter(node => { @@ -192,7 +192,7 @@ export function CodeGraph({ if (!isTarget) return true - deleteIdsMap.set(node.id, true) + deleteIdsMap.add(node.id) const deleted = graph.NodesMap.delete(Number(node.id)) if (deleted && node.expand) { @@ -204,7 +204,7 @@ export function CodeGraph({ links: graph.Elements.links } - deleteNeighbors(expandedNodes) + deleteNeighbors(expandedNodes)?.forEach(id => deleteIdsMap.add(id)) graph.removeLinks() @@ -273,6 +273,8 @@ export function CodeGraph({ nodes: [...currentData.nodes, ...newGraphData.nodes], links: [...currentData.links, ...newGraphData.links] }) + + setCooldownTicks(-1) } else { const deleteNodes = nodes.filter(n => n.expand) if (deleteNodes.length > 0) { @@ -287,6 +289,7 @@ export function CodeGraph({ currentData.links = currentData.links.filter(link => !deleteIdsMap.has(Number(link.source.id)) && !deleteIdsMap.has(Number(link.target.id))) canvasRef.current?.setGraphData(currentData) + setCooldownTicks(-1) } } } diff --git a/app/components/graphView.tsx b/app/components/graphView.tsx index 4c20b3b4..2554e96b 100644 --- a/app/components/graphView.tsx +++ b/app/components/graphView.tsx @@ -24,7 +24,7 @@ interface Props { selectedObjects: Node[] setSelectedObjects: Dispatch> setPosition: Dispatch> - handleExpand: (nodes: Node[], expand: boolean) => void + handleExpand: (nodes: Node[], expand: boolean) => void isShowPath: boolean setPath: Dispatch> isPathResponse: boolean | undefined @@ -207,7 +207,7 @@ export default function GraphView({ ctx.fillStyle = color; ctx.beginPath(); - ctx.arc(node.x, node.y, NODE_SIZE + 4 + ctx.lineWidth / 2, 0, 2 * Math.PI, false); + ctx.arc(node.x, node.y, NODE_SIZE + 2 + ctx.lineWidth / 2, 0, 2 * Math.PI, false); ctx.fill(); }, []) @@ -222,6 +222,9 @@ export default function GraphView({ const even = index % 2 === 0 let curve + ctx.strokeStyle = '#999999'; + ctx.lineWidth = 0.1; + if (start.id === end.id) { if (even) { curve = Math.floor(-(index / 2)) - 3 @@ -231,14 +234,25 @@ export default function GraphView({ link.curve = curve * 0.1 - const radius = NODE_SIZE * link.curve * 6.2; - const angleOffset = -Math.PI / 4; // 45 degrees offset for text alignment - const textX = start.x + radius * Math.cos(angleOffset); - const textY = start.y + radius * Math.sin(angleOffset); + const d = link.curve * 70; + + ctx.beginPath(); + ctx.moveTo(start.x, start.y); + ctx.bezierCurveTo(start.x, start.y - d, start.x + d, start.y, start.x, start.y); + ctx.stroke(); + + // Midpoint of cubic bezier: P0=(sx,sy), P1=(sx,sy-d), P2=(sx+d,sy), P3=(sx,sy) + const textX = start.x + 0.375 * d; + const textY = start.y - 0.375 * d; + + // Tangent at midpoint is (0.75d, 0.75d), angle always resolves to PI/4 + let textAngle = Math.atan2(0.75 * d, 0.75 * d); + if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle); + if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle); ctx.save(); ctx.translate(textX, textY); - ctx.rotate(-angleOffset); + ctx.rotate(textAngle); } else { if (even) { curve = Math.floor(-(index / 2)) @@ -248,8 +262,23 @@ export default function GraphView({ link.curve = curve * 0.1 - const midX = (start.x + end.x) / 2 + (end.y - start.y) * (link.curve / 2); - const midY = (start.y + end.y) / 2 + (start.x - end.x) * (link.curve / 2); + const dx = end.x - start.x; + const dy = end.y - start.y; + const dist = Math.sqrt(dx * dx + dy * dy); + const curvature = link.curve; + const cpX = (start.x + end.x) / 2 + (dy / dist) * curvature * dist; + const cpY = (start.y + end.y) / 2 + (-dx / dist) * curvature * dist; + + // Draw the quadratic bezier curve + ctx.beginPath(); + ctx.moveTo(start.x, start.y); + ctx.quadraticCurveTo(cpX, cpY, end.x, end.y); + ctx.stroke(); + + // Midpoint of quadratic bezier at t=0.5 + const t = 0.5; + const midX = (1 - t) * (1 - t) * start.x + 2 * (1 - t) * t * cpX + t * t * end.x; + const midY = (1 - t) * (1 - t) * start.y + 2 * (1 - t) * t * cpY + t * t * end.y; let textAngle = Math.atan2(end.y - start.y, end.x - start.x) @@ -272,22 +301,26 @@ export default function GraphView({ ctx.restore() }, [graph.Elements.links]) + const linkLineDash = useCallback((link: GraphLink) => { + if (link.data.isPath && !link.data.isPathSelected) return [5, 5] + return null + }, []) + const linkPointerAreaPaint = useCallback((link: GraphLink, color: string, ctx: CanvasRenderingContext2D) => { const start = link.source; const end = link.target; if (!start.x || !start.y || !end.x || !end.y) return - ctx.fillStyle = color; + ctx.strokeStyle = color; + ctx.lineWidth = 1; - // Calculate curve the same way as linkCanvasObject const sameNodesLinks = graph.Elements.links.filter(l => (l.source === start.id && l.target === end.id) || (l.target === start.id && l.source === end.id)) const index = sameNodesLinks.findIndex(l => l.id === link.id) || 0 const even = index % 2 === 0 let curve if (start.id === end.id) { - // Self-loop: draw clickable area at the loop position if (even) { curve = Math.floor(-(index / 2)) - 3 } else { @@ -295,16 +328,13 @@ export default function GraphView({ } const curvature = curve * 0.1 - const radius = NODE_SIZE * curvature * 6.2; - const angleOffset = -Math.PI / 4; - const textX = start.x + radius * Math.cos(angleOffset); - const textY = start.y + radius * Math.sin(angleOffset); + const d = curvature * 70; ctx.beginPath(); - ctx.arc(textX, textY, 5, 0, 2 * Math.PI, false); - ctx.fill(); + ctx.moveTo(start.x, start.y); + ctx.bezierCurveTo(start.x, start.y - d, start.x + d, start.y, start.x, start.y); + ctx.stroke(); } else { - // Regular link: draw clickable area at the curve midpoint if (even) { curve = Math.floor(-(index / 2)) } else { @@ -312,12 +342,16 @@ export default function GraphView({ } const curvature = curve * 0.1 - const midX = (start.x + end.x) / 2 + (end.y - start.y) * (curvature / 2); - const midY = (start.y + end.y) / 2 + (start.x - end.x) * (curvature / 2); + const dx = end.x - start.x; + const dy = end.y - start.y; + const dist = Math.sqrt(dx * dx + dy * dy); + const cpX = (start.x + end.x) / 2 + (dy / dist) * curvature * dist; + const cpY = (start.y + end.y) / 2 + (-dx / dist) * curvature * dist; ctx.beginPath(); - ctx.arc(midX, midY, 5, 0, 2 * Math.PI, false); - ctx.fill(); + ctx.moveTo(start.x, start.y); + ctx.quadraticCurveTo(cpX, cpY, end.x, end.y); + ctx.stroke(); } }, [graph.Elements.links]) @@ -344,6 +378,7 @@ export default function GraphView({ nodePointerAreaPaint={nodePointerAreaPaint} linkCanvasObject={linkCanvasObject} linkPointerAreaPaint={linkPointerAreaPaint} + linkLineDash={linkLineDash} cooldownTicks={cooldownTicks} />
diff --git a/app/page.tsx b/app/page.tsx index d755ea8b..7739e5a4 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,6 @@ 'use client' import { useEffect, useRef, useState } from 'react'; -import { Chat } from './components/chat'; import { Graph, GraphData, Node } from './components/model'; import { AlignRight, BookOpen, BoomBox, Download, Github, HomeIcon, Search, X } from 'lucide-react'; import Link from 'next/link'; @@ -22,6 +21,7 @@ import { cn, GraphRef, Message, Path, PathData, PathNode } from '@/lib/utils'; import type { GraphNode } from '@falkordb/canvas'; import dynamic from 'next/dynamic'; +const Chat = dynamic(() => import('./components/chat').then(mod => mod.Chat), { ssr: false }); const CodeGraph = dynamic(() => import('./components/code-graph').then(mod => mod.CodeGraph), { ssr: false }); type Tip = { @@ -203,6 +203,8 @@ export default function Home() { const handleSearchSubmit = (node: any, canvasRef: GraphRef) => { const canvas = canvasRef.current + debugger + if (canvas) { let chartNode = graph.Elements.nodes.find(n => n.id == node.id) @@ -477,9 +479,9 @@ export default function Home() { selectedPathId={selectedPathId} isPathResponse={isPathResponse} setIsPathResponse={setIsPathResponse} - setData={setData} paths={paths} setPaths={setPaths} + setCooldownTicks={setCooldownTicks} /> @@ -601,10 +603,10 @@ export default function Home() { selectedPathId={selectedPathId} isPathResponse={isPathResponse} setIsPathResponse={setIsPathResponse} - setData={setData} setChatOpen={setChatOpen} paths={paths} setPaths={setPaths} + setCooldownTicks={setCooldownTicks} /> diff --git a/package-lock.json b/package-lock.json index e6346077..d3260c9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.2.0", "dependencies": { - "@falkordb/canvas": "^0.0.37", + "@falkordb/canvas": "^0.0.38", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -251,9 +251,9 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.0.37", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.37.tgz", - "integrity": "sha512-/y4omfjh0GdXp2M5QqPS2OyU8dPHndSCfYq4MbgvjoKvRRAVX4TLcmNMsRELXZX4+18QT7yi4vUbKgG4SEMyNA==", + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.38.tgz", + "integrity": "sha512-Lxsn0S+zG4AO5hRpgaPfsWw4QgeO+ifEY47utZxcTnMjtiI4nUH6sBzpONz8xhNXU7RWywjYaT+jzjb1PIPyGQ==", "license": "MIT", "dependencies": { "d3": "^7.9.0", diff --git a/package.json b/package.json index d75081f9..a9aeeea8 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@falkordb/canvas": "^0.0.37", + "@falkordb/canvas": "^0.0.38", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", From 11e1630b9e1e27362badee2dfa4ddd3cb7c26c6d Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Tue, 24 Feb 2026 16:32:42 +0200 Subject: [PATCH 63/75] Refactor link curvature calculation in GraphView for improved scaling --- app/components/graphView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/graphView.tsx b/app/components/graphView.tsx index 2554e96b..63606835 100644 --- a/app/components/graphView.tsx +++ b/app/components/graphView.tsx @@ -234,7 +234,7 @@ export default function GraphView({ link.curve = curve * 0.1 - const d = link.curve * 70; + const d = link.curve * NODE_SIZE * 11.67; ctx.beginPath(); ctx.moveTo(start.x, start.y); @@ -328,7 +328,7 @@ export default function GraphView({ } const curvature = curve * 0.1 - const d = curvature * 70; + const d = curvature * NODE_SIZE * 11.67; ctx.beginPath(); ctx.moveTo(start.x, start.y); From 5d7ecba4f9cf8f1042ec5d78aff6a197d6dc0909 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 25 Feb 2026 16:01:32 +0200 Subject: [PATCH 64/75] Update @falkordb/canvas to version 0.0.40 and refactor graph component handlers for improved link and node interactions --- app/components/ForceGraph.tsx | 61 ++++++++++-- app/components/graphView.tsx | 181 +++++++-------------------------- app/page.tsx | 60 +++++++++-- package-lock.json | 183 ++++------------------------------ package.json | 2 +- 5 files changed, 162 insertions(+), 325 deletions(-) diff --git a/app/components/ForceGraph.tsx b/app/components/ForceGraph.tsx index 80c7f2fa..decb57f3 100644 --- a/app/components/ForceGraph.tsx +++ b/app/components/ForceGraph.tsx @@ -1,7 +1,7 @@ "use client" import { useCallback, useEffect, useState } from "react" -import type { Data, GraphNode } from "@falkordb/canvas" +import type { Data, GraphLink, GraphNode } from "@falkordb/canvas" import { GraphRef, PATH_COLOR } from "@/lib/utils" import { GraphData, Link, Node } from "./model" @@ -10,15 +10,17 @@ interface Props { data: GraphData canvasRef: GraphRef onNodeClick: (node: Node, event: MouseEvent) => void + onNodeHover: (node: Node | null) => void onNodeRightClick: (node: Node, event: MouseEvent) => void + isNodeSelected: (node: GraphNode) => boolean onLinkClick: (link: Link, event: MouseEvent) => void + onLinkHover: (link: Link | null) => void onLinkRightClick: (link: Link, event: MouseEvent) => void + isLinkSelected: (link: GraphLink) => boolean onBackgroundClick: (event: MouseEvent) => void onBackgroundRightClick: (event: MouseEvent) => void nodeCanvasObject: (node: GraphNode, ctx: CanvasRenderingContext2D) => void nodePointerAreaPaint: (node: GraphNode, color: string, ctx: CanvasRenderingContext2D) => void - linkCanvasObject: (link: any, ctx: CanvasRenderingContext2D) => void - linkPointerAreaPaint: (link: any, color: string, ctx: CanvasRenderingContext2D) => void linkLineDash: (link: any) => number[] | null onZoom: () => void onEngineStop: () => void @@ -51,17 +53,19 @@ export default function ForceGraph({ data, canvasRef, onNodeClick, + onNodeHover, onNodeRightClick, + isNodeSelected, onLinkClick, + onLinkHover, onLinkRightClick, + isLinkSelected, onBackgroundClick, onBackgroundRightClick, onZoom, onEngineStop, nodeCanvasObject, nodePointerAreaPaint, - linkCanvasObject, - linkPointerAreaPaint, linkLineDash, cooldownTicks, backgroundColor = "#FFFFFF", @@ -99,25 +103,49 @@ export default function ForceGraph({ }, [canvasRef, cooldownTicks, canvasLoaded]) // Map node click handler - const handleNodeClick = useCallback((node: any, event: MouseEvent) => { + const handleNodeClick = useCallback((node: GraphNode, event: MouseEvent) => { const originalNode = data.nodes.find(n => n.id === node.id) if (originalNode) onNodeClick(originalNode, event) }, [onNodeClick, data.nodes]) + // Map node hover handler + const handleNodeHover = useCallback((node: GraphNode | null) => { + if (!node) { + onNodeHover(null) + return + } + + const originalNode = data.nodes.find(n => n.id === node.id) + + if (originalNode) onNodeHover(originalNode) + }, [onNodeHover, data.nodes]) + // Map node right click handler - const handleNodeRightClick = useCallback((node: any, event: MouseEvent) => { + const handleNodeRightClick = useCallback((node: GraphNode, event: MouseEvent) => { const originalNode = data.nodes.find(n => n.id === node.id) if (originalNode) onNodeRightClick(originalNode, event) }, [onNodeRightClick, data.nodes]) // Map link click handler - const handleLinkClick = useCallback((link: any, event: MouseEvent) => { + const handleLinkClick = useCallback((link: GraphLink, event: MouseEvent) => { const originalLink = data.links.find(l => l.id === link.id) if (originalLink) onLinkClick(originalLink, event) }, [onLinkClick, data.links]) + // Map link hover handler + const handleLinkHover = useCallback((link: GraphLink | null) => { + if (!link) { + onLinkHover(null) + return + } + + const originalLink = data.links.find(l => l.id === link.id) + + if (originalLink) onLinkHover(originalLink) + }, [onLinkHover, data.links]) + // Map link right click handler - const handleLinkRightClick = useCallback((link: any, event: MouseEvent) => { + const handleLinkRightClick = useCallback((link: GraphLink, event: MouseEvent) => { const originalLink = data.links.find(l => l.id === link.id) if (originalLink) onLinkRightClick(originalLink, event) }, [onLinkRightClick, data.links]) @@ -132,27 +160,38 @@ export default function ForceGraph({ if (!canvasRef.current || !canvasLoaded) return canvasRef.current.setConfig({ autoStopOnSettle: false, + // nodes will display node.data.captionsKeys in the canvas captionsKeys: ["name", "title"], onNodeClick: handleNodeClick, onNodeRightClick: handleNodeRightClick, + onNodeHover: handleNodeHover, + isNodeSelected: isNodeSelected, onLinkClick: handleLinkClick, onLinkRightClick: handleLinkRightClick, + onLinkHover: handleLinkHover, + isLinkSelected: isLinkSelected, onBackgroundClick, onBackgroundRightClick, onEngineStop: handleEngineStop, node: { nodeCanvasObject, nodePointerAreaPaint }, - link: { linkCanvasObject, linkPointerAreaPaint }, linkLineDash, onZoom }) }, [ handleNodeClick, handleNodeRightClick, + handleNodeHover, handleLinkClick, handleLinkRightClick, + handleLinkHover, + isNodeSelected, + isLinkSelected, onBackgroundClick, onBackgroundRightClick, handleEngineStop, + nodeCanvasObject, + nodePointerAreaPaint, + linkLineDash, onZoom, canvasRef, canvasLoaded @@ -168,6 +207,6 @@ export default function ForceGraph({ }, [canvasRef, data, canvasLoaded]) return ( - + ) } diff --git a/app/components/graphView.tsx b/app/components/graphView.tsx index 63606835..aefda794 100644 --- a/app/components/graphView.tsx +++ b/app/components/graphView.tsx @@ -63,6 +63,7 @@ export default function GraphView({ const lastClick = useRef<{ date: Date, name: string }>({ date: new Date(), name: "" }) const [screenSize, setScreenSize] = useState(0) + const [hoverElement, setHoverElement] = useState() useEffect(() => { const handleResize = () => { @@ -106,6 +107,30 @@ export default function GraphView({ setSelectedPathId(link.id) } + const handleNodeHover = useCallback((node: Node | null) => { + setHoverElement(node) + }, []) + + const handleLinkHover = useCallback((link: Link | null) => { + setHoverElement(link) + }, []) + + const isNodeSelected = useCallback((node: GraphNode) => { + if (isPathResponse) { + return node.data.isPathSelected + } else { + return selectedObjects.some(obj => "category" in obj && obj.id === node.id) || (selectedObj && "category" in selectedObj && selectedObj?.id === node.id) || (hoverElement && ('category' in hoverElement) && hoverElement.id === node.id) + } + }, [isPathResponse, selectedObjects, selectedObj, hoverElement]) + + const isLinkSelected = useCallback((link: GraphLink) => { + if (isPathResponse) { + return link.data.isPathSelected + } else { + return selectedObjects.some(obj => "source" in obj && obj.id === link.id) || (selectedObj && "source" in selectedObj && selectedObj?.id === link.id) || (hoverElement && 'source' in hoverElement && hoverElement.id === link.id) + } + }, [isPathResponse, selectedObjects, selectedObj, hoverElement]) + const handleNodeClick = useCallback(async (node: Node) => { const now = new Date() const { date, name } = lastClick.current @@ -141,6 +166,9 @@ export default function GraphView({ const nodeCanvasObject = useCallback((node: GraphNode, ctx: CanvasRenderingContext2D) => { if (!node.x || !node.y) return + const isHovered = !!hoverElement && !('source' in hoverElement) && hoverElement.id === node.id + const isSelected = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id + if (isPathResponse) { if (node.data.isPathSelected) { ctx.fillStyle = node.color; @@ -167,12 +195,12 @@ export default function GraphView({ } else { ctx.fillStyle = node.color; ctx.strokeStyle = 'black'; - ctx.lineWidth = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id ? 1.5 : 1 + ctx.lineWidth = isSelected || isHovered ? 1.5 : 1 } } else { ctx.fillStyle = node.color; ctx.strokeStyle = 'black'; - ctx.lineWidth = selectedObjects.some(obj => obj.id === node.id) || selectedObj?.id === node.id ? 1.5 : 1 + ctx.lineWidth = isSelected || isHovered ? 1.5 : 1 } ctx.beginPath(); @@ -183,7 +211,7 @@ export default function GraphView({ ctx.fillStyle = 'black'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; - ctx.font = '4px Arial'; + ctx.font = '2px Arial'; let name = node.data.name || ""; const textWidth = ctx.measureText(name).width; const ellipsis = '...'; @@ -200,7 +228,7 @@ export default function GraphView({ // add label ctx.fillText(name, node.x, node.y); - }, [selectedObj, selectedObjects, isPathResponse]) + }, [selectedObj, selectedObjects, isPathResponse, hoverElement]) const nodePointerAreaPaint = useCallback((node: GraphNode, color: string, ctx: CanvasRenderingContext2D) => { if (!node.x || !node.y) return @@ -211,150 +239,11 @@ export default function GraphView({ ctx.fill(); }, []) - const linkCanvasObject = useCallback((link: GraphLink, ctx: CanvasRenderingContext2D) => { - const start = link.source; - const end = link.target; - - if (!start.x || !start.y || !end.x || !end.y) return - - const sameNodesLinks = graph.Elements.links.filter(l => (l.source === start.id && l.target === end.id) || (l.target === start.id && l.source === end.id)) - const index = sameNodesLinks.findIndex(l => l.id === link.id) || 0 - const even = index % 2 === 0 - let curve - - ctx.strokeStyle = '#999999'; - ctx.lineWidth = 0.1; - - if (start.id === end.id) { - if (even) { - curve = Math.floor(-(index / 2)) - 3 - } else { - curve = Math.floor((index + 1) / 2) + 2 - } - - link.curve = curve * 0.1 - - const d = link.curve * NODE_SIZE * 11.67; - - ctx.beginPath(); - ctx.moveTo(start.x, start.y); - ctx.bezierCurveTo(start.x, start.y - d, start.x + d, start.y, start.x, start.y); - ctx.stroke(); - - // Midpoint of cubic bezier: P0=(sx,sy), P1=(sx,sy-d), P2=(sx+d,sy), P3=(sx,sy) - const textX = start.x + 0.375 * d; - const textY = start.y - 0.375 * d; - - // Tangent at midpoint is (0.75d, 0.75d), angle always resolves to PI/4 - let textAngle = Math.atan2(0.75 * d, 0.75 * d); - if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle); - if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle); - - ctx.save(); - ctx.translate(textX, textY); - ctx.rotate(textAngle); - } else { - if (even) { - curve = Math.floor(-(index / 2)) - } else { - curve = Math.floor((index + 1) / 2) - } - - link.curve = curve * 0.1 - - const dx = end.x - start.x; - const dy = end.y - start.y; - const dist = Math.sqrt(dx * dx + dy * dy); - const curvature = link.curve; - const cpX = (start.x + end.x) / 2 + (dy / dist) * curvature * dist; - const cpY = (start.y + end.y) / 2 + (-dx / dist) * curvature * dist; - - // Draw the quadratic bezier curve - ctx.beginPath(); - ctx.moveTo(start.x, start.y); - ctx.quadraticCurveTo(cpX, cpY, end.x, end.y); - ctx.stroke(); - - // Midpoint of quadratic bezier at t=0.5 - const t = 0.5; - const midX = (1 - t) * (1 - t) * start.x + 2 * (1 - t) * t * cpX + t * t * end.x; - const midY = (1 - t) * (1 - t) * start.y + 2 * (1 - t) * t * cpY + t * t * end.y; - - let textAngle = Math.atan2(end.y - start.y, end.x - start.x) - - // maintain label vertical orientation for legibility - if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle); - if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle); - - ctx.save(); - ctx.translate(midX, midY); - ctx.rotate(textAngle); - } - - // add label - ctx.globalAlpha = 1; - ctx.fillStyle = 'black'; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.font = '2px Arial'; - ctx.fillText(link.relationship, 0, 0); - ctx.restore() - }, [graph.Elements.links]) - const linkLineDash = useCallback((link: GraphLink) => { if (link.data.isPath && !link.data.isPathSelected) return [5, 5] return null }, []) - const linkPointerAreaPaint = useCallback((link: GraphLink, color: string, ctx: CanvasRenderingContext2D) => { - const start = link.source; - const end = link.target; - - if (!start.x || !start.y || !end.x || !end.y) return - - ctx.strokeStyle = color; - ctx.lineWidth = 1; - - const sameNodesLinks = graph.Elements.links.filter(l => (l.source === start.id && l.target === end.id) || (l.target === start.id && l.source === end.id)) - const index = sameNodesLinks.findIndex(l => l.id === link.id) || 0 - const even = index % 2 === 0 - let curve - - if (start.id === end.id) { - if (even) { - curve = Math.floor(-(index / 2)) - 3 - } else { - curve = Math.floor((index + 1) / 2) + 2 - } - - const curvature = curve * 0.1 - const d = curvature * NODE_SIZE * 11.67; - - ctx.beginPath(); - ctx.moveTo(start.x, start.y); - ctx.bezierCurveTo(start.x, start.y - d, start.x + d, start.y, start.x, start.y); - ctx.stroke(); - } else { - if (even) { - curve = Math.floor(-(index / 2)) - } else { - curve = Math.floor((index + 1) / 2) - } - - const curvature = curve * 0.1 - const dx = end.x - start.x; - const dy = end.y - start.y; - const dist = Math.sqrt(dx * dx + dy * dy); - const cpX = (start.x + end.x) / 2 + (dy / dist) * curvature * dist; - const cpY = (start.y + end.y) / 2 + (-dx / dist) * curvature * dist; - - ctx.beginPath(); - ctx.moveTo(start.x, start.y); - ctx.quadraticCurveTo(cpX, cpY, end.x, end.y); - ctx.stroke(); - } - }, [graph.Elements.links]) - return (
@@ -367,17 +256,19 @@ export default function GraphView({ data={data} canvasRef={canvasRef} onNodeClick={screenSize > Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) || isShowPath ? (node: Node, _evt: MouseEvent) => handleNodeClick(node) : (node: Node, evt: MouseEvent) => handleRightClick(node, evt)} + onNodeHover={handleNodeHover} onNodeRightClick={handleRightClick} + isNodeSelected={isNodeSelected} onLinkClick={screenSize > Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) && isPathResponse ? handleLinkClick : handleRightClick} + onLinkHover={handleLinkHover} onLinkRightClick={handleRightClick} + isLinkSelected={isLinkSelected} onBackgroundClick={unsetSelectedObjects} onBackgroundRightClick={unsetSelectedObjects} onZoom={() => unsetSelectedObjects()} onEngineStop={handleEngineStop} nodeCanvasObject={nodeCanvasObject} nodePointerAreaPaint={nodePointerAreaPaint} - linkCanvasObject={linkCanvasObject} - linkPointerAreaPaint={linkPointerAreaPaint} linkLineDash={linkLineDash} cooldownTicks={cooldownTicks} /> diff --git a/app/page.tsx b/app/page.tsx index 7739e5a4..f8f465c5 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -200,11 +200,9 @@ export default function Home() { return graph.extend(json.result.neighbors, true) } - const handleSearchSubmit = (node: any, canvasRef: GraphRef) => { + const handleSearchSubmit = async (node: any, canvasRef: GraphRef) => { const canvas = canvasRef.current - debugger - if (canvas) { let chartNode = graph.Elements.nodes.find(n => n.id == node.id) @@ -216,11 +214,58 @@ export default function Home() { setZoomedNodes([chartNode]) graph.visibleLinks(true, [chartNode!.id]) - setData({ ...graph.Elements }) + + const currentData = canvas.getGraphData() + + const { dataToGraphData } = await import('@falkordb/canvas') + const graphNode = dataToGraphData({ + nodes: [{ + color: chartNode.color, + id: chartNode.id, + labels: [chartNode.category], + visible: chartNode.visible, + data: { + ...chartNode.data, + isPath: chartNode.isPath, + isPathSelected: chartNode.isPathSelected, + } + }], links: [] + }).nodes[0] + + if (graphNode) { + currentData.nodes.push(graphNode) + } + + canvas.setGraphData(currentData) + + + setTimeout(() => { + canvas.zoomToFit(4, (n: GraphNode) => n.id === chartNode!.id) + }, 0) + setSearchNode(chartNode) + setOptionsOpen(false) + return } + chartNode.visible = true graph.visibleLinks(true, [chartNode!.id]) - setData({ ...graph.Elements }) + + const currentData = canvas.getGraphData() + + const graphNode = currentData.nodes.find(n => n.id === chartNode!.id) + if (graphNode) { + graphNode.visible = true + } + + currentData.links.forEach(canvasLink => { + const appLink = graph.LinksMap.get(canvasLink.id) + + if (appLink) { + canvasLink.visible = appLink.visible + } + }) + + canvas.setGraphData(currentData) } setTimeout(() => { @@ -240,7 +285,7 @@ export default function Home() { graph.Categories.find(c => c.name === name)!.show = show graph.Elements.nodes.forEach(node => { - if (!(node.category === name)) return + if (node.category !== name) return node.visible = show }) @@ -255,6 +300,7 @@ export default function Home() { canvasNode.visible = appNode.visible; } }); + currentData.links.forEach(canvasLink => { const appLink = graph.LinksMap.get(canvasLink.id); @@ -272,7 +318,7 @@ export default function Home() { const handleDownloadImage = async () => { try { const canvases = Array.from(document.querySelectorAll('falkordb-canvas').values()).map(canvas => canvas.shadowRoot?.querySelector('canvas')).filter((c): c is HTMLCanvasElement => !!c); - + if (canvases.length === 0) { toast({ title: "Error", diff --git a/package-lock.json b/package-lock.json index d3260c9f..52f8d36d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.2.0", "dependencies": { - "@falkordb/canvas": "^0.0.38", + "@falkordb/canvas": "v0.0.40", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -52,6 +52,25 @@ "typescript": "^5.7.3" } }, + "../falkordb-canvas": { + "name": "@falkordb/canvas", + "version": "0.0.40", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "@types/react": "^19.2.7", + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", + "eslint": "^9.17.0", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -251,24 +270,8 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.0.38", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.38.tgz", - "integrity": "sha512-Lxsn0S+zG4AO5hRpgaPfsWw4QgeO+ifEY47utZxcTnMjtiI4nUH6sBzpONz8xhNXU7RWywjYaT+jzjb1PIPyGQ==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - } - }, - "node_modules/@falkordb/canvas/node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "resolved": "../falkordb-canvas", + "link": true }, "node_modules/@floating-ui/core": { "version": "1.7.3", @@ -2132,12 +2135,6 @@ "tslib": "^2.8.0" } }, - "node_modules/@tweenjs/tween.js": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", - "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2768,15 +2765,6 @@ "win32" ] }, - "node_modules/accessor-fn": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", - "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3161,16 +3149,6 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/bezier-js": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", - "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3328,18 +3306,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas-color-tracker": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", - "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", - "license": "MIT", - "dependencies": { - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -3627,12 +3593,6 @@ "node": ">=12" } }, - "node_modules/d3-binarytree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", - "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", - "license": "MIT" - }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -3776,22 +3736,6 @@ "node": ">=12" } }, - "node_modules/d3-force-3d": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", - "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", - "license": "MIT", - "dependencies": { - "d3-binarytree": "1", - "d3-dispatch": "1 - 3", - "d3-octree": "1", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -3834,12 +3778,6 @@ "node": ">=12" } }, - "node_modules/d3-octree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", - "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", - "license": "MIT" - }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -4982,20 +4920,6 @@ "dev": true, "license": "ISC" }, - "node_modules/float-tooltip": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", - "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", - "license": "MIT", - "dependencies": { - "d3-selection": "2 - 3", - "kapsule": "^1.16", - "preact": "10" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -5012,32 +4936,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/force-graph": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.0.tgz", - "integrity": "sha512-aTnihCmiMA0ItLJLCbrQYS9mzriopW24goFPgUnKAAmAlPogTSmFWqoBPMXzIfPb7bs04Hur5zEI4WYgLW3Sig==", - "license": "MIT", - "dependencies": { - "@tweenjs/tween.js": "18 - 25", - "accessor-fn": "1", - "bezier-js": "3 - 6", - "canvas-color-tracker": "^1.3", - "d3-array": "1 - 3", - "d3-drag": "2 - 3", - "d3-force-3d": "2 - 3", - "d3-scale": "1 - 4", - "d3-scale-chromatic": "1 - 3", - "d3-selection": "2 - 3", - "d3-zoom": "2 - 3", - "float-tooltip": "^1.7", - "index-array-by": "1", - "kapsule": "^1.16", - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -5454,15 +5352,6 @@ "node": ">=0.8.19" } }, - "node_modules/index-array-by": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", - "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -6057,18 +5946,6 @@ "node": ">=4.0" } }, - "node_modules/kapsule": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", - "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", - "license": "MIT", - "dependencies": { - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6862,16 +6739,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, - "node_modules/preact": { - "version": "10.28.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.1.tgz", - "integrity": "sha512-u1/ixq/lVQI0CakKNvLDEcW5zfCjUQfZdK9qqWuIJtsezuyG6pk9TWj75GMuI/EzRSZB/VAE43sNWWZfiy8psw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7943,12 +7810,6 @@ "node": ">=0.8" } }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/package.json b/package.json index a9aeeea8..626b5523 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@falkordb/canvas": "^0.0.38", + "@falkordb/canvas": "v0.0.40", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", From a7d8ef2c2e84c9cf8adc1dc65ee987676943eb7e Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Wed, 25 Feb 2026 16:09:16 +0200 Subject: [PATCH 65/75] Update @falkordb/canvas dependency version to use caret notation for flexibility --- package-lock.json | 183 ++++++++++++++++++++++++++++++++++++++++------ package.json | 4 +- 2 files changed, 163 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52f8d36d..e206ca0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.2.0", "dependencies": { - "@falkordb/canvas": "v0.0.40", + "@falkordb/canvas": "^0.0.40", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -52,25 +52,6 @@ "typescript": "^5.7.3" } }, - "../falkordb-canvas": { - "name": "@falkordb/canvas", - "version": "0.0.40", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - }, - "devDependencies": { - "@types/d3": "^7.4.3", - "@types/react": "^19.2.7", - "@typescript-eslint/eslint-plugin": "^8.18.2", - "@typescript-eslint/parser": "^8.18.2", - "eslint": "^9.17.0", - "tsup": "^8.5.1", - "typescript": "^5.9.3" - } - }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -270,8 +251,24 @@ } }, "node_modules/@falkordb/canvas": { - "resolved": "../falkordb-canvas", - "link": true + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.40.tgz", + "integrity": "sha512-sEIl10QHUXT5oGXkdLxY8saxStVQSxKfYd2THxJ4bjR77eq9L+OI8KYY5WRunX3UiZhfPiOy9PLaHHq0IuM8jg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + } + }, + "node_modules/@falkordb/canvas/node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/@floating-ui/core": { "version": "1.7.3", @@ -2135,6 +2132,12 @@ "tslib": "^2.8.0" } }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2765,6 +2768,15 @@ "win32" ] }, + "node_modules/accessor-fn": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", + "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3149,6 +3161,16 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bezier-js": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", + "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3306,6 +3328,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-color-tracker": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", + "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", + "license": "MIT", + "dependencies": { + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -3593,6 +3627,12 @@ "node": ">=12" } }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -3736,6 +3776,22 @@ "node": ">=12" } }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", @@ -3778,6 +3834,12 @@ "node": ">=12" } }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -4920,6 +4982,20 @@ "dev": true, "license": "ISC" }, + "node_modules/float-tooltip": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", + "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", + "license": "MIT", + "dependencies": { + "d3-selection": "2 - 3", + "kapsule": "^1.16", + "preact": "10" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -4936,6 +5012,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/force-graph": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.1.tgz", + "integrity": "sha512-uEEX8iRzgq1IKRISOw6RrB2RLMhcI25xznQYrCTVvxZHZZ+A2jH6qIolYuwavVxAMi64pFp2yZm4KFVdD993cg==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "bezier-js": "3 - 6", + "canvas-color-tracker": "^1.3", + "d3-array": "1 - 3", + "d3-drag": "2 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "d3-selection": "2 - 3", + "d3-zoom": "2 - 3", + "float-tooltip": "^1.7", + "index-array-by": "1", + "kapsule": "^1.16", + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -5352,6 +5454,15 @@ "node": ">=0.8.19" } }, + "node_modules/index-array-by": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", + "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -5946,6 +6057,18 @@ "node": ">=4.0" } }, + "node_modules/kapsule": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", + "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", + "license": "MIT", + "dependencies": { + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6739,6 +6862,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/preact": { + "version": "10.28.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", + "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7810,6 +7943,12 @@ "node": ">=0.8" } }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/package.json b/package.json index 626b5523..646de52f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@falkordb/canvas": "v0.0.40", + "@falkordb/canvas": "^0.0.40", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -53,4 +53,4 @@ "tailwindcss": "^3.4.17", "typescript": "^5.7.3" } -} \ No newline at end of file +} From 6cc12a26e84b540502c914a3a7fa8f76d49e8b46 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:57:32 +0200 Subject: [PATCH 66/75] fix pom --- e2e/logic/POM/codeGraph.ts | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index 99b02355..e339c61c 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -520,6 +520,16 @@ export default class CodeGraph extends BasePage { await this.page.mouse.up(); } + async dragFromCanvasCenter(): Promise { + const box = (await this.canvasElement.boundingBox())!; + const centerX = box.x + box.width / 2; + const centerY = box.y + box.height / 2; + await this.page.mouse.move(centerX, centerY); + await this.page.mouse.down(); + await this.page.mouse.move(centerX + 100, centerY + 50); + await this.page.mouse.up(); + } + async isNodeDetailsPanel(): Promise { await this.page.waitForTimeout(500); return this.nodeDetailsPanel.isVisible(); @@ -586,20 +596,24 @@ export default class CodeGraph extends BasePage { return Promise.all(elements.map(element => element.innerHTML())); } - async getGraphNodes(): Promise { + private async waitForGraphData(): Promise { await this.waitForCanvasAnimationToEnd(); - // Wait for the graph data to be available on window object (set by handleEngineStop) + // Wait for the graph data to be available await this.page.waitForFunction(() => { - const data = (window as any).graphDesktop(); - // Check both possible structures: { nodes } or { elements: { nodes } } + const data = (window as any).graphDesktop?.(); return data && ((Array.isArray(data.nodes) && data.nodes.length > 0) || (data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0)); - }, - { timeout: 5000 }); + }, { timeout: 5000 }); - const graphData = await this.page.evaluate(() => { - return (window as any).graphDesktop(); - }); + // Safety guard: wait for engine to fully stop and data to settle + await this.page.waitForTimeout(3000); + await this.waitForCanvasAnimationToEnd(); + + return await this.page.evaluate(() => (window as any).graphDesktop()); + } + + async getGraphNodes(): Promise { + const graphData = await this.waitForGraphData(); let transformData: any = null; for (let attempt = 0; attempt < 3; attempt++) { @@ -686,21 +700,7 @@ export default class CodeGraph extends BasePage { async getGraphDetails(): Promise { await this.canvasElementBeforeGraphSelection.waitFor({ state: 'detached' }); - await this.waitForCanvasAnimationToEnd(); - - // Wait for the graph function to be available and return data - await this.page.waitForFunction(() => { - const data = (window as any).graph?.(); - // Check both possible structures: { nodes } or { elements: { nodes } } - return data && ((Array.isArray(data.nodes) && data.nodes.length > 0) || - (data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0)); - }, { timeout: 5000 }); - - const graphData = await this.page.evaluate(() => { - return (window as any).graph?.(); - }); - - return graphData; + return await this.waitForGraphData(); } async waitForCanvasAnimationToEnd(timeout = 4500): Promise { From 4e5be84df9df73dbe3313608a8ed1ed2f418e8e2 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:57:53 +0200 Subject: [PATCH 67/75] fix canvas tests --- e2e/tests/canvas.spec.ts | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/e2e/tests/canvas.spec.ts b/e2e/tests/canvas.spec.ts index eac29290..a53a8fa0 100644 --- a/e2e/tests/canvas.spec.ts +++ b/e2e/tests/canvas.spec.ts @@ -64,7 +64,8 @@ test.describe("Canvas tests", () => { await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnRemoveNodeViaElementMenu(); const updatedGraph = await codeGraph.getGraphNodes(); - expect(updatedGraph.length).toBeLessThan(initialCount); + const updatedNode = findNodeByName(updatedGraph, nodes[0].nodeName); + expect(updatedNode.visible).toBe(false); }); test(`Validate unhide node functionality after hiding a node in canvas for ${nodes[0].nodeName}`, async () => { @@ -88,7 +89,7 @@ test.describe("Canvas tests", () => { await codeGraph.selectCodeGraphCheckbox(checkboxIndex.toString()); const result = await codeGraph.getGraphNodes(); const findItem = result.find((item: { category: string; }) => item.category === category); - expect(findItem).toBeUndefined(); + expect(findItem.visible).toBeFalsy(); }); }) @@ -126,17 +127,28 @@ test.describe("Canvas tests", () => { }); }) - for (let index = 0; index < 3; index++) { + for (let index = 1; index < 3; index++) { const nodeIndex: number = index + 1; test(`Validate canvas node dragging for node: ${index}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); const initialGraph = await codeGraph.getGraphNodes(); - await codeGraph.changeNodePosition(initialGraph[nodeIndex].screenX, initialGraph[nodeIndex].screenY); + const nodeName = initialGraph[nodeIndex].name || initialGraph[nodeIndex].data?.name; + await codeGraph.fillSearchBar(nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); + const updatedGraph = await codeGraph.getGraphNodes(); + const targetNode = findNodeByName(updatedGraph, nodeName); + const initialX = targetNode.x; + const initialY = targetNode.y; + await codeGraph.dragFromCanvasCenter(); const updateGraph = await codeGraph.getGraphDetails(); const nodes = updateGraph.elements?.nodes || updateGraph.nodes; - expect(nodes[nodeIndex].x).not.toBe(initialGraph[nodeIndex].x); - expect(nodes[nodeIndex].y).not.toBe(initialGraph[nodeIndex].y); + const draggedNode = findNodeByName(nodes, nodeName); + + expect(draggedNode.x).not.toBe(initialX); + expect(draggedNode.y).not.toBe(initialY); }); } @@ -174,10 +186,10 @@ test.describe("Canvas tests", () => { await codeGraph.clickOnShowPathBtn("Show the path"); await codeGraph.insertInputForShowPath("1", firstNode); await codeGraph.insertInputForShowPath("2", secondNode); - const result = await codeGraph.getGraphDetails(); - const firstNodeRes = findNodeByName(result?.nodes, firstNode); + const result = await codeGraph.getGraphNodes(); + const firstNodeRes = findNodeByName(result, firstNode); - const secondnodeRes = findNodeByName(result?.nodes, secondNode); + const secondnodeRes = findNodeByName(result, secondNode); expect(firstNodeRes).toBeDefined(); expect(secondnodeRes).toBeDefined(); From 562b9954573a3724a5614ea5d714ab64bbafb9cc Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:31:37 +0200 Subject: [PATCH 68/75] fix canvas tests --- e2e/tests/canvas.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e/tests/canvas.spec.ts b/e2e/tests/canvas.spec.ts index a53a8fa0..b3428bbd 100644 --- a/e2e/tests/canvas.spec.ts +++ b/e2e/tests/canvas.spec.ts @@ -57,9 +57,12 @@ test.describe("Canvas tests", () => { test(`Validate node hide functionality via element menu in canvas for ${nodes[0].nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); + await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); + await codeGraph.fillSearchBar(nodes[0].nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); const initialGraph = await codeGraph.getGraphNodes(); - const initialCount = initialGraph.length; const targetNode = findNodeByName(initialGraph, nodes[0].nodeName); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); await codeGraph.clickOnRemoveNodeViaElementMenu(); From 2b080c7ad9f0676907ff0efc7334fc42b157c090 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 26 Feb 2026 13:36:13 +0200 Subject: [PATCH 69/75] Implement feature X to enhance user experience and optimize performance --- package-lock.json | 787 ++++++++++++++++++++-------------------------- 1 file changed, 349 insertions(+), 438 deletions(-) diff --git a/package-lock.json b/package-lock.json index e206ca0a..de766618 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,25 @@ "typescript": "^5.7.3" } }, + "../falkordb-canvas": { + "name": "@falkordb/canvas", + "version": "0.0.40", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "@types/react": "^19.2.7", + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", + "eslint": "^9.17.0", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + } + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -65,9 +84,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -86,9 +105,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "license": "MIT", "optional": true, "dependencies": { @@ -107,9 +126,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -190,20 +209,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", + "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", + "minimatch": "^3.1.3", "strip-json-comments": "^3.1.1" }, "engines": { @@ -214,9 +233,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", "dev": true, "license": "MIT", "engines": { @@ -251,51 +270,35 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.0.40", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.40.tgz", - "integrity": "sha512-sEIl10QHUXT5oGXkdLxY8saxStVQSxKfYd2THxJ4bjR77eq9L+OI8KYY5WRunX3UiZhfPiOy9PLaHHq0IuM8jg==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - } - }, - "node_modules/@falkordb/canvas/node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "resolved": "../falkordb-canvas", + "link": true }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", + "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.5" }, "peerDependencies": { "react": ">=16.8.0", @@ -875,15 +878,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.9", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.9.tgz", - "integrity": "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.12.tgz", + "integrity": "sha512-pUvdJN1on574wQHjaBfNGDt9Mz5utDSZFsIIQkMzPgNS8ZvT4H2mwOrOIClwsQOb6EGx5M76/CZr6G8i6pSpLg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.5.9", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.9.tgz", - "integrity": "sha512-kUzXx0iFiXw27cQAViE1yKWnz/nF8JzRmwgMRTMh8qMY90crNsdXJRh2e+R0vBpFR3kk1yvAR7wev7+fCCb79Q==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.12.tgz", + "integrity": "sha512-+ZRSDFTv4aC96aMb5E41rMjysx8ApkryevnvEYZvPZO52KvkqP5rNExLUXJFr9P4s0f3oqNQR6vopCZsPWKDcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -891,9 +894,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", - "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.12.tgz", + "integrity": "sha512-RnRjBtH8S8eXCpUNkQ+543DUc7ys8y15VxmFU9HRqlo9BG3CcBUiwNtF8SNoi2xvGCVJq1vl2yYq+3oISBS0Zg==", "cpu": [ "arm64" ], @@ -907,9 +910,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", - "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.12.tgz", + "integrity": "sha512-nqa9/7iQlboF1EFtNhWxQA0rQstmYRSBGxSM6g3GxvxHxcoeqVXfGNr9stJOme674m2V7r4E3+jEhhGvSQhJRA==", "cpu": [ "x64" ], @@ -923,9 +926,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", - "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.12.tgz", + "integrity": "sha512-dCzAjqhDHwmoB2M4eYfVKqXs99QdQxNQVpftvP1eGVppamXh/OkDAwV737Zr0KPXEqRUMN4uCjh6mjO+XtF3Mw==", "cpu": [ "arm64" ], @@ -939,9 +942,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", - "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.12.tgz", + "integrity": "sha512-+fpGWvQiITgf7PUtbWY1H7qUSnBZsPPLyyq03QuAKpVoTy/QUx1JptEDTQMVvQhvizCEuNLEeghrQUyXQOekuw==", "cpu": [ "arm64" ], @@ -955,9 +958,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", - "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.12.tgz", + "integrity": "sha512-jSLvgdRRL/hrFAPqEjJf1fFguC719kmcptjNVDJl26BnJIpjL3KH5h6mzR4mAweociLQaqvt4UyzfbFjgAdDcw==", "cpu": [ "x64" ], @@ -971,9 +974,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", - "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.12.tgz", + "integrity": "sha512-/uaF0WfmYqQgLfPmN6BvULwxY0dufI2mlN2JbOKqqceZh1G4hjREyi7pg03zjfyS6eqNemHAZPSoP84x17vo6w==", "cpu": [ "x64" ], @@ -987,9 +990,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", - "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.12.tgz", + "integrity": "sha512-xhsL1OvQSfGmlL5RbOmU+FV120urrgFpYLq+6U8C6KIym32gZT6XF/SDE92jKzzlPWskkbjOKCpqk5m4i8PEfg==", "cpu": [ "arm64" ], @@ -1003,9 +1006,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.7", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", - "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.12.tgz", + "integrity": "sha512-Z1Dh6lhFkxvBDH1FoW6OU/L6prYwPSlwjLiZkExIAh8fbP6iI/M7iGTQAJPYJ9YFlWobCZ1PHbchFhFYb2ADkw==", "cpu": [ "x64" ], @@ -1064,63 +1067,17 @@ } }, "node_modules/@playwright/test": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", - "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@playwright/test/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/@playwright/test/node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.57.0" + "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/@playwright/test/node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, "engines": { "node": ">=18" } @@ -2117,9 +2074,9 @@ "license": "MIT" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", - "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", "dev": true, "license": "MIT" }, @@ -2132,12 +2089,6 @@ "tslib": "^2.8.0" } }, - "node_modules/@tweenjs/tween.js": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", - "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2180,15 +2131,15 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", - "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "version": "20.19.34", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.34.tgz", + "integrity": "sha512-by3/Z0Qp+L9cAySEsSNNwZ6WWw8ywgGLPQGgbQDhNRSitqYgkgp4pErd23ZSCavbtUA2CN4jQtoB3T8nk4j3Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -2202,9 +2153,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2244,20 +2195,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", - "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/type-utils": "8.51.0", - "@typescript-eslint/utils": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.2.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2267,8 +2218,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.51.0", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -2283,17 +2234,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", - "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2303,20 +2254,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", - "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.51.0", - "@typescript-eslint/types": "^8.51.0", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2330,14 +2281,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", - "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2348,9 +2299,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", - "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", "dev": true, "license": "MIT", "engines": { @@ -2365,17 +2316,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", - "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0", - "@typescript-eslint/utils": "8.51.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.2.0" + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2385,14 +2336,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", - "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", "dev": true, "license": "MIT", "engines": { @@ -2404,21 +2355,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", - "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.51.0", - "@typescript-eslint/tsconfig-utils": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.2.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2431,43 +2382,56 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", - "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2477,19 +2441,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", - "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.51.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2499,6 +2463,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", @@ -2768,19 +2745,10 @@ "win32" ] }, - "node_modules/accessor-fn": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", - "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -2801,9 +2769,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3065,9 +3033,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "funding": [ { "type": "opencollective", @@ -3085,7 +3053,7 @@ "license": "MIT", "dependencies": { "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -3117,9 +3085,9 @@ } }, "node_modules/axe-core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", - "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", "dev": true, "license": "MPL-2.0", "engines": { @@ -3153,22 +3121,15 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", - "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/bezier-js": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", - "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/binary-extensions": { @@ -3309,9 +3270,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001762", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", - "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", "funding": [ { "type": "opencollective", @@ -3328,18 +3289,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/canvas-color-tracker": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", - "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", - "license": "MIT", - "dependencies": { - "tinycolor2": "^1.6.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -3627,12 +3576,6 @@ "node": ">=12" } }, - "node_modules/d3-binarytree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", - "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", - "license": "MIT" - }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -3776,26 +3719,10 @@ "node": ">=12" } }, - "node_modules/d3-force-3d": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", - "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", - "license": "MIT", - "dependencies": { - "d3-binarytree": "1", - "d3-dispatch": "1 - 3", - "d3-octree": "1", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "license": "ISC", "engines": { "node": ">=12" @@ -3834,12 +3761,6 @@ "node": ">=12" } }, - "node_modules/d3-octree": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", - "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", - "license": "MIT" - }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -4182,9 +4103,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "license": "ISC" }, "node_modules/embla-carousel": { @@ -4422,9 +4343,9 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", "dependencies": { @@ -4434,7 +4355,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", + "@eslint/js": "9.39.3", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -4482,13 +4403,13 @@ } }, "node_modules/eslint-config-next": { - "version": "15.5.9", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.9.tgz", - "integrity": "sha512-852JYI3NkFNzW8CqsMhI0K2CDRxTObdZ2jQJj5CtpEaOkYHn13107tHpNuD/h0WRpU4FAbCdUaxQsrfBtNK9Kw==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.12.tgz", + "integrity": "sha512-ktW3XLfd+ztEltY5scJNjxjHwtKWk6vU2iwzZqSN09UsbBmMeE/cVlJ1yESg6Yx5LW7p/Z8WzUAgYXGLEmGIpg==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.5.9", + "@next/eslint-plugin-next": "15.5.12", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -4725,19 +4646,25 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4801,9 +4728,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4982,20 +4909,6 @@ "dev": true, "license": "ISC" }, - "node_modules/float-tooltip": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", - "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", - "license": "MIT", - "dependencies": { - "d3-selection": "2 - 3", - "kapsule": "^1.16", - "preact": "10" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -5012,32 +4925,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/force-graph": { - "version": "1.51.1", - "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.1.tgz", - "integrity": "sha512-uEEX8iRzgq1IKRISOw6RrB2RLMhcI25xznQYrCTVvxZHZZ+A2jH6qIolYuwavVxAMi64pFp2yZm4KFVdD993cg==", - "license": "MIT", - "dependencies": { - "@tweenjs/tween.js": "18 - 25", - "accessor-fn": "1", - "bezier-js": "3 - 6", - "canvas-color-tracker": "^1.3", - "d3-array": "1 - 3", - "d3-drag": "2 - 3", - "d3-force-3d": "2 - 3", - "d3-scale": "1 - 4", - "d3-scale-chromatic": "1 - 3", - "d3-selection": "2 - 3", - "d3-zoom": "2 - 3", - "float-tooltip": "^1.7", - "index-array-by": "1", - "kapsule": "^1.16", - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -5060,9 +4947,9 @@ } }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5190,9 +5077,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", "dev": true, "license": "MIT", "dependencies": { @@ -5454,15 +5341,6 @@ "node": ">=0.8.19" } }, - "node_modules/index-array-by": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", - "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -6057,18 +5935,6 @@ "node": ">=4.0" } }, - "node_modules/kapsule": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", - "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", - "license": "MIT", - "dependencies": { - "lodash-es": "4" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6148,9 +6014,9 @@ } }, "node_modules/lodash-es": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", - "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, "node_modules/lodash.merge": { @@ -6228,9 +6094,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6310,12 +6176,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.9", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.9.tgz", - "integrity": "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg==", + "version": "15.5.12", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.12.tgz", + "integrity": "sha512-Fi/wQ4Etlrn60rz78bebG1i1SR20QxvV8tVp6iJspjLUSHcZoeUXCt+vmWoEcza85ElZzExK/jJ/F6SvtGktjA==", "license": "MIT", "dependencies": { - "@next/env": "15.5.9", + "@next/env": "15.5.12", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -6328,14 +6194,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.7", - "@next/swc-darwin-x64": "15.5.7", - "@next/swc-linux-arm64-gnu": "15.5.7", - "@next/swc-linux-arm64-musl": "15.5.7", - "@next/swc-linux-x64-gnu": "15.5.7", - "@next/swc-linux-x64-musl": "15.5.7", - "@next/swc-win32-arm64-msvc": "15.5.7", - "@next/swc-win32-x64-msvc": "15.5.7", + "@next/swc-darwin-arm64": "15.5.12", + "@next/swc-darwin-x64": "15.5.12", + "@next/swc-linux-arm64-gnu": "15.5.12", + "@next/swc-linux-arm64-musl": "15.5.12", + "@next/swc-linux-x64-gnu": "15.5.12", + "@next/swc-linux-x64-musl": "15.5.12", + "@next/swc-win32-arm64-msvc": "15.5.12", + "@next/swc-win32-x64-msvc": "15.5.12", "sharp": "^0.34.3" }, "peerDependencies": { @@ -6389,6 +6255,35 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -6696,6 +6591,38 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -6862,16 +6789,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, - "node_modules/preact": { - "version": "10.28.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", - "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7360,9 +7277,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "devOptional": true, "license": "ISC", "bin": { @@ -7830,9 +7747,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", "license": "MIT", "funding": { "type": "github", @@ -7943,12 +7860,6 @@ "node": ">=0.8" } }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -8007,9 +7918,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz", - "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -8405,9 +8316,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { From 772431afe85d9e6b77f9fa752bfc27ece442ce32 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:54:57 +0200 Subject: [PATCH 70/75] fix nodeDetailsPanel tests --- e2e/tests/nodeDetailsPanel.spec.ts | 52 ++++++++++++++++-------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/e2e/tests/nodeDetailsPanel.spec.ts b/e2e/tests/nodeDetailsPanel.spec.ts index 7762b6db..730fbc35 100644 --- a/e2e/tests/nodeDetailsPanel.spec.ts +++ b/e2e/tests/nodeDetailsPanel.spec.ts @@ -5,7 +5,7 @@ import urls from "../config/urls.json"; import { FLASK_GRAPH, GRAPHRAG_SDK } from "../config/constants"; import { findNodeByName } from "../logic/utils"; import { nodes } from "../config/testData"; -import { ApiCalls } from "../logic/api/apiCalls"; + test.describe("Node details panel tests", () => { let browser: BrowserWrapper; @@ -23,8 +23,11 @@ test.describe("Node details panel tests", () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); + await codeGraph.fillSearchBar(node.nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); const graphData = await codeGraph.getGraphNodes(); - + const targetNode = findNodeByName(graphData, node.nodeName); expect(targetNode).toBeDefined(); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); @@ -38,6 +41,9 @@ test.describe("Node details panel tests", () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); + await codeGraph.fillSearchBar(node.nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); const graphData = await codeGraph.getGraphNodes(); const targetNode = findNodeByName(graphData, node.nodeName); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); @@ -52,6 +58,9 @@ test.describe("Node details panel tests", () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); + await codeGraph.fillSearchBar(node.nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); const graphData = await codeGraph.getGraphNodes(); const targetNode = findNodeByName(graphData, node.nodeName); await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); @@ -65,42 +74,35 @@ test.describe("Node details panel tests", () => { await browser.setPageToFullScreen(); await codeGraph.selectGraph(FLASK_GRAPH); const graphData = await codeGraph.getGraphNodes(); - - // Find a node that has src property (actual source code) const targetNode = graphData.find(node => node.src) || graphData[0]; + const nodeName = targetNode.name || targetNode.data?.name; + await codeGraph.fillSearchBar(nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); - await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY); + await codeGraph.rightClickAtCanvasCenter(); await codeGraph.clickOnViewNode(); const copiedText = await codeGraph.clickOnCopyToClipboardNodePanelDetails(); - - // Verify the copied text matches the node's src property expect(copiedText).toBe(targetNode.src || ""); }); + const expectedNodeKeys = ["id", "doc", "name", "path", "src_end", "src_start"]; + nodes.slice(0, 2).forEach((node) => { - test(`Validate view node panel keys via api for ${node.nodeName}`, async () => { + test(`Validate node data contains expected properties for ${node.nodeName}`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); + await codeGraph.fillSearchBar(node.nodeName); + await codeGraph.selectSearchBarOptionBtn("1"); + await codeGraph.waitForCanvasAnimationToEnd(); const graphData = await codeGraph.getGraphNodes(); - const node1 = findNodeByName(graphData, node.nodeName); - const api = new ApiCalls(); - const response = await api.getProject(GRAPHRAG_SDK); - const data: any = response.result.entities.nodes; - const findNode = data.find((nod: any) => nod.properties.name === node.nodeName); - await codeGraph.nodeClick(node1.screenX, node1.screenY); - let elements = await codeGraph.getNodeDetailsPanelElements(); - elements.splice(2, 1); - const apiFields = [ - ...Object.keys(findNode), - ...Object.keys(findNode.properties || {}), - ]; + const targetNode = findNodeByName(graphData, node.nodeName); + expect(targetNode).toBeDefined(); - const isValid = elements.every((field) => { - const cleanedField = field.replace(":", "").trim(); - return apiFields.includes(cleanedField); - }); - expect(isValid).toBe(true); + for (const key of expectedNodeKeys) { + expect(targetNode).toHaveProperty(key); + } }); }); }); From b8d9403bdf0e7a3641266fa7b04d7fb27c29e29e Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:14:49 +0200 Subject: [PATCH 71/75] fix build --- package-lock.json | 181 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 160 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index de766618..565aff53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,25 +52,6 @@ "typescript": "^5.7.3" } }, - "../falkordb-canvas": { - "name": "@falkordb/canvas", - "version": "0.0.40", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "force-graph": "^1.44.4", - "react": "^19.2.3" - }, - "devDependencies": { - "@types/d3": "^7.4.3", - "@types/react": "^19.2.7", - "@typescript-eslint/eslint-plugin": "^8.18.2", - "@typescript-eslint/parser": "^8.18.2", - "eslint": "^9.17.0", - "tsup": "^8.5.1", - "typescript": "^5.9.3" - } - }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -270,8 +251,24 @@ } }, "node_modules/@falkordb/canvas": { - "resolved": "../falkordb-canvas", - "link": true + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.40.tgz", + "integrity": "sha512-sEIl10QHUXT5oGXkdLxY8saxStVQSxKfYd2THxJ4bjR77eq9L+OI8KYY5WRunX3UiZhfPiOy9PLaHHq0IuM8jg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "force-graph": "^1.44.4", + "react": "^19.2.3" + } + }, + "node_modules/@falkordb/canvas/node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/@floating-ui/core": { "version": "1.7.4", @@ -2089,6 +2086,12 @@ "tslib": "^2.8.0" } }, + "node_modules/@tweenjs/tween.js": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", + "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -2745,6 +2748,15 @@ "win32" ] }, + "node_modules/accessor-fn": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/accessor-fn/-/accessor-fn-1.5.3.tgz", + "integrity": "sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -3132,6 +3144,16 @@ "node": ">=6.0.0" } }, + "node_modules/bezier-js": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-6.1.4.tgz", + "integrity": "sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/bezierjs/blob/master/FUNDING.md" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3289,6 +3311,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-color-tracker": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.2.tgz", + "integrity": "sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==", + "license": "MIT", + "dependencies": { + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/canvas2svg": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/canvas2svg/-/canvas2svg-1.0.16.tgz", @@ -3576,6 +3610,12 @@ "node": ">=12" } }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, "node_modules/d3-brush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", @@ -3719,6 +3759,22 @@ "node": ">=12" } }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-format": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", @@ -3761,6 +3817,12 @@ "node": ">=12" } }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, "node_modules/d3-path": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", @@ -4909,6 +4971,20 @@ "dev": true, "license": "ISC" }, + "node_modules/float-tooltip": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/float-tooltip/-/float-tooltip-1.7.5.tgz", + "integrity": "sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==", + "license": "MIT", + "dependencies": { + "d3-selection": "2 - 3", + "kapsule": "^1.16", + "preact": "10" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -4925,6 +5001,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/force-graph": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.51.1.tgz", + "integrity": "sha512-uEEX8iRzgq1IKRISOw6RrB2RLMhcI25xznQYrCTVvxZHZZ+A2jH6qIolYuwavVxAMi64pFp2yZm4KFVdD993cg==", + "license": "MIT", + "dependencies": { + "@tweenjs/tween.js": "18 - 25", + "accessor-fn": "1", + "bezier-js": "3 - 6", + "canvas-color-tracker": "^1.3", + "d3-array": "1 - 3", + "d3-drag": "2 - 3", + "d3-force-3d": "2 - 3", + "d3-scale": "1 - 4", + "d3-scale-chromatic": "1 - 3", + "d3-selection": "2 - 3", + "d3-zoom": "2 - 3", + "float-tooltip": "^1.7", + "index-array-by": "1", + "kapsule": "^1.16", + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -5341,6 +5443,15 @@ "node": ">=0.8.19" } }, + "node_modules/index-array-by": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/index-array-by/-/index-array-by-1.4.2.tgz", + "integrity": "sha512-SP23P27OUKzXWEC/TOyWlwLviofQkCSCKONnc62eItjp69yCZZPqDQtr3Pw5gJDnPeUMqExmKydNZaJO0FU9pw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -5935,6 +6046,18 @@ "node": ">=4.0" } }, + "node_modules/kapsule": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/kapsule/-/kapsule-1.16.3.tgz", + "integrity": "sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==", + "license": "MIT", + "dependencies": { + "lodash-es": "4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6789,6 +6912,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/preact": { + "version": "10.28.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", + "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7860,6 +7993,12 @@ "node": ">=0.8" } }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", From 665039b64967ee84ed8201590e2fdb5d21ddf30d Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:27:51 +0200 Subject: [PATCH 72/75] fix playwright ci sharding --- .github/workflows/playwright.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 7de5dea7..6a94c07c 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -36,7 +36,7 @@ jobs: run: | npm install npm run build - NEXTAUTH_SECRET=SECRET npm start & npx playwright test --reporter=dot,list + NEXTAUTH_SECRET=SECRET npm start & npx playwright test --shard=${{ matrix.shard }}/2 --reporter=dot,list - name: Ensure required directories exist run: | mkdir -p playwright-report From c68bae579c18fdab16151741407d781c4f27194f Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:56:42 +0200 Subject: [PATCH 73/75] fix search Bar tests --- e2e/logic/POM/codeGraph.ts | 25 +++++++++++++++++++++---- e2e/tests/searchBar.spec.ts | 11 +++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index e339c61c..dd961f56 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -103,7 +103,11 @@ export default class CodeGraph extends BasePage { } private get searchBarList(): Locator { - return this.scopedLocator("//div[@data-name='search-bar-list']"); + return this.page.locator('div[data-name="search-bar-list"]'); + } + + private get searchBarListFirstButtonInput(): Locator { + return this.searchBarList.locator("button").first().locator('div p').first(); } /* Chat Locators */ @@ -245,8 +249,12 @@ export default class CodeGraph extends BasePage { return this.scopedLocator(`//div[@data-name='node-details-panel']//button[@title='Copy src to clipboard']`); } + private get canvasTooltip(): Locator { + return this.page.locator('.float-tooltip-kap').first(); + } + private get nodeToolTip(): (node: string) => Locator { - return (node: string) => this.page.locator(`.float-tooltip-kap:has-text("${node}")`); + return (node: string) => this.page.locator('.float-tooltip-kap').filter({ hasText: node }); } private get downloadImageBtn(): Locator { @@ -435,8 +443,12 @@ export default class CodeGraph extends BasePage { await button.click(); } - async getSearchBarInputValue(): Promise { - return await this.searchBarInput.inputValue(); + async getSearchBarInputValue(): Promise { + let res: string | null = null; + await interactWhenVisible(this.searchBarListFirstButtonInput, async (el) => { + res = (await el.innerText())?.trim() ?? null; + }, 'searchBarListFirstButtonInput'); + return res; } async scrollToBottomInSearchBarList(): Promise { @@ -698,6 +710,11 @@ export default class CodeGraph extends BasePage { return await waitForElementToBeVisible(this.nodeToolTip(node)); } + async getNodeToolTipContent(): Promise { + await waitForElementToBeVisible(this.canvasTooltip); + return (await this.canvasTooltip.innerText()).trim(); + } + async getGraphDetails(): Promise { await this.canvasElementBeforeGraphSelection.waitFor({ state: 'detached' }); return await this.waitForGraphData(); diff --git a/e2e/tests/searchBar.spec.ts b/e2e/tests/searchBar.spec.ts index 97043add..ff788133 100644 --- a/e2e/tests/searchBar.spec.ts +++ b/e2e/tests/searchBar.spec.ts @@ -23,7 +23,6 @@ test.describe("search bar tests", () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.fillSearchBar(searchInput); - await delay(1000); const textList = await codeGraph.getSearchBarElementsText(); textList.forEach((text) => { expect(text.toLowerCase()).toContain(searchInput); @@ -36,7 +35,6 @@ test.describe("search bar tests", () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.fillSearchBar(searchInput); - await codeGraph.selectSearchBarOptionBtn("1"); expect(await codeGraph.getSearchBarInputValue()).toBe( completedSearchInput ); @@ -58,8 +56,8 @@ test.describe("search bar tests", () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await codeGraph.selectGraph(GRAPHRAG_SDK); await codeGraph.fillSearchBar(character); - await delay(1000); - expect((await codeGraph.getSearchBarInputValue()).includes(character)).toBe(expectedRes); + const res = await codeGraph.getSearchBarInputValue(); + expect(res !== null && res.includes(character)).toBe(expectedRes); }); }); @@ -76,7 +74,7 @@ test.describe("search bar tests", () => { }) nodes.forEach(({nodeName})=> { - test(`Verify canvas focuses on node ${nodeName} after search`, async () => {//here + test(`Verify canvas focuses on node ${nodeName} after search`, async () => { const codeGraph = await browser.createNewPage(CodeGraph, urls.baseUrl); await browser.setPageToFullScreen(); await codeGraph.selectGraph(GRAPHRAG_SDK); @@ -85,7 +83,8 @@ test.describe("search bar tests", () => { await codeGraph.selectSearchBarOptionBtn("1"); await codeGraph.waitForCanvasAnimationToEnd(); await codeGraph.rightClickAtCanvasCenter(); - expect(await codeGraph.getNodeDetailsHeader()).toContain(nodeName.toUpperCase()); + expect(await codeGraph.getNodeToolTipContent()).toContain(nodeName); + }); }) }) \ No newline at end of file From de3dfd90239c4db7272e696a46fd54e8d0698026 Mon Sep 17 00:00:00 2001 From: Anchel135 Date: Thu, 26 Feb 2026 17:06:44 +0200 Subject: [PATCH 74/75] update @falkordb/canvas dependency to version 0.0.41 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 565aff53..c3a5dc05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "code-graph", "version": "0.2.0", "dependencies": { - "@falkordb/canvas": "^0.0.40", + "@falkordb/canvas": "^0.0.41", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", @@ -251,9 +251,9 @@ } }, "node_modules/@falkordb/canvas": { - "version": "0.0.40", - "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.40.tgz", - "integrity": "sha512-sEIl10QHUXT5oGXkdLxY8saxStVQSxKfYd2THxJ4bjR77eq9L+OI8KYY5WRunX3UiZhfPiOy9PLaHHq0IuM8jg==", + "version": "0.0.41", + "resolved": "https://registry.npmjs.org/@falkordb/canvas/-/canvas-0.0.41.tgz", + "integrity": "sha512-r2DC2Mo9naASr3DDmVKCQVY/S50Hn2bGlmKaFxDRFcn5btiuuvPBqJ3krtC+uz5ZPmad8EoXRRlcQ6KwKJhUZw==", "license": "MIT", "dependencies": { "d3": "^7.9.0", diff --git a/package.json b/package.json index 646de52f..cd22ff9a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@falkordb/canvas": "^0.0.40", + "@falkordb/canvas": "^0.0.41", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", From 519e88d48ee78ce7adbab95c0860a6f007e714b8 Mon Sep 17 00:00:00 2001 From: Naseem Ali <34807727+Naseem77@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:13:09 +0200 Subject: [PATCH 75/75] fix search tests --- e2e/config/testData.ts | 3 ++- e2e/logic/POM/codeGraph.ts | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/e2e/config/testData.ts b/e2e/config/testData.ts index cdc91b81..0463eeee 100644 --- a/e2e/config/testData.ts +++ b/e2e/config/testData.ts @@ -11,7 +11,8 @@ const categorizeCharacters = (characters: string[], expectedRes: boolean): { cha export const specialCharacters: { character: string; expectedRes: boolean }[] = [ ...categorizeCharacters(['%', '*', '(', ')', '-', '[', ']', '{', '}', ';', ':', '"', '|', '~'], false), - ...categorizeCharacters(['!', '@', '$', '^', '_', '=', '+', "'", ',', '.', '<', '>', '/', '?', '\\', '`', '&', '#'], true) + ...categorizeCharacters(['!', '@', '$', '^', '=', '+', "'", ',', '<', '>', '/', '?', '\\', '`', '&', '#'], false), + ...categorizeCharacters(['_', '.'], true) ]; export const nodesPath: { firstNode: string; secondNode: string }[] = [ diff --git a/e2e/logic/POM/codeGraph.ts b/e2e/logic/POM/codeGraph.ts index dd961f56..427f6fb6 100644 --- a/e2e/logic/POM/codeGraph.ts +++ b/e2e/logic/POM/codeGraph.ts @@ -103,7 +103,7 @@ export default class CodeGraph extends BasePage { } private get searchBarList(): Locator { - return this.page.locator('div[data-name="search-bar-list"]'); + return this.scopedLocator('div[data-name="search-bar-list"]'); } private get searchBarListFirstButtonInput(): Locator { @@ -444,11 +444,9 @@ export default class CodeGraph extends BasePage { } async getSearchBarInputValue(): Promise { - let res: string | null = null; - await interactWhenVisible(this.searchBarListFirstButtonInput, async (el) => { - res = (await el.innerText())?.trim() ?? null; - }, 'searchBarListFirstButtonInput'); - return res; + const isVisible = await waitForElementToBeVisible(this.searchBarListFirstButtonInput); + if (!isVisible) return null; + return (await this.searchBarListFirstButtonInput.innerText())?.trim() ?? null; } async scrollToBottomInSearchBarList(): Promise {