Skip to content

GENIE-1218/story/Implement graph view using a separate library#78

Merged
MayaCrmi merged 47 commits intomainfrom
graph-display-new-library
Feb 18, 2026
Merged

GENIE-1218/story/Implement graph view using a separate library#78
MayaCrmi merged 47 commits intomainfrom
graph-display-new-library

Conversation

@MayaCrmi
Copy link
Collaborator

@MayaCrmi MayaCrmi commented Feb 9, 2026

Replace ReactFlow with JointJS for workflow graph display

Summary

This PR replaces the ReactFlowGraph component with a new GraphDisplay component, using a different library (JointJS) for displaying the graph than the one we use for building it. It also introduces a centralized theme color system (deriveThemeColors) so all graph-related components derive their colors from a single primary hex instead of using hardcoded emerald/orange/purple values.

Why

The issue of the edge intersections is one we have been trying to fix for some time now. Previously I tried implementing a lot of algorithmic logic to make it work as we want it to using the current library, but after discussions we've decided to try and use a new library for our case.

What changed

Under the graph creation nothing major changed besides coloring - both for the building blocks, including conditions, and the edge color.
The new graph display look:

image image

Core replacement

File Change
graphs/ReactFlowGraph.tsx Deleted (1,099 lines)
graphs/GraphDisplay.tsx New — JointJS-based graph renderer (647 lines)
graphs/GraphDisplayHelpers.ts New — extracted constants, interfaces, and pure helper functions (281 lines)
utils/graphFlowLayout.ts New — converts GraphFlow YAML spec to layout-friendly LayoutNode[] + LayoutEdge[] (252 lines)
Theme color system
File Change
lib/colorUtils.ts Added deriveThemeColors() — derives primary, primaryLight, primaryDark, conditionAccent, conditionEdge, etc. from a single hex
graphs/GraphCanvas.tsx Replaced hardcoded #8A2BE2 / #10B981 edge colors with theme-derived values
graphs/BidirectionalOffsetEdge.tsx Replaced hardcoded emerald colors with deriveThemeColors
graphs/CustomNode.tsx Replaced hardcoded bg-orange-* condition styles with theme-derived colors
hooks/use-graph-logic.ts Replaced hardcoded #10b981 conditional edge color with conditionEdgeColor
workspace/BuildingBlocksSidebar.tsx Replaced hardcoded orange/emerald with theme-derived colors

Other changes

  • shared/WorkflowStatusBanner.tsx — Split validationFailed into validationFailed (owner-facing) and validationFailedShared (shared-chat-facing) with different messages

  • package.json — Added @joint/core and @joint/layout-directed-graph dependencies

Cleanup (refactoring pass)

  • Removed unused showControls prop from GraphDisplayProps and all consumers (JointJS has no built-in controls like ReactFlow did)

  • Removed unused iconSize variable in badge rendering

  • Removed openElementDetails from the useEffect dependency array (it was never called inside the effect, causing unnecessary full graph rebuilds on callback reference changes)

  • Removed dead nodeSuccessors variable from graphFlowLayout.ts (populated but never read)

  • Extracted GraphDisplayHelpers.ts — all layout constants, overlay interfaces, SVG DOM manipulation (injectSvgDefs, injectLinkAnimations), element block map builder, and category mapping into a dedicated helpers file

Summary by CodeRabbit

  • New Features

    • New read-only GraphDisplay viewer with zoom/fit controls, live per-node status, validation & resource details, interactive badges, and consistent theming across graphs.
    • Sessions now show blueprint display names; sharing UI and toast behavior updated.
  • Bug Fixes

    • Improved caching, cancellation and race‑condition guards to prevent stale blueprint/session data.
    • Start-chat now disabled when sharing is disabled, blueprint invalid, or validation is in progress.
  • Documentation

    • Clarified architecture: edit-mode vs read-only graph rendering strategies.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the ReactFlow-based editor with a JointJS-driven GraphDisplay and useJointGraph hook, centralizes resolved-blueprint fetching and caching with cancellation guards, adds theme-derived color utilities and widespread theming, removes ReactFlowGraph, and updates consumers and UI overlays to integrate the new graph system.

Changes

Cohort / File(s) Summary
GraphDisplay & Helpers
ui/client/src/components/agentic-ai/graphs/GraphDisplay.tsx, ui/client/src/components/agentic-ai/graphs/GraphDisplayHelpers.ts
Adds JointJS-based GraphDisplay and helpers: overlays, zoom controls, live node status, SVG defs, link animations, resource/validation modals, layout constants, and exported overlay/node helper APIs.
JointJS Hook & Layout Converter
ui/client/src/hooks/use-joint-graph.ts, ui/client/src/utils/graphFlowLayout.ts
Introduces useJointGraph hook to manage JointJS lifecycle, sizing, overlays, interactivity, and layout; adds graphFlowToLayoutData and layout types to convert GraphFlow specs to layout-ready structures.
ReactFlow Removal & Graph Canvas
ui/client/src/components/agentic-ai/graphs/ReactFlowGraph.tsx, ui/client/src/components/agentic-ai/graphs/GraphCanvas.tsx
Removes the ReactFlowGraph component entirely; GraphCanvas updated for theme-aware edge coloring and to interoperate within the new dual-graph approach.
Integration & Consumers
ui/client/src/components/agentic-ai/ExecutionTab.tsx, ui/client/src/components/agentic-ai/WorkflowsPanel.tsx, ui/client/src/components/agentic-ai/AgentFlowGraph.tsx, ui/client/src/pages/AgenticOverview.tsx, ui/client/src/pages/AgenticChats.tsx, ui/client/src/pages/AgenticWorkflows.tsx
Replaces ReactFlowGraph with GraphDisplay, removes ReactFlowProvider usage, switches to fetchResolvedBlueprint(s), adds caching and request-id cancellation guards, updates props and rendering flows.
Node Overlays & Graph UI
ui/client/src/components/agentic-ai/graphs/AgentNodeOverlay.tsx, ui/client/src/components/agentic-ai/graphs/InnerRefElement.tsx, ui/client/src/components/agentic-ai/graphs/NodeValidationIndicator.tsx, ui/client/src/components/agentic-ai/graphs/BidirectionalOffsetEdge.tsx, ui/client/src/components/agentic-ai/graphs/CustomNode.tsx
Adds AgentNodeOverlay and NodeStatus type; theming applied to nodes/edges; validation indicator behavior/props changed; per-edge unique marker IDs and accessibility/layout tweaks added.
Theming & Color Utilities
ui/client/src/lib/colorUtils.ts, ui/client/src/workspace/BuildingBlocksSidebar.tsx, ui/client/src/components/agentic-ai/graphs/GraphCanvas.tsx
Adds deriveThemeColors and DerivedThemeColors API; replaces hard-coded colors with theme-derived palettes across UI and graph rendering.
Hooks & Validation Flow
ui/client/src/hooks/use-blueprint-validation.ts, ui/client/src/hooks/use-agentic-data.ts, ui/client/src/hooks/use-graph-logic.ts
Adds stale-request cancellation to blueprint validation, switches agentic data fetch to fetchResolvedBlueprints, and applies theme-based colors in graph logic.
Streaming & Context Exposure
ui/client/src/components/agentic-ai/StreamingDataContext.tsx, ui/client/src/components/agentic-ai/graphs/GraphDisplay.tsx
Exports StreamingDataContext and connects GraphDisplay to streaming context for live per-node status overlays.
CSS, Packages & Build
ui/client/src/index.css, ui/package.json, ui/deployment/Dockerfile
Adds workflow graph CSS and animation rules, adds JointJS dependencies (@joint/core, @joint/layout-directed-graph), and changes pnpm install to --no-frozen-lockfile.
Types, UX & Banners
ui/client/src/types/session.ts, ui/client/src/components/shared/WorkflowStatusBanner.tsx, ui/client/src/components/agentic-ai/ShareWorkflow.tsx, ui/client/src/components/agentic-ai/chat/PublicChat.tsx
Adds optional blueprintName to ChatSession, new validationFailed banner entry, minor toast variant change, and new start-chat guards tied to sharing/validation state.
Docs & Misc
ui/ARCHITECTURE.md, ui/client/src/api/blueprints.ts, ui/client/src/components/dashboard/WorkflowCard.tsx
Docs updated to document dual-graph strategy (ReactFlow for edit, JointJS for read-only); minor API ordering/formatting and small UI non-functional edits.

Sequence Diagram(s)

sequenceDiagram
    participant Component as Agentic Component
    participant GraphDisplay as GraphDisplay
    participant useJointGraph as useJointGraph Hook
    participant JointJS as JointJS Engine
    participant Streaming as StreamingDataContext
    participant ValidationModal as ValidationResultModal

    Component->>GraphDisplay: render(blueprintId, specDict, props)
    GraphDisplay->>useJointGraph: init(options)
    useJointGraph->>JointJS: create Graph & Paper, inject SVG defs
    useJointGraph->>useJointGraph: graphFlowToLayoutData(specDict)
    JointJS->>JointJS: layout nodes/edges, apply markers/animations
    useJointGraph->>GraphDisplay: expose overlays, paperTransform
    GraphDisplay->>Streaming: subscribe node status (if live)
    Streaming-->>GraphDisplay: node status updates
    GraphDisplay->>GraphDisplay: update overlays & visuals
    Component->>GraphDisplay: user interacts (click badge/validation)
    GraphDisplay->>ValidationModal: open with node validation details
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • odaiodeh
  • sfiresht
  • yhabushi79

Poem

🐰
I hopped from Flow to JointJS glade,
Swapped nodes for badges in my shade,
Colors spun from one bright hex,
I cached the blueprints, dodged the specs,
A happy rabbit — overlays displayed!

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (30 files):

⚔️ ci/pipeline-deploy.groovy (content)
⚔️ helm/values/multiagent-resource-values.yaml (content)
⚔️ ui/ARCHITECTURE.md (content)
⚔️ ui/client/src/api/blueprints.ts (content)
⚔️ ui/client/src/components/agentic-ai/AgentFlowGraph.tsx (content)
⚔️ ui/client/src/components/agentic-ai/ExecutionTab.tsx (content)
⚔️ ui/client/src/components/agentic-ai/ShareWorkflow.tsx (content)
⚔️ ui/client/src/components/agentic-ai/StreamingDataContext.tsx (content)
⚔️ ui/client/src/components/agentic-ai/WorkflowsPanel.tsx (content)
⚔️ ui/client/src/components/agentic-ai/chat/PublicChat.tsx (content)
⚔️ ui/client/src/components/agentic-ai/graphs/BidirectionalOffsetEdge.tsx (content)
⚔️ ui/client/src/components/agentic-ai/graphs/CustomNode.tsx (content)
⚔️ ui/client/src/components/agentic-ai/graphs/GraphCanvas.tsx (content)
⚔️ ui/client/src/components/agentic-ai/graphs/InnerRefElement.tsx (content)
⚔️ ui/client/src/components/agentic-ai/graphs/NodeValidationIndicator.tsx (content)
⚔️ ui/client/src/components/agentic-ai/workspace/ValidationResultModal.tsx (content)
⚔️ ui/client/src/components/dashboard/WorkflowCard.tsx (content)
⚔️ ui/client/src/components/shared/WorkflowStatusBanner.tsx (content)
⚔️ ui/client/src/hooks/use-agentic-data.ts (content)
⚔️ ui/client/src/hooks/use-blueprint-validation.ts (content)
⚔️ ui/client/src/hooks/use-graph-logic.ts (content)
⚔️ ui/client/src/index.css (content)
⚔️ ui/client/src/lib/colorUtils.ts (content)
⚔️ ui/client/src/pages/AgenticChats.tsx (content)
⚔️ ui/client/src/pages/AgenticOverview.tsx (content)
⚔️ ui/client/src/pages/AgenticWorkflows.tsx (content)
⚔️ ui/client/src/types/session.ts (content)
⚔️ ui/client/src/workspace/BuildingBlocksSidebar.tsx (content)
⚔️ ui/deployment/Dockerfile (content)
⚔️ ui/package.json (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'GENIE-1218/story/Implement graph view using a separate library' directly and clearly describes the main objective: replacing ReactFlowGraph with a new JointJS-based GraphDisplay component.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch graph-display-new-library

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.

@github-actions

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as outdated.

MayaCrmi added 5 commits February 10, 2026 05:31
…at-community-ai-tools/UnifAI into graph-display-new-library
…at-community-ai-tools/UnifAI into graph-display-new-library
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)
ui/client/src/components/agentic-ai/WorkflowsPanel.tsx (1)

339-367: ⚠️ Potential issue | 🟡 Minor

GraphDisplay with height="100%" may not fill remaining flex space.

The parent div (Line 339) is a flex flex-col container. The Share Section takes fixed height, and GraphDisplay needs to fill the rest. A child with height: 100% in a flex column may not expand as expected — consider wrapping GraphDisplay in a <div className="flex-1 min-h-0"> or passing className with flex-1 to ensure it fills the available space.

Suggested fix
             {selectedBlueprintData?.specDict ? (
+              <div className="flex-1 min-h-0">
               <GraphDisplay
                 blueprintId={selectedFlow.id}
                 specDict={selectedBlueprintData.specDict}
                 height="100%"
                 showBackground={graphProps?.showBackground}
                 interactive={graphProps?.interactive}
                 centerInView={true}
                 animated={true}
                 validationResults={validationResults}
                 isValidating={isValidating}
               />
+              </div>
             ) : (
-              <div className="flex items-center justify-center h-full text-gray-400">
+              <div className="flex-1 flex items-center justify-center text-gray-400">
                 Loading graph...
               </div>
             )}
🤖 Fix all issues with AI agents
In `@ui/client/src/components/agentic-ai/ExecutionTab.tsx`:
- Around line 994-1007: GraphDisplay is being mounted with specDict possibly
undefined (blueprintSpecCache.get(selectedSession.blueprintId)) which causes the
hook inside GraphDisplay to call getBlueprintInfo redundantly; change the render
branch in ExecutionTab so that when selectedSession?.blueprintId exists but
blueprintSpecCache.get(selectedSession.blueprintId) is undefined you render the
same loading/placeholder UI used by WorkflowsPanel (the pattern around
WorkflowsPanel lines 351–367) instead of mounting GraphDisplay; this keeps
GraphDisplay from fetching and waits until fetchResolvedBlueprint (triggered in
handleSessionSelect) populates blueprintSpecCache before passing specDict into
GraphDisplay.

In `@ui/client/src/components/agentic-ai/graphs/GraphDisplayHelpers.ts`:
- Around line 394-396: The fallback for unknown element types in the loop using
layoutNodes -> resolvedElements currently uses CATEGORY_TYPE_TO_PLURAL[el.type]
|| "retrievers", which can incorrectly map unknown types to a real category;
update the logic in GraphDisplayHelpers (the loop referencing layoutNodes,
resolvedElements and CATEGORY_TYPE_TO_PLURAL) to either skip elements when
CATEGORY_TYPE_TO_PLURAL[el.type] is undefined or use a sentinel string such as
"__unknown__" instead of "retrievers" so the unknown type will not match any
real category; ensure downstream code that consumes the category handles the
sentinel (or the absence) safely.
🧹 Nitpick comments (11)
ui/client/src/hooks/use-joint-graph.ts (4)

10-25: Imports split around safeFlushSync definition — consider co-locating.

The imports at Lines 10–11 and Lines 25–53 are separated by the safeFlushSync function definition. While functional, this is an unconventional layout that could confuse readers. Consider moving safeFlushSync below all imports.


118-161: rebuildOverlays has an empty dependency array — correct but worth a comment.

The useCallback captures only refs and state setters (all stable identities), so [] is correct. A brief inline comment would save future readers from second-guessing the lint suppression potential.


347-379: Primary color normalization is duplicated.

Lines 349 and 377–379 both normalize primaryHexRef.current to a hex string, but with slightly different fallback logic. Extract a small helper to unify this.

Suggested helper
+const normalizePrimaryHex = (raw: string | undefined): string => {
+  if (!raw) return "#8b5cf6";
+  return raw.startsWith("#") ? raw : `#${raw}`;
+};
+
 // In the async IIFE:
-const primaryNow = primaryHexRef.current || "#8b5cf6";
+const primaryNow = normalizePrimaryHex(primaryHexRef.current);
 ...
-const linkColor = primaryHexRef.current?.startsWith("#")
-  ? primaryHexRef.current
-  : `#${primaryHexRef.current || "8b5cf6"}`;
+const linkColor = normalizePrimaryHex(primaryHexRef.current);

318-491: The async IIFE is ~170 lines — consider extracting the layout + sizing logic.

The body from data fetch → node/edge creation → layout → sizing → overlay build is a single contiguous block. Extracting a buildGraph(graph, layoutNodes, layoutEdges, primaryHex) helper and a sizeAndFitPaper(paper, container, bbox, centerInView) helper would improve readability and testability without changing behavior.

ui/client/src/components/agentic-ai/graphs/GraphDisplay.tsx (1)

7-13: safeFlushSync is duplicated in use-joint-graph.ts (Lines 18–24).

Extract into a shared utility (e.g., lib/reactUtils.ts) to avoid drift between the two copies.

Suggested refactor
// New file: lib/reactUtils.ts
+import { flushSync } from "react-dom";
+
+/** Runs `fn` synchronously via flushSync; falls back to normal call on error. */
+export function safeFlushSync(fn: () => void): void {
+  try {
+    flushSync(fn);
+  } catch {
+    fn();
+  }
+}

Then import from both files:

-function safeFlushSync(fn: () => void): void { ... }
+import { safeFlushSync } from "@/lib/reactUtils";
ui/client/src/components/agentic-ai/WorkflowsPanel.tsx (2)

124-133: Missing eslint-disable for exhaustive-deps — may produce a lint warning.

fetchAvailableFlows and fetchActiveFlows are captured in the effect but not listed in the [user] dependency array. This is semantically correct (re-fetch only on user change), but React's lint rule will flag it. Either add // eslint-disable-next-line react-hooks/exhaustive-deps or wrap the fetch functions in useCallback.


240-246: Duplicate auto-select logic.

fetchAvailableFlows (Lines 99–101) already auto-selects the first flow when none is selected. This effect duplicates that logic and can cause a redundant onFlowSelect call on initial mount.

ui/client/src/components/agentic-ai/ExecutionTab.tsx (2)

105-106: blueprintSpecCache grows without eviction.

The Map<string, any> is appended to on every session selection (Line 329–333) but never pruned. In a long-running session with many distinct blueprints, this could accumulate unbounded memory. Consider an LRU bound or clearing the cache on user change.


480-483: Missing eslint-disable for exhaustive-deps.

fetchChatSessions is referenced inside the effect but not in the [] dependency array. Consider adding an eslint-disable comment to suppress the inevitable lint warning.

ui/client/src/components/agentic-ai/graphs/AgentNodeOverlay.tsx (2)

20-39: Consider extracting the inline prop type into a named interface.

The component's props are defined inline in the function signature (Lines 30–39), making it harder to reuse or reference the type externally. A named AgentNodeOverlayProps interface would improve discoverability.


86-110: IIFE for conditional rendering is functional but unusual.

The {hasStatus && (() => { ... })()} pattern is correct but might surprise contributors. A simple {hasStatus && nodeStatus === "PROGRESS" ? <motion.div .../> : <div .../>} would be more idiomatic 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: 1

🤖 Fix all issues with AI agents
In `@ui/client/src/components/agentic-ai/graphs/GraphDisplay.tsx`:
- Around line 320-348: The catch block for fetchResourceById currently only logs
at debug and leaves the user uninformed; replace the console.debug in the .catch
with a user-visible error flow by invoking the app's toast/snackbar utility (or
set an inline error state) to surface the failure, and also ensure you still
call setLoadingResource(false) (it already does in .finally) and keep resource
details closed; update the .catch to log at error level (console.error) and call
something like showToast/enqueueSnackbar or setResourceDetailsError to display a
brief, clear message, referencing fetchResourceById, setLoadingResource,
setResourceDetailsElement, and setResourceDetailsOpen.
🧹 Nitpick comments (6)
ui/client/src/hooks/use-joint-graph.ts (2)

19-55: Imports placed after function definition.

safeFlushSync is defined at lines 19–26, splitting the import block. Move all imports to the top of the file (before any function definitions) for conventional module structure.


294-318: waitForValidSize timer is not cleared on cancellation.

When the effect cleanup sets cancelled = true, the ResizeObserver callback will disconnect and resolve, but the setTimeout at line 313 keeps running. It's benign (calling disconnect() and resolve() on an already-settled promise are no-ops), but the dangling timer is unnecessary.

🧹 Proposed fix
     const waitForValidSize = (
       el: HTMLElement,
       minDim = 50,
       timeoutMs = 3000,
     ): Promise<boolean> =>
       new Promise((resolve) => {
         if (el.clientWidth >= minDim && el.clientHeight >= minDim) {
           resolve(true);
           return;
         }
+        let timer: ReturnType<typeof setTimeout>;
         const ro = new ResizeObserver(() => {
-          if (cancelled) { ro.disconnect(); resolve(false); return; }
+          if (cancelled) { ro.disconnect(); clearTimeout(timer); resolve(false); return; }
           if (el.clientWidth >= minDim && el.clientHeight >= minDim) {
             ro.disconnect();
             clearTimeout(timer);
             resolve(true);
           }
         });
         ro.observe(el);
-        const timer = setTimeout(() => {
+        timer = setTimeout(() => {
           ro.disconnect();
           // Last-ditch check — container may be big enough now
           resolve(el.clientWidth >= minDim && el.clientHeight >= minDim);
         }, timeoutMs);
       });
ui/client/src/components/agentic-ai/graphs/GraphDisplay.tsx (1)

8-15: safeFlushSync is duplicated in both use-joint-graph.ts and GraphDisplay.tsx.

Extract it into a shared utility (e.g., utils/safeFlushSync.ts) to keep a single source of truth.

ui/client/src/components/agentic-ai/ExecutionTab.tsx (3)

248-261: Local isSharingDisabled shadows the component-level state variable.

The local variable at line 254 has the same name as the state variable declared at line 100 and its setter. This is confusing and could lead to accidental misuse. Rename the local variable (e.g., sessionSharingDisabled).


100-106: blueprintSpecCache as a Map in useState is unconventional in React.

While the immutable update pattern at line 338–342 (new Map(prev)) is correct, Map in useState can be error-prone if future contributors mutate it directly. A plain Record<string, any> would be more idiomatic for React state and avoids the need for new Map() copies.

♻️ Suggested change
-  const [blueprintSpecCache, setBlueprintSpecCache] = useState<Map<string, any>>(new Map());
+  const [blueprintSpecCache, setBlueprintSpecCache] = useState<Record<string, any>>({});

Then update the setter:

-  setBlueprintSpecCache(prev => {
-    const next = new Map(prev);
-    next.set(session.blueprintId, resolvedBlueprint.spec_dict);
-    return next;
-  });
+  setBlueprintSpecCache(prev => ({
+    ...prev,
+    [session.blueprintId]: resolvedBlueprint.spec_dict,
+  }));

And the lookup:

-  specDict={blueprintSpecCache.get(selectedSession.blueprintId)}
+  specDict={blueprintSpecCache[selectedSession.blueprintId]}

489-492: fetchChatSessions referenced inside useEffect without being in the dependency array.

fetchChatSessions is not wrapped in useCallback and closes over state/props that may change. The empty [] dependency array means it runs once on mount, which is likely intentional, but ESLint's react-hooks/exhaustive-deps rule would flag this. Either wrap fetchChatSessions in useCallback with appropriate deps, or inline the logic in the effect.

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

🧹 Nitpick comments (1)
ui/client/src/components/agentic-ai/graphs/AgentNodeOverlay.tsx (1)

86-110: Consider extracting the IIFE inside JSX into a named sub-component or variable.

The {hasStatus && (() => { ... })()} pattern on lines 86–110 (and similarly for statusPill on lines 48–77) is functional but unusual in React. Extracting these into small named components (e.g., StatusBorder) or pre-computed variables above the return improves readability and avoids re-creating the closure on every render.

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

In `@ui/client/src/components/agentic-ai/graphs/AgentNodeOverlay.tsx` around lines
86 - 110, Extract the inline IIFE used for rendering the status border in
AgentNodeOverlay (the `{hasStatus && (() => { ... })()}` block that uses hdr,
nodeStatus and STATUS_STYLES) into a small named React component (e.g.,
StatusBorder) or a pre-computed variable above the component return; move the
logic that computes `s` and `borderStyle` out of the JSX so it’s not recreated
as a closure each render, accept hdr and nodeStatus as props (or capture them in
the variable), and replace the IIFE in the JSX with a simple <StatusBorder .../>
or `{borderElem}` reference; do the same refactor for the `statusPill` IIFE so
both are readable and avoid recreating closures on every render.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/client/src/components/agentic-ai/graphs/AgentNodeOverlay.tsx`:
- Around line 133-144: In AgentNodeOverlay adjust the computed maxWidth so it
never goes negative: replace the current expression for maxWidth with a clamped
value using Math.max(0, ...) so when computing maxWidth for hdr (use the same
conditional on hasStatus) you do Math.max(0, hasStatus ? hdr.width - 120 :
hdr.width - 40); update the style block where hdr.label is rendered so maxWidth
is the clamped value.
- Around line 186-189: The onClick handler on the badge currently calls
e.stopPropagation() unconditionally, so even when interactive is false the click
is swallowed; update the handler used in AgentNodeOverlay so that
e.stopPropagation() and onBadgeClick(badge.element.id) only run when interactive
is true (i.e., guard both calls behind if (interactive) { ... }), leaving
non-interactive badges to be transparent to pointer events and allowing the
graph canvas to receive clicks.

In `@ui/client/src/components/agentic-ai/graphs/NodeValidationIndicator.tsx`:
- Around line 67-73: The component NodeValidationIndicator renders an outer div
with a static "cursor-pointer" but only invokes onClick when !isValid, which
misleads users; update the className computation in NodeValidationIndicator (the
JSX that builds `className` using `className`, `isValid`, and current
hover/scale classes) to apply "cursor-pointer" only when the click is actionable
(i.e., when `!isValid && typeof onClick === 'function'`) and use a non-clickable
cursor (e.g., "cursor-default" or omit cursor class) when `isValid` is true so
the pointer only appears for clickable states. Ensure you reference the same
props `isValid` and `onClick` used in the onClick handler when adjusting the
class string.

---

Duplicate comments:
In `@ui/client/src/components/agentic-ai/graphs/NodeValidationIndicator.tsx`:
- Around line 59-65: The tooltipContent currently includes an "0 errors" segment
when errorCount === 0; update the tooltip construction in
NodeValidationIndicator (tooltipContent) to only include the errors segment if
errorCount > 0 and only include the warnings segment if warningCount > 0,
joining them with ", " when both exist; preserve correct pluralization for
"error(s)" and "warning(s)" and ensure the isValid branch still returns
"Validation passed".

---

Nitpick comments:
In `@ui/client/src/components/agentic-ai/graphs/AgentNodeOverlay.tsx`:
- Around line 86-110: Extract the inline IIFE used for rendering the status
border in AgentNodeOverlay (the `{hasStatus && (() => { ... })()}` block that
uses hdr, nodeStatus and STATUS_STYLES) into a small named React component
(e.g., StatusBorder) or a pre-computed variable above the component return; move
the logic that computes `s` and `borderStyle` out of the JSX so it’s not
recreated as a closure each render, accept hdr and nodeStatus as props (or
capture them in the variable), and replace the IIFE in the JSX with a simple
<StatusBorder .../> or `{borderElem}` reference; do the same refactor for the
`statusPill` IIFE so both are readable and avoid recreating closures on every
render.

Nirsisr
Nirsisr previously approved these changes Feb 18, 2026
Lina-AbuYousef
Lina-AbuYousef previously approved these changes Feb 18, 2026
sfiresht
sfiresht previously approved these changes Feb 18, 2026
@MayaCrmi MayaCrmi dismissed stale reviews from sfiresht, Lina-AbuYousef, and Nirsisr via a423750 February 18, 2026 09:34
@MayaCrmi MayaCrmi merged commit 9d40e82 into main Feb 18, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants