Skip to content

Conversation

@Anchel123
Copy link
Contributor

@Anchel123 Anchel123 commented Dec 11, 2025

Summary by CodeRabbit

  • New Features

    • Native canvas renderer with improved node/edge styling (color, size, caption) and persistent style customization.
    • CSV export from table view.
    • Client-side encryption utilities for securely storing secrets.
  • Chores

    • App wiring migrated to canvas-driven data flow and dynamic panel loading.
    • Build/runtime upgraded to Node 24.
  • Bug Fixes

    • Improved selection/visibility sync, zoom-to-fit, and updated end-to-end test expectations.

✏️ Tip: You can customize this high-level summary in your review settings.

PR Summary by Typo

Overview

This pull request migrates the graph visualization component from react-force-graph-2d to the new @falkordb/canvas library. This significant change updates the underlying graph rendering technology, streamlines data handling, and modernizes the visualization stack.

Key Changes

  • Replaced react-force-graph-2d with @falkordb/canvas as the primary graph visualization library.
  • Updated Node.js versions to 24 in GitHub Actions workflows for CI/CD.
  • Refactored graph data models (Node, Link) and utility functions to align with the @falkordb/canvas API.
  • Rewrote the ForceGraph component to utilize the new web component, removing custom D3 force calculations and canvas rendering logic.
  • Adapted various UI components (DataPanel, DataTable, GraphView, SchemaView, Toolbar) to interact with the new canvas component and its data model.
  • Introduced a new ForceGraphContext to manage graph visualization state, replacing the previous ViewportContext.

Work Breakdown

Category Lines Changed
New Work 346 (26.9%)
Churn 705 (54.9%)
Refactor 234 (18.2%)
Total Changes 1285
To turn off PR summary, please visit Notification settings.

@vercel
Copy link

vercel bot commented Dec 11, 2025

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

Project Deployment Review Updated (UTC)
falkordb-browser Ready Ready Preview, Comment Jan 12, 2026 0:09am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Walkthrough

Replaces react-force-graph-2d with a FalkorDB canvas, migrates graph model from wrapper objects to ID-based plain shapes with style-driven color/size/caption, threads canvasRef/graphData via a new ForceGraphContext, syncs panels to the canvas, adds client-side AES‑GCM helpers, bumps Node to 24 in CI/Docker, and normalizes semicolons and component props.

Changes

Cohort / File(s) Summary
Core Graph Model
app/api/graph/model.ts
Convert Node/Link to ID-based plain shapes; move color/size/caption into style objects; remove wrapper/displayName/curve; add nodesMap/linksMap; update GraphInfo/Graph signatures.
Canvas Integration & Types
app/components/ForceGraph.tsx, package.json, lib/utils.ts, app/components/provider.ts
Replace ForceGraph2D with <falkordb-canvas/>; add @falkordb/canvas; introduce canvasRef, graphData/setGraphData, event wiring and convertToCanvasData; update GraphRef type.
Context & Providers
app/components/provider.ts, app/providers.tsx
Rename ViewportContext → ForceGraphContext; expose canvasRef, graphData/setGraphData; add setIsLoading; remove display-text-priority plumbing and migrate defaults.
Views / Pages
app/graph/GraphView.tsx, app/graph/page.tsx, app/graph/layout.tsx, app/schema/_page.tsx
Thread canvasRef/graphData through pages; dynamic imports SSR-disabled; update props and lifecycle to sync canvas; remove parentWidth/parentHeight props.
Panels & Data Sync
app/graph/DataPanel.tsx, app/graph/DataTable.tsx, app/graph/CreateElementPanel.tsx, app/graph/CustomizeStylePanel.tsx
Add canvasRef/setData props; after mutations call getGraphData/setGraphData to sync canvas; migrate style persistence keys to color/size/caption.
Schema & Controls
app/schema/SchemaView.tsx, app/graph/controls.tsx, app/graph/toolbar.tsx, app/graph/Selector.tsx
Replace chartRefcanvasRef; add graphData/setGraphData/setIsLoading where needed; update zoom/fit calls; change type guards to property-presence checks.
UI & Graph Panels
app/graph/graphInfo.tsx, app/graph/labels.tsx
Render labels/relationships from style fields; update customize/save/cancel flows and live preview via canvas updates; use EMPTY_DISPLAY_NAME.
Encryption Utilities
lib/encryption.ts
New client-side AES‑GCM helpers: encryptValue, decryptValue, isEncrypted, isCryptoAvailable.
E2E & Tests
e2e/..., e2e/infra/utils.ts, e2e/logic/POM/*
Update selectors/locators to falkordb-canvas, poll engine-status, adapt canvas evaluations; add CREATE_PERSON_RELATIONSHIP; update tests and localStorage keys for new style fields.
CI / Runtime / Tooling
.github/workflows/*, Dockerfile, .eslintrc.json
Bump Node to 24 in CI and Dockerfile; add semicolon ESLint rules and exclude *.d.ts; remove react-force-graph-2d, add @falkordb/canvas.
Misc / Formatting & Props
many app/* files
Wide semicolon/formatting normalization; several components changed props (CreateGraph, ExportGraph, DataPanel, DataTable); minor guard and prop-name updates for canvas API.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Providers as App Providers
    participant ForceCtx as ForceGraphContext
    participant View as GraphView / Panels
    participant Canvas as falkordb-canvas
    participant Store as graphData

    Providers->>ForceCtx: provide canvasRef, graphData, setGraphData, setIsLoading
    ForceCtx-->>View: supply canvasRef, graphData, setters
    View->>Canvas: mount <falkordb-canvas/> with canvasRef + graphData
    Canvas->>Canvas: initialize engine & internal graph
    Note over Canvas: User actions (click / hover / right-click / background)
    Canvas-->>View: emit events (node/link click, hover, background)
    View->>ForceCtx: update selection or call setGraphData(updated)
    ForceCtx->>Store: setGraphData(updated)
    Store-->>Canvas: Canvas receives new graphData and re-renders
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Staging #1331 — touches graph styling and model changes (label style objects, CustomizeStylePanel, and model reshaping) closely related to this migration.

Suggested reviewers

  • shahar-biron

Poem

Canvas wakes; old wrappers fall away,
IDs march on and styles light the way,
Context lends the ref with careful art,
Panels sync and charts redraw their heart,
AES hums soft — a tidy, modern start.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.38% 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 'Fix #1270 Falkordb canvas' is vague and generic. While it references an issue number, it doesn't clearly describe the primary change (migration from react-force-graph-2d to @falkordb/canvas). Consider using a more descriptive title following conventional commit format, such as 'refactor: migrate graph visualization to @falkordb/canvas' or 'refactor(graph): replace react-force-graph-2d with @falkordb/canvas'.
✅ 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

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
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: 7

🧹 Nitpick comments (5)
types/falkordb-canvas.d.ts (1)

6-13: Consider adding more specific prop types if applicable.

The current typing uses generic React.HTMLAttributes<HTMLElement>, which is functional for basic HTML attributes. If the falkordb-canvas web component accepts specific attributes or properties beyond standard HTML attributes, consider defining a more specific interface.

However, if the component primarily uses imperative methods (like setConfig(), getData() shown in the example), the current typing is appropriate.

falkordb-canvas/falkordb-canvas.d.ts (1)

7-7: Use specific return type for getGraph().

The getGraph() method returns any, but the implementation in falkordb-canvas.ts returns ForceGraphInstance. Use the specific type for better type safety.

-  getGraph(): any;
+  getGraph(): ForceGraphInstance;
falkordb-canvas/falkordb-cnavas-utils.ts (2)

72-105: Duplicate key lookup logic.

The same Object.keys(nodeData).find() pattern appears twice (lines 78-81 and 93-96). Consider extracting to a helper.

+const findKeyIgnoreCase = (obj: Record<string, unknown>, name: string) =>
+  Object.keys(obj).find((k) => k.toLowerCase() === name.toLowerCase());
+
 export const getNodeDisplayText = (
   node: Node,
   displayTextPriority: TextPriority[]
 ) => {
   const { data: nodeData } = node;
   const displayText = displayTextPriority.find(({ name, ignore }) => {
-    const key = ignore
-      ? Object.keys(nodeData).find(
-          (k) => k.toLowerCase() === name.toLowerCase()
-        )
-      : name;
+    const key = ignore ? findKeyIgnoreCase(nodeData, name) : name;
 
     return (
       key &&
       nodeData[key] &&
       typeof nodeData[key] === "string" &&
       nodeData[key].trim().length > 0
     );
   });
 
   if (displayText) {
-    const key = displayText.ignore
-      ? Object.keys(nodeData).find(
-          (k) => k.toLowerCase() === displayText.name.toLowerCase()
-        )
-      : displayText.name;
+    const key = displayText.ignore
+      ? findKeyIgnoreCase(nodeData, displayText.name)
+      : displayText.name;

110-121: Document magic number for text height calculation.

The value 1.125 for halfTextHeight appears to be derived from font metrics but isn't documented.

+  // Approximate half text height based on 2px font size (see drawNode ctx.font)
+  const HALF_TEXT_HEIGHT_APPROX = 1.125;
   const ellipsis = "...";
   const ellipsisWidth = ctx.measureText(ellipsis).width;
-  const halfTextHeight = 1.125;
+  const halfTextHeight = HALF_TEXT_HEIGHT_APPROX;
app/components/ForceGraph.tsx (1)

189-193: Consider a type-safe helper for extracting link endpoint IDs.

The as any casts handle the force-graph quirk where source/target can be either the resolved GraphNode object or the original numeric ID. A small helper would improve clarity and type safety.

const getLinkEndpointId = (endpoint: GraphNode | number): number =>
    typeof endpoint === 'object' ? endpoint.id : endpoint;

Then use:

-const targetId = typeof (link.target as any) === 'object' ? (link.target as any).id : link.target;
-const sourceId = typeof (link.source as any) === 'object' ? (link.source as any).id : link.source;
+const targetId = getLinkEndpointId(link.target as GraphNode | number);
+const sourceId = getLinkEndpointId(link.source as GraphNode | number);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4dd4eed and 30ee24c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • .eslintrc.json (1 hunks)
  • app/api/graph/model.ts (10 hunks)
  • app/components/ForceGraph.tsx (8 hunks)
  • app/graph/GraphView.tsx (2 hunks)
  • app/graph/page.tsx (3 hunks)
  • app/graph/toolbar.tsx (5 hunks)
  • app/schema/_page.tsx (3 hunks)
  • falkordb-canvas/falkordb-canvas-types.ts (1 hunks)
  • falkordb-canvas/falkordb-canvas.d.ts (1 hunks)
  • falkordb-canvas/falkordb-canvas.example.html (1 hunks)
  • falkordb-canvas/falkordb-canvas.ts (1 hunks)
  • falkordb-canvas/falkordb-cnavas-utils.ts (1 hunks)
  • lib/utils.ts (4 hunks)
  • package.json (1 hunks)
  • types/falkordb-canvas.d.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

⚙️ CodeRabbit configuration file

Review TypeScript code for type safety, proper error handling, and Next.js best practices.

Files:

  • falkordb-canvas/falkordb-canvas.d.ts
  • falkordb-canvas/falkordb-canvas.ts
  • falkordb-canvas/falkordb-cnavas-utils.ts
  • types/falkordb-canvas.d.ts
  • lib/utils.ts
  • app/api/graph/model.ts
  • falkordb-canvas/falkordb-canvas-types.ts
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/graph/toolbar.tsx
  • app/schema/_page.tsx
  • app/graph/GraphView.tsx
  • app/graph/page.tsx
  • app/components/ForceGraph.tsx
**/api/**/*

⚙️ CodeRabbit configuration file

Review API routes for security, error handling, and proper HTTP status codes.

Files:

  • app/api/graph/model.ts
🧬 Code graph analysis (5)
app/schema/_page.tsx (1)
lib/utils.ts (1)
  • GraphRef (21-21)
falkordb-canvas/falkordb-cnavas-utils.ts (3)
app/api/graph/model.ts (6)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
  • GraphData (79-82)
  • Node (53-63)
  • Link (65-77)
falkordb-canvas/falkordb-canvas-types.ts (7)
  • Data (68-71)
  • GraphData (53-56)
  • GraphNode (21-37)
  • GraphLink (39-51)
  • Node (58-61)
  • Link (63-66)
  • TextPriority (73-76)
lib/utils.ts (1)
  • TextPriority (25-28)
lib/utils.ts (2)
falkordb-canvas/falkordb-canvas-types.ts (3)
  • ForceGraphInstance (80-80)
  • GraphNode (21-37)
  • Node (58-61)
app/api/graph/model.ts (1)
  • Node (53-63)
app/api/graph/model.ts (1)
falkordb-canvas/falkordb-canvas-types.ts (2)
  • Node (58-61)
  • Link (63-66)
app/graph/page.tsx (1)
lib/utils.ts (1)
  • GraphRef (21-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Setup and cache dependencies
🔇 Additional comments (29)
.eslintrc.json (1)

43-45: LGTM!

Excluding declaration files from ESLint is appropriate, especially with the new TypeScript declaration files added in this PR.

app/graph/GraphView.tsx (2)

110-110: LGTM! Safer access pattern for node visibility.

The change from link.source.visible to graph.NodesMap.get(link.source)?.visible correctly adapts to the new data model where source/target are numeric IDs rather than object references. The optional chaining provides additional safety.


133-133: LGTM! Improved type guard.

Using "labels" in e is a more robust type guard than !!e.labels for distinguishing between Node and Link types in TypeScript.

app/schema/_page.tsx (3)

4-4: LGTM! Import aligns with new graph reference type.

The addition of GraphRef import supports the transition away from ForceGraphMethods to the new canvas-based architecture.


44-44: LGTM! Type change aligns with new architecture.

The change from ForceGraphMethods to GraphRef["current"] is consistent with the new canvas-based graph rendering system introduced across this PR.


165-165: LGTM! Improved type narrowing.

Using !("source" in element) is a more robust way to distinguish between Node and Link types compared to checking the truthiness of element.source. This pattern is consistent with similar changes throughout the PR.

app/graph/page.tsx (3)

4-4: LGTM! Consistent with schema page changes.

The GraphRef import supports the architectural transition to the new canvas-based rendering system.


70-70: LGTM! Type change aligns with new architecture.

Consistent with the changes in app/schema/_page.tsx, this adopts the new GraphRef type for canvas-based graph rendering.


248-248: LGTM! Improved type guard.

Using the "in" operator for type narrowing is more robust and consistent with the pattern established in app/schema/_page.tsx.

falkordb-canvas/falkordb-canvas.example.html (1)

1-221: LGTM! Well-structured example demonstrates the web component API.

This example HTML file effectively demonstrates how to use the FalkorDB Force Graph web component, including configuration, event handling, and dynamic graph manipulation. The code is clear and serves as good documentation for developers integrating the component.

Note: Confirm this file is intended as documentation/example code and not accidentally included as production code (its location in falkordb-canvas/ suggests it's intentional).

app/graph/toolbar.tsx (3)

92-93: LGTM! Safer property access in search filter.

Using the "in" operator to check for relationship and labels properties before accessing them provides better type safety and prevents potential runtime errors.


108-108: LGTM! Type-safe element detection for zoom.

The use of "labels" in element correctly distinguishes between Node and Link types, enabling appropriate zoom behavior for each.


225-225: LGTM! Proper type narrowing with safe casts.

The pattern of using "source" in suggestion as a type guard on line 225, followed by safe type casts on lines 243 and 253, is a correct TypeScript approach. The casts are safe because they follow the runtime type check.

Also applies to: 243-243, 253-253

package.json (1)

62-62: The force-graph package at version 1.51.0 exists and has no known security vulnerabilities. No action required.

lib/utils.ts (1)

384-386: Improved type guard using in operator.

The change from !!e.labels to "labels" in e is more robust. It correctly checks for property existence rather than truthiness, avoiding false negatives when labels is an empty array.

falkordb-canvas/falkordb-canvas.d.ts (1)

12-22: JSX intrinsic element augmentation looks correct.

The global JSX namespace extension properly maps the custom element tag to React-compatible props.

falkordb-canvas/falkordb-canvas.ts (3)

68-73: Good cleanup in disconnectedCallback.

Properly destroying the graph instance when the element is removed from the DOM prevents memory leaks.


287-291: Mutating node coordinates with fallback to zero.

The fallback to 0 for undefined coordinates is reasonable for initial render, but be aware this places unpositioned nodes at the origin, which may cause visual overlap until the simulation runs.


453-458: Good SSR-safe custom element registration.

The typeof window !== "undefined" check prevents errors during server-side rendering.

falkordb-canvas/falkordb-cnavas-utils.ts (1)

52-70: Clean bidirectional data conversion.

The graphDataToData function properly strips runtime properties and converts object references back to numeric IDs.

app/api/graph/model.ts (4)

457-488: calculateLinkCurve correctly updated for numeric IDs.

The comparison logic now properly uses numeric source and target values instead of object references.


798-830: visibleLinks and removeLinks use correct numeric comparisons.

The methods properly use link.source and link.target as numeric IDs with .find() lookups against node IDs.


903-904: Type guards updated consistently.

The filters correctly use "labels" in n for nodes and "source" in l for links, consistent with the updated type structure.


53-77: Breaking change: Link type now uses numeric source/target instead of object references.

The Link type has been updated to use numeric IDs for source and target (lines 69-70) rather than object references. This aligns with the canvas-based rendering approach. All internal usages in model.ts (lines 458, 798-830) and consumers in GraphView.tsx, toolbar.tsx, and ForceGraph.tsx have been updated to handle numeric ID comparisons correctly.

Note: ForceGraph.tsx includes defensive type guards (lines 190-191) to handle both object and numeric types—ensure this defensive logic remains if backward compatibility is needed.

falkordb-canvas/falkordb-canvas-types.ts (1)

1-80: LGTM! Well-structured type definitions.

The types clearly distinguish between the simplified input types (Node, Link, Data) and the canvas-runtime types (GraphNode, GraphLink, GraphData) with physics properties. The eslint-disable for any on the data payload is acceptable for flexible graph metadata.

app/components/ForceGraph.tsx (4)

291-314: Configuration sync looks correct.

The callbacks are properly memoized via useCallback, so the frequent setConfig calls are intentional and necessary to keep the canvas in sync with React state.


37-54: Clean data transformation.

The explicit property mapping ensures only canvas-relevant fields are passed through, avoiding accidental leakage of internal model properties.


234-270: Handlers correctly bridge canvas and model elements.

The lookups via graph.NodesMap and graph.LinksMap properly translate the lightweight canvas types back to full model objects. The 'source' in element check is a reliable discriminator.


324-334: LGTM! Clean web component integration.

The <falkordb-canvas> custom element with ref pattern is the correct approach for integrating web components in React.

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: 4

Caution

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

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

206-211: Missing dependencies in useMemo for viewport context.

The dependency array [viewport, data] is incomplete. The context object includes setViewport and setData, which should also be included: [viewport, data, setViewport, setData]. While useState setters are typically stable, including all values from the closure in the dependency array is required for correctness and follows React hooks best practices.

Also, the comment about removing isSaved appears to be incorrect—isSaved doesn't exist in the ViewportContextType definition and no consumers reference it.

♻️ Duplicate comments (5)
falkordb-canvas/falkordb-canvas.ts (3)

13-18: Typo in import path.

Already noted in previous review - filename should be falkordb-canvas-utils.ts not falkordb-cnavas-utils.ts.


57-58: Uninitialized graph property.

Already noted in previous review - consider initializing or adding null checks.


104-106: Type alignment with declaration file.

Already noted in previous review - ensure getData()/setData() types match between .d.ts and implementation.

lib/utils.ts (1)

21-21: GraphRef type correctly permits undefined via ForceGraphInstance.

The past review concern about removing the undefined union is addressed by the ForceGraphInstance type definition itself (line 82 in falkordb-canvas-types.ts), which includes | undefined. Consumers can safely access chartRef.current with appropriate null checks, as demonstrated in line 229 of this file.

app/components/ForceGraph.tsx (1)

86-94: Fix useEffect dependency array: should be empty.

The past review comment is correct. This effect initializes chartRef.current from the canvas instance after mount, but the dependency array [chartRef, data] causes it to re-run on every data change. Refs are stable objects and should not be in dependency arrays. Additionally, re-initializing the ref on data changes is unnecessary and could cause issues.

     }, [chartRef, data])
+    }, [])
🧹 Nitpick comments (5)
app/schema/DataPanel.tsx (1)

69-72: Consider removing type from dependency array.

Since type is computed from object on every render (Line 51), including both in the dependency array is redundant. When object changes, type will be recalculated automatically.

-    }, [object, type])
+    }, [object])
falkordb-canvas/falkordb-canvas.ts (1)

583-623: Event handlers are duplicated between initGraph and updateEventHandlers.

The same event handler registrations appear in both initGraph (Lines 263-304) and updateEventHandlers. Consider extracting to a single method called from both places.

 private initGraph() {
   // ... graph setup ...
-  .onNodeClick((node: GraphNode, event: MouseEvent) => {
-    if (this.config.onNodeClick) {
-      this.config.onNodeClick(node, event);
-    }
-  })
-  // ... other handlers ...
+  this.updateEventHandlers();
   
   this.setupForces();
 }
falkordb-canvas/falkordb-canvas-types.ts (1)

60-73: Type name collision: Node, Link, and Data conflict with model types.

This file exports Node, Link, and Data types that have different shapes than the identically-named types in app/api/graph/model.ts. This creates ambiguity—consumers must use path-specific imports to avoid confusion, and TypeScript errors may occur if the wrong type is imported.

Consider prefixing these types to clarify their purpose:

-export type Node = Omit<
+export type CanvasNode = Omit<
   GraphNode,
   "x" | "y" | "vx" | "vy" | "fx" | "fy" | "displayName"
 >;

-export type Link = Omit<GraphLink, "curve" | "source" | "target"> & {
+export type CanvasLink = Omit<GraphLink, "curve" | "source" | "target"> & {
   source: number;
   target: number;
 };

-export interface Data {
-  nodes: Node[];
-  links: Link[];
+export interface CanvasData {
+  nodes: CanvasNode[];
+  links: CanvasLink[];
 }

Then update consumers (e.g., ForceGraph.tsx line 10 and line 33) accordingly.

app/components/ForceGraph.tsx (2)

108-108: Remove chartRef from useEffect dependencies.

Including chartRef in the dependency array is unnecessary because refs are stable objects that don't trigger re-renders. The effect should depend only on values that actually change.

-    }, [chartRef, graph.Id, currentTab, graph.Elements.nodes.length, isLoading])
+    }, [graph.Id, currentTab, graph.Elements.nodes.length, isLoading])

159-163: Simplify link filtering with proper type handling.

The type casting (link.target as any) and (link.source as any) suggests uncertainty about the runtime shape of link source/target. This is error-prone and defeats TypeScript's type safety.

-                const isTarget = graph.Elements.links.some(link => {
-                    const targetId = typeof (link.target as any) === 'object' ? (link.target as any).id : link.target;
-                    const sourceId = typeof (link.source as any) === 'object' ? (link.source as any).id : link.source;
-                    return targetId === node.id && nodes.some(n => n.id === sourceId);
-                });
+                const isTarget = graph.Elements.links.some(link => 
+                    link.target === node.id && nodes.some(n => n.id === link.source)
+                );

If links can have both numeric IDs and object references, add a helper:

const getLinkId = (ref: number | { id: number }): number => 
    typeof ref === 'object' ? ref.id : ref;
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30ee24c and 7a83ed4.

📒 Files selected for processing (17)
  • app/components/ForceGraph.tsx (7 hunks)
  • app/components/provider.ts (0 hunks)
  • app/graph/CreateElementPanel.tsx (1 hunks)
  • app/graph/DataPanel.tsx (1 hunks)
  • app/graph/DataTable.tsx (1 hunks)
  • app/graph/GraphView.tsx (4 hunks)
  • app/graph/Selector.tsx (1 hunks)
  • app/graph/page.tsx (4 hunks)
  • app/providers.tsx (1 hunks)
  • app/schema/CreateElementPanel.tsx (1 hunks)
  • app/schema/DataPanel.tsx (2 hunks)
  • app/schema/SchemaView.tsx (1 hunks)
  • app/schema/_page.tsx (3 hunks)
  • falkordb-canvas/falkordb-canvas-types.ts (1 hunks)
  • falkordb-canvas/falkordb-canvas.d.ts (1 hunks)
  • falkordb-canvas/falkordb-canvas.ts (1 hunks)
  • lib/utils.ts (4 hunks)
💤 Files with no reviewable changes (1)
  • app/components/provider.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/graph/Selector.tsx
  • app/graph/DataTable.tsx
  • app/schema/DataPanel.tsx
  • app/graph/CreateElementPanel.tsx
  • app/schema/SchemaView.tsx
  • app/schema/_page.tsx
  • app/graph/page.tsx
  • app/components/ForceGraph.tsx
  • app/providers.tsx
  • app/schema/CreateElementPanel.tsx
  • app/graph/DataPanel.tsx
  • app/graph/GraphView.tsx
**/*.ts

⚙️ CodeRabbit configuration file

Review TypeScript code for type safety, proper error handling, and Next.js best practices.

Files:

  • falkordb-canvas/falkordb-canvas.ts
  • falkordb-canvas/falkordb-canvas.d.ts
  • falkordb-canvas/falkordb-canvas-types.ts
  • lib/utils.ts
🧬 Code graph analysis (8)
falkordb-canvas/falkordb-canvas.ts (3)
falkordb-canvas/falkordb-canvas-types.ts (5)
  • GraphData (55-58)
  • ForceGraphConfig (3-21)
  • Data (70-73)
  • GraphNode (23-39)
  • GraphLink (41-53)
app/api/graph/model.ts (4)
  • GraphData (79-82)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
falkordb-canvas/falkordb-cnavas-utils.ts (4)
  • graphDataToData (52-70)
  • dataToGraphData (15-46)
  • getNodeDisplayText (72-105)
  • wrapTextForCircularNode (110-166)
falkordb-canvas/falkordb-canvas.d.ts (2)
falkordb-canvas/falkordb-canvas-types.ts (3)
  • ForceGraphConfig (3-21)
  • Data (70-73)
  • ForceGraphInstance (82-82)
app/api/graph/model.ts (3)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
app/schema/_page.tsx (1)
lib/utils.ts (1)
  • GraphRef (21-21)
app/graph/page.tsx (1)
lib/utils.ts (1)
  • GraphRef (21-21)
falkordb-canvas/falkordb-canvas-types.ts (2)
lib/utils.ts (1)
  • TextPriority (25-28)
app/api/graph/model.ts (6)
  • GraphData (79-82)
  • Node (53-63)
  • Link (65-77)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
app/schema/CreateElementPanel.tsx (2)
falkordb-canvas/falkordb-canvas-types.ts (1)
  • Node (60-63)
app/api/graph/model.ts (1)
  • Node (53-63)
lib/utils.ts (2)
falkordb-canvas/falkordb-canvas-types.ts (3)
  • ForceGraphInstance (82-82)
  • GraphNode (23-39)
  • Node (60-63)
app/api/graph/model.ts (1)
  • Node (53-63)
app/graph/GraphView.tsx (1)
app/components/provider.ts (1)
  • ViewportContext (281-286)
🔇 Additional comments (23)
app/graph/DataTable.tsx (1)

1-662: LGTM! Clean removal of display text priority logic.

The removal of BrowserSettingsContext dependency and getNodeDisplayKey helper function aligns with the broader refactor to move display text responsibilities to the FalkorDB canvas utilities. This simplifies the component's concerns.

app/graph/page.tsx (2)

70-70: LGTM! Improved type safety with GraphRef.

The use of GraphRef["current"] provides better type safety compared to the previous ForceGraphMethods-based typing, aligning with the canvas-based rendering changes.


246-246: LGTM! Safer type guard with property existence check.

The change from !element.source to !("source" in element) is a more robust way to distinguish between Node and Link types. This structural check is consistent with similar updates across the codebase and avoids potential issues with falsy values.

app/graph/Selector.tsx (1)

619-619: LGTM! Consistent type guard improvement.

The change to "labels" in e provides a safer structural check for Node types. The logic correctly ensures edge creation is only enabled when exactly 2 nodes are selected.

app/graph/CreateElementPanel.tsx (1)

11-12: LGTM! Import reorganization aligns with canvas utilities.

The split of imports—keeping cn in lib/utils and moving getNodeDisplayText to falkordb-canvas/falkordb-cnavas-utils—is consistent with the broader refactor moving display text responsibilities to the FalkorDB canvas layer.

app/schema/CreateElementPanel.tsx (1)

636-637: LGTM! Improved type narrowing for node swap.

The change to "labels" in e provides safer type narrowing when filtering to Node types before swapping their order. This structural check is more reliable than truthiness checks and aligns with similar updates across the codebase.

app/graph/DataPanel.tsx (1)

37-37: LGTM! Safer type determination.

The change from !object.source to !("source" in object) is a more reliable way to distinguish between Node and Link types. This structural check avoids potential issues with falsy values and aligns with the broader pattern of safer type guards across the PR.

app/schema/SchemaView.tsx (1)

5-5: LGTM! Simplified state management.

The removal of useState and parent dimension state management aligns with the transition to canvas-based rendering, where ForceGraph no longer requires parent width/height props. This simplifies the component.

app/schema/DataPanel.tsx (1)

51-51: Safer type discrimination using in operator.

This correctly identifies Node vs Link by checking property existence rather than truthiness. Handles edge cases where source could be 0 or another falsy value.

app/schema/_page.tsx (2)

44-44: Ref typing is consistent with GraphRef definition.

Using GraphRef["current"] extracts the ForceGraphInstance type correctly. The uninitialized ref aligns with optional graph initialization.


165-165: Consistent type guard pattern.

Same safer "source" in element check used across the codebase for Node vs Link discrimination.

app/graph/GraphView.tsx (4)

5-5: Import cleanup looks good.

Removed unused useState after removing parentHeight/parentWidth state variables.


63-63: Context destructuring aligns with provider changes.

Removed isSaved from ViewportContext, consistent with app/components/provider.ts updates.


108-108: Correct adaptation to new Link type.

Since Link.source and Link.target are now numeric IDs, using graph.NodesMap.get(link.source) is the correct way to retrieve the actual Node object for visibility checks.


131-131: Safer type guard for element discrimination.

Using "labels" in e properly checks for Node type membership instead of relying on truthiness.

falkordb-canvas/falkordb-canvas.ts (3)

228-308: Well-structured graph initialization with force simulation.

The force constants, collision handling, and rendering setup are comprehensive. The degree-based link distance/strength adjustments handle crowded graphs nicely.


385-430: Node rendering with text wrapping looks solid.

Correctly handles missing coordinates, caches display names, and wraps text for circular nodes.


658-661: Safe custom element registration.

Good defensive check for SSR environments and duplicate registration.

lib/utils.ts (1)

385-385: LGTM: Type guard correctly identifies nodes.

The "labels" in e check properly distinguishes nodes from links, as GraphNode has labels while GraphLink has relationship.

falkordb-canvas/falkordb-canvas-types.ts (2)

23-39: LGTM: GraphNode type properly extends NodeObject.

The type correctly combines force-graph's NodeObject with domain-specific properties and optional simulation coordinates.


3-21: LGTM: Comprehensive configuration interface.

The ForceGraphConfig interface provides a well-structured API for configuring the canvas, including event handlers, styling, and simulation controls.

app/components/ForceGraph.tsx (2)

33-50: LGTM: Data conversion correctly maps between type systems.

The convertToCanvasData function properly transforms GraphData (model types with expand/collapsed) to Data (canvas types), omitting simulation-specific properties.


257-279: Large dependency array is acceptable for reactive configuration.

While the dependency array is extensive, each dependency is legitimately referenced in the configuration object. The callbacks (handleNodeClick, etc.) depend on state, so frequent updates are expected. This is acceptable for maintaining correct canvas configuration.

@barakb
Copy link
Contributor

barakb commented Dec 14, 2025

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 14, 2025

✅ 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: 5

Caution

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

⚠️ Outside diff range comments (3)
app/schema/DataPanel.tsx (1)

51-72: Avoid non-narrowing union access (object.labels / object.relationship).

Use explicit casts (or a shared type guard) so this doesn’t rely on structural typing quirks.

-const type = !("source" in object)
+const type = !("source" in object)

 useEffect(() => {
   setAttributes(Object.entries(object.data).filter(([key, val]) => !(key === "name" && Number(val) === object.id)).map(([key, val]) => [key, Array.isArray(val) ? val : (val as string).split(',')]))
-  setLabel(type ? [...object.labels] : [object.relationship])
+  setLabel(type ? [...(object as Node).labels] : [(object as Link).relationship])
 }, [object, type])
app/schema/_page.tsx (1)

4-45: Standardize chartRef type to prevent undefined ref from being passed as non-undefined prop.

chartRef is declared as useRef<GraphRef["current"]>() (which defaults to MutableRefObject<ForceGraphInstance | undefined>), but all consuming components expect chartRef: GraphRef where GraphRef = MutableRefObject<ForceGraphInstance>. This creates a type safety gap.

Update lib/utils.ts to allow null:

-export type GraphRef = MutableRefObject<ForceGraphInstance>;
+export type GraphRef = MutableRefObject<ForceGraphInstance | null>;

Then initialize chartRef with null in app/schema/_page.tsx and app/graph/page.tsx:

-const chartRef = useRef<GraphRef["current"]>()
+const chartRef = useRef<GraphRef["current"]>(null)
app/graph/page.tsx (1)

4-71: Fix chartRef initialization to use explicit null and proper type union.

The current initialization useRef<GraphRef["current"]>() defaults to undefined and misses the null initialization pattern used throughout the codebase. This ref is passed to components expecting a properly typed ref object and should initialize with null for consistency and type safety:

-const chartRef = useRef<GraphRef["current"]>()
+const chartRef = useRef<GraphRef["current"] | null>(null)

This pattern matches how other refs in the app are initialized (e.g., panelRef, editorRef, parentRef) and aligns with strict type checking.

♻️ Duplicate comments (9)
falkordb-canvas/falkordb-canvas.d.ts (1)

12-22: JSX augmentation enables React TypeScript support.

The global JSX.IntrinsicElements augmentation correctly maps the 'falkordb-canvas' tag name to FalkorDBForceGraph, enabling proper TypeScript support in React components.

Note: A previous review identified a duplicate JSX augmentation in types/falkordb-canvas.d.ts that should be removed to avoid type conflicts.

lib/utils.ts (2)

223-243: Shadow DOM access is fragile and tightly coupled.

Directly querying document.querySelector("falkordb-canvas") and accessing its shadow DOM assumes a single canvas instance and specific internal structure.

A previous review suggested adding a public API method like getCanvasDimensions() to the canvas component to avoid breaking encapsulation. This approach would be more robust and handle multiple instances correctly.


21-21: GraphRef type no longer permits undefined.

The previous type allowed undefined, but the new GraphRef type requires ForceGraphInstance to always be present. This could cause runtime issues if chartRef.current is accessed before initialization.

A previous review suggested restoring the optional behavior: MutableRefObject<ForceGraphInstance | undefined>. Alternatively, ensure all consumers null-check before accessing.

falkordb-canvas/falkordb-canvas.ts (3)

13-18: Import path contains typo.

The import path "./falkordb-cnavas-utils" has a typo ("cnavas" should be "canvas"). This should be corrected for clarity.

Note: A previous review identified this typo. Consider renaming the file from falkordb-cnavas-utils.ts to falkordb-canvas-utils.ts and updating all references.


57-83: Uninitialized graph property may cause runtime errors.

The graph property is declared but not initialized, and getGraph() returns it without null checks. If called before connectedCallback() completes, it will return undefined.

A previous review suggested making the property nullable or adding initialization guards. Consider updating the type to ForceGraphInstance | null = null and adding appropriate checks in getGraph().


89-94: Clean up timeout on disconnect to prevent memory leaks.

The handleEngineStop method schedules a setTimeout (line 573) that could fire after the component is disconnected, leading to memory leaks or stale references.

A previous review suggested tracking the timeout ID and clearing it in disconnectedCallback(). Add a property for the timeout ID and clear it here.

falkordb-canvas/falkordb-cnavas-utils.ts (1)

15-46: Non-null assertions could cause runtime errors.

If a link references a node ID that doesn't exist in the nodes array (lines 39-40), sourceNode or targetNode will be undefined, and the non-null assertion (!) will mask the error.

A previous review suggested adding validation or filtering:

  • Skip links with missing endpoints
  • Throw a descriptive error with the missing node IDs
  • Log a warning for debugging

This would make data integrity issues discoverable rather than causing silent failures downstream.

falkordb-canvas/falkordb-canvas-types.ts (1)

75-78: Remove duplicate TextPriority type definition.

This type is already defined in lib/utils.ts (lines 25-28). Duplicating it creates maintenance burden and potential for inconsistency.

A previous review suggested removing this definition and importing from lib/utils.ts instead:

+import { TextPriority } from "@/lib/utils";
+
 export interface ForceGraphConfig {
   // ...
 }

 // ... rest of file ...

-export type TextPriority = {
-  name: string;
-  ignore: boolean;
-};
app/components/ForceGraph.tsx (1)

85-95: Initialize chartRef.current once (remove data dep); current deps can thrash and still race element readiness.

This is the same underlying issue as the earlier review: depending on data will re-run the initialization even though the graph instance should be stable, and it may still run before the web component is ready.

     useEffect(() => {
         if (canvasRef.current) {
             const forceGraph = canvasRef.current.getGraph();
 
             if (forceGraph) {
                 chartRef.current = forceGraph;
             }
         }
-    }, [chartRef, data])
+    }, [])

If readiness is async, consider wiring an explicit onReady/event from the web component instead of polling via React effects.

🧹 Nitpick comments (6)
types/falkordb-canvas.d.ts (1)

6-13: Consider adding custom props interface for better type safety.

The current declaration allows <falkordb-canvas> in JSX but doesn't expose any custom attributes or event handlers specific to this web component. If the component accepts custom props (e.g., config, data, event callbacks), consider extending the interface:

 declare namespace JSX {
   interface IntrinsicElements {
     'falkordb-canvas': React.DetailedHTMLProps<
-      React.HTMLAttributes<HTMLElement>,
-      HTMLElement
+      React.HTMLAttributes<HTMLElement> & {
+        ref?: React.Ref<FalkorDBForceGraph>;
+        // Add custom props here if needed
+      },
+      FalkorDBForceGraph
     >;
   }
 }
+
+declare class FalkorDBForceGraph extends HTMLElement {
+  setConfig(config: Partial<import('../falkordb-canvas/falkordb-canvas-types').ForceGraphConfig>): void;
+  getData(): import('../falkordb-canvas/falkordb-canvas-types').Data;
+  setData(data: import('../falkordb-canvas/falkordb-canvas-types').Data): void;
+  getGraph(): import('force-graph').ForceGraphInstance | undefined;
+}
app/api/graph/model.ts (2)

793-816: Consider optimizing node lookups for better performance.

The repeated .find() calls inside the loop create O(n×m) complexity. For larger graphs, pre-compute visible node IDs:

 public visibleLinks(visible: boolean) {
+  const visibleNodeIds = new Set(
+    this.elements.nodes.filter((n) => n.visible).map((n) => n.id)
+  );
+
   this.elements.links.forEach((link) => {
+    const sourceVisible = visibleNodeIds.has(link.source);
+    const targetVisible = visibleNodeIds.has(link.target);
+
     if (
       this.RelationshipsMap.get(link.relationship)!.show &&
       visible &&
-      this.elements.nodes.find((n) => n.id === link.source)?.visible &&
-      this.elements.nodes.find((n) => n.id === link.target)?.visible
+      sourceVisible &&
+      targetVisible
     ) {
       link.visible = true;
     }

     if (
       !visible &&
-      (this.elements.nodes.find((n) => n.id === link.source)?.visible ===
-        false ||
-        this.elements.nodes.find((n) => n.id === link.target)?.visible ===
-          false)
+      (!sourceVisible || !targetVisible)
     ) {
       link.visible = false;
     }
   });
 }

818-859: Avoid repeated .map().includes() inside filter.

Creating a new array on every iteration is inefficient. Pre-compute the node ID set:

 public removeLinks(ids: number[] = []): Relationship[] {
+  const nodeIds = new Set(this.elements.nodes.map((n) => n.id));
   const links = this.elements.links.filter(
     (link) => ids.includes(link.source) || ids.includes(link.target)
   );

   this.elements = {
     nodes: this.elements.nodes,
     links: this.elements.links
       .map((link) => {
         if (
           (ids.length !== 0 && !links.includes(link)) ||
-          (this.elements.nodes.map((n) => n.id).includes(link.source) &&
-            this.elements.nodes.map((n) => n.id).includes(link.target))
+          (nodeIds.has(link.source) && nodeIds.has(link.target))
         ) {
           return link;
         }
         // ... rest unchanged
app/schema/SchemaView.tsx (1)

5-5: handleCooldown typing looks overly restrictive (ticks?: 0).

If handleCooldown is intended to accept only 0 | undefined to “stop”, rename it to reflect that (e.g., stopCooldown) or widen the type to what callers may reasonably pass (commonly number | undefined) to avoid accidental misuse as the canvas API evolves.

app/graph/Selector.tsx (1)

610-624: Prefer a shared type guard over inline "labels" in e" checks.

This is correct, but consider using a helper (e.g., isNode(el): el is Node) to keep the runtime/type-narrowing behavior consistent across Schema/Graph UIs.

app/schema/CreateElementPanel.tsx (1)

635-638: Guard against unexpected selection shape before swapping.

Even if the UI should only allow 2 nodes, nodes[1] can still be undefined if state gets out of sync. Consider a defensive fallback.

 onClick={() => setSelectedNodes && setSelectedNodes(prev => {
   const nodes = prev.filter((e): e is Node => "labels" in e)
-  return [nodes[1], nodes[0]]
+  if (nodes.length !== 2) return prev
+  return [nodes[1], nodes[0]]
 })}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 774e59b and 7a83ed4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (24)
  • .eslintrc.json (1 hunks)
  • app/api/graph/model.ts (10 hunks)
  • app/components/ForceGraph.tsx (7 hunks)
  • app/components/provider.ts (0 hunks)
  • app/graph/CreateElementPanel.tsx (1 hunks)
  • app/graph/DataPanel.tsx (1 hunks)
  • app/graph/DataTable.tsx (1 hunks)
  • app/graph/GraphView.tsx (4 hunks)
  • app/graph/Selector.tsx (1 hunks)
  • app/graph/page.tsx (4 hunks)
  • app/graph/toolbar.tsx (5 hunks)
  • app/providers.tsx (1 hunks)
  • app/schema/CreateElementPanel.tsx (1 hunks)
  • app/schema/DataPanel.tsx (2 hunks)
  • app/schema/SchemaView.tsx (1 hunks)
  • app/schema/_page.tsx (3 hunks)
  • falkordb-canvas/falkordb-canvas-types.ts (1 hunks)
  • falkordb-canvas/falkordb-canvas.d.ts (1 hunks)
  • falkordb-canvas/falkordb-canvas.example.html (1 hunks)
  • falkordb-canvas/falkordb-canvas.ts (1 hunks)
  • falkordb-canvas/falkordb-cnavas-utils.ts (1 hunks)
  • lib/utils.ts (4 hunks)
  • package.json (1 hunks)
  • types/falkordb-canvas.d.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • app/components/provider.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/graph/DataTable.tsx
  • app/schema/DataPanel.tsx
  • app/graph/DataPanel.tsx
  • app/graph/toolbar.tsx
  • app/schema/CreateElementPanel.tsx
  • app/graph/Selector.tsx
  • app/schema/SchemaView.tsx
  • app/providers.tsx
  • app/graph/page.tsx
  • app/schema/_page.tsx
  • app/components/ForceGraph.tsx
  • app/graph/CreateElementPanel.tsx
  • app/graph/GraphView.tsx
**/*.ts

⚙️ CodeRabbit configuration file

Review TypeScript code for type safety, proper error handling, and Next.js best practices.

Files:

  • falkordb-canvas/falkordb-cnavas-utils.ts
  • lib/utils.ts
  • falkordb-canvas/falkordb-canvas.d.ts
  • falkordb-canvas/falkordb-canvas-types.ts
  • app/api/graph/model.ts
  • falkordb-canvas/falkordb-canvas.ts
  • types/falkordb-canvas.d.ts
**/api/**/*

⚙️ CodeRabbit configuration file

Review API routes for security, error handling, and proper HTTP status codes.

Files:

  • app/api/graph/model.ts
🧬 Code graph analysis (11)
app/graph/toolbar.tsx (2)
falkordb-canvas/falkordb-canvas-types.ts (2)
  • Link (65-68)
  • Node (60-63)
app/api/graph/model.ts (2)
  • Link (65-77)
  • Node (53-63)
app/schema/CreateElementPanel.tsx (2)
falkordb-canvas/falkordb-canvas-types.ts (1)
  • Node (60-63)
app/api/graph/model.ts (1)
  • Node (53-63)
app/graph/page.tsx (1)
lib/utils.ts (1)
  • GraphRef (21-21)
lib/utils.ts (2)
falkordb-canvas/falkordb-canvas-types.ts (3)
  • ForceGraphInstance (82-82)
  • GraphNode (23-39)
  • Node (60-63)
app/api/graph/model.ts (1)
  • Node (53-63)
app/schema/_page.tsx (1)
lib/utils.ts (1)
  • GraphRef (21-21)
falkordb-canvas/falkordb-canvas.d.ts (2)
falkordb-canvas/falkordb-canvas-types.ts (3)
  • ForceGraphConfig (3-21)
  • Data (70-73)
  • ForceGraphInstance (82-82)
app/api/graph/model.ts (3)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
falkordb-canvas/falkordb-canvas-types.ts (2)
lib/utils.ts (1)
  • TextPriority (25-28)
app/api/graph/model.ts (6)
  • GraphData (79-82)
  • Node (53-63)
  • Link (65-77)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
app/api/graph/model.ts (1)
falkordb-canvas/falkordb-canvas-types.ts (2)
  • Node (60-63)
  • Link (65-68)
app/components/ForceGraph.tsx (5)
falkordb-canvas/falkordb-canvas-types.ts (6)
  • GraphData (55-58)
  • Data (70-73)
  • Node (60-63)
  • Link (65-68)
  • GraphNode (23-39)
  • GraphLink (41-53)
app/api/graph/model.ts (6)
  • GraphData (79-82)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
  • Node (53-63)
  • Link (65-77)
app/components/provider.ts (2)
  • IndicatorContext (266-269)
  • BrowserSettingsContext (155-214)
lib/utils.ts (1)
  • getTheme (366-380)
falkordb-canvas/falkordb-canvas.ts (1)
  • setData (108-113)
falkordb-canvas/falkordb-canvas.ts (3)
falkordb-canvas/falkordb-canvas-types.ts (5)
  • ForceGraphInstance (82-82)
  • GraphData (55-58)
  • Data (70-73)
  • GraphNode (23-39)
  • GraphLink (41-53)
app/api/graph/model.ts (4)
  • GraphData (79-82)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
app/components/ForceGraph.tsx (1)
  • ForceGraph (52-294)
app/graph/GraphView.tsx (1)
app/components/provider.ts (1)
  • ViewportContext (281-286)
🔇 Additional comments (38)
.eslintrc.json (1)

43-45: LGTM!

Excluding *.d.ts files from ESLint is a sensible choice—declaration files often contain ambient type definitions that don't conform to typical linting rules. This aligns well with the new canvas type declarations added in this PR.

app/providers.tsx (1)

206-211: LGTM!

The dependency array correctly reflects the context value after removing isSaved. This aligns with the broader refactor moving cooldown handling out of the viewport context.

app/graph/DataTable.tsx (1)

15-15: LGTM!

Clean removal of BrowserSettingsContext import—consistent with dropping the display text priority logic that's no longer needed with the new canvas-based approach.

app/api/graph/model.ts (3)

53-77: LGTM! Clean type simplification.

The migration from wrapper classes to plain object shapes with numeric IDs for source/target is a solid choice. This improves serialization, aligns with the canvas component's data model, and reduces complexity.


861-904: Type discrimination approach works but is coupled to type shape.

Using "labels" in element and "source" in element for type guards is functional given the current type definitions. Consider adding type guard utilities in lib/utils.ts (e.g., isNode(element), isLink(element)) to centralize this logic and make future type changes safer.


457-488: LGTM!

The link curve calculation is cleaner with direct numeric ID comparisons. Self-loop handling (start === end) and bidirectional link detection work correctly.

package.json (1)

62-62: Confirm both force-graph and react-force-graph-2d are needed long-term.

The version ^1.51.0 is current (latest on npm) with no known security concerns. However, verify whether both packages should coexist permanently—force-graph (the lower-level canvas library) alongside react-force-graph-2d (line 77) suggests a transition phase. Clarify the long-term dependency strategy in the PR description.

app/schema/_page.tsx (1)

163-170: Good: element type detection via property presence.

const type = !("source" in element) is a safer discriminator than falsy checks (e.g., !element.source).

app/graph/page.tsx (2)

197-231: Good: handleCreateElement no longer depends on handleCooldown.

This should reduce unnecessary callback re-creation while keeping the logic intact.


245-251: Good: element type detection via "source" in element".

This aligns with the refactor to primitive link/node shapes and avoids falsy pitfalls.

app/graph/DataPanel.tsx (1)

35-59: Good: safer node/link discrimination with "source" in object".

Also nice that label derivation keeps the cast on the node branch.

falkordb-canvas/falkordb-canvas.example.html (3)

94-108: LGTM! Sample data structure is correct.

The node and link shapes match the Data type from falkordb-canvas-types.ts, with numeric source/target references and proper initialization of display properties.


116-157: LGTM! Configuration setup is correct.

The configuration object matches the ForceGraphConfig interface, with proper event handler signatures and display text priority.


163-217: LGTM! Control functions demonstrate the canvas API correctly.

The functions properly use getData(), setData(), getZoom(), zoom(), and zoomToFit() methods from the canvas component API.

app/graph/toolbar.tsx (3)

89-94: LGTM! Runtime type guards are correctly implemented.

The search filtering now uses "relationship" in el and "labels" in el checks before casting, which is the proper pattern for discriminating union types at runtime.


107-112: LGTM! Zoom logic correctly handles nodes and links.

The conditional check "labels" in element properly discriminates between nodes and links, and the zoom filter correctly identifies relevant nodes for both cases.


223-258: LGTM! Suggestion rendering uses correct type discrimination.

The "source" in suggestion check properly discriminates links from nodes, and the subsequent casts to Link or Node are safe.

app/graph/GraphView.tsx (4)

5-5: LGTM! Import cleanup aligns with state removal.

Removing useState is correct since the component no longer manages local dimension state.


63-63: LGTM! Viewport context usage updated.

Removing isSaved from the destructured context aligns with the broader refactor to simplify viewport state management.


108-110: LGTM! Relationship visibility check updated correctly.

Now that link.source and link.target are numeric IDs, looking them up in graph.NodesMap is the correct approach. The optional chaining safely handles missing nodes.


131-131: LGTM! Type guard updated to use runtime property check.

Using "labels" in e instead of !!e.labels is the correct pattern for discriminating Node types at runtime.

falkordb-canvas/falkordb-canvas.d.ts (1)

3-10: LGTM! Web component declaration structure is correct.

The class declaration properly extends HTMLElement and declares the public API methods with appropriate types, including getGraph() returning ForceGraphInstance | undefined to handle uninitialized state.

falkordb-canvas/falkordb-canvas.ts (5)

104-113: LGTM! getData/setData types are consistent.

The implementation uses Data type, and the declaration file (falkordb-canvas.d.ts lines 5-6) correctly declares getData(): Data and setData(data: Data), so the types are aligned.

Note: A previous review flagged a type mismatch, but the current code is consistent.


228-383: LGTM! Graph initialization and force configuration look correct.

The ForceGraph initialization properly configures all event handlers and rendering callbacks. The dynamic force calculations based on node degree are a nice touch for handling varying graph densities.


385-430: LGTM! Node drawing implementation is efficient.

The caching of displayName on the node avoids redundant text wrapping calculations, and the two-line text rendering handles long labels well.


432-532: LGTM! Link drawing with efficient text caching.

The relationship text metrics are cached to avoid redundant measurements, and the background rectangle ensures labels remain readable over edges. The bezier curve positioning math is correct.


561-581: LGTM! Engine stop handler logic is correct.

The zoom-to-fit behavior with dynamic padding based on node count is appropriate, and the delayed loading state update gives the simulation time to settle.

Note: A previous review identified that the setTimeout ID should be stored and cleared in disconnectedCallback() to prevent memory leaks.

lib/utils.ts (2)

1-11: LGTM! Import changes align with canvas refactor.

The imports now reference ForceGraphInstance and GraphNode from the canvas types module, correctly reflecting the shift to canvas-based rendering.


383-386: LGTM! Type guard updated to use runtime property check.

Using "labels" in e instead of !!e.labels is the correct pattern for discriminating Node types at runtime.

falkordb-canvas/falkordb-cnavas-utils.ts (3)

52-70: LGTM! GraphData to Data conversion is clean.

The function correctly strips runtime properties and converts node references back to numeric IDs.


72-105: LGTM! Display text extraction logic is robust.

The function properly handles the ignore flag for case-insensitive key matching and validates that the result is a non-empty string before using it.


110-166: LGTM! Text wrapping algorithm is well-implemented.

The function correctly calculates available width using the Pythagorean theorem, handles word splitting for long words, and adds ellipsis when needed. The two-line wrapping is appropriate for circular node labels.

falkordb-canvas/falkordb-canvas-types.ts (4)

3-21: LGTM! ForceGraphConfig interface is well-structured.

The configuration interface provides a clean API surface with optional properties for dimensions, colors, display priorities, event handlers, and loading/selection state.


23-53: LGTM! GraphNode and GraphLink types are comprehensive.

The types properly extend NodeObject from force-graph and include all necessary runtime properties for simulation (positions, velocities, forces) plus display state.


55-73: LGTM! Data types cleanly separate runtime from serializable state.

The Node and Link types appropriately strip runtime properties, making them suitable for API boundaries. The conversion between Data and GraphData is handled by utility functions.


80-82: LGTM! ForceGraphInstance type correctly includes undefined.

The type properly represents that a graph instance may not be initialized, and the generic parameters ensure type safety for node and link operations.

app/components/ForceGraph.tsx (2)

281-287: Verify <falkordb-canvas> fills the container; consider explicit sizing styles if the custom element defaults to inline.

Depending on how the web component is implemented, you may need display: block; width: 100%; height: 100% on the element (or have the component respect the passed width/height via config). If users report a “0px tall canvas”, this is the first place I’d look.

Also applies to: 290-292


7-16: This review comment is incorrect. The typing useRef<FalkorDBForceGraph>(null) on line 77 is correct and does not need modification.

The FalkorDBForceGraph class extends HTMLElement and is properly declared in the JSX IntrinsicElements mapping for the <falkordb-canvas> custom element. Under strict: true in tsconfig.json, useRef<T>(null) correctly infers the ref type as RefObject<T>, which is the appropriate type for a custom element that will call methods like .getGraph(), .setConfig(), and .setData(). Adding | null to the generic parameter would be semantically incorrect and is unnecessary.

The side-effect import on line 15 (import "@/falkordb-canvas/falkordb-canvas") is also correct for custom element registration and is not duplicated.

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

Caution

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

⚠️ Outside diff range comments (1)
app/schema/DataPanel.tsx (1)

210-222: Potential duplicate entries when adding attributes.

The handleAddAttribute function spreads the current object into a new array entry ([...schema.Elements.nodes, { ...object as Node, ... }]) rather than updating the existing element. This appears to add a duplicate node/link with the new attribute instead of modifying the existing one in place. Compare with handleSetAttribute (lines 112-126), which correctly uses .map() to update the existing element.

Apply this diff to fix the logic:

             if (ok) {
                 if (type) {
                     // eslint-disable-next-line no-param-reassign
                     schema.Elements = {
                         ...schema.Elements,
-                        nodes: [...schema.Elements.nodes, { ...object as Node, data: { ...object.data, [newAttribute[0]]: newAttribute[1] } }]
+                        nodes: schema.Elements.nodes.map(element =>
+                            element.id === object.id ? { ...element, data: { ...element.data, [newAttribute[0]]: newAttribute[1] } } : element
+                        )
                     }
                 } else {
                     // eslint-disable-next-line no-param-reassign
                     schema.Elements = {
                         ...schema.Elements,
-                        links: [...schema.Elements.links, { ...object as Link, data: { ...object.data, [newAttribute[0]]: newAttribute[1] } }]
+                        links: schema.Elements.links.map(element =>
+                            element.id === object.id ? { ...element, data: { ...element.data, [newAttribute[0]]: newAttribute[1] } } : element
+                        )
                     }
                 }
+                setData({ ...schema.Elements })
                 setAttributes(prev => [...prev, newAttribute])
♻️ Duplicate comments (7)
lib/utils.ts (2)

21-21: GraphRef should allow undefined to prevent runtime errors before initialization.

The type removes the | undefined union, which can cause issues when chartRef.current is accessed before the canvas initializes.

-export type GraphRef = MutableRefObject<ForceGraphInstance>;
+export type GraphRef = MutableRefObject<ForceGraphInstance | undefined>;

230-232: Shadow DOM access is fragile and assumes a single canvas instance.

Directly querying document.querySelector("falkordb-canvas") and accessing shadowRoot is tightly coupled to implementation details. Consider exposing a public API method on the canvas component for retrieving dimensions.

app/components/ForceGraph.tsx (5)

32-49: Normalize source/target to numeric IDs to prevent type mismatch.

force-graph can mutate source/target to node objects at runtime. The canvas expects numeric IDs, so normalization is needed:

 links: graphData.links.map(({ id, relationship, color, visible, source, target, data }) => ({
     id,
     relationship,
     color,
     visible,
-    source,
-    target,
+    source: typeof source === "object" && source !== null ? (source as any).id : source,
+    target: typeof target === "object" && target !== null ? (target as any).id : target,
     data
 }))

88-96: Dependency array should be [] for one-time initialization.

Including chartRef and data causes this effect to re-run unnecessarily. Since refs are stable and this is initialization logic, use an empty array:

-    }, [chartRef, data])
+    }, [])

98-112: Missing viewport in deps and uncleaned timeout can fire post-unmount.

Add cleanup and include viewport in dependencies:

     useEffect(() => {
         if (!isLoading && viewport && chartRef.current && !mount) {
             const { zoom, centerX, centerY } = viewport;
             
-            setTimeout(() => {
+            const t = window.setTimeout(() => {
                 if (chartRef.current) {
                     chartRef.current.zoom(zoom, 0);
                     chartRef.current.centerAt(centerX, centerY, 0);
                     setMount(true)
                 }
             }, 100);
+            return () => window.clearTimeout(t);
         }
-        // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [chartRef, graph.Id, currentTab, graph.Elements.nodes.length, isLoading])
+    }, [graph.Id, currentTab, graph.Elements.nodes.length, isLoading, viewport, mount])

238-245: Selection uses reference equality; prefer ID matching.

selectedElements.includes(fullElement) fails after data refresh since objects are re-created. Use ID-based comparison:

-        if (selectedElements.includes(fullElement)) {
-            setSelectedElements(selectedElements.filter((el) => el !== fullElement))
+        const sameKind = (a: Node | Link, b: Node | Link) => ("source" in a) === ("source" in b);
+        const isSelected = selectedElements.some(el => sameKind(el, fullElement) && el.id === fullElement.id);
+        if (isSelected) {
+            setSelectedElements(selectedElements.filter(el => !(sameKind(el, fullElement) && el.id === fullElement.id)))

273-278: Using ref.current in dependency array is an anti-pattern.

React doesn't track mutations to ref objects. Use ResizeObserver instead:

+    useEffect(() => {
+        if (!canvasRef.current || !parentRef.current) return;
+        const parent = parentRef.current;
+        const canvas = canvasRef.current;
+        
+        const observer = new ResizeObserver(() => {
+            canvas.setWidth(parent.clientWidth);
+            canvas.setHeight(parent.clientHeight);
+        });
+        observer.observe(parent);
+        
+        // Initial set
+        canvas.setWidth(parent.clientWidth);
+        canvas.setHeight(parent.clientHeight);
+        
+        return () => observer.disconnect();
+    }, []);
-    useEffect(() => {
-        if (!canvasRef.current || !parentRef.current) return;
-        canvasRef.current.setWidth(parentRef.current.clientWidth);
-        canvasRef.current.setHeight(parentRef.current.clientHeight);
-    }, [parentRef.current?.clientWidth, parentRef.current?.clientHeight]);
🧹 Nitpick comments (12)
app/components/ForceGraph.tsx (1)

341-346: Consider removing defaultProps - deprecated in React 18+.

defaultProps is deprecated for function components. Default parameters in the signature (already present at line 63-64) are preferred.

-ForceGraph.defaultProps = {
-    type: "graph",
-    currentTab: "Graph",
-    viewport: undefined,
-    setViewport: undefined,
-}
app/graph/page.tsx (1)

69-69: Consider narrowing chartRef type.

The type GraphRef["current"] results in ForceGraphInstance which is not optional, but the ref is initialized as undefined. Consider using GraphRef["current"] | undefined or GraphRef directly for type safety.

-    const chartRef = useRef<GraphRef["current"]>()
+    const chartRef = useRef<GraphRef["current"] | undefined>(undefined)
app/schema/_page.tsx (4)

4-5: Clarify chartRef typing versus GraphRef

GraphRef is already a MutableRefObject<ForceGraphInstance>, while chartRef is inferred as MutableRefObject<GraphRef["current"] | undefined>. If consumers (e.g. Selector, SchemaView) expect GraphRef, this subtle mismatch can make TS unhappy.

Consider tightening this to something like:

-const chartRef = useRef<GraphRef["current"]>()
+const chartRef = useRef<GraphRef["current"] | null>(null)

and updating the chartRef prop types to accept MutableRefObject<GraphRef["current"] | null> for consistency.

Also applies to: 43-45, 287-288, 306-307


47-48: Broaden handleCooldown argument type

const handleCooldown = (ticks?: 0) restricts callers to passing only 0 or undefined, even though cooldownTicks is number | undefined. If SchemaView ever wants to pass a real tick count, this will be a type error.

-const handleCooldown = (ticks?: 0) => {
+const handleCooldown = (ticks?: number) => {
   setCooldownTicks(ticks)
 }

This keeps types aligned with the state you store.

Also applies to: 128-130, 301-308


301-317: setIsLoading prop cleanly exposes canvas loading control

Wiring isLoading={isCanvasLoading} and setIsLoading={setIsCanvasLoading} into SchemaView keeps loading concerns colocated with the page state while letting the canvas drive transitions.

Looks good; just ensure SchemaView always toggles loading in response to the actual request lifecycle to avoid stale spinners.


275-340: Suggested PR title (conventional commit)

Given the changes here and in the graph model, a concise conventional-commit style title could be:

feat: migrate schema graph view to FalkorDB canvas

app/api/graph/model.ts (6)

53-77: New Node/Link shapes and MemoryValue union look consistent

Moving to plain Node/Link objects with numeric source/target aligns with how extendNode, extendEdge, and the rest of Graph now operate. The recursive MemoryValue = number | Map<string, MemoryValue> also matches its use in GraphInfo.MemoryUsage.

Only minor nit: if you ever want stronger typing for data, consider making Node/Link generic instead of using any, but that’s not required for this refactor.

Also applies to: 110-111


451-455: Minor: simplify Graph.create by passing id into Graph.empty

You currently create an empty graph with an empty id and then overwrite graph.id:

const graph = Graph.empty(undefined, currentLimit, graphInfo);
graph.extend(...);
graph.id = id;

For clarity, you could pass id directly:

-const graph = Graph.empty(
-  undefined,
-  currentLimit,
-  graphInfo
-);
+const graph = Graph.empty(
+  id,
+  currentLimit,
+  graphInfo
+);

Behavior is equivalent but makes the constructor intent more obvious.


736-738: Rebuilding nodesMap/linksMap in extend keeps maps in sync

Refreshing the maps from this.elements.nodes/links after extending ensures lookups stay consistent even as elements are added.

One follow-up you might consider is also pruning nodesMap/linksMap in mutation helpers like removeElements, so they don’t hold references to removed nodes/links between extend calls.


800-823: Optional: cache node visibility when toggling link visibility

visibleLinks repeatedly does this.elements.nodes.find(...) per link, which is O(VE). If graphs can get large, you could precompute a Map<number, boolean> (or Set of visible IDs) outside the loop and reuse it:

const visibleNodes = new Map(this.elements.nodes.map(n => [n.id, n.visible]));
this.elements.links.forEach(link => {
  const srcVisible = visibleNodes.get(link.source);
  const tgtVisible = visibleNodes.get(link.target);
  // ...
});

Not required, but a simple optimization if this becomes hot.


825-866: Clarify removeLinks parameter semantics and avoid repeated map

With Link.source/target now numeric IDs, ids appears to represent node IDs whose incident links should be removed. That matches the usage from the schema page, but it’s not obvious from the signature.

Consider:

  • Renaming ids to nodeIds for clarity.
  • Precomputing node IDs once instead of calling this.elements.nodes.map(...) twice per link, e.g.:
const nodeIds = new Set(this.elements.nodes.map(n => n.id));
...
(nodeIds.has(link.source) && nodeIds.has(link.target))

This keeps intent clearer and avoids repeated allocations.


868-912: removeElements type guards match new Node/Link shapes

Using "labels" in element to detect nodes and "source" in element to detect links is a clean way to discriminate the union now that Link.source is an ID. The final filter that rebuilds this.elements from nodes/links is slightly redundant with the earlier splice operations, but functionally harmless.

No functional issues spotted in this refactor.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a83ed4 and 6e45bea.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • app/api/graph/model.ts (12 hunks)
  • app/components/ForceGraph.tsx (7 hunks)
  • app/components/provider.ts (6 hunks)
  • app/graph/CreateElementPanel.tsx (1 hunks)
  • app/graph/DataPanel.tsx (5 hunks)
  • app/graph/DataTable.tsx (4 hunks)
  • app/graph/GraphView.tsx (6 hunks)
  • app/graph/page.tsx (5 hunks)
  • app/providers.tsx (6 hunks)
  • app/schema/DataPanel.tsx (9 hunks)
  • app/schema/SchemaView.tsx (4 hunks)
  • app/schema/_page.tsx (6 hunks)
  • lib/utils.ts (4 hunks)
  • package.json (1 hunks)
  • types/falkordb-canvas.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • types/falkordb-canvas.d.ts
  • package.json
  • app/graph/CreateElementPanel.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/graph/DataTable.tsx
  • app/schema/SchemaView.tsx
  • app/graph/DataPanel.tsx
  • app/components/ForceGraph.tsx
  • app/schema/DataPanel.tsx
  • app/providers.tsx
  • app/graph/page.tsx
  • app/graph/GraphView.tsx
  • app/schema/_page.tsx
**/*.ts

⚙️ CodeRabbit configuration file

Review TypeScript code for type safety, proper error handling, and Next.js best practices.

Files:

  • lib/utils.ts
  • app/api/graph/model.ts
  • app/components/provider.ts
**/api/**/*

⚙️ CodeRabbit configuration file

Review API routes for security, error handling, and proper HTTP status codes.

Files:

  • app/api/graph/model.ts
🧬 Code graph analysis (7)
app/graph/DataPanel.tsx (1)
app/components/provider.ts (1)
  • ForceGraphContext (294-299)
lib/utils.ts (1)
app/api/graph/model.ts (1)
  • Node (53-63)
app/schema/DataPanel.tsx (1)
app/api/graph/model.ts (1)
  • GraphData (79-82)
app/providers.tsx (1)
app/components/provider.ts (1)
  • ForceGraphContext (294-299)
app/graph/page.tsx (2)
app/components/provider.ts (1)
  • ForceGraphContext (294-299)
lib/utils.ts (1)
  • GraphRef (21-21)
app/graph/GraphView.tsx (1)
app/components/provider.ts (2)
  • GraphContext (228-249)
  • ForceGraphContext (294-299)
app/schema/_page.tsx (1)
lib/utils.ts (1)
  • GraphRef (21-21)
🔇 Additional comments (31)
lib/utils.ts (1)

383-386: LGTM!

The updated isTwoNodes type guard using "labels" in e is a safer runtime check than the previous !!e.labels approach, correctly discriminating between Node and Link types.

app/graph/DataTable.tsx (3)

15-15: LGTM!

Clean addition of ForceGraphContext import to access setData for propagating graph mutations.


229-229: LGTM!

Proper data propagation after property set - spreading graph.Elements ensures React detects the state change.


289-289: LGTM!

Consistent data propagation pattern after property removal.

app/schema/SchemaView.tsx (2)

29-29: LGTM!

Addition of setIsLoading prop cleanly integrates the loading state control with the canvas-based rendering flow.


120-134: LGTM!

ForceGraph receives all required props including the new setIsLoading, properly wiring the canvas loading state.

app/graph/DataPanel.tsx (4)

14-14: LGTM!

Clean import of ForceGraphContext for accessing the shared setData function.


38-38: LGTM!

Using "source" in object is a proper type discriminator between Node and Link types, replacing the previous truthy check.


93-93: LGTM!

Correct data propagation after adding a label to ensure UI reflects the change.


126-126: LGTM!

Consistent data propagation after label removal.

app/schema/DataPanel.tsx (2)

52-52: Improved type guard using property existence check.

The change from !object.source to !("source" in object) is a safer approach for discriminating between Node and Link types. The previous check could fail if source was falsy (e.g., 0 or "").


127-127: Consistent data refresh pattern after mutations.

The setData({ ...schema.Elements }) calls ensure the graph UI stays in sync after attribute and label mutations. This aligns with the broader PR pattern of propagating graph state via ForceGraphContext.

Also applies to: 175-175, 287-287, 319-319

app/graph/page.tsx (3)

44-44: Context migration to ForceGraphContext.

The switch from ViewportContext to ForceGraphContext is consistent with the provider changes. The setData is now correctly consumed from the new context.

Also applies to: 61-61


243-245: Consistent type guard improvement.

The change from !element.source to !("source" in element) provides safer type narrowing, consistent with the same pattern applied in DataPanel.tsx.


226-229: Data update after element creation.

The setData call ensures the graph UI refreshes after creating new elements. The dependency array is correctly updated.

app/components/provider.ts (3)

107-107: Added setIsLoading to GraphContextType.

This addition enables child components to control the loading state, supporting the new data flow pattern where loading is triggered from various graph operations.


139-144: Context type renamed and simplified.

The rename from ViewportContextType to ForceGraphContextType better reflects the context's purpose. Removing isSaved simplifies the API surface.


294-299: ForceGraphContext export with proper defaults.

The context is properly initialized with sensible defaults for viewport and data state.

app/providers.tsx (3)

206-211: Simplified viewportContext dependencies.

The removal of isSaved from the dependency array aligns with the type changes in ForceGraphContextType.


386-387: setIsLoading added to graphContext.

The setIsLoading is now exposed through GraphContext, enabling child components to control loading state.


613-613: ForceGraphContext.Provider wiring.

The provider tree correctly uses ForceGraphContext.Provider with viewportContext value, replacing the previous ViewportContext.

Also applies to: 667-667

app/graph/GraphView.tsx (6)

5-5: Updated imports for context migration.

The removal of useState and addition of ForceGraphContext import reflects the component's shift to context-based state management.

Also applies to: 9-9


60-61: Context consumption split between GraphContext and ForceGraphContext.

The component correctly consumes isLoading and setIsLoading from GraphContext, while graph data and viewport state come from ForceGraphContext. This separation aligns with the new context architecture.


106-108: Improved relationship visibility filtering.

Using graph.NodesMap.get(link.source)?.visible is safer than direct property access, handling cases where the node might not exist in the map.


129-129: Safer type guard for add-edge predicate.

Using "labels" in e is more reliable than !!e.labels for distinguishing nodes from links, consistent with the type guard pattern used elsewhere in this PR.


208-223: ForceGraph props updated for new API.

The component correctly passes setIsLoading and the updated handleCooldown to ForceGraph, aligning with the component's new prop interface.


29-29: handleCooldown signature is intentionally restrictive and correct.

The signature (ticks?: 0) => void allows only undefined or literal 0, which matches all call sites in the codebase (handleCooldown(0) and handleCooldown(cooldownTicks === undefined ? 0 : undefined)). This restrictive typing is intentional and enforces valid cooldown states correctly.

app/schema/_page.tsx (2)

153-205: Node/link type guard in delete handler matches new Link shape

Using const type = !("source" in element) to distinguish nodes from links is compatible with the new Link type (which always has source) and keeps the subsequent mutation logic correct for both branches.

No change requested; just a note that this relies on Node never gaining a source field in the future.


237-246: DataPanel receiving setData keeps schema edits in sync

Passing setData into DataPanel ensures mutations performed in the side panel can immediately update the shared data used by SchemaView/canvas, avoiding local copies or manual refreshes.

This wiring looks correct and consistent with how data is used elsewhere in the component.

app/api/graph/model.ts (2)

461-492: calculateLinkCurve still correctly groups multi-edges by endpoints

Using numeric start = link.source / end = link.target and comparing them directly keeps the curve calculation consistent with the new Link shape, including self-loops (start === end) and multi-edge offsetting.

No issues spotted here.


573-655: extendEdge uses numeric node IDs consistently

Assigning:

source: cell.sourceId,
target: cell.destinationId,

and creating placeholder nodes when source/target are missing matches the new Link structure and preserves label/relationship bookkeeping.

Just double-check that all consumers have been updated away from link.source.id/link.target.id to use the numeric IDs directly.

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: 0

♻️ Duplicate comments (5)
app/components/ForceGraph.tsx (5)

87-96: Canvas → chartRef initialization effect doesn’t need reactive deps.

This effect’s job is to wire chartRef.current from canvasRef.current.getGraph() once the canvas is available. Including chartRef and especially data in the dependency array causes unnecessary re-runs without any functional benefit (both are stable ref/data inputs for this wiring step).

You can simplify to a one-time mount effect:

-    useEffect(() => {
-        if (canvasRef.current) {
-            const forceGraph = canvasRef.current.getGraph();
-
-            if (forceGraph) {
-                chartRef.current = forceGraph;
-            }
-        }
-    }, [chartRef, data])
+    useEffect(() => {
+        if (!canvasRef.current) return;
+        const graphInstance = canvasRef.current.getGraph();
+        if (graphInstance) {
+            chartRef.current = graphInstance;
+        }
+    }, [])

If there’s a known case where the internal graph becomes available only after some later event, we can instead key this off a dedicated “ready” signal from the canvas rather than data.


32-49: Normalize link.source/link.target to numeric IDs before passing to canvas.

Right now convertToCanvasData forwards source and target as-is. With falkordb-canvas, the Link shape expects numeric IDs, and any upstream mutation that turns source/target into node objects will break the canvas at runtime.

You can defensively normalize to IDs when building Data:

 const convertToCanvasData = (graphData: GraphData): Data => ({
@@
-    links: graphData.links.map(({ id, relationship, color, visible, source, target, data }) => ({
-        id,
-        relationship,
-        color,
-        visible,
-        source,
-        target,
-        data
-    }))
+    links: graphData.links.map(({ id, relationship, color, visible, source, target, data }) => ({
+        id,
+        relationship,
+        color,
+        visible,
+        source: typeof source === "object" && source !== null ? (source as any).id : (source as any),
+        target: typeof target === "object" && target !== null ? (target as any).id : (target as any),
+        data
+    }))
 });

This keeps the canvas Data shape aligned with its type contract and avoids subtle failures if upstream code ever passes link objects instead of ids.


98-112: Viewport restore effect: missing viewport in deps and setTimeout can fire after unmount.

This effect reads viewport and schedules a setTimeout, but:

  • viewport is not in the dependency array, so viewport changes while the component is mounted won’t be respected.
  • The timeout isn’t cleared, so it can fire after unmount and still touch chartRef.current.

You can make the effect reactive to viewport and add proper cleanup:

-    // Load saved viewport on mount
-    useEffect(() => {
-        if (!isLoading && viewport && chartRef.current && !mount) {
-            const { zoom, centerX, centerY } = viewport;
-            
-            setTimeout(() => {
-                if (chartRef.current) {
-                    chartRef.current.zoom(zoom, 0);
-                    chartRef.current.centerAt(centerX, centerY, 0);
-                    setMount(true)
-                }
-            }, 100);
-        }
-        // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [chartRef, graph.Id, currentTab, graph.Elements.nodes.length, isLoading])
+    // Load saved viewport on mount / graph change
+    useEffect(() => {
+        if (!isLoading && viewport && chartRef.current && !mount) {
+            const { zoom, centerX, centerY } = viewport;
+            const timeoutId = window.setTimeout(() => {
+                if (chartRef.current) {
+                    chartRef.current.zoom(zoom, 0);
+                    chartRef.current.centerAt(centerX, centerY, 0);
+                    setMount(true);
+                }
+            }, 100);
+            return () => window.clearTimeout(timeoutId);
+        }
+    }, [graph.Id, currentTab, graph.Elements.nodes.length, isLoading, viewport, mount])

This keeps viewport restore in sync with its inputs and prevents post-unmount access to the chart.


226-247: Selection relies on reference equality; prefer id-based comparison for robustness.

selectedElements.includes(fullElement) and filtering via el !== fullElement assume the same object instances are always reused. After data refreshes or graph re-instantiation, logically identical nodes/links with new object identities won’t compare equal, leading to “can’t unselect” or duplicated selection entries.

You can switch to id- and kind-based matching:

-        if (!fullElement) return;
-
-        if (evt.ctrlKey) {
-            if (selectedElements.includes(fullElement)) {
-                setSelectedElements(selectedElements.filter((el) => el !== fullElement))
-            } else {
-                setSelectedElements([...selectedElements, fullElement])
-            }
-        } else {
-            setSelectedElements([fullElement])
-        }
+        if (!fullElement) return;
+
+        const sameKind = (a: Node | Link, b: Node | Link) => ("source" in a) === ("source" in b);
+        const isSelected = selectedElements.some(
+            el => sameKind(el, fullElement) && el.id === fullElement.id
+        );
+
+        if (evt.ctrlKey) {
+            setSelectedElements(
+                isSelected
+                    ? selectedElements.filter(el => !(sameKind(el, fullElement) && el.id === fullElement.id))
+                    : [...selectedElements, fullElement]
+            );
+        } else {
+            setSelectedElements([fullElement]);
+        }

This keeps selection stable across data updates while preserving the ctrl-click behavior.


273-279: Canvas dimensions are not reacting to container resize; prefer ResizeObserver.

This effect sets width/height once based on parentRef.current.clientWidth/Height. Using parentRef.current?.clientWidth in the dependency array won’t detect DOM resizes, so the canvas won’t respond to window resizes or layout changes (sidebar toggle, panel resize, etc.).

Adopting a ResizeObserver (mirroring patterns elsewhere in the app) would make this robust:

-    // Update dimensions
-    useEffect(() => {
-        if (!canvasRef.current || !parentRef.current) return;
-        canvasRef.current.setWidth(parentRef.current.clientWidth);
-        canvasRef.current.setHeight(parentRef.current.clientHeight);
-    }, [parentRef.current?.clientWidth, parentRef.current?.clientHeight]);
+    // Update dimensions on container resize
+    useEffect(() => {
+        const canvas = canvasRef.current;
+        const parent = parentRef.current;
+        if (!canvas || !parent || typeof ResizeObserver === "undefined") return;
+
+        const applySize = () => {
+            canvas.setWidth(parent.clientWidth);
+            canvas.setHeight(parent.clientHeight);
+        };
+
+        applySize();
+        const observer = new ResizeObserver(() => applySize());
+        observer.observe(parent);
+
+        return () => observer.disconnect();
+    }, [])

That way, the canvas always matches its container without relying on unrelated re-renders.

🧹 Nitpick comments (1)
app/components/ForceGraph.tsx (1)

51-67: Overall: solid canvas migration; address a few integration edge cases + suggested PR title.

The migration to falkordb-canvas is generally well-structured: props surface is clear, hooks usage is mostly sound, and the new effects cleanly bridge React state/context into the canvas API. The remaining issues are mainly around data normalization (source/target), viewport restore, selection robustness, and resize handling.

Suggested PR title (conventional commit):

refactor: migrate ForceGraph to FalkorDB canvas renderer

Also applies to: 334-346

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e45bea and eb33091.

📒 Files selected for processing (1)
  • app/components/ForceGraph.tsx (7 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/components/ForceGraph.tsx
🔇 Additional comments (1)
app/components/ForceGraph.tsx (1)

309-332: Event wiring and data flow to falkordb-canvas look clean and idiomatic.

The separation of concerns here is good: config (callbacks/selection predicates/loading handler) is set via setConfig, while color/text/loading/cooldown and Data updates are handled in dedicated effects keyed off their respective inputs. That keeps the React side simple and makes the canvas the single source of truth for rendering.

No changes needed in this block.

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: 4

Caution

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

⚠️ Outside diff range comments (1)
app/schema/_page.tsx (1)

240-275: Unreachable code: isTwoNodes block will never execute.

The isTwoNodes(selectedElements) check at line 262 is unreachable because the preceding condition at line 240 (selectedElements.length !== 0) already returns early for any non-empty array, including when there are exactly two nodes.

If this branch is intended for the "Add Edge" flow when two nodes are selected, the logic needs restructuring:

     const getCurrentPanel = () => {
+        if (isAddEdge && isTwoNodes(selectedElements)) {
+            return (
+                <SchemaCreateElement
+                    onCreate={handleCreateElement}
+                    setIsAdd={handleSetIsAddEdge}
+                    selectedNodes={selectedElements}
+                    setSelectedNodes={setSelectedElements}
+                    type={false}
+                />
+            )
+        }
+
         if (selectedElements.length !== 0) {
             return (
                 <DataPanel
                     ...
                 />
             )
         }

         if (isAddNode) {
             return (
                 <SchemaCreateElement
                     ...
                     type
                 />
             )
         }

-        if (isTwoNodes(selectedElements)) {
-            return (
-                <SchemaCreateElement
-                    ...
-                />
-            )
-        }
-
         return null
     }
♻️ Duplicate comments (4)
app/components/ForceGraph.tsx (3)

34-51: Normalize source/target to numeric IDs in convertToCanvasData.

The canvas Link type expects source: number and target: number, but force-graph can mutate these to node objects at runtime. Pass them through without normalization risks type mismatch.

 links: graphData.links.map(({ id, relationship, color, visible, source, target, data }) => ({
     id,
     relationship,
     color,
     visible,
-    source,
-    target,
+    source: typeof source === "object" && source !== null ? (source as { id: number }).id : source,
+    target: typeof target === "object" && target !== null ? (target as { id: number }).id : target,
     data
 }))

197-217: Selection uses reference equality; consider id-based matching.

selectedElements.includes(fullElement) will fail after data refresh when element objects are re-created. Use id + type matching instead.

-        if (selectedElements.includes(fullElement)) {
-            setSelectedElements(selectedElements.filter((el) => el !== fullElement))
+        const isLink = 'source' in fullElement;
+        const isSelected = selectedElements.some(el => 
+            el.id === fullElement.id && ('source' in el) === isLink
+        );
+        if (isSelected) {
+            setSelectedElements(selectedElements.filter((el) => 
+                !(el.id === fullElement.id && ('source' in el) === isLink)
+            ))

244-249: Avoid ref properties in useEffect dependency array.

parentRef.current?.clientWidth and parentRef.current?.clientHeight in deps won't trigger re-renders when the container resizes. Use a ResizeObserver instead.

+    useEffect(() => {
+        if (!canvasRef.current || !parentRef.current) return;
+        const parent = parentRef.current;
+        const observer = new ResizeObserver(() => {
+            canvasRef.current?.setWidth(parent.clientWidth);
+            canvasRef.current?.setHeight(parent.clientHeight);
+        });
+        observer.observe(parent);
+        canvasRef.current.setWidth(parent.clientWidth);
+        canvasRef.current.setHeight(parent.clientHeight);
+        return () => observer.disconnect();
+    }, [canvasRef]);
-    useEffect(() => {
-        if (!canvasRef.current || !parentRef.current) return;
-        canvasRef.current.setWidth(parentRef.current.clientWidth);
-        canvasRef.current.setHeight(parentRef.current.clientHeight);
-    }, [parentRef.current?.clientWidth, parentRef.current?.clientHeight, canvasRef]);
app/providers.tsx (1)

369-369: Remove handleCooldown from runQuery dependency array.

handleCooldown is listed in deps but never called inside runQuery. This causes unnecessary callback recreation.

-  }, [graphName, limit, timeout, fetchInfo, fetchCount, handleCooldown, handelGetNewQueries]);
+  }, [graphName, limit, timeout, fetchInfo, fetchCount, handelGetNewQueries]);
🧹 Nitpick comments (3)
app/schema/_page.tsx (1)

45-45: Consider using standard ref typing.

Using GraphRef["current"] as the ref type is unconventional. The standard pattern would be:

-    const canvasRef = useRef<GraphRef["current"]>(null)
+    const canvasRef = useRef<FalkorDBCanvas | null>(null)

This is clearer and doesn't require the indexed access type.

app/components/provider.ts (1)

297-304: Minor inconsistency: graphData default vs type.

The type declares graphData: CanvasData | undefined, but the default value is { nodes: [], links: [] }. Consider aligning these:

Either change the default to match the type:

-  graphData: { nodes: [], links: [] },
+  graphData: undefined,

Or update the type if undefined is never expected:

-  graphData: CanvasData | undefined;
+  graphData: CanvasData;
app/graph/toolbar.tsx (1)

225-225: Rename type to isLink for clarity.

The variable holds a boolean indicating whether the suggestion is a link, but type reads like it should be a string or enum.

-                                    const type = "source" in suggestion
+                                    const isLink = "source" in suggestion

Then update usages on lines 243 and 253 accordingly.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb33091 and 826a894.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • app/components/ForceGraph.tsx (4 hunks)
  • app/components/provider.ts (7 hunks)
  • app/graph/DataTable.tsx (5 hunks)
  • app/graph/GraphView.tsx (8 hunks)
  • app/graph/Selector.tsx (5 hunks)
  • app/graph/controls.tsx (2 hunks)
  • app/graph/layout.tsx (1 hunks)
  • app/graph/page.tsx (6 hunks)
  • app/graph/toolbar.tsx (8 hunks)
  • app/providers.tsx (9 hunks)
  • app/schema/SchemaView.tsx (3 hunks)
  • app/schema/_page.tsx (7 hunks)
  • app/settings/browserSettings.tsx (1 hunks)
  • lib/utils.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/graph/DataTable.tsx
  • app/schema/SchemaView.tsx
  • app/graph/page.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/settings/browserSettings.tsx
  • app/providers.tsx
  • app/graph/toolbar.tsx
  • app/graph/controls.tsx
  • app/graph/layout.tsx
  • app/graph/Selector.tsx
  • app/components/ForceGraph.tsx
  • app/graph/GraphView.tsx
  • app/schema/_page.tsx
**/*.ts

⚙️ CodeRabbit configuration file

Review TypeScript code for type safety, proper error handling, and Next.js best practices.

Files:

  • lib/utils.ts
  • app/components/provider.ts
🧬 Code graph analysis (7)
lib/utils.ts (1)
app/api/graph/model.ts (1)
  • Node (53-63)
app/providers.tsx (1)
app/components/provider.ts (1)
  • ForceGraphContext (297-304)
app/graph/toolbar.tsx (2)
lib/utils.ts (1)
  • GraphRef (21-21)
app/api/graph/model.ts (2)
  • Link (65-77)
  • Node (53-63)
app/graph/controls.tsx (3)
lib/utils.ts (1)
  • GraphRef (21-21)
app/components/ui/Button.tsx (1)
  • Props (11-21)
app/components/provider.ts (1)
  • IndicatorContext (282-285)
app/graph/Selector.tsx (1)
lib/utils.ts (1)
  • GraphRef (21-21)
app/graph/GraphView.tsx (3)
app/components/ForceGraph.tsx (1)
  • ForceGraph (53-319)
lib/utils.ts (1)
  • GraphRef (21-21)
app/components/provider.ts (1)
  • GraphContext (231-252)
app/components/provider.ts (1)
app/api/graph/model.ts (1)
  • GraphData (79-82)
🔇 Additional comments (18)
app/settings/browserSettings.tsx (1)

11-11: LGTM!

The TextPriority type import is correctly migrated from the local utils to the falkordb-canvas library, aligning with the broader canvas integration.

app/graph/layout.tsx (1)

1-9: Verify if force-dynamic is intentional for all graph routes.

The layout correctly exports dynamic = 'force-dynamic', which opts out of static generation for all routes under /graph. This is appropriate if all graph pages require real-time data, but be aware it applies to all child routes.

If only specific pages need dynamic rendering, consider moving the export to individual page.tsx files instead.

lib/utils.ts (2)

21-21: LGTM!

The GraphRef type now uses FalkorDBCanvas | null, which is the standard React ref pattern. This addresses the previous review concern about missing the nullable union type.


340-342: Improved type guard using in operator.

The change from !!e.labels to "labels" in e is a more robust type guard. The in operator properly narrows the type without risking false negatives from empty arrays.

app/schema/_page.tsx (1)

160-160: LGTM!

The type check using !("source" in element) is consistent with the updated isTwoNodes guard pattern and properly discriminates between Node and Link types.

app/components/provider.ts (2)

140-147: LGTM on the context type rename.

The rename from ViewportContextType to ForceGraphContextType and the addition of graphData/setGraphData properly supports the canvas-based rendering architecture.


108-108: LGTM!

The setIsLoading function is properly added to GraphContextType with appropriate typing.

app/graph/controls.tsx (1)

32-38: LGTM!

Clean migration to canvasRef with proper null safety via optional chaining.

app/graph/toolbar.tsx (1)

107-112: LGTM!

The zoomToFit call correctly uses "labels" in element to distinguish Node from Link when filtering nodes for focus.

app/providers.tsx (2)

256-260: LGTM!

Simplified handleCooldown by removing the unused isSetLoading parameter.


618-618: LGTM!

Clean replacement of ViewportContext with ForceGraphContext throughout the provider tree.

Also applies to: 672-672

app/graph/Selector.tsx (2)

619-620: LGTM!

Using "labels" in e is a correct type guard to verify both selected elements are nodes before enabling edge creation.


35-50: LGTM!

Interface updates cleanly separate SchemaProps (requires canvasRef) from GraphProps (excludes it) with proper discriminated union typing.

Also applies to: 52-67

app/graph/GraphView.tsx (5)

21-24: Good use of dynamic import for client-side rendering.

The dynamic import with ssr: false correctly ensures the canvas-based ForceGraph only loads in the browser, and the Spinning fallback provides good UX during load.


66-67: Context refactoring correctly separates concerns.

The split between GraphContext (graph state, loading) and ForceGraphContext (simulation data, viewport) cleanly organizes the canvas-based rendering state.


112-112: Relationship filtering logic is correct.

The visibility check correctly ensures relationships are only shown when both source and target nodes are visible, using optional chaining to safely handle missing nodes.


135-135: Property existence check correctly distinguishes nodes from links.

Changing from !!e.labels to "labels" in e is semantically better: it checks for the property's presence rather than truthiness, correctly distinguishing nodes (which have a labels property) from links (which don't).


214-230: ForceGraph integration correctly wired with new canvas-based data flow.

The props for graphData, setGraphData, canvasRef, and setIsLoading align with the canvas-based rendering architecture and match the ForceGraph component signature.

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

♻️ Duplicate comments (6)
app/components/ForceGraph.tsx (6)

33-50: Still need to normalize link.source/link.target to numeric IDs.

This was flagged in a previous review but remains unaddressed. The canvas expects source: number; target: number, but runtime mutations can convert these to objects, causing type mismatches.


92-98: Remove debug console.log (previously flagged).

Line 93 still contains the debug log that was flagged for removal in a previous review.


101-112: Remove debug console.log in cleanup (previously flagged).

Line 106 still contains the debug log in the cleanup that was flagged for removal.


217-225: Selection reference equality issue remains (previously flagged).

Lines 218-221 still use includes(fullElement) which breaks when objects are recreated after data refresh.


254-259: ResizeObserver still missing (previously flagged).

Dimensions effect won't trigger on actual resize because parentRef.current?.clientWidth in dependencies doesn't cause re-renders when the DOM changes. A ResizeObserver is needed as flagged in previous review.


308-322: Missing graphData in dependencies (previously flagged).

Line 313 reads graphData but it's not in the dependency array, causing potential stale closure issues.

🧹 Nitpick comments (1)
app/graph/page.tsx (1)

15-17: Consider adding a loading fallback for consistency.

Selector and GraphView both have loading placeholders, but CreateElementPanel doesn't. This could cause a minor layout shift when the panel loads.

 const CreateElementPanel = dynamicImport(() => import("./CreateElementPanel"), {
     ssr: false,
+    loading: () => <div className="h-full animate-pulse rounded-md border border-border bg-background" />,
 });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 826a894 and 3dc0071.

📒 Files selected for processing (2)
  • app/components/ForceGraph.tsx (4 hunks)
  • app/graph/page.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/graph/page.tsx
  • app/components/ForceGraph.tsx
🧬 Code graph analysis (1)
app/graph/page.tsx (2)
app/components/provider.ts (1)
  • ForceGraphContext (297-304)
lib/utils.ts (1)
  • GraphRef (21-21)
🔇 Additional comments (18)
app/graph/page.tsx (7)

4-10: LGTM on import changes.

The migration to ForceGraphContext and addition of GraphRef type aligns with the canvas-based rendering approach. The dynamicImport rename avoids potential shadowing with reserved/common identifiers.


47-47: LGTM on context migration.

Correctly extracts only setData from ForceGraphContext, which is all that's needed for the element creation and deletion flows.


72-72: LGTM on ref typing.

Using GraphRef["current"] maintains type consistency with the centralized GraphRef definition, ensuring the ref type stays synchronized if the underlying canvas type changes.


229-232: LGTM on callback update.

The setData dependency is correctly included, and spreading graph.Elements ensures React detects the state change.


248-248: Good improvement on type narrowing.

Using "source" in element is the correct way to discriminate union types. The previous !element.source would incorrectly identify a Link as a Node if source happened to be falsy.


374-392: LGTM on GraphView prop update.

The canvasRef prop correctly replaces chartRef, aligning with the canvas-based rendering migration. All other props remain appropriately threaded through.


285-299: LGTM on deletion handler updates.

The setData dependency is correctly included, and the cleanup flow properly updates the graph state after element removal.

app/components/ForceGraph.tsx (11)

84-89: LGTM: Dynamic import setup is correct.

The effect correctly loads the web component once on mount with an appropriate empty dependency array.


114-134: LGTM: Callback dependencies are complete.

All referenced values are properly included in the dependency array.


170-188: LGTM: Dependency array correctly updated.

The unnecessary displayTextPriority has been removed from dependencies as flagged in previous review.


190-204: LGTM: Hover handling is well-structured.

Proper type discrimination and minimal dependencies.


228-231: LGTM: Callback dependencies are correct.


233-241: LGTM: Selection check logic is sound.

Both callbacks correctly include their dependencies.


243-247: LGTM: Engine stop handler is correct.


249-251: LGTM: Stale closure issue fixed.

The dependency array now correctly includes setIsLoading as flagged in previous review.


262-289: LGTM: Granular config effects are well-structured.

Each effect correctly updates a single canvas property with appropriate dependencies.


291-305: LGTM: Event handler wiring is complete.

All callback dependencies are properly tracked.


324-328: LGTM: Clean render structure.

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: 0

♻️ Duplicate comments (5)
app/components/ForceGraph.tsx (5)

33-50: Still missing source/target normalization to numeric IDs.

Past review flagged this: falkordb-canvas expects source: number; target: number, but force-graph can mutate these to node objects at runtime. Lines 46-47 still pass them through unchanged, creating a type mismatch.

Apply this diff to normalize:

 const convertToCanvasData = (graphData: GraphData): Data => ({
     nodes: graphData.nodes.map(({ id, labels, color, visible, data }) => ({
         id,
         labels,
         color,
         visible,
         data
     })),
     links: graphData.links.map(({ id, relationship, color, visible, source, target, data }) => ({
         id,
         relationship,
         color,
         visible,
-        source,
-        target,
+        source: typeof source === "object" && source !== null ? (source as any).id : source,
+        target: typeof target === "object" && target !== null ? (target as any).id : target,
         data
     }))
 });

141-145: Unsafe link.source/target comparison - still not fixed.

Past review flagged this: link.target and link.source can be node objects at runtime. Lines 142-143 assign them directly but line 144 compares as if they're always numeric IDs.

Extract IDs safely:

                 const isTarget = graph.Elements.links.some(link => {
-                    const targetId = link.target;
-                    const sourceId = link.source;
+                    const targetId = typeof link.target === 'object' ? link.target.id : link.target;
+                    const sourceId = typeof link.source === 'object' ? link.source.id : link.source;
                     return targetId === node.id && nodes.some(n => n.id === sourceId);
                 });

202-222: Selection still uses reference equality - past issue unresolved.

Lines 214-216 use .includes(fullElement) which fails when element objects are re-created (common after setData({ ...graph.Elements })).

Use ID-based matching:

         if (evt.ctrlKey) {
-            if (selectedElements.includes(fullElement)) {
-                setSelectedElements(selectedElements.filter((el) => el !== fullElement))
+            const isSelected = selectedElements.some(el => 
+                el.id === fullElement.id && ('source' in el) === ('source' in fullElement)
+            );
+            if (isSelected) {
+                setSelectedElements(selectedElements.filter(el => 
+                    !(el.id === fullElement.id && ('source' in el) === ('source' in fullElement))
+                ))
             } else {
                 setSelectedElements([...selectedElements, fullElement])
             }

249-255: ResizeObserver still not implemented - past issue unresolved.

Past review flagged that canvas dimensions are set once but won't update on container resize. Using parentRef.current?.clientWidth in deps doesn't work—refs don't trigger re-renders. Implement ResizeObserver like other files in the codebase (CreateElementPanel.tsx, EditorComponent.tsx).

     // Update dimensions
     useEffect(() => {
-        if (!canvasRef.current || !parentRef.current || !canvasLoaded) return;
+        if (!parentRef.current || !canvasLoaded) return;
 
-        canvasRef.current.setWidth(parentRef.current.clientWidth);
-        canvasRef.current.setHeight(parentRef.current.clientHeight);
-    }, [parentRef.current?.clientWidth, parentRef.current?.clientHeight, canvasRef, canvasLoaded]);
+        const observer = new ResizeObserver((entries) => {
+            const entry = entries[0];
+            if (entry && canvasRef.current) {
+                canvasRef.current.setWidth(entry.contentRect.width);
+                canvasRef.current.setHeight(entry.contentRect.height);
+            }
+        });
+        
+        observer.observe(parentRef.current);
+        return () => observer.disconnect();
+    }, [canvasRef, canvasLoaded]);

303-318: Missing graphData in dependency array - stale closure risk.

Past review flagged this: line 309 reads graphData but it's not in the deps (line 318). The eslint-disable on line 317 suggests awareness, but this creates a stale closure bug.

-        // eslint-disable-next-line react-hooks/exhaustive-deps
-    }, [canvasRef, data, setGraphData, canvasLoaded])
+    }, [data, graphData, setGraphData, canvasLoaded])
🧹 Nitpick comments (3)
app/components/ForceGraph.tsx (3)

92-96: Remove canvasRef from dependency array - refs are stable.

Refs don't change between renders, so including canvasRef in the deps is unnecessary and can cause confusion.

-    }, [canvasRef, viewport, canvasLoaded])
+    }, [viewport, canvasLoaded])

98-108: Remove canvasRef from dependency array.

The ref object is stable and shouldn't trigger re-runs. The cleanup function captures the current canvas instance correctly.

-    }, [canvasRef, graph.Id, setGraphData, setViewport, canvasLoaded])
+    }, [graph.Id, setGraphData, setViewport, canvasLoaded])

257-301: Remove canvasRef from all effect dependencies.

Lines 261, 266, 272, 278, 284, 301 include canvasRef in dependency arrays, but refs are stable and shouldn't trigger re-runs. This is an optional cleanup.

-    }, [canvasRef, background, canvasLoaded]);
+    }, [background, canvasLoaded]);

-    }, [canvasRef, foreground, canvasLoaded]);
+    }, [foreground, canvasLoaded]);

-    }, [canvasRef, displayTextPriority, canvasLoaded]);
+    }, [displayTextPriority, canvasLoaded]);

-    }, [canvasRef, isLoading, canvasLoaded]);
+    }, [isLoading, canvasLoaded]);

-    }, [canvasRef, cooldownTicks, canvasLoaded]);
+    }, [cooldownTicks, canvasLoaded]);

-    }, [handleNodeClick, handleRightClick, handleHover, handleUnselected, checkIsNodeSelected, checkIsLinkSelected, handleEngineStop, handleLoadingChange, canvasRef, canvasLoaded])
+    }, [handleNodeClick, handleRightClick, handleHover, handleUnselected, checkIsNodeSelected, checkIsLinkSelected, handleEngineStop, handleLoadingChange, canvasLoaded])
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3dc0071 and f1170b3.

📒 Files selected for processing (1)
  • app/components/ForceGraph.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/components/ForceGraph.tsx
🧬 Code graph analysis (1)
app/components/ForceGraph.tsx (2)
app/api/graph/model.ts (7)
  • Graph (294-1089)
  • GraphData (79-82)
  • Node (53-63)
  • Link (65-77)
  • Data (108-108)
  • Data (400-402)
  • Data (404-406)
lib/utils.ts (2)
  • GraphRef (21-21)
  • getTheme (323-337)
🔇 Additional comments (2)
app/components/ForceGraph.tsx (2)

84-89: LGTM - dynamic import pattern is correct.

The falkordb-canvas web component is loaded client-side only, and canvasLoaded state properly gates subsequent effects.


229-237: LGTM - ID-based selection checks are correct.

Unlike the right-click handler, these callbacks properly use el.id === node.id comparisons and discriminate node vs link with 'source' in el.

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

🤖 Fix all issues with AI agents
In @app/providers.tsx:
- Around line 447-482: During plain-text secret key migration (when
storedSecretKey is non-encrypted), ensure failures don't leave the plain text in
localStorage: after calling encryptValue(storedSecretKey) inside the try/catch,
if encryption fails or an exception is thrown, remove the plain text via
localStorage.removeItem("secretKey"), clear the in-memory key with
setSecretKey(''), and surface an error to the user (use toast.error(...) if
toast is available, otherwise stash the error to display after mount). Update
the catch block and the branch handling a falsy encryptedKey to perform these
removals and notification instead of only logging.
🧹 Nitpick comments (6)
e2e/tests/customizeStyle.spec.ts (2)

469-469: Consider adding proper types for canvas node objects.

The use of any with eslint-disable bypasses type safety. Define an interface or type for canvas node objects to improve maintainability and catch potential issues at compile time.

Example type definition
interface CanvasNode {
  labels?: string[];
  color: string;
  // Add other properties as needed
}

// Usage
const targetNode = nodes.find((n: CanvasNode) => n.labels?.includes("person1"));

Also applies to: 496-496


455-455: Note: Extended timeout may indicate performance concerns.

The 40-second timeout is significantly longer than typical e2e tests. While the comment explains this is for canvas operations in Firefox CI, consider investigating whether the canvas rendering performance can be optimized or if more efficient waiting strategies could reduce this timeout.

lib/encryption.ts (2)

102-135: Consider explicit error propagation instead of silent empty string return.

Returning an empty string on encryption failure (line 133) makes it difficult for callers to distinguish between legitimate empty input and encryption errors. While the error is logged, consider either throwing the error or returning a Result type to enable proper error handling upstream.

♻️ Alternative error handling pattern
-export async function encryptValue(value: string): Promise<string> {
+export async function encryptValue(value: string): Promise<string | null> {
   if (!value) return '';
   
   try {
     // ... encryption logic ...
     return ENCRYPTED_PREFIX + arrayBufferToBase64(combined.buffer);
   } catch (error) {
     console.error('Error encrypting value:', error);
-    // Return empty string on error to avoid storing unencrypted data
-    return '';
+    // Return null to signal encryption failure
+    return null;
   }
 }

Callers can then check for null to detect encryption failures explicitly.


171-176: Comment mentions clearing corrupted value but doesn't implement it.

The comment on line 173 states "Clear the corrupted encrypted value from localStorage if possible" but the implementation only returns an empty string. If clearing is intended, implement it; otherwise, remove or clarify the comment.

♻️ Implement cleanup if intended

If you want to clear corrupted values:

   } catch (error) {
     console.warn('Failed to decrypt value - possibly due to key mismatch or corrupted data. Clearing stored value.', error);
-    // Clear the corrupted encrypted value from localStorage if possible
-    // Return empty string on error
+    // Note: Cannot clear from here as we don't have the key name
+    // Caller should handle cleanup if needed
     return '';
   }

Alternatively, accept a storage key parameter to enable cleanup within this function.

app/components/ForceGraph.tsx (1)

240-251: Global debug helper exposes internal graph data.

Line 246 assigns window[type] as a debug function to access graph data. While useful for development, consider:

  1. Documenting this debug interface
  2. Gating it behind a development flag
  3. Adding a comment explaining its purpose

Example:

 const handleEngineStop = useCallback(() => {
   const canvas = canvasRef.current;
   
   if (!canvas) return;
   
-  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  // Debug helper: allows inspecting graph data in browser console via window.graph() or window.schema()
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
   (window as any)[type] = () => canvas.getGraphData();
app/providers.tsx (1)

139-198: Early returns in saveSettings prevent saving other settings on encryption failure.

Lines 159 and 172 return early when encryption is unavailable or fails, preventing the update of other settings (timeout, limit, etc.). This could confuse users who expect non-secret settings to save successfully.

Consider one of these approaches:

Option 1: Save other settings even if encryption fails
       // Only encrypt and save secret key if it has changed
+      let secretKeyError = false;
       if (newSecretKey !== secretKey) {
         if (newSecretKey) {
           // Key has a value - encrypt it
           if (!isCryptoAvailable()) {
             toast({
               title: "Error",
               description: "Encryption not available in this browser. Cannot save secret key.",
               variant: "destructive",
             });
-            return; // Stop saving if crypto not available
+            secretKeyError = true;
           }
           
-          try {
+          if (!secretKeyError) {
+            try {
               const encryptedKey = await encryptValue(newSecretKey);
               localStorage.setItem("secretKey", encryptedKey);
-          } catch (error) {
+            } catch (error) {
               console.error('Failed to encrypt secret key:', error);
               toast({
                 title: "Error",
                 description: "Could not encrypt secret key. Please try again.",
                 variant: "destructive",
               });
-            return; // Stop saving if encryption fails
+              secretKeyError = true;
+            }
           }
         } else {
           // Key is empty - remove it from storage
           localStorage.removeItem("secretKey");
         }
       }
       
       // Update context
       // ... (save other settings)
-      setSecretKey(newSecretKey);
+      if (!secretKeyError) {
+        setSecretKey(newSecretKey);
+      }
Option 2: Show different toast for partial save
       if (secretKeyError) {
         toast({
           title: "Settings partially saved",
           description: "Other settings saved, but secret key could not be encrypted.",
           variant: "warning",
         });
       } else {
         toast({
           title: "Settings saved",
           description: "Your settings have been saved.",
         });
       }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75b2d6a and d1aed9e.

📒 Files selected for processing (4)
  • app/components/ForceGraph.tsx
  • app/providers.tsx
  • e2e/tests/customizeStyle.spec.ts
  • lib/encryption.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

⚙️ CodeRabbit configuration file

Review TypeScript code for type safety, proper error handling, and Next.js best practices.

Files:

  • e2e/tests/customizeStyle.spec.ts
  • lib/encryption.ts
e2e/**/*

⚙️ CodeRabbit configuration file

Review test code for proper assertions, test coverage, and maintainability.

Files:

  • e2e/tests/customizeStyle.spec.ts
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/components/ForceGraph.tsx
  • app/providers.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-13T09:41:15.717Z
Learnt from: Naseem77
Repo: FalkorDB/falkordb-browser PR: 1208
File: .github/workflows/playwright.yml:264-264
Timestamp: 2025-11-13T09:41:15.717Z
Learning: The hardcoded ENCRYPTION_KEY value "a3f5d8c2b7e4f1a0c9d6e8b2f5a7c3d1e9f2a4b8c6d3e7f1a5b9c2d8e4f0a6b3" in .github/workflows/playwright.yml at the run-token-tests job is a test-only encryption key used for CI token tests and is not a production secret.

Applied to files:

  • lib/encryption.ts
  • app/providers.tsx
🧬 Code graph analysis (2)
e2e/tests/customizeStyle.spec.ts (1)
e2e/infra/utils.ts (1)
  • CREATE_PERSON_RELATIONSHIP (16-17)
app/components/ForceGraph.tsx (3)
app/api/graph/model.ts (8)
  • Graph (339-1180)
  • GraphData (84-87)
  • Node (56-68)
  • Link (70-82)
  • Relationship (183-189)
  • Data (113-113)
  • Data (445-447)
  • Data (449-451)
lib/utils.ts (2)
  • GraphRef (27-27)
  • getTheme (337-351)
app/components/provider.ts (1)
  • IndicatorContext (275-278)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: security-scan
🔇 Additional comments (10)
e2e/tests/customizeStyle.spec.ts (5)

8-8: LGTM: Import addition aligns with test requirements.

The new import supports the expanded test coverage for relationship-specific scenarios.


180-180: LGTM: Property name updates align with canvas migration.

The changes from customCaption/customSize/customColor to caption/size/color are consistent with the @falkordb/canvas API refactoring described in the PR objectives.

Also applies to: 280-280, 320-320, 385-386, 447-447


322-329: LGTM: Enhanced color verification improves test coverage.

The addition of label button color verification complements localStorage checks, ensuring color persistence is validated both in storage and in the rendered UI. RGB conversion is correct.


159-180: No action required. The test correctly uses CREATE_PERSON_RELATIONSHIP because the test validates caption selection with the "occupation" property, which only exists in this richer query definition. CREATE_QUERY lacks this property, so it's intentionally not used here. The selective use across tests is appropriate and justified.


361-361: Verify "ID" is explicitly available as a selectable caption property.

The test selects "ID" as a caption, but this property is not explicitly defined in the test data (CREATE_PERSON_RELATIONSHIP defines: name, age, gender, occupation). According to FalkorDB documentation, node identifiers must be explicit properties to be selectable as captions—internal node IDs alone are not sufficient. Confirm that "ID" is defined in the test data setup or available through another mechanism before this test runs.

lib/encryption.ts (1)

1-194: LGTM - Solid encryption implementation with Web Crypto API.

The encryption utilities follow security best practices:

  • AES-GCM with 256-bit keys
  • Proper random IV generation (12 bytes)
  • Key persistence via localStorage
  • Crypto availability detection

Error handling returns empty strings which is acceptable for preventing unencrypted data storage, though explicit error propagation could improve caller experience.

app/components/ForceGraph.tsx (3)

298-312: Verify data synchronization pattern with disabled deps warning.

The effect on line 298-312 disables the exhaustive-deps warning. The pattern appears intentional:

  1. External code sets graphData to trigger a full data load
  2. Effect detects graphData, applies it, then clears it (line 305)
  3. Otherwise, it converts and applies data

However, excluding graphData from dependencies means changes to graphData while the effect is inactive won't trigger updates. Verify this matches the intended data flow.

If this pattern is correct, add a comment explaining why graphData is intentionally excluded:

   // Update canvas data
   useEffect(() => {
     const canvas = canvasRef.current;
     
     if (!canvas || !canvasLoaded) return;
     
+    // Note: graphData is intentionally excluded from deps to prevent loops.
+    // External code sets graphData to trigger reload, effect clears it after use.
     if (graphData) {
       canvas.setGraphData(graphData);
       setGraphData(undefined);
     } else {
       const canvasData = convertToCanvasData(data);
       canvas.setData(canvasData);
     }
     
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [canvasRef, data, setGraphData, canvasLoaded]);

314-317: Ensure TypeScript declaration for custom web component.

The <falkordb-canvas /> element is a custom web component. Verify that TypeScript has proper declarations to avoid type errors. If not already present, add to a .d.ts file:

declare namespace JSX {
  interface IntrinsicElements {
    'falkordb-canvas': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
  }
}

Check if @falkordb/canvas package exports these declarations, or if they need to be added locally.


1-317: LGTM - Well-structured canvas migration with proper async handling.

The refactor successfully replaces ForceGraph2D with @falkordb/canvas:

  • Clean data transformation via convertToCanvasData
  • Proper async loading with canvasLoaded guard
  • Event handlers use stable callbacks
  • State synchronization via focused useEffect hooks

The dynamic import pattern and viewport persistence are correctly implemented.

app/providers.tsx (1)

1-752: LGTM - Solid encryption integration with backward-compatible migration.

The provider updates successfully integrate encryption utilities:

  • Secret keys are encrypted using AES-GCM before localStorage persistence
  • Existing plain text keys are migrated to encrypted format
  • Crypto availability is checked before operations
  • ForceGraphContext properly wires canvas state

The initialization flow handles three cases correctly: no crypto available, already encrypted, and plain text migration. Error handling is present throughout with appropriate user feedback via toasts.

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: 4

Caution

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

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

147-161: Remove test.only() before merging — this will skip all other tests.

test.only() restricts Playwright to run only these two tests, causing the entire remaining test suite to be skipped in CI. This appears to be leftover debugging instrumentation.

🐛 Proposed fix
-  test.only(`@readwrite validate that deleting graph relation doesn't decreases node count`, async () => {
+  test(`@readwrite validate that deleting graph relation doesn't decreases node count`, async () => {
-  test.only(`@readwrite validate that deleting graph connected node decreases relation count by one`, async () => {
+  test(`@readwrite validate that deleting graph connected node decreases relation count by one`, async () => {
🤖 Fix all issues with AI agents
In @app/components/ForceGraph.tsx:
- Around line 306-311: Remove the debug console.log call in the else block of
ForceGraph.tsx: after calling convertToCanvasData(data) and assigning to
canvasData, delete the console.log(canvasData) statement so only
canvas.setData(canvasData) runs (or replace with a conditional debug logger if
you prefer environment-gated logging); the relevant symbols to locate are
convertToCanvasData, canvasData, and canvas.setData.
- Around line 214-219: The selection check has an operator precedence bug: e.id
=== fullElement.id only applies to the second branch due to && binding tighter
than ||; update the conditional inside the ctrlKey branch (the logic that
inspects selectedElements, e, and fullElement) so the id equality is checked for
both cases by grouping the type-matching OR into parentheses and then ANDing
with e.id === fullElement.id (i.e. (( "source" in e && "source" in fullElement)
|| (!("source" in e) && !("source" in fullElement))) && e.id ===
fullElement.id), then keep the existing setSelectedElements toggling behavior.

In @app/providers.tsx:
- Line 211: The useMemo dependency array contains a duplicated entry
`navigateToSettings`; remove the redundant `navigateToSettings` from the
dependency array in the useMemo call (the array that currently lists
displayChat, navigateToSettings, contentPersistence, ..., showMemoryUsage) so
each dependency appears only once, leaving a unique list of dependencies for the
memoization.

In @package.json:
- Line 16: The package.json dependency for "@falkordb/canvas" points to a
non-existent published version "^0.0.24"; change the dependency entry for
"@falkordb/canvas" to use the published version "^0.0.23" (or an exact "0.0.23"
if you want to avoid caret ranges) so installs succeed and consider locking to
0.0.23 due to pre-1.0 instability.
🧹 Nitpick comments (4)
e2e/tests/queryHistory.spec.ts (1)

58-63: Consider using the query variable in the assertion.

Line 63 duplicates the query string literal. Using the variable improves maintainability:

-        expect(await graph.getQueryHistoryEditorContent(query)).toBe("CREATE (n:Person { name: 'Alice' }) RETURN n");
+        expect(await graph.getQueryHistoryEditorContent(query)).toBe(query);
app/components/ForceGraph.tsx (1)

244-247: Debug helper exposed on window object.

This exposes graph data retrieval via window[type](). Consider guarding this behind a development environment check or removing it before production.

♻️ Optional: Guard with environment check
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        (window as any)[type] = () => canvas.getGraphData();
+        if (process.env.NODE_ENV === 'development') {
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
+            (window as any)[type] = () => canvas.getGraphData();
+        }
app/api/graph/model.ts (2)

1038-1045: Minor: Trailing whitespace and empty lines.

Lines 1038 and 1044-1045 contain trailing whitespace or redundant empty lines.

♻️ Cleanup
       category.elements = category.elements.filter(
         (element) => element.id !== selectedElement.id
       );
-
       if (category.elements.length === 0) {
         this.Labels.splice(
           this.Labels.findIndex((c) => c.name === category.name),
           1
         );
-        
         this.LabelsMap.delete(category.name);
       }

1112-1121: Minor: Redundant empty lines.

Multiple consecutive empty lines reduce code density unnecessarily.

♻️ Cleanup
       selectedElement.labels.splice(emptyCategoryIndex, 1);
-
-
-
       const emptyCategory = this.labelsMap.get("");
-
       if (emptyCategory) {
         emptyCategory.elements = emptyCategory.elements.filter(
           (e) => e.id !== selectedElement.id
         );
-
         if (emptyCategory.elements.length === 0) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1aed9e and b3d36dd.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • app/api/graph/model.ts
  • app/components/ForceGraph.tsx
  • app/providers.tsx
  • e2e/tests/canvas.spec.ts
  • e2e/tests/graph.spec.ts
  • e2e/tests/queryHistory.spec.ts
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/tests/canvas.spec.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/providers.tsx
  • app/components/ForceGraph.tsx
**/*.ts

⚙️ CodeRabbit configuration file

Review TypeScript code for type safety, proper error handling, and Next.js best practices.

Files:

  • e2e/tests/queryHistory.spec.ts
  • e2e/tests/graph.spec.ts
  • app/api/graph/model.ts
e2e/**/*

⚙️ CodeRabbit configuration file

Review test code for proper assertions, test coverage, and maintainability.

Files:

  • e2e/tests/queryHistory.spec.ts
  • e2e/tests/graph.spec.ts
**/api/**/*

⚙️ CodeRabbit configuration file

Review API routes for security, error handling, and proper HTTP status codes.

Files:

  • app/api/graph/model.ts
🧠 Learnings (1)
📚 Learning: 2025-11-13T09:41:15.717Z
Learnt from: Naseem77
Repo: FalkorDB/falkordb-browser PR: 1208
File: .github/workflows/playwright.yml:264-264
Timestamp: 2025-11-13T09:41:15.717Z
Learning: The hardcoded ENCRYPTION_KEY value "a3f5d8c2b7e4f1a0c9d6e8b2f5a7c3d1e9f2a4b8c6d3e7f1a5b9c2d8e4f0a6b3" in .github/workflows/playwright.yml at the run-token-tests job is a test-only encryption key used for CI token tests and is not a production secret.

Applied to files:

  • app/providers.tsx
🧬 Code graph analysis (1)
e2e/tests/queryHistory.spec.ts (2)
e2e/infra/utils.ts (1)
  • getRandomString (198-202)
e2e/logic/POM/queryHistoryComponent.ts (1)
  • QueryHistory (5-83)
🔇 Additional comments (15)
e2e/tests/graph.spec.ts (1)

84-84: Minor formatting normalization — LGTM.

Semicolon addition aligns with the codebase style.

e2e/tests/queryHistory.spec.ts (3)

5-5: Formatting consistency improvements look good.

Semicolon additions align with the codebase style.

Also applies to: 16-16, 20-20, 67-67


28-33: Good assertion addition.

Adding getQueryHistory(query) assertion properly validates the core functionality being tested.


46-49: Verify tooltip now displays node ID instead of node data.

The assertion expects "0" (likely the internal node ID) rather than node-related content like "Alice". This appears to be a behavioral change from the @falkordb/canvas migration.

While this should be stable for a fresh graph, hardcoding "0" is fragile if:

  • Node ID assignment changes
  • The graph has pre-existing data

Consider adding a comment explaining this is the expected node ID, or verify this is the intended tooltip behavior with the new canvas library.

app/components/ForceGraph.tsx (2)

82-87: LGTM!

Dynamic import pattern for the web component is appropriate for client-side only loading.


316-318: LGTM!

Clean JSX rendering of the falkordb-canvas web component with proper ref binding.

app/providers.tsx (4)

148-178: LGTM!

Solid encryption handling with proper availability checks, error handling, and user feedback via toast notifications.


447-478: LGTM!

Good migration path for existing plain text keys to encrypted format, with proper fallback handling for corrupted or unavailable encryption.


21-23: LGTM!

Dynamic import with SSR disabled is appropriate for the GraphInfoPanel component that depends on browser APIs.


233-241: Dependency array incomplete in forceGraphContext memo.

The memoized object includes setViewport and setGraphData, but the dependency array is missing these state setters. Update the dependency array to [canvasRef, viewport, setViewport, data, setData, graphData, setGraphData] to prevent stale closures.

Likely an incorrect or invalid review comment.

app/api/graph/model.ts (4)

56-82: LGTM!

Clean migration of Node and Link types from wrapper-based to plain objects with numeric IDs. The simplified structure aligns well with the canvas-based rendering requirements.


155-162: LGTM!

The LabelStyle interface properly extends LinkStyle with optional size and caption properties, maintaining type safety for the style-driven approach.


781-782: LGTM!

Rebuilding the maps after extend ensures data consistency between the arrays and lookup maps.


866-888: LGTM!

The visibleLinks method correctly uses nodesMap lookups with optional chaining for safe access when checking node visibility.

package.json (1)

15-16: Remove the suspicious "-" dependency.

The "-" package at line 15 appears to be a typo or placeholder. A package name consisting of a single hyphen is not a standard npm package name and is highly suspicious. It should be removed.

🔧 Suggested fix
   "dependencies": {
-    "-": "^0.0.1",
     "@falkordb/canvas": "^0.0.24",

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: 6

Caution

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

⚠️ Outside diff range comments (2)
app/graph/Chat.tsx (2)

137-216: Wrap switch case declarations in blocks.

Variables declared in switch cases without blocks can be accessed from other cases, leading to potential bugs. Biome correctly flags message (line 139) and statusCode (line 197).

♻️ Proposed fix
                     switch (eventType) {
-                        case "Status":
-                            const message = {
+                        case "Status": {
+                            const message = {
                                 role: "assistant" as const,
                                 content: eventData.trim(),
                                 type: eventType
                             };
 
                             setMessages(prev => [...prev, message]);
                             break;
+                        }
 
                         case "CypherQuery":
                             // ... unchanged ...

-                        case "error":
-                            const statusCode = Number(line.split("status:")[1].split(" ")[0]);
+                        case "error": {
+                            const statusCode = Number(line.split("status:")[1].split(" ")[0]);
 
                             if (statusCode === 401 || statusCode >= 500) setIndicator("offline");

                             toast({
                                 title: "Error",
                                 description: eventData,
                                 variant: "destructive",
                             });

                             isResult = true;
                             break;
+                        }

236-317: Wrap switch case declarations in blocks in getMessage.

Same issue here: content (line 239) and i (line 257) need block scoping.

♻️ Proposed fix
     const getMessage = (message: Message, index?: number) => {
         switch (message.type) {
-            case "Status":
-                const content = <>
+            case "Status": {
+                const content = <>
                     {
                         messages[messages.length - 1].type === "Status" && messages[messages.length - 1] === message &&
                         <Loader2 className="animate-spin" size={15} />
                     }
                     <p className="text-sm">{message.content}</p>
                 </>;

                 return index !== undefined ? (
                     <li className="flex gap-2 items-center" key={index}>
                         {content}
                     </li>
                 ) : (
                     <div className="flex gap-2 items-center">
                         {content}
                     </div>
                 );
-            case "CypherQuery":
-                const i = messages.findIndex(m => m === message);
+            }
+            case "CypherQuery": {
+                const i = messages.findIndex(m => m === message);

                 return (
                     // ... JSX unchanged ...
                 );
+            }
             default:
                 return (
                     <p className="text-wrap whitespace-pre-wrap">{message.content}</p>
                 );
         }
     };
🤖 Fix all issues with AI agents
In @app/api/chat/route.tsx:
- Line 62: The SSE messages are malformed because multiple fields are
concatenated on one line; update each writer.write(encoder.encode(...)) call
(e.g., the error write at writer.write(encoder.encode(`event: error status:
${400} data: ${JSON.stringify(validation.error)}\n\n`)) and the similar writes
around the code paths you were pointed to) to emit proper SSE lines: write an
"event: <name>\n" line, then any other fields each on their own line (e.g.,
"status: <code>\n" if you need it), then "data: <json-string>\n\n" to terminate
the event; apply this change to the occurrences mentioned (lines where
writer.write/encoder.encode are used at the error/data sends: 62, 78, 113, 117,
121, 125, 141, 146) so the client can parse each SSE event correctly.
- Around line 150-152: The abort listener registered via
request.signal.addEventListener currently calls writer.close() unconditionally
and can throw if the writer was already closed; fix this by tracking closure
state or guarding the call: add a boolean (e.g., writerClosed) set to true
wherever writer.close() is called/when stream finishes, and in the abort handler
check if (!writerClosed) { writerClosed = true; try { writer.close(); } catch
(e) { /* ignore TypeError if already closed */ } }, or alternatively wrap
writer.close() in a try/catch and ignore the specific TypeError; also remove or
noop the abort listener when normal completion occurs to avoid racing.

In @app/components/ForceGraph.tsx:
- Around line 297-313: The effect in useEffect uses graphData (and calls
canvas.setGraphData and setGraphData) but omits graphData from the dependency
array, so add graphData to the dependency list for this effect to ensure it
re-runs when graphData changes; update the dependencies to include graphData
(i.e., include canvasRef, data, graphData, setGraphData, canvasLoaded) and
remove the eslint-disable comment so React can validate dependencies for
convertToCanvasData and the canvas update logic.

In @app/graph/Chat.tsx:
- Around line 344-351: The click handler is querying
`.status-group[key="${index}"]` but React's `key` isn't rendered to the DOM; add
a data attribute to the corresponding <li> (e.g., data-index or data-user-index)
in the element that renders the status group (the same element currently using
key) and update the selector in the onClick handler to use that data attribute
(e.g., `.status-group[data-user-index="${index}"]`); keep the existing
setMessagesList logic and only change the DOM attribute and query string.

In @app/providers.tsx:
- Line 58: canvasRef is typed incorrectly as GraphRef["current"] though useRef
should be given the ref object type; change the generic to GraphRef (or
React.RefObject<GraphRef>) when calling useRef so canvasRef has the correct
RefObject type and can be passed to children expecting GraphRef; update the
declaration that creates canvasRef (the useRef call) to use GraphRef (or import
and use React.RefObject<GraphRef>) instead of GraphRef["current"].
- Around line 447-478: The decryptValue(storedSecretKey) call can throw but is
not wrapped in try/catch; wrap the decryption branch (the
isEncrypted(storedSecretKey) path) in a try/catch, catch any error from
decryptValue, log the error via console.warn/console.error, clear state with
setSecretKey('') and remove the stored secret with
localStorage.removeItem("secretKey") (matching the existing falsy-decryption
behavior), and keep the existing null/undefined check for decryptedKey as a
fallback.
🧹 Nitpick comments (9)
app/api/chat/route.tsx (3)

23-23: Event name casing inconsistency.

EventType defines "Error" but the SSE events emitted use lowercase "error" (lines 62, 78, 141, 146). Consider aligning them for consistency and type safety.


128-128: Consider awaiting writer.close() before returning.

writer.close() returns a Promise. Not awaiting it means the Response may be returned while the close operation is still pending, which could lead to subtle timing issues.

♻️ Proposed fix
-            writer.close();
+            await writer.close();

Apply to all writer.close() calls (lines 63, 79, 128, 142, 147).

Also applies to: 142-142, 147-147


129-143: Consider differentiating error status codes.

The inner catch block always emits status 400, but errors like network failures or internal exceptions should return 500. API key errors are appropriately 400, but other errors conflate client and server issues.

app/graph/Chat.tsx (1)

1-2: Consider removing the no-case-declarations eslint-disable.

The static analysis correctly flags switch declarations that should be block-scoped (lines 139, 197, 239, 257). Rather than disabling the lint rule, wrap those declarations in blocks. This prevents accidental access across case clauses.

app/providers.tsx (2)

282-286: Unusual type annotation for ticks parameter.

The type ticks?: 0 restricts the parameter to only accept undefined or the literal 0. If this is intentional (only allowing reset to 0), consider adding a comment. Otherwise, use number | undefined.

Proposed fix if broader number type intended
-  const handleCooldown = useCallback((ticks?: 0) => {
+  const handleCooldown = useCallback((ticks?: number) => {

139-178: Encryption error handling prevents saving other settings.

When encryption fails (lines 159, 172), the function returns early, preventing other settings from being saved. Consider saving non-sensitive settings first, then attempting encryption.

Proposed restructure
    saveSettings: async () => {
      // Save settings to local storage
      localStorage.setItem("runDefaultQuery", newRunDefaultQuery.toString());
      localStorage.setItem("contentPersistence", newContentPersistence.toString());
      localStorage.setItem("timeout", newTimeout.toString());
      localStorage.setItem("defaultQuery", newDefaultQuery);
      localStorage.setItem("limit", newLimit.toString());
      localStorage.setItem("refreshInterval", newRefreshInterval.toString());
      localStorage.setItem("model", newModel);

+      // Update non-sensitive context state first
+      setContentPersistence(newContentPersistence);
+      setRunDefaultQuery(newRunDefaultQuery);
+      setDefaultQuery(newDefaultQuery);
+      setTimeout(newTimeout);
+      setLimit(newLimit);
+      setLastLimit(limit);
+      setModel(newModel);
+      setRefreshInterval(newRefreshInterval);

      // Only encrypt and save secret key if it has changed
      if (newSecretKey !== secretKey) {
        if (newSecretKey) {
          // Key has a value - encrypt it
          if (!isCryptoAvailable()) {
            toast({
              title: "Error",
              description: "Encryption not available in this browser. Cannot save secret key.",
              variant: "destructive",
            });
-            return; // Stop saving if crypto not available
+            setHasChanges(false);
+            return;
          }
          // ... rest of encryption logic
app/components/ForceGraph.tsx (3)

167-185: Double-click detection uses 1000ms threshold.

The 1000ms window for detecting double-clicks (line 175) is unusually long. Standard double-click intervals are typically 200-500ms. This may lead to unintended node expansions.

Proposed fix
-        if (now.getTime() - date.getTime() < 1000 && name === node.id) {
+        if (now.getTime() - date.getTime() < 400 && name === node.id) {

214-222: Complex nested condition is hard to follow.

The condition at line 215 combines multiple checks in a single expression. Consider extracting to a helper function for readability.

Proposed refactor
+    const isSameElementType = (a: Node | Link, b: Node | Link) =>
+        ('source' in a && 'source' in b) || (!('source' in a) && !('source' in b));

     const handleRightClick = useCallback((element: GraphNode | GraphLink, evt: MouseEvent) => {
         // Find the full element from the graph
         let fullElement: Node | Link | undefined;
         if ('source' in element) {
             fullElement = graph.LinksMap.get(element.id);
         } else {
             fullElement = graph.NodesMap.get(element.id);
         }

         if (!fullElement) return;

         if (evt.ctrlKey) {
-            if (selectedElements.find(e => (("source" in e && "source" in fullElement) || (!("source" in e) && !("source" in fullElement))) && e.id === fullElement.id)) {
+            const isAlreadySelected = selectedElements.find(e => 
+                isSameElementType(e, fullElement) && e.id === fullElement.id
+            );
+            if (isAlreadySelected) {
                 setSelectedElements(selectedElements.filter((el) => el !== fullElement));
             } else {
                 setSelectedElements([...selectedElements, fullElement]);
             }

244-246: Debug code assigns to global window object.

Assigning canvas.getGraphData() to window[type] appears to be debug code. Consider removing or guarding with a development-only check.

Proposed fix
-        // eslint-disable-next-line @typescript-eslint/no-explicit-any
-        (window as any)[type] = () => canvas.getGraphData();
+        if (process.env.NODE_ENV === 'development') {
+            // eslint-disable-next-line @typescript-eslint/no-explicit-any
+            (window as any)[type] = () => canvas.getGraphData();
+        }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3d36dd and 8730986.

📒 Files selected for processing (7)
  • .github/workflows/playwright.yml
  • app/api/chat/route.tsx
  • app/components/ForceGraph.tsx
  • app/components/Header.tsx
  • app/graph/Chat.tsx
  • app/providers.tsx
  • app/settings/browserSettings.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/components/Header.tsx
  • app/settings/browserSettings.tsx
  • .github/workflows/playwright.yml
🧰 Additional context used
📓 Path-based instructions (2)
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/providers.tsx
  • app/components/ForceGraph.tsx
  • app/api/chat/route.tsx
  • app/graph/Chat.tsx
**/api/**/*

⚙️ CodeRabbit configuration file

Review API routes for security, error handling, and proper HTTP status codes.

Files:

  • app/api/chat/route.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-13T09:41:15.717Z
Learnt from: Naseem77
Repo: FalkorDB/falkordb-browser PR: 1208
File: .github/workflows/playwright.yml:264-264
Timestamp: 2025-11-13T09:41:15.717Z
Learning: The hardcoded ENCRYPTION_KEY value "a3f5d8c2b7e4f1a0c9d6e8b2f5a7c3d1e9f2a4b8c6d3e7f1a5b9c2d8e4f0a6b3" in .github/workflows/playwright.yml at the run-token-tests job is a test-only encryption key used for CI token tests and is not a production secret.

Applied to files:

  • app/providers.tsx
🧬 Code graph analysis (3)
app/components/ForceGraph.tsx (2)
app/api/graph/model.ts (8)
  • Graph (339-1182)
  • GraphData (84-87)
  • Node (56-68)
  • Link (70-82)
  • Relationship (183-189)
  • Data (113-113)
  • Data (445-447)
  • Data (449-451)
lib/utils.ts (2)
  • GraphRef (27-27)
  • getTheme (337-351)
app/api/chat/route.tsx (2)
app/api/auth/[...nextauth]/options.ts (2)
  • session (382-401)
  • getClient (405-483)
app/api/validate-body.ts (2)
  • validateBody (294-310)
  • chatRequest (201-226)
app/graph/Chat.tsx (3)
app/components/provider.ts (2)
  • IndicatorContext (275-278)
  • BrowserSettingsContext (156-222)
lib/utils.ts (2)
  • Message (63-74)
  • cn (84-86)
app/api/chat/route.tsx (1)
  • EventType (23-23)
🪛 Biome (2.1.2)
app/graph/Chat.tsx

[error] 139-143: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 197-197: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 239-245: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 257-257: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Run cluster tests
  • GitHub Check: Run tests part 3 (except settings)
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: Run settings tests
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run token tests
  • GitHub Check: Run TLS tests
  • GitHub Check: security-scan
🔇 Additional comments (10)
app/api/chat/route.tsx (2)

6-21: LGTM!

GET handler correctly validates authentication and returns appropriate responses.


28-40: LGTM!

Proper URL encoding of credentials prevents injection issues and handles special characters correctly.

app/graph/Chat.tsx (1)

31-58: LGTM!

The status message grouping logic and scroll behavior are implemented correctly.

app/providers.tsx (2)

233-241: LGTM!

The forceGraphContext memoization correctly includes all dependencies and properly exposes canvasRef, graphData, and setGraphData for the new canvas-based architecture.


679-733: LGTM!

The provider hierarchy is correctly wired with ForceGraphContext.Provider replacing the previous viewport context, maintaining proper nesting order.

app/components/ForceGraph.tsx (5)

96-109: Potential stale closure in cleanup effect.

The cleanup function captures canvas from the effect body, but setViewport and setGraphData in the dependency array may cause the effect to re-run, making canvas reference potentially outdated. The current pattern is acceptable since you're re-capturing canvas from canvasRef.current on each run.


82-87: LGTM!

Dynamic import ensures the web component is only loaded client-side, preventing SSR issues. The canvasLoaded state correctly gates subsequent canvas operations.


280-295: LGTM!

The config setup effect properly updates all event handlers and selection callbacks, with correct dependency tracking.


315-317: No action needed—@falkordb/canvas provides proper TypeScript declarations.

The package @falkordb/canvas (v0.0.24) is installed and provides TypeScript type definitions. The component correctly imports types from the package (GraphRef, Data, GraphNode, etc.) and the canvasRef is properly typed. The <falkordb-canvas> web component has the necessary JSX intrinsic element declarations for TypeScript.


33-50: Verify canvas API support for node/link style properties.

convertToCanvasData omits expand, collapsed, size, and caption from nodes and links. These properties are actively used throughout the app (style customization, node expansion, persistence). Confirm whether the @falkordb/canvas Data type supports these fields. If not, add a comment explaining the intentional omission.

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: 0

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

317-320: Consider using a stricter assertion once tooltip behavior stabilizes.

The disjunctive check tooltip.includes("Bob") || tooltip.includes("1") accommodates both the old (name-based) and new (ID-based) tooltip formats during the canvas migration. However, this permissiveness could mask regressions if the expected behavior changes unexpectedly.

Once the migration is complete and tooltip behavior is deterministic, consider tightening this assertion to expect only the correct value. If both are valid outputs depending on configuration, add a comment explaining when each case applies.

♻️ Suggested improvement (post-migration)
-      // Get the tooltip content and verify it contains "Bob" or "1" (node ID)
-      const tooltip = await chat.getNodeCanvasToolTip();
-      expect(tooltip).toBeDefined();
-      expect(tooltip && (tooltip.includes("Bob") || tooltip.includes("1"))).toBeTruthy();
+      // Get the tooltip content and verify it contains the expected node identifier
+      const tooltip = await chat.getNodeCanvasToolTip();
+      expect(tooltip).toBeDefined();
+      // TODO: Once canvas migration is complete, assert the specific expected value
+      expect(tooltip).toContain("1"); // Node ID in new canvas rendering
app/graph/Chat.tsx (8)

1-2: ESLint disable comments don't suppress external static analysis.

The /* eslint-disable no-case-declarations */ pragma only affects ESLint. Biome (the static analysis tool) still flags switch declaration issues at multiple locations. Consider wrapping case block variables in braces to satisfy both tools and improve code clarity.


138-146: Wrap case declarations in block scope.

The message constant is declared without block scope, which Biome flags as noSwitchDeclarations. This can lead to accidental access from other case clauses.

♻️ Proposed fix
                         case "Status":
+                        {
                             const message = {
                                 role: "assistant" as const,
                                 content: eventData.trim(),
                                 type: eventType
                             };

                             setMessages(prev => [...prev, message]);
-                            break;
+                            break;
+                        }

196-208: Wrap case declarations in block scope.

The statusCode constant at line 197 needs block scope to prevent accidental access from other cases.

♻️ Proposed fix
                         case "error":
+                        {
                             const statusCode = Number(line.split("status:")[1].split(" ")[0]);

                             if (statusCode === 401 || statusCode >= 500) setIndicator("offline");

                             toast({
                                 title: "Error",
                                 description: eventData,
                                 variant: "destructive",
                             });

                             isResult = true;
-                            break;
+                            break;
+                        }

106-126: Consider adding AbortController for fetch cleanup.

If the component unmounts during an active stream (e.g., user closes the chat panel), processStream will continue attempting state updates on an unmounted component. This can cause memory leaks and React warnings.

♻️ Suggested approach
// In component body, create ref for AbortController
const abortControllerRef = useRef<AbortController | null>(null);

// In handleSubmit, before fetch:
abortControllerRef.current = new AbortController();

const response = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
    signal: abortControllerRef.current.signal
});

// Add useEffect cleanup:
useEffect(() => {
    return () => {
        abortControllerRef.current?.abort();
    };
}, []);

238-255: Wrap case declarations in block scope.

The content constant in case "Status" needs block scope per static analysis.

♻️ Proposed fix
             case "Status":
+            {
                 const content = <>
                     {
                         messages[messages.length - 1].type === "Status" && messages[messages.length - 1] === message &&
                         <Loader2 className="animate-spin" size={15} />
                     }
                     <p className="text-sm">{message.content}</p>
                 </>;

                 return index !== undefined ? (
                     <li className="flex gap-2 items-center" key={index}>
                         {content}
                     </li>
                 ) : (
                     <div className="flex gap-2 items-center">
                         {content}
                     </div>
                 );
+            }

256-311: Wrap case declarations in block scope.

The i constant in case "CypherQuery" needs block scope.

♻️ Proposed fix
             case "CypherQuery":
+            {
                 const i = messages.findIndex(m => m === message);

                 return (
                     // ... rest of JSX
                 );
+            }

400-417: Consider adding aria-label for accessibility.

The input field relies solely on a placeholder for context. Screen reader users would benefit from an explicit label.

♻️ Optional improvement
                     <Input
                         data-testid="chatInput"
                         className="w-1 grow bg-transparent border-none text-foreground text-lg SofiaSans"
                         placeholder="Type your message here..."
+                        aria-label="Chat message"
                         value={newMessage}
                         onChange={(e) => setNewMessage(e.target.value)}
                     />

53-58: Consider using useRef instead of querySelector.

Direct DOM queries work but are less idiomatic in React. A ref would be more reliable if the class name changes.

♻️ Optional improvement
const chatContainerRef = useRef<HTMLUListElement>(null);

const scrollToBottom = () => {
    if (chatContainerRef.current) {
        chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
    }
};

// In JSX:
<ul ref={chatContainerRef} className="... chat-container">
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8730986 and 602d9b6.

📒 Files selected for processing (3)
  • app/graph/Chat.tsx
  • e2e/tests/chat.spec.ts
  • e2e/tests/graph.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • e2e/tests/graph.spec.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

⚙️ CodeRabbit configuration file

Review TypeScript code for type safety, proper error handling, and Next.js best practices.

Files:

  • e2e/tests/chat.spec.ts
e2e/**/*

⚙️ CodeRabbit configuration file

Review test code for proper assertions, test coverage, and maintainability.

Files:

  • e2e/tests/chat.spec.ts
**/*.tsx

⚙️ CodeRabbit configuration file

Review React components for proper hooks usage, accessibility, and performance.

Files:

  • app/graph/Chat.tsx
🧬 Code graph analysis (1)
app/graph/Chat.tsx (3)
app/components/provider.ts (4)
  • IndicatorContext (275-278)
  • GraphContext (224-245)
  • QueryLoadingContext (285-288)
  • BrowserSettingsContext (156-222)
lib/utils.ts (2)
  • Message (63-74)
  • cn (84-86)
app/api/chat/route.tsx (1)
  • EventType (23-23)
🪛 Biome (2.1.2)
app/graph/Chat.tsx

[error] 139-143: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 197-197: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 239-245: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 257-257: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Run settings tests
  • GitHub Check: Run cluster tests
  • GitHub Check: Run token tests
  • GitHub Check: Run tests part 2 (except settings)
  • GitHub Check: Run tests part 3 (except settings)
  • GitHub Check: Run TLS tests
  • GitHub Check: Run tests part 1 (except settings)
  • GitHub Check: security-scan
🔇 Additional comments (2)
app/graph/Chat.tsx (2)

31-51: LGTM!

The message grouping logic correctly consolidates consecutive Status messages. The effect has proper dependencies.


160-180: LGTM!

Good defensive parsing with graceful fallback to an error message when JSON parsing fails.

@Anchel123 Anchel123 requested review from Naseem77 and barakb and removed request for Naseem77 January 12, 2026 12:28
@Anchel123 Anchel123 linked an issue Jan 12, 2026 that may be closed by this pull request
4 tasks
@Anchel123 Anchel123 changed the title Falkordb canvas Fix #1270 Falkordb canvas Jan 12, 2026
@Anchel123 Anchel123 merged commit 18d7f92 into staging Jan 12, 2026
17 checks passed
@Anchel123 Anchel123 deleted the falkordb-canvas branch January 12, 2026 13:45
@coderabbitai coderabbitai bot mentioned this pull request Jan 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Browser] Extract main canvas as web component

5 participants