Skip to content

Staging#358

Merged
Anchel123 merged 270 commits intomainfrom
staging
Mar 4, 2026
Merged

Staging#358
Anchel123 merged 270 commits intomainfrom
staging

Conversation

@gkorland
Copy link
Contributor

@gkorland gkorland commented Jan 22, 2025

Summary by CodeRabbit

  • New Features

    • Image download for graphs, improved path selection/visualization, and node+link interactions on canvas
    • Responsive desktop/mobile layouts with drawer/carousel tips and improved search-driven node navigation
    • Chat/tips UI improvements and a modal data panel with copy & "go to repo" actions
  • Bug Fixes

    • More stable canvas rendering, scrolling, input handling, tooltips, and path-related UI flows
  • Chores

    • Dependency updates, CI/test improvements, and Docker compose for local development

@vercel
Copy link

vercel bot commented Jan 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
code-graph Ready Ready Preview, Comment Mar 3, 2026 11:46am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

PR refactors core graph/model/types and replaces ForceGraph2D with a new canvas-based ForceGraph, expands Chat/CodeGraph public props to drive state via refs, adds drawer/carousel UI, updates e2e tests and tooling, and introduces docker-compose plus infrastructure/config updates.

Changes

Cohort / File(s) Summary
Graph model & types
app/components/model.ts, lib/utils.ts
Introduces Label, redefines Node/Link shapes (primitive source/target IDs, data map), adds LabelsMap/LinksMap getters, and centralizes path/message types and GraphRef/PATH_COLOR.
Canvas & rendering
app/components/ForceGraph.tsx, app/components/graphView.tsx, app/components/graphView.tsx
Adds new FalkorDB canvas-based ForceGraph component, converts GraphData→canvas Data, wires canvas events to app callbacks, and updates GraphView to use GraphRef and support Node
CodeGraph & toolbar
app/components/code-graph.tsx, app/components/toolbar.tsx
CodeGraph API expanded (id, graph, canvasRef, handlers for search/category/download, zoom/path state). Toolbar switched to canvasRef, adds download and cooldown switch.
Chat & input flow
app/components/chat.tsx, app/components/Input.tsx, app/components/combobox.tsx
Chat now accepts external state/refs (messages, query, paths, canvasRef), refactors path submission/selection and suggestion UI. Input binds to node?.name; Combobox callback made async.
Element menus & panels
app/components/elementMenu.tsx, app/components/dataPanel.tsx
ElementMenu and DataPanel now accept `Node
Page & layout
app/page.tsx, app/layout.tsx
Device-aware layout with separate desktop/mobile canvas refs, dynamic imports for Chat/CodeGraph, new handlers (handleSearchSubmit, onCategoryClick, handleDownloadImage), and mobile Drawer UI. Removed GoogleAnalytics import.
New UI primitives
components/ui/carousel.tsx, components/ui/drawer.tsx, components/ui/switch.tsx
Adds Embla-based Carousel components, Vaul-based Drawer primitives, and a Radix Switch wrapper.
Styling & config
app/globals.css, tailwind.config.js, components.json
Adds chart CSS vars, .control-button, Tailwind container and chart color tokens, and new path aliases plus iconLibrary.
E2E POM & utils
e2e/logic/POM/codeGraph.ts, e2e/logic/utils.ts
POM gains mobile-awareness, scoped locators, canvas interactions (download/right-click/hover), and new helpers (interactWhenVisible, waitForStableText). Test data and constants updated.
E2E tests
e2e/tests/*.spec.ts, e2e/config/testData.ts, e2e/config/constants.ts
Tests migrated to search-driven node flows, added screenshot/download and tooltip tests, adjusted method names and constants (CHAT_OPTIONS_COUNT).
Tooling & infra
.github/workflows/*, playwright.config.ts, Dockerfile, docker-compose.yml, package.json
Node bumped to 24, Playwright artifacts/screenshot handling updated, new docker-compose for FalkorDB/backend, Dockerfile base updated, package.json bumped and new deps added, typecheck script added.
API
app/api/repo/route.ts
Adds active POST handler to proxy analyze_folder/analyze_repo based on file:// local check and unified error handling.

Sequence Diagram

sequenceDiagram
    participant User
    participant Page as Page
    participant CodeGraph
    participant Chat
    participant ForceGraph
    participant Canvas as `@falkordb/canvas`
    participant API as Backend

    User->>Page: search/select node
    Page->>CodeGraph: handleSearchSubmit(searchNode)
    CodeGraph->>ForceGraph: update data via canvasRef
    ForceGraph->>Canvas: convertToCanvasData / setConfig
    Canvas-->>User: render graph

    User->>Canvas: click/right-click node
    Canvas-->>ForceGraph: onNodeClick/onRightClick
    ForceGraph->>CodeGraph: selected Node|Link
    CodeGraph->>Chat: update selectedPath/messages
    Chat->>API: request path data
    API-->>Chat: path response
    Chat->>CodeGraph: addGraphElements
    CodeGraph->>ForceGraph: merge new nodes/links
    ForceGraph->>Canvas: update data / re-render
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related PRs

  • Staging #315: Overlapping refactors touching Chat, CodeGraph, model types, and graph rendering integrations.

Suggested Reviewers

  • AviAvni

Poem

🐰 Hop, skip, and refactor we go,
Canvas and graphs steal the show,
Paths now glow in yellow bright,
Links and Nodes unite in sight,
State flows smooth from desktop to mobile's light!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title "Staging" is vague and does not convey meaningful information about the changeset. It does not clearly summarize the primary change. Replace with a descriptive title that captures the main changes, e.g., "Refactor graph components and add canvas-based rendering"
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch staging

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

Hi! Looks like you've reached your API usage limit. You can increase it from your account settings page here: app.greptile.com/settings/billing/code-review

💡 (1/5) You can manually trigger the bot by mentioning @greptileai in a comment!

9 file(s) reviewed, no comment(s)
Edit PR Review Bot Settings | Greptile

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (2)
app/components/graphView.tsx (1)

Line range hint 105-166: Improve double-click detection implementation.

The current implementation has potential issues:

  1. The double-click detection uses a fixed 1000ms threshold which might not match the system's double-click speed setting.
  2. The implementation doesn't handle the case where a user clicks different nodes within the threshold.

Consider this improved implementation:

-const lastClick = useRef<{ date: Date, name: string }>({ date: new Date(), name: "" })
+const lastClick = useRef<{ time: number, id: number }>({ time: 0, id: 0 })

-const isDoubleClick = now.getTime() - date.getTime() < 1000 && name === node.name
+const isDoubleClick = (now.getTime() - lastClick.current.time < 500) && (lastClick.current.id === node.id)
+lastClick.current = { time: now.getTime(), id: node.id }
app/components/chat.tsx (1)

Line range hint 157-215: Improve zoom functionality implementation.

Several improvements can be made to the zoom functionality:

  1. The setTimeout with 0ms delay is unnecessary
  2. Magic numbers should be constants
  3. Add proper error handling for undefined chart

Apply these changes:

+const ZOOM_DURATION = 1000;
+const ZOOM_PADDING = 150;

 const handleSetSelectedPath = (p: PathData) => {
   const chart = chartRef.current
   
-  if (!chart) return
+  if (!chart) {
+    console.warn('Chart reference is not available');
+    return;
+  }
   
   // ... rest of the function ...
   
-  setTimeout(() => {
-    chart.zoomToFit(1000, 150, (n: NodeObject<Node>) => p.nodes.some(node => node.id === n.id));
-  }, 0)
+  chart.zoomToFit(ZOOM_DURATION, ZOOM_PADDING, (n: NodeObject<Node>) => p.nodes.some(node => node.id === n.id));
 }
🧰 Tools
🪛 Biome (1.9.4)

[error] 163-163: Comparing to itself is potentially pointless.

(lint/suspicious/noSelfCompare)

🧹 Nitpick comments (5)
e2e/tests/chat.spec.ts (1)

113-113: Remove unnecessary empty line within the loop.

The empty line within the loop affects readability without adding value.

    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);
-      
    }
app/components/model.ts (2)

179-215: Add error handling for edge cases.

While the node creation logic is sound, it would benefit from additional error handling for edge cases.

      let source = this.nodesMap.get(edgeData.src_node)
      let target = this.nodesMap.get(edgeData.dest_node)

+     if (edgeData.src_node === undefined || edgeData.dest_node === undefined) {
+       console.warn('Invalid edge data: Missing source or destination node ID');
+       return;
+     }

      if (!source) {

229-253: Add documentation for curve calculation logic.

The curve calculation logic is complex and would benefit from documentation explaining the mathematical reasoning.

Add a comment block explaining the curve calculation:

+    // Calculate curve values for graph edges:
+    // - For self-loops (start === end):
+    //   - Even indices: negative values starting from -3
+    //   - Odd indices: positive values starting from 2
+    // - For regular edges:
+    //   - Even indices: negative values starting from 0
+    //   - Odd indices: positive values starting from 1
+    // The final curve value is scaled by 0.1 to create subtle curves
     newElements.links.forEach(link => {
e2e/logic/POM/codeGraph.ts (1)

284-285: Consider removing redundant delay.

The code waits for the loading image to be hidden and then adds an additional 2-second delay, which might be unnecessary.

Consider removing the redundant delay:

 async getTextInLastChatElement(): Promise<string>{
     await this.page.waitForSelector('img[alt="Waiting for response"]', { state: 'hidden' });
-    await delay(2000);
     return (await this.lastElementInChat.textContent())!;
 }
app/components/chat.tsx (1)

Line range hint 272-313: Extract common zoom functionality.

The zoom logic is duplicated between handleSetSelectedPath and handleSubmit. Consider extracting it into a reusable function.

Apply these changes:

+const ZOOM_DURATION = 1000;
+const ZOOM_PADDING = 150;
+
+const zoomToNodes = (chart: ForceGraphMethods, nodes: any[]) => {
+  if (!chart) {
+    console.warn('Chart reference is not available');
+    return;
+  }
+  chart.zoomToFit(ZOOM_DURATION, ZOOM_PADDING, (n: NodeObject<Node>) => 
+    nodes.some(node => node.id === n.id)
+  );
+};

 const handleSubmit = async () => {
   const chart = chartRef.current
   
   if (!chart) return
   
   // ... rest of the function ...
   
-  setTimeout(() => {
-    chart.zoomToFit(1000, 150, (n: NodeObject<Node>) => 
-      formattedPaths.some(p => p.nodes.some(node => node.id === n.id))
-    );
-  }, 0)
+  zoomToNodes(chart, formattedPaths.flatMap(p => p.nodes));
 }

 const handleSetSelectedPath = (p: PathData) => {
   // ... rest of the function ...
-  setTimeout(() => {
-    chart.zoomToFit(1000, 150, (n: NodeObject<Node>) => 
-      p.nodes.some(node => node.id === n.id)
-    );
-  }, 0)
+  zoomToNodes(chart, p.nodes);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f419fe6 and d47f502.

📒 Files selected for processing (9)
  • app/components/chat.tsx (9 hunks)
  • app/components/code-graph.tsx (5 hunks)
  • app/components/elementMenu.tsx (3 hunks)
  • app/components/graphView.tsx (5 hunks)
  • app/components/model.ts (3 hunks)
  • app/page.tsx (1 hunks)
  • e2e/logic/POM/codeGraph.ts (4 hunks)
  • e2e/tests/canvas.spec.ts (1 hunks)
  • e2e/tests/chat.spec.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build
  • GitHub Check: test
  • GitHub Check: test
🔇 Additional comments (11)
e2e/tests/chat.spec.ts (1)

120-121: LGTM! Good test isolation.

Moving the API calls inside the test scope improves test isolation and follows testing best practices.

app/components/elementMenu.tsx (1)

17-17: LGTM! Consistent function naming.

The typo fix from handelExpand to handleExpand is applied consistently across the component.

Also applies to: 22-22, 71-71, 77-77, 117-117, 123-123

app/components/model.ts (1)

55-55: LGTM! Safe default parameter.

Adding a default value of 0 ensures the function always returns a valid color.

app/page.tsx (1)

284-284: LGTM! Enhanced graph interaction.

Adding the chartRef prop to the Chat component enables improved graph visualization capabilities.

e2e/tests/canvas.spec.ts (1)

46-46: LGTM! Good improvement to test reliability.

Adding clickCenter() before zoom operations ensures consistent test behavior by establishing a known starting position.

app/components/graphView.tsx (2)

64-87: Good implementation of responsive graph sizing.

The resize observer implementation properly handles both window resizing and parent container changes. The cleanup in the useEffect hook correctly removes event listeners and disconnects the observer.


173-174: LGTM! Proper usage of dynamic dimensions.

Using state-managed dimensions ensures the graph properly responds to container size changes.

app/components/code-graph.tsx (1)

Line range hint 244-357: LGTM! Good cleanup of handler names.

The renaming from 'handel*' to 'handle*' improves code consistency and fixes typos.

e2e/logic/POM/codeGraph.ts (2)

155-157: LGTM! Good addition of loading state locator.

The new locator helps track loading states consistently across tests.


426-426: Verify the impact of changing to right-click.

The change from left-click to right-click for node selection might affect other tests that depend on this behavior.

Let's verify the impact:

✅ Verification successful

Right-click change appears safe to implement

The tests use the nodeClick() abstraction method rather than direct click events, focusing on outcomes rather than the click mechanism. The change from left to right click is an implementation detail that won't affect the test behavior as long as the nodeClick() method maintains its core functionality of node selection.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for tests that might be affected by the click behavior change
rg -l "nodeClick" "e2e/tests"

Length of output: 87


Script:

#!/bin/bash
# Check the content of test files for click-related code
echo "=== canvas.spec.ts ==="
rg -A 5 -B 5 "click" "e2e/tests/canvas.spec.ts"
echo -e "\n=== nodeDetailsPanel.spec.ts ==="
rg -A 5 -B 5 "click" "e2e/tests/nodeDetailsPanel.spec.ts"

Length of output: 8469

app/components/chat.tsx (1)

110-110: LGTM!

The function signature has been correctly updated to include the new chartRef prop.

… handling and neighbor deletion logic. Updated deleteNeighbors function to handle expanded nodes correctly and replaced direct calls with handleExpand in GraphView for better clarity and maintainability.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
app/components/Input.tsx (1)

Line range hint 201-201: Warning: Dynamic Tailwind class names may not work in production.

The dynamic class name bg-${colorName} might not survive Tailwind's purge in production. Consider using style objects or safelist these color classes in your Tailwind config.

Example safelist configuration in tailwind.config.js:

module.exports = {
  safelist: [
    {
      pattern: /bg-(red|blue|green|yellow|purple|pink|indigo|gray)-/,
      variants: ['hover', 'focus', 'active']
    }
  ]
}
🧹 Nitpick comments (1)
app/components/Input.tsx (1)

Line range hint 147-160: Consider extracting input validation logic.

While the validation works correctly, consider extracting it into a reusable function for better maintainability.

+const INVALID_CHARS_PATTERN = /[%*()\-\[\]{};:"|~]/;
+const getInvalidCharacterMessage = (char: string) => 
+  `The character "${char}" is not allowed in this field.`;
+
+const validateInput = (value: string): { isValid: boolean; message: string } => {
+  const match = value.match(INVALID_CHARS_PATTERN);
+  return match 
+    ? { isValid: false, message: getInvalidCharacterMessage(match[0]) }
+    : { isValid: true, message: '' };
+};
+
 onChange={(e) => {
     const newVal = e.target.value
-    const invalidChars = /[%*()\-\[\]{};:"|~]/;
-
-    if (invalidChars.test(newVal)) {
-        e.target.setCustomValidity(`The character "${newVal.match(invalidChars)?.[0]}" is not allowed in this field.`);
+    const { isValid, message } = validateInput(newVal);
+    if (!isValid) {
+        e.target.setCustomValidity(message);
         e.target.reportValidity();
         return;
     }
     e.target.setCustomValidity('');
     onValueChange({ name: newVal })
 }}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d47f502 and 43c94c7.

📒 Files selected for processing (1)
  • app/components/Input.tsx (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: test
🔇 Additional comments (2)
app/components/Input.tsx (2)

18-18: LGTM! Good architectural improvement.

The removal of the value prop in favor of using node?.name makes the component more focused and reduces prop drilling.


Line range hint 48-79: LGTM! Well-implemented search functionality.

The implementation includes:

  • Proper debouncing with timeout
  • Clean error handling with user feedback
  • Appropriate cleanup in useEffect

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/components/chat.tsx (1)

64-69: ⚠️ Potential issue | 🟡 Minor

Missing paths in useEffect dependency array.

The effect accesses paths to find the selected path, but paths is not in the dependency array. This could cause stale closure issues where updates to paths don't trigger re-evaluation.

Proposed fix
     useEffect(() => {
         const p = paths.find((path) => [...path.links, ...path.nodes].some((e: any) => e.id === selectedPathId))

         if (!p) return
         handleSetSelectedPath(p)
-    }, [selectedPathId])
+    }, [selectedPathId, paths])

Note: You may also need to add handleSetSelectedPath to the dependency array or wrap it in useCallback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/chat.tsx` around lines 64 - 69, The effect using useEffect
reads `paths` to find the selected path but only lists `selectedPathId` in its
dependency array, causing stale closures; update the dependency array for that
useEffect to include `paths` (and also include or memoize
`handleSetSelectedPath`) so the effect re-runs whenever `paths` or the setter
change; alternatively wrap `handleSetSelectedPath` in useCallback and include it
in the dependency list to avoid unnecessary re-renders.
♻️ Duplicate comments (9)
playwright.config.ts (1)

22-23: ⚠️ Potential issue | 🟡 Minor

Update the stale CI parallelism comment.
Line 22 says CI opts out of parallel tests, but Line 23 explicitly sets 3 workers on CI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playwright.config.ts` around lines 22 - 23, The comment above the Playwright
config is stale: it claims CI opts out of parallel tests but the workers
property explicitly sets workers: process.env.CI ? 3 : undefined. Fix by making
them consistent: either change the comment to state that CI runs with 3 workers
(e.g., "Use 3 workers on CI") or change the code to opt out (set workers:
process.env.CI ? undefined : undefined or workers: process.env.CI ? 1 :
undefined) depending on intent; update the comment near the workers property to
accurately describe the behavior of the workers setting.
README.md (2)

109-109: ⚠️ Potential issue | 🟡 Minor

Fix the GitHub Issues link.

The link points to GraphRAG-SDK but this README is for the code-graph repository.

🔧 Proposed fix
-* [GitHub Issues](https://github.com/FalkorDB/GraphRAG-SDK/issues)
+* [GitHub Issues](https://github.com/FalkorDB/code-graph/issues)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 109, Update the GitHub Issues link in README.md: replace
the existing link text "[GitHub
Issues](https://github.com/FalkorDB/GraphRAG-SDK/issues)" so it points to the
code-graph repository issues URL (e.g.,
https://github.com/FalkorDB/code-graph/issues), ensuring the "[GitHub Issues]"
entry now references the correct repository; keep the link label unchanged.

9-9: ⚠️ Potential issue | 🟡 Minor

Remove stray character.

Line 9 contains a lone - which appears to be a leftover from editing.

🔧 Proposed fix
-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 9, Remove the stray lone '-' character found in the README
(the isolated dash on line 9) by deleting that character so the file contains
only intended content; ensure no other stray punctuation remains and run a quick
preview to confirm formatting is unchanged.
app/globals.css (1)

132-139: ⚠️ Potential issue | 🟡 Minor

Remove duplicate @layer base declaration.

This block duplicates the @layer base declaration at lines 85-94. Additionally, line 137 misses font-roberto that exists in the original at line 91.

🔧 Proposed fix
-@layer base {
-  * {
-    `@apply` border-border;
-  }
-  body {
-    `@apply` bg-background text-foreground;
-  }
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 132 - 139, Remove the duplicate `@layer` base
block and merge its rules into the existing `@layer` base (avoid repeating the
`@layer` base declaration); specifically ensure the body selector inside the
retained `@layer` base includes the missing font utility by adding the
font-roberto utility to the existing body `@apply` (so body uses bg-background
text-foreground and font-roberto) and keep the universal * { `@apply`
border-border } rule in that single `@layer` base.
app/components/dataPanel.tsx (1)

21-27: ⚠️ Potential issue | 🟡 Minor

Remove duplicated isPathSelected from excludedProperties.

The same key appears twice, which is redundant and easy to miss in future edits.

Suggested fix
     "curve",
     "__indexColor",
-    "isPathSelected",
     "__controlPoints",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/dataPanel.tsx` around lines 21 - 27, The excludedProperties
array contains a duplicate string "isPathSelected"; remove the redundant
"isPathSelected" entry from the excludedProperties definition (leave a single
occurrence) to avoid redundancy and potential confusion when editing, locating
the array by the symbol name excludedProperties in dataPanel.tsx and updating
that list accordingly.
e2e/tests/canvas.spec.ts (1)

222-225: ⚠️ Potential issue | 🟡 Minor

Remove unsafe non-null assertions before API/path assertions.

Lines 222, 224, and 225 still rely on ! for node ids; this was already flagged and remains brittle.

Suggested fix
       expect(firstNodeRes).toBeDefined();
       expect(secondNodeRes).toBeDefined();
+      if (!firstNodeRes || !secondNodeRes) throw new Error("Path nodes were not found in graph payload");

       const api = new ApiCalls();
-      const response = await api.showPath(GRAPHRAG_SDK ,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);
+      expect(callsRelationObject?.src_node).toBe(firstNodeRes.id);
+      expect(callsRelationObject?.dest_node).toBe(secondNodeRes.id);
#!/bin/bash
# Verify remaining non-null id assertions in canvas tests
rg -nP '!\.id' e2e/tests/canvas.spec.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/canvas.spec.ts` around lines 222 - 225, Replace unsafe non-null
assertions by asserting presence and guarding before accessing ids: before
calling api.showPath(GRAPHRAG_SDK, firstNodeRes!.id, secondNodeRes!.id) ensure
firstNodeRes and secondNodeRes are defined (e.g.,
expect(firstNodeRes).toBeDefined(); expect(secondNodeRes).toBeDefined()) and
then call showPath with their .id without using `!`; after getting response,
assert response.result.paths exists and has at least one path, find
callsRelationObject, and assert callsRelationObject is defined before checking
callsRelationObject.src_node and callsRelationObject.dest_node (use
expect(callsRelationObject).toBeDefined();
expect(callsRelationObject!.src_node).toBe(firstNodeRes.id); etc.).
e2e/tests/chat.spec.ts (1)

69-87: ⚠️ Potential issue | 🟡 Minor

Avoid non-null assertions after optional chaining in regex extraction.

Lines 69 and 85 can fail with unclear errors when the regex does not match.

Suggested fix
       const result = await chat.getTextInLastChatElement();
-      const number = result.match(/\d+/g)?.[0]!;
+      const match = result.match(/\d+/g);
+      expect(match?.[0]).toBeDefined();
+      const number = match![0];
@@
     const uiResponse = await chat.getTextInLastChatElement();
-    const number = uiResponse.match(/\d+/g)?.[0]!;
-    
-    expect(number).toEqual(apiResponse.result.response.match(/\d+/g)?.[0]);
+    const uiMatch = uiResponse.match(/\d+/g);
+    const apiMatch = apiResponse.result.response.match(/\d+/g);
+    expect(uiMatch?.[0]).toBeDefined();
+    expect(apiMatch?.[0]).toBeDefined();
+    expect(uiMatch![0]).toEqual(apiMatch![0]);
#!/bin/bash
# Verify remaining optional-chain non-null assertions in this file
rg -nP '\.match\(/\\d\+\/g\)\?\.\[0\]!' e2e/tests/chat.spec.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/chat.spec.ts` around lines 69 - 87, The regex extractions using
optional chaining plus non-null assertion (e.g., uiResponse.match(/\d+/g)?.[0]!
and apiResponse.result.response.match(/\d+/g)?.[0]!) can throw unclear runtime
errors when there's no match; update the test to safely handle missing matches
by removing the non-null assertions and checking that the match exists before
using it: capture the match arrays (from uiResponse.match and
apiResponse.result.response.match), assert they are defined/contain at least one
element (or fail the test with a clear message) and then compare the first
element; look for the variables uiResponse, apiResponse, number and the test
"Validate UI response matches API response for a given question in chat" to
apply this change.
package.json (1)

33-33: ⚠️ Potential issue | 🟠 Major

next version concern appears unresolved from prior review.

Line 33 still targets ^15.5.8, which was previously flagged for advisories. Please move to the first patched 15.x version (or newer supported line).

Check official Next.js advisories/release notes for next@15.5.8 and identify the first patched 15.x version that resolves those advisories.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 33, Update the "next" dependency in package.json (the
"next" entry currently set to "^15.5.8") to the first patched 15.x release that
resolves the flagged advisories (or to a newer supported release line if
preferred); consult Next.js release notes/security advisories to identify that
exact patched 15.x version and replace the version string accordingly, then run
your lockfile update (npm/yarn/pnpm) and verify the new version is installed and
tests/builds succeed.
app/components/code-graph.tsx (1)

106-110: ⚠️ Potential issue | 🟠 Major

Fix Delete-key handling for single selection and link deletion.

Line 108 is still inverted, and Line 109 always deletes as "nodes" even when selectedObj is a Link. This blocks valid single-delete flows and can target the wrong entity type.

Proposed fix
         const handleKeyDown = (event: KeyboardEvent) => {
             if (event.key === 'Delete') {
-                if (selectedObj && selectedObjects.length === 0) return
-                handleRemove([...selectedObjects.map(obj => obj.id), selectedObj?.id].filter(id => id !== undefined), "nodes");
+                if (!selectedObj && selectedObjects.length === 0) return
+
+                const isSelectedObjLink = !!selectedObj && "source" in selectedObj
+                if (isSelectedObjLink && selectedObjects.length === 0) {
+                    handleRemove([selectedObj.id], "links")
+                    return
+                }
+
+                const nodeIds = [
+                    ...selectedObjects.map(obj => obj.id),
+                    ...(!isSelectedObjLink && selectedObj ? [selectedObj.id] : []),
+                ]
+                if (nodeIds.length === 0) return
+                handleRemove(nodeIds, "nodes")
             }
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/code-graph.tsx` around lines 106 - 110, The Delete-key handler
in handleKeyDown incorrectly returns when a single selection exists and always
calls handleRemove with "nodes"; invert the early-return check so it returns
only when there is no selection (i.e., if (!selectedObj &&
selectedObjects.length === 0) return), and determine the correct removal type by
inspecting the selected item(s): if selectedObj?.type === "Link" (or instanceof
Link depending on your model) call handleRemove(..., "links"), otherwise call
handleRemove(..., "nodes"); when mixing selections, compute ids and types
accordingly (e.g., map selectedObjects to their ids and ensure you pass the
appropriate entity type rather than hardcoding "nodes").
🟡 Minor comments (12)
.github/dependabot.yml-13-16 (1)

13-16: ⚠️ Potential issue | 🟡 Minor

Group name doesn't match the configured update types.

The group is named npm-minor-patch, suggesting it should include both minor and patch updates, but only patch is specified in update-types. Either:

  • Add - "minor" if the intent is to group both minor and patch updates, or
  • Rename the group to npm-patch to accurately reflect its scope.
Option A: Include minor updates
     groups:
       npm-minor-patch:
         update-types:
           - "patch"
+          - "minor"
Option B: Rename group to match scope
     groups:
-      npm-minor-patch:
+      npm-patch:
         update-types:
           - "patch"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/dependabot.yml around lines 13 - 16, The group name
"npm-minor-patch" in the dependabot configuration doesn't match its configured
update-types; either add "- \"minor\"" to the update-types list to include minor
and patch updates, or rename the group to "npm-patch" so the group name reflects
that only patch updates are included—update the groups entry for npm-minor-patch
and the update-types key accordingly.
components/ui/carousel.tsx-109-121 (1)

109-121: ⚠️ Potential issue | 🟡 Minor

Missing cleanup for reInit event listener.

The effect adds listeners for both reInit and select events, but the cleanup only removes the select listener. This could cause a memory leak.

🔧 Proposed fix
       return () => {
+        api?.off("reInit", onSelect)
         api?.off("select", onSelect)
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/carousel.tsx` around lines 109 - 121, The effect registers both
"reInit" and "select" listeners on the carousel API but only removes "select" on
cleanup; update the cleanup inside the React.useEffect that references api and
onSelect to also call api?.off("reInit", onSelect) (i.e., remove both listeners)
so both event handlers are detached when the effect tears down.
docker-compose.yml-9-10 (1)

9-10: ⚠️ Potential issue | 🟡 Minor

Review the volume mount scope.

Mounting the entire current directory (./) to /data/ could inadvertently expose sensitive files (e.g., .env, .git, credentials) to the container. Consider mounting only the specific directories needed.

🛡️ Proposed fix to limit volume scope
     volumes:
-      - ./:/data/
+      - ./data:/data/

Create a dedicated data directory for FalkorDB persistence.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` around lines 9 - 10, The docker-compose volume mounts the
entire repo (./) into the container as /data/, risking exposure of sensitive
files; change the volume to mount only a dedicated data directory (e.g., create
a local "data" folder) or specific required subfolders instead of "./", and
update the volumes entry (the current "- ./:/data/") to reference that dedicated
directory (or explicit paths) so only FalkorDB persistence data is exposed to
the container.
e2e/tests/nodeDetailsPanel.spec.ts-44-50 (1)

44-50: ⚠️ Potential issue | 🟡 Minor

Assert targetNode exists before clicking by coordinates.

In this flow, targetNode is used directly for screenX/screenY; add a guard to avoid accidental runtime failures.

Suggested fix
       const graphData = await codeGraph.getGraphNodes();
       const targetNode = findNodeByName(graphData, node.nodeName);
+      expect(targetNode).toBeDefined();
+      if (!targetNode) throw new Error(`Node not found: ${node.nodeName}`);
       await codeGraph.nodeClick(targetNode.screenX, targetNode.screenY);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/nodeDetailsPanel.spec.ts` around lines 44 - 50, The test uses
targetNode (returned from findNodeByName) directly for coordinates which can be
undefined; add an explicit guard/assert after obtaining graphData and targetNode
(from getGraphNodes and findNodeByName) to fail fast if targetNode is missing
(e.g., expect(targetNode).toBeDefined() or throw a clear Error) before calling
codeGraph.nodeClick(targetNode.screenX, targetNode.screenY), ensuring the test
doesn't crash with an unclear runtime exception.
e2e/logic/utils.ts-16-35 (1)

16-35: ⚠️ Potential issue | 🟡 Minor

waitForStableText can silently return unstable text on timeout.

Line 34 returns the last sampled text even if stabilization never occurred. That weakens test determinism.

Suggested fix
 export const waitForStableText = async (locator: Locator, timeout: number = 5000): Promise<string> => {
@@
-    return stableText;
+    throw new Error(`Text did not stabilize within ${timeout}ms. Last value: "${stableText}"`);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logic/utils.ts` around lines 16 - 35, The waitForStableText helper
(function waitForStableText) currently returns the last sampled text on timeout
which can hide flaky failures; change it so that if the loop finishes without
observing a stable, non-empty value it throws an Error (include the last sampled
text and the timeout value in the message for debugging) instead of returning
stableText, and keep the existing pollingInterval/maxChecks logic and the
locator usage (locator.textContent(), locator.page().waitForTimeout()) intact.
e2e/tests/canvas.spec.ts-128-131 (1)

128-131: ⚠️ Potential issue | 🟡 Minor

Guard fallback arrays before reading .length.

If both shapes are missing, nodes/links become undefined and the length checks crash.

Suggested fix
-      const nodes = result.elements?.nodes || result.nodes;
-      const links = result.elements?.links || result.links;
+      const nodes = result.elements?.nodes ?? result.nodes ?? [];
+      const links = result.elements?.links ?? result.links ?? [];
       expect(nodes.length).toBeGreaterThan(1);
       expect(links.length).toBeGreaterThan(1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/canvas.spec.ts` around lines 128 - 131, The test currently assumes
nodes and links are arrays and reads .length directly, which will throw if both
result.elements?.nodes and result.nodes are undefined; update the extraction to
ensure a safe array fallback (e.g., use nullish coalescing or an explicit array
check) so nodes and links are always arrays before asserting lengths—refer to
the variables nodes and links and the expressions result.elements?.nodes ||
result.nodes (or replace with result.elements?.nodes ?? result.nodes ?? []) and
similarly for links, then assert against the guaranteed array length.
app/components/toolbar.tsx-10-10 (1)

10-10: ⚠️ Potential issue | 🟡 Minor

Unusual type signature for setCooldownTicks.

The type (ticks?: 0) => void only allows undefined or the literal 0. Based on usage in app/page.tsx and app/components/graphView.tsx, the function is called with -1 as well (e.g., setCooldownTicks(-1)). This type should be widened.

Proposed fix
-    setCooldownTicks: (ticks?: 0) => void
+    setCooldownTicks: (ticks: number | undefined) => void
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/toolbar.tsx` at line 10, The type for setCooldownTicks is too
narrow — it currently permits only undefined or the literal 0; update its
signature (in app/components/toolbar.tsx where setCooldownTicks is declared) to
accept a number (e.g., ticks?: number or ticks: number | undefined) so callers
like setCooldownTicks(-1) are allowed; ensure any related usages and prop types
(where ToolbarProps or similar is defined) are updated accordingly to reflect
the widened numeric type.
app/page.tsx-338-346 (1)

338-346: ⚠️ Potential issue | 🟡 Minor

Viewport check may exclude valid canvases.

The visibility check requires the canvas to be fully within the viewport (rect.bottom <= window.innerHeight && rect.right <= window.innerWidth). If the canvas is larger than the viewport or partially scrolled, no canvas will match, causing a silent early return. Consider checking for intersection rather than full containment.

Proposed fix for partial visibility
         // 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;
+        const isInViewport = rect.width > 0 &&
+          rect.height > 0 &&
+          rect.top < window.innerHeight &&
+          rect.left < window.innerWidth &&
+          rect.bottom > 0 &&
+          rect.right > 0;

         return isInViewport;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 338 - 346, The current isInViewport check in
app/page.tsx requires full containment and will miss canvases that are larger
than or partially in view; update the logic around the isInViewport calculation
(the rect checks using rect.top/left/bottom/right and
window.innerHeight/window.innerWidth) to test for intersection instead of full
containment: keep rect.width > 0 && rect.height > 0, then require rect.bottom >
0 && rect.right > 0 && rect.top < window.innerHeight && rect.left <
window.innerWidth so a canvas that is partially visible still counts as
in-viewport.
app/components/graphView.tsx-138-138 (1)

138-138: ⚠️ Potential issue | 🟡 Minor

Double-click threshold of 1000ms is unusually long.

The typical double-click threshold is 300-500ms. A 1000ms threshold may cause unintended double-click detection from separate intentional clicks.

Proposed fix
-        const isDoubleClick = now.getTime() - date.getTime() < 1000 && name === node.data.name
+        const isDoubleClick = now.getTime() - date.getTime() < 400 && name === node.data.name
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/graphView.tsx` at line 138, The double-click detection uses a
1000ms window which is too long; update the comparison in the isDoubleClick
computation to use a standard threshold (e.g., 300–500ms) and preferably extract
it to a named constant for clarity. Replace the hardcoded 1000 in the
isDoubleClick expression (where now, date, name and node.data.name are used)
with a constant like DOUBLE_CLICK_THRESHOLD = 500 and compare now.getTime() -
date.getTime() < DOUBLE_CLICK_THRESHOLD so the threshold is easy to adjust and
consistent.
app/components/toolbar.tsx-62-66 (1)

62-66: ⚠️ Potential issue | 🟡 Minor

Guard against undefined handleDownloadImage before invoking.

handleDownloadImage is optional (handleDownloadImage?: () => void), but the onClick handler invokes it without a check. Clicking this button when the prop is undefined will cause a runtime error.

Proposed fix
             <button
                 className="hidden md:block control-button"
                 title="downloadImage"
-                onClick={handleDownloadImage}
+                onClick={() => handleDownloadImage?.()}
             >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/toolbar.tsx` around lines 62 - 66, The Download button calls
the optional handler directly and can throw if undefined; update the onClick
usage in the Toolbar component to guard against undefined handleDownloadImage
(e.g., use a safe call or only attach the handler when present), and consider
disabling the button when handleDownloadImage is not provided so the UI reflects
absence of an action; modify the JSX around the button (the element using
className "hidden md:block control-button" and title "downloadImage") to use
handleDownloadImage?.() or conditionally set onClick/disabled accordingly.
app/components/elementMenu.tsx-125-136 (1)

125-136: ⚠️ Potential issue | 🟡 Minor

Redundant onClick handler on anchor element.

The <a> tag already has href and target="_blank", so the onClick handler that calls window.open with the same URL is redundant and will cause the link to open twice in some browsers.

Proposed fix
                     <a
                         className="p-2"
                         href={objURL}
                         target="_blank"
+                        rel="noopener noreferrer"
                         title="Go to repo"
-                        onClick={() => {
-                            window.open(objURL, '_blank');
-                        }}
                     >
                         <Globe color="white" />
                     </a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/elementMenu.tsx` around lines 125 - 136, Remove the redundant
onClick handler on the anchor in elementMenu.tsx that calls window.open(objURL,
'_blank'); the <a> already uses href={objURL} and target="_blank", so delete the
onClick={() => { window.open(objURL, '_blank'); }} from the anchor (the element
rendering the Globe icon) to prevent the link from opening twice in some
browsers.
app/components/code-graph.tsx-207-207 (1)

207-207: ⚠️ Potential issue | 🟡 Minor

Use block syntax for forEach callbacks to avoid implicit return values.

Lines 207 and 411 have arrow function callbacks with implicit returns (from Set.add(...) and assignment), making the intent unclear and inconsistent with other forEach calls in the file.

Proposed fixes
-        deleteNeighbors(expandedNodes)?.forEach(id => deleteIdsMap.add(id))
+        deleteNeighbors(expandedNodes)?.forEach(id => {
+            deleteIdsMap.add(id)
+        })
-                                                    graph.Categories.forEach(c => c.show = true);
+                                                    graph.Categories.forEach(c => {
+                                                        c.show = true
+                                                    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/code-graph.tsx` at line 207, The forEach callbacks use concise
arrow expressions that rely on implicit returns (e.g.,
deleteNeighbors(expandedNodes)?.forEach(id => deleteIdsMap.add(id))), which is
inconsistent and unclear; update these to use block-bodied arrow functions with
explicit statements (e.g., id => { deleteIdsMap.add(id); }) so intent is clear
and matches other forEach usages — locate the occurrences around deleteNeighbors
and the similar callback at line ~411 and replace the concise arrow bodies with
block bodies containing explicit calls or assignments.
🧹 Nitpick comments (9)
e2e/config/constants.ts (1)

3-3: Fix the exported constant typo before it spreads (CHAT_OPTTIONS_COUNT).
Line 3 exports a misspelled public identifier. Consider adding a correctly spelled alias and keeping the old name temporarily for compatibility.

Proposed non-breaking cleanup
-export const CHAT_OPTTIONS_COUNT = 6;
+export const CHAT_OPTIONS_COUNT = 6;
+export const CHAT_OPTTIONS_COUNT = CHAT_OPTIONS_COUNT; // backward compatibility alias
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/config/constants.ts` at line 3, Export a correctly spelled constant name
and keep the misspelled one as a backward-compatible alias: add a new exported
symbol CHAT_OPTIONS_COUNT with the same value as the existing
CHAT_OPTTIONS_COUNT and re-export the old CHAT_OPTTIONS_COUNT (or assign it from
the new constant) so consumers using the misspelled identifier keep working
while code is migrated to CHAT_OPTIONS_COUNT.
lib/utils.ts (1)

7-10: Avoid any[] in shared graph/message contracts.
Lines 8-9 and Line 34 remove most type safety from the new centralized model. Use concrete types and reuse PathData in Message to prevent drift.

Proposed typing cleanup
 export type PathData = {
-  nodes: any[]
-  links: any[]
+  nodes: Node[]
+  links: Link[]
 }
@@
 export interface Message {
   type: MessageTypes;
   text?: string;
-  paths?: { nodes: any[], links: any[] }[];
+  paths?: PathData[];
   graphName?: string;
 }

Also applies to: 31-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils.ts` around lines 7 - 10, The PathData type uses any[] for nodes and
links and the Message type (referenced in this file) duplicates loose typing;
replace any[] with concrete, shared types (e.g., define Node and Link interfaces
or reuse existing ones) and update PathData to nodes: Node[] and links: Link[];
then change Message to reference PathData instead of duplicating fields so both
the graph contract and Message reuse the same strongly typed structure (update
all occurrences of PathData, Message, Node, and Link in this module).
components/ui/carousel.tsx (2)

88-99: Keyboard navigation doesn't account for vertical orientation.

The handleKeyDown uses ArrowLeft/ArrowRight regardless of orientation. For vertical carousels, ArrowUp/ArrowDown would be more intuitive.

♻️ Proposed enhancement
     const handleKeyDown = React.useCallback(
       (event: React.KeyboardEvent<HTMLDivElement>) => {
-        if (event.key === "ArrowLeft") {
+        const prevKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp"
+        const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown"
+        if (event.key === prevKey) {
           event.preventDefault()
           scrollPrev()
-        } else if (event.key === "ArrowRight") {
+        } else if (event.key === nextKey) {
           event.preventDefault()
           scrollNext()
         }
       },
-      [scrollPrev, scrollNext]
+      [orientation, scrollPrev, scrollNext]
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/carousel.tsx` around lines 88 - 99, handleKeyDown always maps
ArrowLeft/ArrowRight to scrollPrev/scrollNext, which ignores vertical carousels;
update handleKeyDown to branch on the carousel orientation (e.g., an orientation
prop or isVertical flag) and use ArrowUp/ArrowDown for vertical mode while
keeping ArrowLeft/ArrowRight for horizontal mode, still calling scrollPrev and
scrollNext and calling event.preventDefault() for handled keys; locate the
callback by name handleKeyDown and update its dependency array if you reference
any new orientation variable.

208-214: Minor: Remove extra space in className.

There's a double space between absolute and h-8.

🔧 Proposed fix
-        "absolute  h-8 w-8 rounded-full",
+        "absolute h-8 w-8 rounded-full",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/carousel.tsx` around lines 208 - 214, The className string
passed to the cn() call in the Carousel component has an extra space between
"absolute" and "h-8"; update the class string inside the className prop (the
cn(...) expression in components/ui/carousel.tsx where the element builds
"absolute  h-8 w-8 rounded-full") to remove the double space so it becomes
"absolute h-8 w-8 rounded-full".
README.md (1)

3-3: Consider using h2 for consistent heading levels.

The heading jumps from h1 (#) directly to h3 (###). For proper document structure, consider using h2 (##) instead.

📝 Proposed fix
-### Visualize your repository with our graph for code analysis
+## Visualize your repository with our graph for code analysis
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 3, The heading "Visualize your repository with our graph
for code analysis" is an h3 while the document starts with h1; change this
header from "### Visualize your repository with our graph for code analysis" to
an h2 by replacing the three hashes with two ("##") so heading levels are
consistent and follow the h1 -> h2 structure in README.md.
e2e/logic/POM/codeGraph.ts (1)

403-403: Magic number for animation delay.

The 2000ms timeout is a hardcoded magic number. Consider extracting it to a named constant or making it configurable for better maintainability.

Proposed fix
+const GRAPH_ANIMATION_DELAY = 2000;
+
 // In selectGraph method:
-        await this.page.waitForTimeout(2000); // graph animation delay
+        await this.page.waitForTimeout(GRAPH_ANIMATION_DELAY);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logic/POM/codeGraph.ts` at line 403, The hardcoded 2000ms in the call to
this.page.waitForTimeout(2000) is a magic number; extract it into a named
constant (e.g., ANIMATION_DELAY_MS) or make it configurable (pass as a parameter
or read from a config) and replace the literal in the method in codeGraph.ts so
the delay is maintainable and easy to adjust; update any related tests or usages
to reference the new constant/config option.
app/components/ForceGraph.tsx (2)

154-156: Remove unused canvasRef from handleEngineStop dependencies.

canvasRef is listed in the dependency array but is not used in the callback body. This can cause unnecessary re-creations of the callback.

Proposed fix
     const handleEngineStop = useCallback(() => {
         onEngineStop()
-    }, [canvasRef, onEngineStop])
+    }, [onEngineStop])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/ForceGraph.tsx` around lines 154 - 156, The handleEngineStop
useCallback includes an unused dependency canvasRef which causes needless
re-creations; update the dependency array for handleEngineStop to only include
onEngineStop (i.e., useCallback(() => { onEngineStop() }, [onEngineStop])) so
the callback only re-creates when onEngineStop changes and remove canvasRef from
the dependency list.

106-151: Consider memoizing node/link lookups for better performance.

The event handlers use data.nodes.find() and data.links.find() which are O(n) operations. For large graphs, this could impact performance during rapid interactions like hovering. Consider using a Map for O(1) lookups.

Proposed optimization
+// Add memoized maps
+const nodesMap = useMemo(() => new Map(data.nodes.map(n => [n.id, n])), [data.nodes])
+const linksMap = useMemo(() => new Map(data.links.map(l => [l.id, l])), [data.links])

 // Map node click handler
 const handleNodeClick = useCallback((node: GraphNode, event: MouseEvent) => {
-    const originalNode = data.nodes.find(n => n.id === node.id)
+    const originalNode = nodesMap.get(node.id)
     if (originalNode) onNodeClick(originalNode, event)
-}, [onNodeClick, data.nodes])
+}, [onNodeClick, nodesMap])

Apply the same pattern to all other handlers (handleNodeHover, handleNodeRightClick, handleLinkClick, handleLinkHover, handleLinkRightClick).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/ForceGraph.tsx` around lines 106 - 151, Replace repeated O(n)
searches in handlers by building memoized lookup maps for nodes and links (e.g.,
nodeById and linkById) using useMemo derived from data.nodes and data.links,
then change handleNodeClick, handleNodeHover, handleNodeRightClick,
handleLinkClick, handleLinkHover, and handleLinkRightClick to use
nodeById.get(node.id) / linkById.get(link.id) for O(1) lookups; update each
handler's dependency array to depend on the corresponding memoized map (not the
raw arrays) and preserve the current null-check behavior in
handleNodeHover/handleLinkHover.
app/components/elementMenu.tsx (1)

145-161: Redundant type cast after type guard.

After the "category" in obj check on line 146, TypeScript narrows obj to Node. The explicit cast obj as Node on lines 150 and 156 is redundant and can be removed for cleaner code.

Proposed refactor
             {
                 "category" in obj &&
                 <>
                     <button
                         className="p-2"
-                        onClick={() => handleExpand([obj as Node], true)}
+                        onClick={() => handleExpand([obj], true)}
                     >
                         <Maximize2 color="white" />
                     </button>
                     <button
                         className="p-2"
-                        onClick={() => handleExpand([obj as Node], false)}
+                        onClick={() => handleExpand([obj], false)}
                     >
                         <Minimize2 color="white" />
                     </button>
                 </>
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/elementMenu.tsx` around lines 145 - 161, The "category" in obj
type guard already narrows obj to Node, so remove the redundant casts obj as
Node in the two onClick handlers and pass obj directly to handleExpand; update
both calls inside the fragment (the Maximize2 and Minimize2 buttons) to use
handleExpand([obj], true/false) so the code is cleaner and leverages
TypeScript's narrowing (symbols: obj, Node, handleExpand).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a89f17 and 1d41240.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • public/code-graph-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (39)
  • .github/dependabot.yml
  • .github/workflows/nextjs.yml
  • .github/workflows/playwright.yml
  • Dockerfile
  • README.md
  • app/api/repo/route.ts
  • app/components/ForceGraph.tsx
  • app/components/Input.tsx
  • app/components/chat.tsx
  • app/components/code-graph.tsx
  • app/components/combobox.tsx
  • app/components/dataPanel.tsx
  • app/components/elementMenu.tsx
  • app/components/graphView.tsx
  • app/components/model.ts
  • app/components/toolbar.tsx
  • app/globals.css
  • app/layout.tsx
  • app/page.tsx
  • components.json
  • components/ui/carousel.tsx
  • components/ui/drawer.tsx
  • components/ui/switch.tsx
  • docker-compose.yml
  • e2e/config/constants.ts
  • e2e/config/testData.ts
  • e2e/infra/ui/basePage.ts
  • e2e/infra/ui/browserWrapper.ts
  • e2e/logic/POM/codeGraph.ts
  • e2e/logic/utils.ts
  • e2e/tests/canvas.spec.ts
  • e2e/tests/chat.spec.ts
  • e2e/tests/navBar.spec.ts
  • e2e/tests/nodeDetailsPanel.spec.ts
  • e2e/tests/searchBar.spec.ts
  • lib/utils.ts
  • package.json
  • playwright.config.ts
  • tailwind.config.js
💤 Files with no reviewable changes (1)
  • app/layout.tsx

@barakb
Copy link
Contributor

barakb commented Mar 2, 2026

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
app/components/Input.tsx (1)

46-57: ⚠️ Potential issue | 🟠 Major

Handle network exceptions in the debounced fetch path.

If fetch throws (network/CORS/abort), this block can produce unhandled rejections and leave stale suggestions visible.

Proposed fix
-            const result = await fetch(`/api/repo/${prepareArg(graph.Id)}/?prefix=${prepareArg(node?.name)}`, {
-                method: 'POST'
-            })
-
-            if (!result.ok) {
-                toast({
-                    variant: "destructive",
-                    title: "Uh oh! Something went wrong.",
-                    description: "Please try again later.",
-                })
-                return
-            }
+            let result: Response
+            try {
+                result = await fetch(`/api/repo/${prepareArg(graph.Id)}/?prefix=${prepareArg(node?.name)}`, {
+                    method: 'POST'
+                })
+            } catch {
+                setOptions([])
+                setOpen(false)
+                toast({
+                    variant: "destructive",
+                    title: "Uh oh! Something went wrong.",
+                    description: "Please try again later.",
+                })
+                return
+            }
+
+            if (!result.ok) {
+                setOptions([])
+                setOpen(false)
+                toast({
+                    variant: "destructive",
+                    title: "Uh oh! Something went wrong.",
+                    description: "Please try again later.",
+                })
+                return
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/Input.tsx` around lines 46 - 57, The debounced fetch call
using
fetch(`/api/repo/${prepareArg(graph.Id)}/?prefix=${prepareArg(node?.name)}`) can
throw network/CORS/abort exceptions and currently has no try/catch; wrap the
await fetch in a try/catch inside the debounced path (the function where this
fetch lives in Input.tsx), and on catch call the same toast error UI, clear or
hide suggestions (e.g., call the local setter used to store suggestions like
setSuggestions([]) or setIsSuggestionsVisible(false)), and ensure any
loading/abort state (e.g., setIsLoading) is reset so stale suggestions aren’t
left visible.
app/components/chat.tsx (1)

211-221: ⚠️ Potential issue | 🟠 Major

Handle thrown fetch errors in both chat request flows.

Both sendQuery and handleSubmit only handle !result.ok. If fetch throws (network/offline), pending state/messages are not reconciled.

Also applies to: 249-262

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/chat.tsx` around lines 211 - 221, Both chat flows (sendQuery
and handleSubmit) only check result.ok and don't handle fetch throwing; wrap the
fetch calls in try/catch in both functions (sendQuery and handleSubmit), and in
the catch block reconcile UI by removing the pending user message via
setMessages (same logic used for !result.ok), append a MessageTypes.Response
error message like "Sorry but I couldn't answer your question, please try
rephrasing.", and clear any loading/pending state flags so the UI doesn't stay
stuck; ensure the catch mirrors the existing error-path behavior at the fetch
success check so both network errors and non-OK responses behave identically.
app/page.tsx (1)

102-128: ⚠️ Potential issue | 🟠 Major

Fix isSubmit state handling in repo creation flow.

Line 105 sets loading before validation, and Line 113 can return without resetting it. Also, fetch exceptions are not caught, so loading can remain stuck.

🐛 Suggested fix
 async function onCreateRepo(e: React.FormEvent<HTMLFormElement>) {
   e.preventDefault()
 
-  setIsSubmit(true)
-
   if (!createURL) {
     toast({
       variant: "destructive",
       title: "Uh oh! Something went wrong.",
       description: "Please enter a URL.",
     })
+    setIsSubmit(false)
     return
   }
 
-  const result = await fetch(`/api/repo/?url=${prepareArg(createURL)}`, {
-    method: 'POST',
-  })
+  setIsSubmit(true)
+  try {
+    const result = await fetch(`/api/repo/?url=${prepareArg(createURL)}`, {
+      method: 'POST',
+    })
 
-  if (!result.ok) {
-    toast({
-      variant: "destructive",
-      title: "Uh oh! Something went wrong.",
-      description: await result.text(),
-    })
-    setIsSubmit(false)
-    return
-  }
+    if (!result.ok) {
+      toast({
+        variant: "destructive",
+        title: "Uh oh! Something went wrong.",
+        description: await result.text(),
+      })
+      return
+    }
 
-  const graphName = createURL.split('/').pop()!
+    const graphName = createURL.split('/').pop()!
 
-  setOptions(prev => [...prev, graphName])
-  setSelectedValue(graphName)
-  setCreateURL("")
-  setCreateOpen(false)
-  setIsSubmit(false)
+    setOptions(prev => [...prev, graphName])
+    setSelectedValue(graphName)
+    setCreateURL("")
+    setCreateOpen(false)
 
-  toast({
-    title: "Success",
-    description: `Project ${graphName} created successfully`,
-  })
+    toast({
+      title: "Success",
+      description: `Project ${graphName} created successfully`,
+    })
+  } catch {
+    toast({
+      variant: "destructive",
+      title: "Uh oh! Something went wrong.",
+      description: "Failed to create project. Please try again.",
+    })
+  } finally {
+    setIsSubmit(false)
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 102 - 128, The onCreateRepo handler sets
setIsSubmit(true) before validation and can return early without resetting it
and also doesn't catch fetch errors; move the loading toggling so validation
happens before setting setIsSubmit(true) OR ensure every early return resets it;
wrap the fetch call in a try/catch/finally around the await fetch so any
exception triggers setIsSubmit(false) in the catch or finally, and keep the
existing non-ok branch calling setIsSubmit(false); update onCreateRepo,
setIsSubmit usage, and the fetch block (referencing onCreateRepo, setIsSubmit,
prepareArg and the fetch result handling) so loading state is always cleared on
all paths.
♻️ Duplicate comments (18)
.github/workflows/playwright.yml (1)

39-39: ⚠️ Potential issue | 🟠 Major

Fix startup race and remove CLI reporter override at Line 39.

The job still starts the app and runs Playwright immediately in one command, which is race-prone. Also, --reporter=dot,list overrides reporter config and can suppress expected HTML outputs.

Suggested fix
-        NEXTAUTH_SECRET=SECRET npm start & npx playwright test --shard=${{ matrix.shard }}/2 --reporter=dot,list
+        NEXTAUTH_SECRET=SECRET npm start &
+        for i in {1..60}; do
+          curl -fsS http://127.0.0.1:3000 >/dev/null && break
+          sleep 1
+        done
+        npx playwright test --shard=${{ matrix.shard }}/2
#!/bin/bash
set -euo pipefail

echo "Workflow segment:"
cat -n .github/workflows/playwright.yml | sed -n '34,45p'

echo
echo "Reporter config in Playwright config files:"
fd -t f 'playwright.config.*' -x cat -n {} | sed -n '1,220p' | rg -n 'reporter|outputFolder|html'

Based on learnings: In Playwright, CLI --reporter flags completely override the reporter configuration in playwright.config.ts, even when the config file has conditional reporter settings for CI environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/playwright.yml at line 39, The workflow currently starts
the app and runs Playwright in one command which causes a race and also forces
CLI reporter overrides; change the job to (1) start the server in the background
and wait for it to be healthy (use wait-on or start-server-and-test), e.g., run
the server (the current NEXTAUTH_SECRET=... npm start) in its own step or use
start-server-and-test to wait for the ready URL, and (2) remove the CLI reporter
override (--reporter=dot,list) from the Playwright invocation so the reporters
configured in playwright.config.* are respected; ensure the Playwright step runs
something like `npx playwright test --shard=${{ matrix.shard }}/2` after the
readiness wait.
README.md (2)

109-109: ⚠️ Potential issue | 🟡 Minor

Point GitHub Issues to this repository.

Line 109 still links to FalkorDB/GraphRAG-SDK/issues instead of FalkorDB/code-graph/issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 109, Update the GitHub Issues link in the README: find the
list entry that reads "* [GitHub
Issues](https://github.com/FalkorDB/GraphRAG-SDK/issues)" and change the URL to
"https://github.com/FalkorDB/code-graph/issues" so it points to the code-graph
repository instead of GraphRAG-SDK.

9-9: ⚠️ Potential issue | 🟡 Minor

Remove stray hyphen line.

Line 9 contains an isolated - and should be deleted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 9, Delete the stray isolated hyphen line (a single "-"
character) present in the README content (the lone line containing just "-") so
the file no longer contains that extraneous line; simply remove that line from
the README to fix the duplicate/stray hyphen.
app/api/repo/route.ts (3)

43-51: ⚠️ Potential issue | 🟠 Major

Add a timeout to backend fetch in POST.

Lines 43-51 can hang indefinitely if the backend stalls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/repo/route.ts` around lines 43 - 51, The POST fetch call that assigns
to result (calling `${url}/${isLocal ? "analyze_folder" : "analyze_repo"}`) can
hang indefinitely; wrap this fetch in an AbortController with a timeout (e.g.,
30s) and pass the controller.signal into the fetch options, and ensure you clear
the timeout after the request completes and call controller.abort() on timeout
so the request is canceled and errors are handled upstream (update the code
around the existing fetch usage and error handling for result accordingly).

53-60: ⚠️ Potential issue | 🟠 Major

Do not return raw upstream error text to clients.

Lines 53-60 still expose backend error bodies directly in API responses.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/repo/route.ts` around lines 53 - 60, The catch block and the earlier
throw use raw upstream response bodies (await result.text() and returning
err.message) which exposes internal error details to clients; change the code so
you log the full upstream error/response body server-side (console.error or
processLogger) and throw or return a sanitized client-facing message instead
(e.g., "Upstream service error" or a generic failure string) via
NextResponse.json; specifically update the throw in the block that calls
result.text() and the catch return that currently uses err.message/String(err)
so the client only receives the generic message while full error details remain
in server logs for debugging.

41-43: ⚠️ Potential issue | 🔴 Critical

Block untrusted file:// input from selecting analyze_folder.

Line 41 still allows arbitrary clients to route requests to local-folder analysis.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/repo/route.ts` around lines 41 - 43, Currently the client-controlled
check using repo_url and isLocal lets callers pick "analyze_folder" by sending
file:// URLs; instead enforce the decision server-side by rejecting untrusted
file:// inputs: update the handler that reads repo_url and the isLocal logic so
that if repo_url.startsWith("file://") you either return a 400 error (deny) or
only allow it when a server-only safeguard is present (e.g.,
process.env.ALLOW_LOCAL_ANALYSIS === "true" AND the request is authenticated/has
an admin token); then use the sanitized isLocal value to select between
"analyze_folder" and "analyze_repo" in the fetch call. Ensure you reference and
modify the repo_url/isLocal check and the fetch call that builds
`${url}/${isLocal ? "analyze_folder" : "analyze_repo"}` so clients cannot force
local-folder analysis.
docker-compose.yml (1)

33-34: ⚠️ Potential issue | 🔴 Critical

Do not commit secrets in Compose environment values.

Line 34 still hardcodes SECRET_TOKEN; this is a credential leak risk.

Proposed fix
-      - OPENAI_API_KEY=YOUR_OPENAI_API_KEY
-      - SECRET_TOKEN=sC0tTerMania
+      - OPENAI_API_KEY=${OPENAI_API_KEY}
+      - SECRET_TOKEN=${SECRET_TOKEN}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` around lines 33 - 34, Remove the hardcoded SECRET_TOKEN
environment value from the Compose environment section and replace it with a
reference to an external environment variable (e.g., use variable substitution
${SECRET_TOKEN} or an env_file), ensure the runtime reads SECRET_TOKEN from the
environment rather than committing it; update any README or deployment docs to
instruct maintainers to place the secret in a local .env or CI secret store and
add that local .env to .gitignore so SECRET_TOKEN (and OPENAI_API_KEY) are never
committed in docker-compose.yml.
package.json (1)

33-33: ⚠️ Potential issue | 🟠 Major

Upgrade next from ^15.5.8 to a patched 15.x release.

Line 33 still uses the same version previously flagged for advisories.

Is next@15.5.8 affected by known security advisories, and what is the first patched version in the 15.x line?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 33, The package.json still pins the "next" dependency
at "^15.5.8" which is flagged by advisories; update the "next" entry in
package.json to the first patched 15.x release (replace the version string for
the "next" dependency) and then run your package manager (npm/yarn/pnpm) to
reinstall and re-run audit tests; ensure the change targets the 15.x patched
version (i.e., set "next" to the patched 15.x release) and verify by running
npm/yarn audit or checking the advisory that the new version resolves the
issues.
e2e/tests/chat.spec.ts (2)

123-124: ⚠️ Potential issue | 🟠 Major

Question-option index is off by one.

This currently selects options 2..6 instead of 1..5 and can overflow available options.

🔧 Proposed fix
-  for (let index = 1; index < 6; index++) {
-    const questionNumber = index + 1;
+  for (let index = 1; index <= CHAT_OPTTIONS_COUNT; index++) {
+    const questionNumber = index;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/chat.spec.ts` around lines 123 - 124, The for-loop selecting
question options is off-by-one: it iterates for (let index = 1; index < 6;
index++) and computes questionNumber = index + 1, causing selection of options
2..6 instead of 1..5 and risking overflow; change the iteration so the selected
option range is 1..5 by either starting index at 0 and using questionNumber =
index + 1, or start index at 1 and set questionNumber = index (update the loop
bounds accordingly), locating the change around the for loop and the
questionNumber constant in chat.spec.ts.

69-70: ⚠️ Potential issue | 🟡 Minor

Remove unsafe ?.[0]! extraction in response parsing.

Both places can fail with unclear errors when regex does not match.

🔧 Proposed fix
-      const number = result.match(/\d+/g)?.[0]!;
-      responses.push(number);
+      const match = result.match(/\d+/g);
+      if (!match?.[0]) throw new Error("UI response does not contain a number");
+      responses.push(match[0]);
@@
-    const number = uiResponse.match(/\d+/g)?.[0]!;
-    
-    expect(number).toEqual(apiResponse.result.response.match(/\d+/g)?.[0]);
+    const uiMatch = uiResponse.match(/\d+/g);
+    const apiMatch = apiResponse.result.response.match(/\d+/g);
+    if (!uiMatch?.[0] || !apiMatch?.[0]) {
+      throw new Error("Unable to extract numeric token from UI/API response");
+    }
+    expect(uiMatch[0]).toEqual(apiMatch[0]);
#!/bin/bash
# Verify unsafe optional-chain non-null assertions in this spec.
rg -nP '\?\.\[0\]!' e2e/tests/chat.spec.ts

Also applies to: 85-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/chat.spec.ts` around lines 69 - 70, The extraction using
result.match(/\d+/g)?.[0]! is unsafe — replace it by explicitly calling const
matches = result.match(/\d+/g) and checking matches && matches.length > 0 before
using matches[0]; if no match is found throw or fail the test with a clear error
message including the original result string. Apply the same change to both
occurrences referenced (the one assigning number from result and the similar
code at lines ~85-87) so responses.push uses a validated matches[0] rather than
an optional-chain non-null assertion.
app/components/dataPanel.tsx (3)

94-112: ⚠️ Potential issue | 🟠 Major

Fix valueRenderer signature/keyPath usage for react-json-tree.

keyPath should be consumed from rest parameters and checked via keyPath[0], not compared as a scalar string.

🔧 Proposed fix
-                                                valueRenderer={(valueAsString, value, keyPath) => {
-                                                    if (keyPath === "src") {
+                                                valueRenderer={(valueAsString, value, ...keyPath) => {
+                                                    if (keyPath[0] === "src") {
                                                         return <SyntaxHighlighter
                                                             language="python"
                                                             style={{
@@
-                                                    return <span className="text-white">{value as string}</span>
+                                                    return <span className="text-white">{String(valueAsString)}</span>
                                                 }}
In react-json-tree, what is the exact function signature of valueRenderer, and is keyPath passed as rest parameters (...keyPath)?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/dataPanel.tsx` around lines 94 - 112, The valueRenderer passed
to react-json-tree is using keyPath as a scalar but react-json-tree supplies it
via rest parameters; update the valueRenderer signature to accept the rest
params (e.g., valueRenderer={(valueAsString, value, ...keyPath) => { ... }}) and
then check keyPath[0] === "src" when deciding to render SyntaxHighlighter
(references: valueRenderer, keyPath, SyntaxHighlighter); ensure other branches
still return the plain span when keyPath[0] is not "src".

132-146: ⚠️ Potential issue | 🟠 Major

Use a single navigation path for “Go to repo” and add rel security.

Current anchor can open two tabs (href + window.open) and relies on newTab.scroll against a likely cross-origin page.

🔧 Proposed fix
                             <a
                                 className="flex items-center gap-2 p-2"
-                                href={url}
+                                href={obj.data.src_start ? `${url}#L${obj.data.src_start}` : url}
                                 target="_blank"
+                                rel="noopener noreferrer"
                                 title="Go to repo"
-                                onClick={() => {
-                                    const newTab = window.open(url, '_blank');
-
-                                    if (!obj.data.src_start || !obj.data.src_end || !newTab) return
-
-                                    newTab.scroll({
-                                        top: obj.data.src_start,
-                                        behavior: 'smooth'
-                                    })
-                                }}
                             >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/dataPanel.tsx` around lines 132 - 146, The anchor currently
opens two navigation paths (href + window.open) and calls newTab.scroll which
will fail cross-origin; remove the window.open call and any newTab.scroll usage
inside the onClick so the link uses a single navigation path via href +
target="_blank", and add rel="noopener noreferrer" to the <a> to fix the
security issue; if scrolling is required only for same-origin repos, replace the
unconditional window.open/newTab.scroll logic with a safe same-origin check (or
a separate handler) that uses window.open and scroll only when location.origin
=== new URL(url).origin, otherwise fall back to the simple href behavior — look
for the anchor with target="_blank", its onClick handler, and references to
obj.data.src_start / obj.data.src_end / newTab.scroll to modify.

73-76: ⚠️ Potential issue | 🟠 Major

Add a null guard before rendering nested object JSON.

typeof null === "object", so Object.entries(value) can throw here.

🔧 Proposed fix
-                                        : typeof value === "object" ?
+                                        : value !== null && typeof value === "object" ?
                                             <JSONTree
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/dataPanel.tsx` around lines 73 - 76, The rendering condition
that checks typeof value === "object" can crash because typeof null ===
"object"; update the conditional used before rendering JSONTree in dataPanel.tsx
to explicitly guard against null (e.g., check value !== null && typeof value ===
"object") so Object.entries(value) is only called for non-null objects; keep the
same filtering using excludedProperties and ensure the JSONTree data prop only
receives the safe Object.fromEntries(Object.entries(value)....
e2e/tests/nodeDetailsPanel.spec.ts (1)

76-80: ⚠️ Potential issue | 🟠 Major

Guard empty graph state before deriving nodeName.

This block still assumes graphData has at least one node and can dereference undefined.

🔧 Proposed fix
 const graphData = await codeGraph.getGraphNodes();
+expect(graphData.length).toBeGreaterThan(0);
 const targetNode = graphData.find(node => node.src) || graphData[0];
-const nodeName = targetNode.name || targetNode.data?.name;
+expect(targetNode).toBeDefined();
+const nodeName = targetNode?.name || targetNode?.data?.name;
+if (!nodeName) throw new Error("Unable to resolve node name for search");
 await codeGraph.fillSearchBar(nodeName);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/nodeDetailsPanel.spec.ts` around lines 76 - 80, The test assumes
graphData has nodes and dereferences targetNode without guarding; update the
logic around codeGraph.getGraphNodes() to handle an empty or undefined graph
(graphData) and a missing targetNode before deriving nodeName and calling
codeGraph.fillSearchBar/selectSearchBarOptionBtn; for example, check if
graphData is falsy or length===0 and early-return or fail the test with a clear
message, and also ensure targetNode is defined before using targetNode.name or
targetNode.data?.name so fillSearchBar is only called with a valid string.
e2e/logic/POM/codeGraph.ts (2)

151-153: ⚠️ Potential issue | 🟠 Major

Fix malformed XPath interpolation in locateNodeInLastChatPath.

contains(text(), ${node}) injects an unquoted value and produces invalid XPath for normal string node names.

🔧 Proposed fix
 private get locateNodeInLastChatPath(): (node: string) => Locator {
-    return (node: string) => this.page.locator(`(//main[`@data-name`='main-chat']//button//span[contains(text(), ${node})])[last()]`);
+    return (node: string) =>
+        this.page.locator(`(//main[`@data-name`='main-chat']//button//span[contains(text(), ${JSON.stringify(node)})])[last()]`);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logic/POM/codeGraph.ts` around lines 151 - 153, The XPath built in
locateNodeInLastChatPath injects an unquoted node string causing invalid XPath;
change the interpolation to produce a properly quoted and escaped XPath string
(e.g. use JSON.stringify(node) when building the template literal) so the
locator becomes something like
this.page.locator(`(//main[`@data-name`='main-chat']//button//span[contains(text(),
${JSON.stringify(node)})])[last()]`), ensuring node values with quotes are
escaped; update the locateNodeInLastChatPath function accordingly.

591-605: ⚠️ Potential issue | 🟠 Major

Use mobile-aware graph accessor in waitForGraphData.

The method still hardcodes graphDesktop, so mobile-mode tests can read the wrong graph instance.

🔧 Proposed fix
 private async waitForGraphData(): Promise<any> {
+    const graphAccessor = this.isMobile ? "graphMobile" : "graphDesktop";
     await this.waitForCanvasAnimationToEnd();
-    await this.page.waitForFunction(() => {
-        const data = (window as any).graphDesktop?.();
+    await this.page.waitForFunction((accessor) => {
+        const data = (window as any)[accessor]?.();
         return data && ((Array.isArray(data.nodes) && data.nodes.length > 0) ||
             (data.elements && Array.isArray(data.elements.nodes) && data.elements.nodes.length > 0));
-    }, { timeout: 5000 });
+    }, graphAccessor, { timeout: 5000 });

     await this.page.waitForTimeout(3000);
     await this.waitForCanvasAnimationToEnd();

-    return await this.page.evaluate(() => (window as any).graphDesktop());
+    return await this.page.evaluate((accessor) => (window as any)[accessor]?.(), graphAccessor);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logic/POM/codeGraph.ts` around lines 591 - 605, The waitForGraphData
method currently hardcodes (window as any).graphDesktop which breaks mobile-mode
tests; update waitForGraphData to use a mobile-aware accessor: inside the
page.waitForFunction check and the final page.evaluate call, resolve the graph
instance by preferring (window as any).graphMobile if present/active (or any
runtime flag that indicates mobile) and fall back to (window as
any).graphDesktop, and use that result for node/elements existence checks and
the returned value; ensure both the wait condition and the returned evaluation
use the same mobile-aware accessor logic so mobile tests read the correct graph
instance.
e2e/tests/canvas.spec.ts (1)

222-225: ⚠️ Potential issue | 🟡 Minor

Remove non-null assertions in path/API validation.

This still relies on firstNodeRes!.id / secondNodeRes!.id, which can crash before giving a clear test diagnosis.

🔧 Proposed fix
 expect(firstNodeRes).toBeDefined();
 expect(secondNodeRes).toBeDefined();
+if (!firstNodeRes || !secondNodeRes) throw new Error("Path nodes were not resolved from graph data");

 const api = new ApiCalls();
-const response = await api.showPath(GRAPHRAG_SDK ,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);
+expect(callsRelationObject?.src_node).toBe(firstNodeRes.id);
+expect(callsRelationObject?.dest_node).toBe(secondNodeRes.id);
#!/bin/bash
# Verify non-null assertion usage in canvas spec.
rg -nP '!\.id' e2e/tests/canvas.spec.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/canvas.spec.ts` around lines 222 - 225, The test is using non-null
assertions (firstNodeRes!.id / secondNodeRes!.id) which can throw before the
test reports a clear failure; update the api.showPath call and subsequent
expectations to explicitly assert presence of the node responses and their ids
first (e.g., add expect(firstNodeRes).toBeDefined() and
expect(firstNodeRes?.id).toBeDefined() and same for secondNodeRes) and then use
the extracted ids (firstId and secondId) for api.showPath and assertions against
callsRelationObject; also guard that response.result.paths and
response.result.paths[0] exist before calling find on them and assert that
callsRelationObject is defined before checking its src_node/dest_node,
referencing api.showPath, GRAPHRAG_SDK, firstNodeRes, secondNodeRes, and
callsRelationObject.
app/components/code-graph.tsx (1)

175-178: ⚠️ Potential issue | 🟠 Major

Await onFetchGraph and handle failures in selection flow.

Line 177 starts an async operation without awaiting/catch, so rejections can escape and leave state partially updated.

🐛 Suggested fix
 async function handleSelectedValue(value: string) {
     setGraphName(value)
-    onFetchGraph(value)
+    try {
+        await onFetchGraph(value)
+    } catch (error) {
+        toast({
+            variant: "destructive",
+            title: "Uh oh! Something went wrong.",
+            description: "Failed to fetch graph.",
+        })
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/code-graph.tsx` around lines 175 - 178, handleSelectedValue
updates UI state then calls onFetchGraph without awaiting or error handling,
allowing rejections to leave state inconsistent; change handleSelectedValue to
await onFetchGraph(value) inside a try/catch, and either setGraphName only after
successful fetch or roll back the previous graph name in the catch and surface
the error (e.g., set an error state or call an onError handler). Specifically
modify the async function handleSelectedValue to: await onFetchGraph(value) in a
try block, update setGraphName after success (or revert on failure), and
handle/log the caught error so promise rejections do not escape.
🧹 Nitpick comments (10)
app/components/combobox.tsx (1)

54-54: Prefer theme token classes over hardcoded gray borders.

At Line 54, border-gray-400 md:border-gray-100 hardcodes palette values and can become inconsistent with theme/dark-mode styling. Using semantic border tokens from your UI system will be more maintainable.

♻️ Proposed refactor
-            <SelectTrigger className="z-10 md:z-0 rounded-md border border-gray-400 md:border-gray-100 focus:ring-0 focus:ring-offset-0">
+            <SelectTrigger className="z-10 md:z-0 rounded-md border border-input focus:ring-0 focus:ring-offset-0">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/combobox.tsx` at line 54, Replace the hardcoded color utility
classes on the SelectTrigger element (currently "border-gray-400
md:border-gray-100") with your design-system semantic border token classes so
borders respond to theme/dark-mode changes; locate the SelectTrigger JSX in
app/components/combobox.tsx and swap those classes for the appropriate semantic
tokens used across the codebase (e.g., border-default, border-muted,
border-surface or whatever token names your tailwind/design system exposes)
ensuring you preserve existing responsive breakpoint usage (md:...) and other
classes like rounded-md and focus:ring-0.
e2e/infra/ui/basePage.ts (1)

1-1: Use a type-only import for Page.

Page is only used for typing in property and constructor parameter annotations, so this should be a type import.

Suggested diff
-import { Page } from '@playwright/test';
+import { type Page } from '@playwright/test';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/infra/ui/basePage.ts` at line 1, The import of Page should be a type-only
import because it's used only for typing in BasePage; change the import
statement to a type import (e.g., use "import type { Page } from
'@playwright/test'") and keep the rest of the class/constructor/property
annotations (references to Page inside the BasePage constructor and as the page
property) unchanged so the compiler treats it as erased-only type usage.
e2e/infra/ui/browserWrapper.ts (1)

1-1: Prefer type-only imports for Playwright types.

Browser, BrowserContext, and Page are used exclusively in type annotations. Converting them to type-only imports enables better tree-shaking and clarifies intent.

Suggested diff
-import { chromium, Browser, BrowserContext, Page } from '@playwright/test';
+import { chromium, type Browser, type BrowserContext, type Page } from '@playwright/test';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/infra/ui/browserWrapper.ts` at line 1, Import the Playwright runtime
separately from the types: change the combined import so that chromium remains a
normal import but Browser, BrowserContext, and Page are imported as type-only
imports (e.g., use "import { chromium } from '@playwright/test';" and "import
type { Browser, BrowserContext, Page } from '@playwright/test';") so those
symbols are treated solely as types and enable better tree-shaking and intent
clarity in browserWrapper.ts.
components/ui/carousel.tsx (1)

88-99: Support Up/Down keys for vertical orientation.

For vertical carousels, Line 90 and Line 93 still bind only left/right arrows. Mapping Up/Down in vertical mode improves keyboard accessibility and expected behavior.

♿ Suggested adjustment
     const handleKeyDown = React.useCallback(
       (event: React.KeyboardEvent<HTMLDivElement>) => {
-        if (event.key === "ArrowLeft") {
+        const prevKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp"
+        const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown"
+        if (event.key === prevKey) {
           event.preventDefault()
           scrollPrev()
-        } else if (event.key === "ArrowRight") {
+        } else if (event.key === nextKey) {
           event.preventDefault()
           scrollNext()
         }
       },
-      [scrollPrev, scrollNext]
+      [orientation, scrollPrev, scrollNext]
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/carousel.tsx` around lines 88 - 99, The keyboard handler
handleKeyDown currently only maps ArrowLeft/ArrowRight to scrollPrev/scrollNext;
update it to also map ArrowUp/ArrowDown when the carousel is in vertical mode
(e.g., check the orientation prop or isVertical flag) so Up triggers scrollPrev
and Down triggers scrollNext, still calling event.preventDefault(); ensure you
reference and use the existing scrollPrev and scrollNext functions and add the
orientation/isVertical variable to the React.useCallback dependency array so the
handler updates when orientation changes.
lib/utils.ts (2)

7-10: Consider using stronger types instead of any[].

PathData uses any[] for both nodes and links, but Node and Link types are already imported. Using proper types would improve type safety and IDE support.

Suggested fix
 export type PathData = {
-  nodes: any[]
-  links: any[]
+  nodes: Node[]
+  links: Link[]
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils.ts` around lines 7 - 10, PathData currently types nodes and links
as any[], weakening type safety; change the type to use the imported Node and
Link types (e.g., nodes: Node[] and links: Link[]) by updating the PathData type
definition so it references the Node and Link symbols instead of any[] to
improve typings and IDE support.

31-36: Consider stronger typing for Message.paths.

The paths property uses any[] for nodes and links. If these are the same as PathData, consider reusing that type for consistency.

Suggested fix
 export interface Message {
   type: MessageTypes;
   text?: string;
-  paths?: { nodes: any[], links: any[] }[];
+  paths?: PathData[];
   graphName?: string;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils.ts` around lines 31 - 36, The Message interface currently types
paths as { nodes: any[], links: any[] }[] which is too loose; change
Message.paths to reuse the existing PathData type (or define a PathsEntry type)
so nodes and links are strongly typed—update the Message declaration (symbol:
Message and property: paths) to reference PathData (or the correct node/link
types) and adjust any call sites that construct paths to match the stronger
type.
Dockerfile (1)

2-2: Consider using Node.js 22 LTS for production stability.

Node.js 24 is the "current" release line but not yet LTS. Based on learnings, Node.js 22 (codename: Jod) is an LTS version and is suitable for production use in Dockerfiles. Using an LTS version provides longer support and greater stability for production deployments.

Suggested fix to use LTS version
 # Use a Node.js base image
-FROM node:24
+FROM node:22
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` at line 2, The Dockerfile currently pins the base image to the
non-LTS line ("FROM node:24"); update the base image to the Node.js 22 LTS
release by replacing that reference with the Node 22 LTS tag (e.g., "node:22" or
your preferred node:22 variant) so the container uses the stable LTS runtime for
production deployments.
components/ui/drawer.tsx (1)

47-47: Avoid forcing transparent overlay by default.

Line 47 overrides the overlay’s default backdrop and removes modal dimming globally; consider letting DrawerOverlay keep its default unless explicitly overridden by callers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/drawer.tsx` at line 47, The Drawer currently forces a
transparent backdrop by hardcoding "bg-transparent" into the className on
DrawerOverlay, which removes modal dimming globally; change the DrawerOverlay
usage in the Drawer component so it does not include "bg-transparent" by default
— simply pass overlayClassName (if any) or no extra classes so the underlying
DrawerOverlay default styling remains intact; if you need a transparent overlay
make it opt-in by applying overlayClassName from callers rather than hardcoding
inside the Drawer component.
e2e/logic/POM/codeGraph.ts (1)

713-732: Fail explicitly when canvas engine never reaches stopped.

Current timeout path exits silently, so callers may continue on a moving graph and create flaky tests.

♻️ Suggested improvement
 async waitForCanvasAnimationToEnd(timeout = 4500): Promise<void> {
   // ...
   while (Date.now() - startTime < timeout) {
     const status = await this.canvasElement.getAttribute("data-engine-status");
     if (status === "stopped") {
       return;
     }
     await this.page.waitForTimeout(500);
   }
+  throw new Error(`Canvas engine did not reach "stopped" within ${timeout}ms`);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logic/POM/codeGraph.ts` around lines 713 - 732, The
waitForCanvasAnimationToEnd method silently returns when the canvas engine
doesn't reach "stopped"; update waitForCanvasAnimationToEnd (which uses
this.canvasElement and this.page.locator("falkordb-canvas")) to throw a
descriptive error if the loop times out (i.e., after the timeout parameter
elapses) instead of returning silently — include the timeout value and current
status in the error message so callers fail fast when the canvas never reaches
"stopped".
app/components/ForceGraph.tsx (1)

83-89: Clean up the global graph accessor on unmount.

Line 88 writes a function to window, but this effect never removes it. After remounts, stale closures can point to detached canvas instances.

♻️ Suggested fix
 useEffect(() => {
     const canvas = canvasRef.current

     if (!canvas) return

-    (window as any)[id === "desktop" ? "graphDesktop" : "graphMobile"] = () => canvas.getGraphData();
+    const key = id === "desktop" ? "graphDesktop" : "graphMobile"
+    ;(window as any)[key] = () => canvas.getGraphData()
+
+    return () => {
+        delete (window as any)[key]
+    }
 }, [canvasRef, id])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/ForceGraph.tsx` around lines 83 - 89, The effect in
ForceGraph.tsx assigns a global accessor on window using id (graphDesktop or
graphMobile) but never removes it, causing stale closures; update the useEffect
that references canvasRef and id to return a cleanup function that removes or
undefines (delete or set to undefined) (window as any)[id === "desktop" ?
"graphDesktop" : "graphMobile"], ensuring the global is cleared on unmount or
when id/canvasRef changes so canvasRef.current.getGraphData() no longer points
to a detached canvas.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 051c6d8 and 72a3394.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • public/code-graph-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (38)
  • .github/workflows/nextjs.yml
  • .github/workflows/playwright.yml
  • Dockerfile
  • README.md
  • app/api/repo/route.ts
  • app/components/ForceGraph.tsx
  • app/components/Input.tsx
  • app/components/chat.tsx
  • app/components/code-graph.tsx
  • app/components/combobox.tsx
  • app/components/dataPanel.tsx
  • app/components/elementMenu.tsx
  • app/components/graphView.tsx
  • app/components/model.ts
  • app/components/toolbar.tsx
  • app/globals.css
  • app/layout.tsx
  • app/page.tsx
  • components.json
  • components/ui/carousel.tsx
  • components/ui/drawer.tsx
  • components/ui/switch.tsx
  • docker-compose.yml
  • e2e/config/constants.ts
  • e2e/config/testData.ts
  • e2e/infra/ui/basePage.ts
  • e2e/infra/ui/browserWrapper.ts
  • e2e/logic/POM/codeGraph.ts
  • e2e/logic/utils.ts
  • e2e/tests/canvas.spec.ts
  • e2e/tests/chat.spec.ts
  • e2e/tests/navBar.spec.ts
  • e2e/tests/nodeDetailsPanel.spec.ts
  • e2e/tests/searchBar.spec.ts
  • lib/utils.ts
  • package.json
  • playwright.config.ts
  • tailwind.config.js
💤 Files with no reviewable changes (1)
  • app/layout.tsx

Update SECRET_TOKEN in docker-compose.yml to use placeholder
@barakb
Copy link
Contributor

barakb commented Mar 3, 2026

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
e2e/tests/canvas.spec.ts (1)

111-121: ⚠️ Potential issue | 🟡 Minor

Assert lookup results before accessing properties.

firstNode, secondNode, firstNode1, and secondNode1 could be undefined. Add guards to get clear failure messages.

     const firstNode = findNodeByName(initialGraph, path.firstNode);
     const secondNode = findNodeByName(initialGraph, path.secondNode);
+    expect(firstNode).toBeDefined();
+    expect(secondNode).toBeDefined();
     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);
+    expect(firstNode1).toBeDefined();
+    expect(secondNode1).toBeDefined();
     expect(firstNode1.isPath).toBe(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/canvas.spec.ts` around lines 111 - 121, The test accesses
properties on variables that may be undefined (firstNode, secondNode,
firstNode1, secondNode1); add explicit existence assertions before property
access by using expect(...).toBeDefined() or guard checks after calling
findNodeByName (e.g., expect(firstNode).toBeDefined();
expect(secondNode).toBeDefined(); then assert .isPath), and do the same for
firstNode1 and secondNode1 after updateGraph so failures show clear messages;
update the test around findNodeByName and the subsequent expects to include
these guards.
♻️ Duplicate comments (14)
playwright.config.ts (1)

22-23: ⚠️ Potential issue | 🟡 Minor

Update the stale CI parallelism comment.

The comment says CI opts out of parallel tests, but Line 23 explicitly sets workers to 3.

📝 Suggested fix
-  /* Opt out of parallel tests on CI. */
+  /* Limit CI parallelism to 3 workers. */
   workers: process.env.CI ? 3 : undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playwright.config.ts` around lines 22 - 23, The comment is misleading: the
workers config actually forces CI parallelism to 3 via the workers:
process.env.CI ? 3 : undefined line. Update the comment to accurately describe
that CI runs with 3 workers (or change the expression if you intend to opt out);
locate the workers setting (workers and process.env.CI) and either revise the
comment to "Set CI workers to 3" or modify the ternary to the intended behavior.
README.md (2)

9-9: ⚠️ Potential issue | 🟡 Minor

Remove stray hyphen line.

Line 9 is an orphan - and should be deleted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 9, Remove the stray orphan line that contains only a
single hyphen ("-") from the README content; locate the standalone line with
just "-" and delete it so the document no longer contains that lone hyphen.

109-109: ⚠️ Potential issue | 🟡 Minor

Point the issues link to this repository.

Line 109 currently links to FalkorDB/GraphRAG-SDK/issues; this README is for FalkorDB/code-graph.

Suggested change
-* [GitHub Issues](https://github.com/FalkorDB/GraphRAG-SDK/issues)
+* [GitHub Issues](https://github.com/FalkorDB/code-graph/issues)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 109, Update the issues link in README (the bullet entry "*
[GitHub Issues](https://github.com/FalkorDB/GraphRAG-SDK/issues)") to point to
this repository's issues URL for FalkorDB/code-graph (i.e., replace the
GraphRAG-SDK issues URL with https://github.com/FalkorDB/code-graph/issues) so
the "GitHub Issues" link references the correct repository.
app/api/repo/route.ts (3)

41-45: ⚠️ Potential issue | 🔴 Critical

Block untrusted file:// requests from selecting local-folder analysis.

Line 41 lets any caller route to analyze_folder by sending a file:// URL. That should be denied by default (or strictly gated by a server-only allow flag/authz check).

Suggested hardening
-		const isLocal = repo_url.startsWith("file://")
+		const parsed = new URL(repo_url);
+		const allowLocalAnalysis = process.env.ENABLE_LOCAL_ANALYSIS === "true";
+		const isLocal = parsed.protocol === "file:";
+		if (isLocal && !allowLocalAnalysis) {
+			return NextResponse.json("Local file analysis is disabled", { status: 403 });
+		}
+		if (!["http:", "https:", "file:"].includes(parsed.protocol)) {
+			return NextResponse.json("Unsupported URL scheme", { status: 400 });
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/repo/route.ts` around lines 41 - 45, Currently the handler in
route.ts lets callers choose analyze_folder when repo_url.startsWith("file://");
change that logic so local file:// analysis is denied unless a server-side allow
condition is met (e.g., an explicit environment flag like ALLOW_LOCAL_ANALYSIS
or a server-only auth check), and return a 400/403 if file:// is requested
without the allow flag; update the isLocal determination in the same block that
builds the fetch URL and ensure analyze_folder is only used when the allow
flag/check passes (otherwise force use of analyze_repo or reject the request).

43-51: ⚠️ Potential issue | 🟠 Major

Add a timeout to upstream fetch to prevent hung API requests.

This call has no abort/timeout path; a stalled backend can hang the route indefinitely.

Suggested change
 		const result = await fetch(`${url}/${isLocal ? "analyze_folder" : "analyze_repo"}`, {
 			method: 'POST',
 			body: JSON.stringify({ repo_url, ignore: ["./.github", "./sbin", "./.git", "./deps", "./bin", "./build"] }),
 			headers: {
 				"Authorization": token,
 				'Content-Type': 'application/json'
 			},
+			signal: AbortSignal.timeout(30_000),
 			cache: 'no-store'
 		});
#!/bin/bash
# Verify missing timeout in this route
cat -n app/api/repo/route.ts | sed -n '39,53p'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/repo/route.ts` around lines 43 - 51, The fetch call that assigns
result (the code around result = await fetch(...)) has no timeout/abort and can
hang; fix it by creating an AbortController, pass controller.signal into the
fetch options (signal: controller.signal), start a timer (e.g., setTimeout) that
calls controller.abort() after a sensible timeout (e.g., 10-30s), and clear that
timer after fetch resolves or in finally; also handle the abort case in the
surrounding async handler (catch the abort error and return an appropriate
504/timeout response). Ensure you modify the fetch invocation in route.ts where
result is created and add the controller/timer setup and cleanup.

17-18: ⚠️ Potential issue | 🟠 Major

Do not return raw upstream/internal errors to clients.

Throwing await result.text() and returning err.message leaks backend details. Log full errors server-side, but return sanitized client messages.

Suggested change
-		if (!result.ok) {
-			throw new Error(await result.text())
-		}
+		if (!result.ok) {
+			const upstream = await result.text();
+			console.error("Repo API upstream failure", { status: result.status, upstream });
+			throw new Error("Upstream service error");
+		}
...
-		return NextResponse.json(err instanceof Error ? err.message : String(err), { status: 400 })
+		return NextResponse.json("Request failed", { status: 502 })

Also applies to: 25-25, 54-55, 60-60

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/repo/route.ts` around lines 17 - 18, The code is throwing raw
upstream/internal error text (e.g., throw new Error(await result.text()) and
rethrowing err.message), which leaks backend details to clients; change those
sites so you log the full error server-side (use the existing logger or
console.error) and throw/return a sanitized message to the client (e.g., new
Error("Upstream service error" or "Internal error processing request")). Update
every occurrence mentioned (the throw new Error(await result.text()) call and
the places that rethrow err.message) to log the original error and return a
generic client-facing error string instead.
package.json (1)

33-35: ⚠️ Potential issue | 🟠 Major

Align App Router stack to React 19.

next@15.x with react@18.3.1 / react-dom@18.3.1 is a risky pairing for App Router usage. Please bump React/ReactDOM to 19-compatible versions to avoid runtime/build inconsistencies.

Suggested change
-    "react": "^18.3.1",
-    "react-dom": "^18.3.1",
+    "react": "^19.0.0",
+    "react-dom": "^19.0.0",
#!/bin/bash
# Verify current versions and App Router usage before bumping
rg -n '^\s*"next"\s*:\s*' package.json
rg -n '^\s*"react"\s*:\s*' package.json
rg -n '^\s*"react-dom"\s*:\s*' package.json
fd -t d '^app$' .
fd -t f '^route\.ts$' app || true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 33 - 35, package.json currently pins "next" at
^15.5.8 while "react" and "react-dom" are ^18.3.1 which is incompatible with the
App Router—update the "react" and "react-dom" entries to React 19–compatible
versions that match your Next 15.x peer requirements (e.g., bump both to a 19.x
release), run package manager install and rebuild to ensure no peer conflicts,
and run the provided checks (search for "next", "react", "react-dom" and verify
an "app" directory or route.ts usage) to confirm App Router usage; adjust any
code that relies on React 18-only APIs if build/runtime errors appear.
.github/workflows/playwright.yml (1)

39-39: ⚠️ Potential issue | 🟠 Major

Split app startup from test execution and drop CLI reporter override.

Line 39 introduces two issues: no readiness wait before tests (flaky CI) and --reporter=dot,list overrides playwright.config.ts reporters.

Suggested change
-        NEXTAUTH_SECRET=SECRET npm start & npx playwright test --shard=${{ matrix.shard }}/2 --reporter=dot,list
+        NEXTAUTH_SECRET=SECRET npm start &
+        for i in {1..60}; do
+          curl -fsS http://127.0.0.1:3000 >/dev/null && break
+          sleep 1
+        done
+        npx playwright test --shard=${{ matrix.shard }}/2
#!/bin/bash
# Verify workflow invocation and configured reporters
cat -n .github/workflows/playwright.yml | sed -n '34,45p'
fd -t f 'playwright.config.*' -x cat -n {}

Based on learnings: In Playwright, CLI --reporter flags completely override the reporter configuration in playwright.config.ts, even when the config file has conditional reporter settings for CI environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/playwright.yml at line 39, The workflow currently starts
the app and launches tests in one combined command using NEXTAUTH_SECRET=SECRET
npm start & npx playwright test --shard=${{ matrix.shard }}/2
--reporter=dot,list which causes flaky tests (no readiness wait) and forcibly
overrides reporters in playwright.config.ts; fix by splitting startup and test
steps: run npm start in background (or use a proper background launcher), wait
for an explicit readiness probe (e.g., loop/poll a health endpoint or use
wait-for script against your app host/port) before running npx playwright test
--shard=${{ matrix.shard }}/2 (remove --reporter=dot,list so reporters from
playwright.config.ts are honored), and ensure the background server is
terminated/cleaned up after tests complete.
app/globals.css (1)

97-97: ⚠️ Potential issue | 🟠 Major

Fix Biome parsing for Tailwind @apply directives.

Line 97 and Line 109 use valid Tailwind syntax, but Biome is currently flagging them as parse errors. This should be fixed in Biome config so CI/lint does not fail on valid CSS.

#!/bin/bash
# Confirm failing directives and inspect Biome config
rg -n '@apply' app/globals.css
fd 'biome\.json(c)?$|\.biomerc(\.json)?$'
cat -n biome.json 2>/dev/null || true

Also applies to: 109-109

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` at line 97, Biome is flagging valid Tailwind `@apply`
directives (e.g. the "@apply text-blue flex gap-2 p-2 border border-blue
hover:opacity-80 rounded-lg text-left w-fit;" line) because the CSS parser/rules
aren't configured for PostCSS/Tailwind at-rules; update your Biome config
(biome.json or .biomerc.json) to use the PostCSS/Tailwind-aware CSS parser
and/or relax unknown at-rule checks for Tailwind so `@apply` is accepted (for
example, set the CSS parser/syntax to postcss and disable or adjust the
at-rule-unknown rule), then re-run the linter to verify lines containing
"@apply" no longer error.
e2e/tests/chat.spec.ts (2)

123-124: ⚠️ Potential issue | 🟡 Minor

Question-option indexing is off by one.

Line 124 shifts selection to 2..6 while the loop label implies 1..N. This skips the first option and misaligns the assertion intent.

🔢 Fix index mapping
-  for (let index = 1; index < 6; index++) {
-    const questionNumber = index + 1;
+  for (let index = 1; index <= CHAT_OPTIONS_COUNT; index++) {
+    const questionNumber = index;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/chat.spec.ts` around lines 123 - 124, The loop in chat.spec.ts uses
for (let index = 1; index < 6; index++) but then sets const questionNumber =
index + 1 which shifts selection to 2..6 and skips the first option; change the
mapping so questionNumber aligns with the loop intent—either start index at 0
and keep questionNumber = index + 1, or keep index starting at 1 and set
questionNumber = index—update the loop variables/assignment where index and
questionNumber are defined to restore 1..N indexing for the assertions in this
test.

69-69: ⚠️ Potential issue | 🟡 Minor

Remove unsafe non-null assertions after optional chaining.

Line 69 and Line 85 use ?.[0]!, which can still crash when regex matching fails. Assert the match explicitly before indexing.

✅ Safer extraction pattern
-      const number = result.match(/\d+/g)?.[0]!;
+      const match = result.match(/\d+/g);
+      expect(match?.[0]).toBeDefined();
+      const number = match![0];
...
-    const number = uiResponse.match(/\d+/g)?.[0]!;
-    
-    expect(number).toEqual(apiResponse.result.response.match(/\d+/g)?.[0]);
+    const uiMatch = uiResponse.match(/\d+/g);
+    const apiMatch = apiResponse.result.response.match(/\d+/g);
+    expect(uiMatch?.[0]).toBeDefined();
+    expect(apiMatch?.[0]).toBeDefined();
+    expect(uiMatch![0]).toEqual(apiMatch![0]);
#!/bin/bash
# Verify no forbidden non-null assertions remain in this test file
rg -nP '\?\.\[0\]!' e2e/tests/chat.spec.ts

Also applies to: 85-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/chat.spec.ts` at line 69, The code uses unsafe non-null assertions
after optional chaining in expressions like const number =
result.match(/\d+/g)?.[0]! which can still throw if match returns null; update
both occurrences (the const named number derived from result.match and the
similar occurrence at line 85) to explicitly check the match result before
indexing—e.g., call const match = result.match(/\d+/g); if (!match) throw or
fail the test with a clear message, then use match[0]; ensure you replace the
?.[0]! usage with this explicit null-check pattern referencing result.match and
the local match variable.
e2e/tests/nodeDetailsPanel.spec.ts (1)

79-82: ⚠️ Potential issue | 🟠 Major

Guard empty graph and unresolved nodeName before search input.

Line 79/Line 80 can dereference undefined when graphData is empty or malformed, causing an early crash unrelated to the test objective.

🛠️ Proposed guard
     const graphData = await codeGraph.getGraphNodes();
+    expect(graphData.length).toBeGreaterThan(0);
     const targetNode = graphData.find(node => node.src) || graphData[0];
+    expect(targetNode).toBeDefined();
+    if (!targetNode) throw new Error("No suitable node found in FLASK graph");
     const nodeName = targetNode.name || targetNode.data?.name;
+    expect(nodeName).toBeDefined();
+    if (!nodeName) throw new Error("Unable to resolve node name for search");
     await codeGraph.fillSearchBar(nodeName);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/nodeDetailsPanel.spec.ts` around lines 79 - 82, The test
dereferences graphData and targetNode without guards which can throw if
graphData is empty or malformed; update the block that computes targetNode and
nodeName (references: graphData, targetNode, nodeName) to first assert or return
early if graphData is falsy/empty, ensure targetNode exists, and ensure nodeName
is a defined non-empty string before calling codeGraph.fillSearchBar and
codeGraph.selectSearchBarOptionBtn; if no valid nodeName is available, either
skip the search steps or fail the test with a clear message so the test doesn't
crash with an unrelated exception.
app/components/elementMenu.tsx (2)

47-48: ⚠️ Potential issue | 🟠 Major

Use container height for vertical clamping.

Line 48 clamps top using containerWidth, which applies horizontal size to vertical bounds and can place the menu incorrectly on Y-axis.

Suggested fix
-const [containerWidth, setContainerWidth] = useState(0);
+const [containerWidth, setContainerWidth] = useState(0);
+const [containerHeight, setContainerHeight] = useState(0);

 ref={(ref) => {
   if (!ref) return
   setContainerWidth(ref.clientWidth)
+  setContainerHeight(ref.clientHeight)
 }}

 style={{
   left: Math.max(8, Math.min(position.x - containerWidth / 2, (parentRef?.current?.clientWidth || 0) - containerWidth - 8)),
-  top: Math.max(8, Math.min(position.y - 153, (parentRef?.current?.clientHeight || 0) - containerWidth - 8)),
+  top: Math.max(8, Math.min(position.y - 153, (parentRef?.current?.clientHeight || 0) - containerHeight - 8)),
 }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/elementMenu.tsx` around lines 47 - 48, The vertical clamp for
top uses containerWidth by mistake; update the top calculation to use the
container's height instead (e.g., replace containerWidth with containerHeight)
so the expression becomes Math.max(8, Math.min(position.y - 153,
(parentRef?.current?.clientHeight || 0) - containerHeight - 8)); ensure
containerHeight is defined/derived the same way containerWidth is (or compute it
from the same DOM ref) and keep the other identifiers (position.y, parentRef)
unchanged.

125-132: ⚠️ Potential issue | 🟠 Major

Harden external link behavior and avoid duplicate tab opens.

Lines 128-132 combine target="_blank" with manual window.open, which can open two tabs. Also add rel="noopener noreferrer" for security.

Suggested fix
 <a
   className="p-2"
   href={objURL}
   target="_blank"
+  rel="noopener noreferrer"
   title="Go to repo"
-  onClick={() => {
-    window.open(objURL, '_blank');
-  }}
 >
   <Globe color="white" />
 </a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/elementMenu.tsx` around lines 125 - 132, The anchor element
(className="p-2", href={objURL}, target="_blank"}, onClick handler) is opening
the same URL twice and lacks secure rel attributes; either remove the manual
window.open call or remove target="_blank" and use window.open with
rel="noopener noreferrer" — if keeping the onClick for analytics, call
event.preventDefault() then window.open(objURL, '_blank',
'noopener,noreferrer'); otherwise keep target="_blank" and add rel="noopener
noreferrer" and delete the onClick to avoid duplicate tabs.
🧹 Nitpick comments (9)
components/ui/carousel.tsx (2)

88-99: Keyboard navigation does not adapt to vertical orientation.

For vertical carousels, users intuitively expect ArrowUp and ArrowDown keys for navigation, but this handler always uses ArrowLeft/ArrowRight regardless of orientation. This may cause accessibility and usability issues for vertical carousels.

Proposed fix to support orientation-aware keyboard navigation
     const handleKeyDown = React.useCallback(
       (event: React.KeyboardEvent<HTMLDivElement>) => {
-        if (event.key === "ArrowLeft") {
+        const prevKey = orientation === "horizontal" ? "ArrowLeft" : "ArrowUp"
+        const nextKey = orientation === "horizontal" ? "ArrowRight" : "ArrowDown"
+
+        if (event.key === prevKey) {
           event.preventDefault()
           scrollPrev()
-        } else if (event.key === "ArrowRight") {
+        } else if (event.key === nextKey) {
           event.preventDefault()
           scrollNext()
         }
       },
-      [scrollPrev, scrollNext]
+      [orientation, scrollPrev, scrollNext]
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/carousel.tsx` around lines 88 - 99, handleKeyDown currently
only handles "ArrowLeft"/"ArrowRight" which breaks keyboard navigation for
vertical carousels; update the handleKeyDown callback to branch on the carousel
orientation (e.g., check the orientation prop or state used by this component)
and use "ArrowUp"/"ArrowDown" when orientation === "vertical" and
"ArrowLeft"/"ArrowRight" otherwise, calling scrollPrev()/scrollNext() and
event.preventDefault() accordingly; also add the orientation identifier to the
hook dependency array so the callback updates when orientation changes (refer to
handleKeyDown, scrollPrev, scrollNext, and the orientation prop/state).

210-210: Minor: Double space in className.

There's an extra space in the className string ("absolute h-8 w-8").

Fix
       className={cn(
-        "absolute  h-8 w-8 rounded-full",
+        "absolute h-8 w-8 rounded-full",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/carousel.tsx` at line 210, In the Carousel component update the
offending className string that contains a double space ("absolute  h-8 w-8
rounded-full") to remove the extra space so it becomes a single-space separated
class list (e.g., "absolute h-8 w-8 rounded-full"); locate the JSX element or
prop where that string is used (the carousel navigation button/className) and
correct the spacing in the literal.
.github/workflows/playwright.yml (1)

26-27: Use a single install strategy in CI.

npm ci is already executed, then npm install runs again in the test step. Remove the second install to keep runs deterministic and faster.

Suggested change
       run: |
-        npm install
         npm run build
         NEXTAUTH_SECRET=SECRET npm start & npx playwright test --shard=${{ matrix.shard }}/2 --reporter=dot,list

Also applies to: 37-37

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/playwright.yml around lines 26 - 27, The workflow runs npm
ci already, so remove the redundant npm install invocation in the test step (the
second install that appears after the "run: npm ci" and before/within the
"Install Playwright Browsers" step) to keep CI deterministic and faster; keep
the dedicated "Install Playwright Browsers" step (or replace its npm install
call with only the Playwright browser install command such as the npx playwright
install call) so browsers are still installed but no extra npm install is
executed.
components/ui/drawer.tsx (1)

56-56: Mark the drag handle as decorative for assistive technology.

Line 56 renders a non-semantic visual handle. Add aria-hidden="true" to avoid unnecessary screen-reader noise.

♿ Suggested tweak
-      <div className={cn("mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted", handleClassName)} />
+      <div
+        aria-hidden="true"
+        className={cn("mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted", handleClassName)}
+      />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/drawer.tsx` at line 56, The visual drag handle div rendered in
the Drawer component (the element with className built from "mx-auto mt-4 h-2
w-[100px] rounded-full bg-muted" and handleClassName) should be marked
decorative for assistive tech; update that div to include aria-hidden="true" so
screen readers ignore it (locate the div in components/ui/drawer.tsx and add the
aria attribute to the same JSX element).
app/components/ForceGraph.tsx (1)

88-89: Clean up global graph accessors on unmount.

Line 88 sets a global function but never removes it. This can leave stale references after component teardown/remount.

🧹 Suggested cleanup
   useEffect(() => {
     const canvas = canvasRef.current

     if (!canvas) return

-    (window as any)[id === "desktop" ? "graphDesktop" : "graphMobile"] = () => canvas.getGraphData();
+    const key = id === "desktop" ? "graphDesktop" : "graphMobile"
+    ;(window as any)[key] = () => canvas.getGraphData()
+
+    return () => {
+      delete (window as any)[key]
+    }
   }, [canvasRef, id])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/ForceGraph.tsx` around lines 88 - 89, The effect in
ForceGraph.tsx sets a global accessor (window[ id === "desktop" ? "graphDesktop"
: "graphMobile" ] = () => canvas.getGraphData()) but never removes it; add a
cleanup function returned from the useEffect that deletes or nulls that global
key on unmount (guarding for the same key computed from id) so stale references
to canvas.getGraphData() are removed; reference the existing symbols
canvas.getGraphData, canvasRef, id and the window keys
"graphDesktop"/"graphMobile" when implementing the cleanup.
e2e/tests/searchBar.spec.ts (1)

85-86: Canvas-center right-click is brittle for node verification.

Line 85 assumes the selected node is exactly centered after animation. This can intermittently miss the node and fail Line 86. Prefer clicking resolved node coordinates (from graph data) before asserting tooltip content.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/tests/searchBar.spec.ts` around lines 85 - 86, The test is brittle
because codeGraph.rightClickAtCanvasCenter() may miss the intended node; instead
resolve the target node's coordinates and click there before asserting the
tooltip. Replace the center click with a deterministic click using the node's
coordinates (obtainable from the graph data or a helper like
codeGraph.getNodePosition(nodeName) or graphData.getNodeByName(nodeName).x/y),
call the click at those coordinates (e.g., codeGraph.clickAt(x,y)), then assert
via codeGraph.getNodeToolTipContent() contains nodeName.
app/components/code-graph.tsx (2)

206-213: Avoid returning values from forEach callbacks.

The forEach at line 208 returns the result of deleteIdsMap.add(), which Biome flags as suspicious. While harmless here, it's clearer to use an explicit statement.

♻️ Suggested fix
-        deleteNeighbors(expandedNodes)?.forEach(id => deleteIdsMap.add(id))
+        deleteNeighbors(expandedNodes)?.forEach(id => {
+            deleteIdsMap.add(id)
+        })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/code-graph.tsx` around lines 206 - 213, The forEach callback
currently implicitly returns the result of deleteIdsMap.add(), which is flagged
as suspicious; change the call site (where
deleteNeighbors(expandedNodes)?.forEach(...)) to avoid returning values from the
callback by first capturing the result of deleteNeighbors(expandedNodes) into a
variable and then iterating with an explicit statement (e.g., if the result is
truthy, call .forEach(id => { deleteIdsMap.add(id); }) or use a for...of loop)
so the callback body contains a clear statement instead of an expression —
target the deleteNeighbors(...) invocation and the deleteIdsMap.add usage to
implement this.

410-416: Avoid returning values from forEach callbacks.

The assignment c.show = true returns true, which Biome flags as suspicious in a forEach callback.

♻️ Suggested fix
-                                                    graph.Categories.forEach(c => c.show = true);
+                                                    graph.Categories.forEach(c => {
+                                                        c.show = true
+                                                    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/code-graph.tsx` around lines 410 - 416, The forEach callback
on graph.Categories returns the assignment result (c.show = true) which Biome
flags; change the callback to use a statement block or an explicit loop so it
doesn't return a value — e.g., replace graph.Categories.forEach(c => c.show =
true) with a block-style callback (graph.Categories.forEach(c => { c.show =
true; })) or a for..of loop over graph.Categories; keep the existing
graph.getElements().forEach((element) => { element.visible = true }) as-is.
app/components/graphView.tsx (1)

253-255: Consider a more sensible default for mobile breakpoint.

The fallback value of 0 means if NEXT_PUBLIC_MOBILE_BREAKPOINT is unset, isDesktop will always be true (any screenSize > 0). This is likely the intended behavior but a common breakpoint like 768 might be safer.

 const mobileBreakpointRaw = Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT)
-const mobileBreakpoint = Number.isFinite(mobileBreakpointRaw) ? mobileBreakpointRaw : 0
+const mobileBreakpoint = Number.isFinite(mobileBreakpointRaw) ? mobileBreakpointRaw : 768
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/graphView.tsx` around lines 253 - 255, The fallback mobile
breakpoint of 0 makes isDesktop (computed from screenSize > mobileBreakpoint)
true whenever NEXT_PUBLIC_MOBILE_BREAKPOINT is unset; change the fallback to a
sensible default like 768 by updating how mobileBreakpoint is computed (use
mobileBreakpointRaw and mobileBreakpoint) so that
Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) falls back to 768 when not
valid, ensuring isDesktop behaves correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/components/chat.tsx`:
- Around line 118-119: Guard against empty or missing path nodes before indexing
p.nodes in the Chat component: before computing firstNodeId and lastNodeId (and
the similar use around the later occurrence at the p.nodes[...]-location) check
that p.nodes exists and p.nodes.length > 0, and handle the empty case (e.g.,
skip indexing, set safe defaults, or bail out of that iteration). Update the
logic around the expressions that reference p.nodes[0].id and
p.nodes[p.nodes.length - 1].id to use this guard so malformed/empty path
responses do not throw at runtime.
- Around line 170-177: The loop that marks path elements uses e.isPathSelected =
true for endpoints and never clears isPathSelected for shared non-endpoint
nodes, causing stale selection state; update the logic in the forEach that
iterates over elements (the block referencing pLinkIds, pNodeIds, firstNodeId,
lastNodeId) to explicitly set both e.isPathSelected = false and e.isPath = false
for elements that are not part of the new path, and only set e.isPathSelected =
true for endpoints (firstNodeId/lastNodeId) or e.isPath = true for other path
elements; apply the same deterministic reset to the analogous loop at the other
occurrence (the block around lines 209-217) to ensure no stale flags remain.

In `@app/components/combobox.tsx`:
- Line 9: onSelectedValue (typed as (value: string) => Promise<void>) is passed
directly to the Select's onValueChange, which lets async rejections escape the
event handler; update the call site so the Select receives a non-async callback
that invokes handleSelectedValue/onSelectedValue and catches errors (e.g., wrap
with () => handleSelectedValue(value).catch(err => { /* surface error: set error
state, show toast, or log via processLogger */ })), ensuring any rejection from
onFetchGraph is handled and user-visible feedback is provided; keep the original
function signatures (onSelectedValue, handleSelectedValue, onFetchGraph) but add
the catching wrapper where you pass the callback to onValueChange/Select.

In `@app/components/model.ts`:
- Around line 173-175: When building Node objects where you currently spread
nodeData.properties into data (the occurrences around nodeData and the Node.data
construction), ensure Node.data.name is always set and is a string: if
nodeData.properties.name is missing or falsy, set name to a derived string
(e.g., String(nodeData.id) or a computed label) and avoid assigning raw numeric
ids. Update the spots that currently do "...nodeData.properties" so they
explicitly set data.name = nodeData.properties.name ?? String(nodeData.id) (or
similar) and coerce to string to prevent numeric names breaking name-dependent
UI paths.

In `@e2e/logic/utils.ts`:
- Around line 34-35: The timeout path in waitForStableText currently returns
stableText which can hide failures; change the timeout handling in
waitForStableText so that instead of returning stableText when the timeout
elapses it throws a clear Error (including context like the last observed
stableText and the timeout duration) to fail tests explicitly; update references
to stableText and the timeout variable inside waitForStableText so callers see a
thrown exception rather than a silent return on timeout.
- Around line 5-13: waitToBeEnabled currently throws if locator.elementHandle()
returns null because it doesn't wait for the element to be attached; modify
waitToBeEnabled to first await locator.waitFor({ state: "attached", timeout })
(using the same timeout parameter) before calling locator.elementHandle(), then
proceed to use locator.page().waitForFunction(...) to wait until the element
does not have the "disabled" attribute; keep existing error handling but rely on
locator.waitFor to surface a timeout if the element never attaches.

In `@e2e/tests/canvas.spec.ts`:
- Around line 86-88: The test currently reads the graph and immediately accesses
targetNodeForUpdateGraph.visible, which can throw if findNodeByName returns
undefined; update the assertions around codeGraph.getGraphNodes()/findNodeByName
so you first assert targetNodeForUpdateGraph is defined (e.g.,
expect(targetNodeForUpdateGraph).toBeDefined()) using the result of
findNodeByName(updatedGraph, nodes[0].nodeName), then assert its visible
property—this ensures a clear test failure when the node is missing and prevents
a runtime error.
- Around line 97-99: The test currently assumes findItem exists after calling
codeGraph.getGraphNodes(); change the assertion to first verify the lookup
result (the variable findItem from result.find using category) is defined before
accessing its .visible property—e.g., add an assertion like
expect(findItem).toBeDefined() or expect(findItem).not.toBeUndefined()
immediately after the find, then assert expect(findItem.visible).toBeFalsy();
this prevents a runtime error when no node matches the category.

In `@lib/utils.ts`:
- Around line 6-9: The exported type PathData uses any[] which weakens typing;
change PathData.nodes and PathData.links from any[] to unknown[] and then update
the Message type to reference PathData for its paths property (reuse PathData
instead of duplicating types) so Message.paths: PathData[] (and remove
duplicated any[] usage) to improve downstream type safety.

In `@playwright.config.ts`:
- Around line 25-26: The CLI `--reporter=dot,list` flag is overriding the
Playwright config reporters so the reporter setting (reporter) and artifact
paths (outputFolder/outputDir) in playwright.config.ts are ignored; fix by
either removing the `--reporter=dot,list` from the CI command in the workflow so
Playwright uses the config's reporter and respects outputFolder/outputDir, or
else update the reporter entry in playwright.config.ts to match the CI reporter
(e.g., change the reporter setting used by the `reporter` symbol to include dot
and list) so CI and config align.

---

Outside diff comments:
In `@e2e/tests/canvas.spec.ts`:
- Around line 111-121: The test accesses properties on variables that may be
undefined (firstNode, secondNode, firstNode1, secondNode1); add explicit
existence assertions before property access by using expect(...).toBeDefined()
or guard checks after calling findNodeByName (e.g.,
expect(firstNode).toBeDefined(); expect(secondNode).toBeDefined(); then assert
.isPath), and do the same for firstNode1 and secondNode1 after updateGraph so
failures show clear messages; update the test around findNodeByName and the
subsequent expects to include these guards.

---

Duplicate comments:
In @.github/workflows/playwright.yml:
- Line 39: The workflow currently starts the app and launches tests in one
combined command using NEXTAUTH_SECRET=SECRET npm start & npx playwright test
--shard=${{ matrix.shard }}/2 --reporter=dot,list which causes flaky tests (no
readiness wait) and forcibly overrides reporters in playwright.config.ts; fix by
splitting startup and test steps: run npm start in background (or use a proper
background launcher), wait for an explicit readiness probe (e.g., loop/poll a
health endpoint or use wait-for script against your app host/port) before
running npx playwright test --shard=${{ matrix.shard }}/2 (remove
--reporter=dot,list so reporters from playwright.config.ts are honored), and
ensure the background server is terminated/cleaned up after tests complete.

In `@app/api/repo/route.ts`:
- Around line 41-45: Currently the handler in route.ts lets callers choose
analyze_folder when repo_url.startsWith("file://"); change that logic so local
file:// analysis is denied unless a server-side allow condition is met (e.g., an
explicit environment flag like ALLOW_LOCAL_ANALYSIS or a server-only auth
check), and return a 400/403 if file:// is requested without the allow flag;
update the isLocal determination in the same block that builds the fetch URL and
ensure analyze_folder is only used when the allow flag/check passes (otherwise
force use of analyze_repo or reject the request).
- Around line 43-51: The fetch call that assigns result (the code around result
= await fetch(...)) has no timeout/abort and can hang; fix it by creating an
AbortController, pass controller.signal into the fetch options (signal:
controller.signal), start a timer (e.g., setTimeout) that calls
controller.abort() after a sensible timeout (e.g., 10-30s), and clear that timer
after fetch resolves or in finally; also handle the abort case in the
surrounding async handler (catch the abort error and return an appropriate
504/timeout response). Ensure you modify the fetch invocation in route.ts where
result is created and add the controller/timer setup and cleanup.
- Around line 17-18: The code is throwing raw upstream/internal error text
(e.g., throw new Error(await result.text()) and rethrowing err.message), which
leaks backend details to clients; change those sites so you log the full error
server-side (use the existing logger or console.error) and throw/return a
sanitized message to the client (e.g., new Error("Upstream service error" or
"Internal error processing request")). Update every occurrence mentioned (the
throw new Error(await result.text()) call and the places that rethrow
err.message) to log the original error and return a generic client-facing error
string instead.

In `@app/components/elementMenu.tsx`:
- Around line 47-48: The vertical clamp for top uses containerWidth by mistake;
update the top calculation to use the container's height instead (e.g., replace
containerWidth with containerHeight) so the expression becomes Math.max(8,
Math.min(position.y - 153, (parentRef?.current?.clientHeight || 0) -
containerHeight - 8)); ensure containerHeight is defined/derived the same way
containerWidth is (or compute it from the same DOM ref) and keep the other
identifiers (position.y, parentRef) unchanged.
- Around line 125-132: The anchor element (className="p-2", href={objURL},
target="_blank"}, onClick handler) is opening the same URL twice and lacks
secure rel attributes; either remove the manual window.open call or remove
target="_blank" and use window.open with rel="noopener noreferrer" — if keeping
the onClick for analytics, call event.preventDefault() then window.open(objURL,
'_blank', 'noopener,noreferrer'); otherwise keep target="_blank" and add
rel="noopener noreferrer" and delete the onClick to avoid duplicate tabs.

In `@app/globals.css`:
- Line 97: Biome is flagging valid Tailwind `@apply` directives (e.g. the "@apply
text-blue flex gap-2 p-2 border border-blue hover:opacity-80 rounded-lg
text-left w-fit;" line) because the CSS parser/rules aren't configured for
PostCSS/Tailwind at-rules; update your Biome config (biome.json or
.biomerc.json) to use the PostCSS/Tailwind-aware CSS parser and/or relax unknown
at-rule checks for Tailwind so `@apply` is accepted (for example, set the CSS
parser/syntax to postcss and disable or adjust the at-rule-unknown rule), then
re-run the linter to verify lines containing "@apply" no longer error.

In `@e2e/tests/chat.spec.ts`:
- Around line 123-124: The loop in chat.spec.ts uses for (let index = 1; index <
6; index++) but then sets const questionNumber = index + 1 which shifts
selection to 2..6 and skips the first option; change the mapping so
questionNumber aligns with the loop intent—either start index at 0 and keep
questionNumber = index + 1, or keep index starting at 1 and set questionNumber =
index—update the loop variables/assignment where index and questionNumber are
defined to restore 1..N indexing for the assertions in this test.
- Line 69: The code uses unsafe non-null assertions after optional chaining in
expressions like const number = result.match(/\d+/g)?.[0]! which can still throw
if match returns null; update both occurrences (the const named number derived
from result.match and the similar occurrence at line 85) to explicitly check the
match result before indexing—e.g., call const match = result.match(/\d+/g); if
(!match) throw or fail the test with a clear message, then use match[0]; ensure
you replace the ?.[0]! usage with this explicit null-check pattern referencing
result.match and the local match variable.

In `@e2e/tests/nodeDetailsPanel.spec.ts`:
- Around line 79-82: The test dereferences graphData and targetNode without
guards which can throw if graphData is empty or malformed; update the block that
computes targetNode and nodeName (references: graphData, targetNode, nodeName)
to first assert or return early if graphData is falsy/empty, ensure targetNode
exists, and ensure nodeName is a defined non-empty string before calling
codeGraph.fillSearchBar and codeGraph.selectSearchBarOptionBtn; if no valid
nodeName is available, either skip the search steps or fail the test with a
clear message so the test doesn't crash with an unrelated exception.

In `@package.json`:
- Around line 33-35: package.json currently pins "next" at ^15.5.8 while "react"
and "react-dom" are ^18.3.1 which is incompatible with the App Router—update the
"react" and "react-dom" entries to React 19–compatible versions that match your
Next 15.x peer requirements (e.g., bump both to a 19.x release), run package
manager install and rebuild to ensure no peer conflicts, and run the provided
checks (search for "next", "react", "react-dom" and verify an "app" directory or
route.ts usage) to confirm App Router usage; adjust any code that relies on
React 18-only APIs if build/runtime errors appear.

In `@playwright.config.ts`:
- Around line 22-23: The comment is misleading: the workers config actually
forces CI parallelism to 3 via the workers: process.env.CI ? 3 : undefined line.
Update the comment to accurately describe that CI runs with 3 workers (or change
the expression if you intend to opt out); locate the workers setting (workers
and process.env.CI) and either revise the comment to "Set CI workers to 3" or
modify the ternary to the intended behavior.

In `@README.md`:
- Line 9: Remove the stray orphan line that contains only a single hyphen ("-")
from the README content; locate the standalone line with just "-" and delete it
so the document no longer contains that lone hyphen.
- Line 109: Update the issues link in README (the bullet entry "* [GitHub
Issues](https://github.com/FalkorDB/GraphRAG-SDK/issues)") to point to this
repository's issues URL for FalkorDB/code-graph (i.e., replace the GraphRAG-SDK
issues URL with https://github.com/FalkorDB/code-graph/issues) so the "GitHub
Issues" link references the correct repository.

---

Nitpick comments:
In @.github/workflows/playwright.yml:
- Around line 26-27: The workflow runs npm ci already, so remove the redundant
npm install invocation in the test step (the second install that appears after
the "run: npm ci" and before/within the "Install Playwright Browsers" step) to
keep CI deterministic and faster; keep the dedicated "Install Playwright
Browsers" step (or replace its npm install call with only the Playwright browser
install command such as the npx playwright install call) so browsers are still
installed but no extra npm install is executed.

In `@app/components/code-graph.tsx`:
- Around line 206-213: The forEach callback currently implicitly returns the
result of deleteIdsMap.add(), which is flagged as suspicious; change the call
site (where deleteNeighbors(expandedNodes)?.forEach(...)) to avoid returning
values from the callback by first capturing the result of
deleteNeighbors(expandedNodes) into a variable and then iterating with an
explicit statement (e.g., if the result is truthy, call .forEach(id => {
deleteIdsMap.add(id); }) or use a for...of loop) so the callback body contains a
clear statement instead of an expression — target the deleteNeighbors(...)
invocation and the deleteIdsMap.add usage to implement this.
- Around line 410-416: The forEach callback on graph.Categories returns the
assignment result (c.show = true) which Biome flags; change the callback to use
a statement block or an explicit loop so it doesn't return a value — e.g.,
replace graph.Categories.forEach(c => c.show = true) with a block-style callback
(graph.Categories.forEach(c => { c.show = true; })) or a for..of loop over
graph.Categories; keep the existing graph.getElements().forEach((element) => {
element.visible = true }) as-is.

In `@app/components/ForceGraph.tsx`:
- Around line 88-89: The effect in ForceGraph.tsx sets a global accessor
(window[ id === "desktop" ? "graphDesktop" : "graphMobile" ] = () =>
canvas.getGraphData()) but never removes it; add a cleanup function returned
from the useEffect that deletes or nulls that global key on unmount (guarding
for the same key computed from id) so stale references to canvas.getGraphData()
are removed; reference the existing symbols canvas.getGraphData, canvasRef, id
and the window keys "graphDesktop"/"graphMobile" when implementing the cleanup.

In `@app/components/graphView.tsx`:
- Around line 253-255: The fallback mobile breakpoint of 0 makes isDesktop
(computed from screenSize > mobileBreakpoint) true whenever
NEXT_PUBLIC_MOBILE_BREAKPOINT is unset; change the fallback to a sensible
default like 768 by updating how mobileBreakpoint is computed (use
mobileBreakpointRaw and mobileBreakpoint) so that
Number(process.env.NEXT_PUBLIC_MOBILE_BREAKPOINT) falls back to 768 when not
valid, ensuring isDesktop behaves correctly.

In `@components/ui/carousel.tsx`:
- Around line 88-99: handleKeyDown currently only handles
"ArrowLeft"/"ArrowRight" which breaks keyboard navigation for vertical
carousels; update the handleKeyDown callback to branch on the carousel
orientation (e.g., check the orientation prop or state used by this component)
and use "ArrowUp"/"ArrowDown" when orientation === "vertical" and
"ArrowLeft"/"ArrowRight" otherwise, calling scrollPrev()/scrollNext() and
event.preventDefault() accordingly; also add the orientation identifier to the
hook dependency array so the callback updates when orientation changes (refer to
handleKeyDown, scrollPrev, scrollNext, and the orientation prop/state).
- Line 210: In the Carousel component update the offending className string that
contains a double space ("absolute  h-8 w-8 rounded-full") to remove the extra
space so it becomes a single-space separated class list (e.g., "absolute h-8 w-8
rounded-full"); locate the JSX element or prop where that string is used (the
carousel navigation button/className) and correct the spacing in the literal.

In `@components/ui/drawer.tsx`:
- Line 56: The visual drag handle div rendered in the Drawer component (the
element with className built from "mx-auto mt-4 h-2 w-[100px] rounded-full
bg-muted" and handleClassName) should be marked decorative for assistive tech;
update that div to include aria-hidden="true" so screen readers ignore it
(locate the div in components/ui/drawer.tsx and add the aria attribute to the
same JSX element).

In `@e2e/tests/searchBar.spec.ts`:
- Around line 85-86: The test is brittle because
codeGraph.rightClickAtCanvasCenter() may miss the intended node; instead resolve
the target node's coordinates and click there before asserting the tooltip.
Replace the center click with a deterministic click using the node's coordinates
(obtainable from the graph data or a helper like
codeGraph.getNodePosition(nodeName) or graphData.getNodeByName(nodeName).x/y),
call the click at those coordinates (e.g., codeGraph.clickAt(x,y)), then assert
via codeGraph.getNodeToolTipContent() contains nodeName.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 051c6d8 and e58cc3b.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • public/code-graph-logo.svg is excluded by !**/*.svg
📒 Files selected for processing (38)
  • .github/workflows/nextjs.yml
  • .github/workflows/playwright.yml
  • Dockerfile
  • README.md
  • app/api/repo/route.ts
  • app/components/ForceGraph.tsx
  • app/components/Input.tsx
  • app/components/chat.tsx
  • app/components/code-graph.tsx
  • app/components/combobox.tsx
  • app/components/dataPanel.tsx
  • app/components/elementMenu.tsx
  • app/components/graphView.tsx
  • app/components/model.ts
  • app/components/toolbar.tsx
  • app/globals.css
  • app/layout.tsx
  • app/page.tsx
  • components.json
  • components/ui/carousel.tsx
  • components/ui/drawer.tsx
  • components/ui/switch.tsx
  • docker-compose.yml
  • e2e/config/constants.ts
  • e2e/config/testData.ts
  • e2e/infra/ui/basePage.ts
  • e2e/infra/ui/browserWrapper.ts
  • e2e/logic/POM/codeGraph.ts
  • e2e/logic/utils.ts
  • e2e/tests/canvas.spec.ts
  • e2e/tests/chat.spec.ts
  • e2e/tests/navBar.spec.ts
  • e2e/tests/nodeDetailsPanel.spec.ts
  • e2e/tests/searchBar.spec.ts
  • lib/utils.ts
  • package.json
  • playwright.config.ts
  • tailwind.config.js
💤 Files with no reviewable changes (1)
  • app/layout.tsx

@Anchel123 Anchel123 requested a review from AviAvni March 3, 2026 14:30
@Anchel123 Anchel123 merged commit bab0090 into main Mar 4, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants