Skip to content

move some handlers to useAgentSelectionSync, remove useDefaultSubAgentNodeIdRef#2855

Merged
dimaMachina merged 18 commits intomainfrom
refreshAgentGraph2
Mar 27, 2026
Merged

move some handlers to useAgentSelectionSync, remove useDefaultSubAgentNodeIdRef#2855
dimaMachina merged 18 commits intomainfrom
refreshAgentGraph2

Conversation

@dimaMachina
Copy link
Copy Markdown
Collaborator

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 26, 2026

🦋 Changeset detected

Latest commit: 0cc9b74

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@inkeep/agents-manage-ui Patch
@inkeep/agents-api Patch
@inkeep/agents-cli Patch
@inkeep/agents-core Patch
@inkeep/agents-email Patch
@inkeep/agents-mcp Patch
@inkeep/agents-sdk Patch
@inkeep/agents-work-apps Patch
@inkeep/ai-sdk-provider Patch
@inkeep/create-agents Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 26, 2026

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

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Mar 27, 2026 0:11am
agents-docs Ready Ready Preview, Comment Mar 27, 2026 0:11am
agents-manage-ui Ready Ready Preview, Comment Mar 27, 2026 0:11am

Request Review

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog bot commented Mar 26, 2026

TL;DR — Extracts selection-sync logic and related handlers (closeSidePane, backToAgent, selectedNode, selectedEdge) from the main agent page component into a new useAgentSelectionSync hook, and removes the useDefaultSubAgentNodeIdRef hook entirely — replacing it with direct form.getValues() calls.

Key changes

  • Extract useAgentSelectionSync hook — Moves onSelectionChange, the graph-key-sync effect, closeSidePane, backToAgent, and derived selectedNode/selectedEdge lookups into a dedicated hook with a typed interface.
  • Remove useDefaultSubAgentNodeIdRef — Deletes the ref+subscription pattern in favor of reading defaultSubAgentNodeId directly from the form at call sites.
  • Update Biome config — Removes the useDefaultSubAgentNodeIdRef stable-result entry and adds useSidePane with its stable results.

Summary | 5 files | 14 commits | base: mainrefreshAgentGraph2


Selection sync extracted into useAgentSelectionSync

Before: onSelectionChange, the graph-key-mirroring useEffect, closeSidePane, backToAgent, and the selectedNode/selectedEdge derivations all lived inline in the ~700-line Agent component.
After: A single useAgentSelectionSync hook owns all selection ↔ query-state synchronization and returns { backToAgent, closeSidePane, selectedEdge, selectedNode }.

The hook accepts nodes, edges, side-pane state (isOpen, nodeId, edgeId), and the three setters it needs. Internally it calls useOnSelectionChange and runs the reconciliation effect, then exposes a clearCanvasSelection helper shared by closeSidePane and backToAgent to avoid duplicated deselection logic.

use-agent-selection-sync.ts · page.client.tsx


useDefaultSubAgentNodeIdRef removed

Before: A dedicated hook subscribed to form changes and kept a useRef in sync with defaultSubAgentNodeId; two consumers read .current.
After: Both call sites (page.client.tsx and use-animate-graph.ts) call form.getValues('defaultSubAgentNodeId') directly, eliminating the ref indirection entirely.

The hook file is deleted and its Biome stableResult entry is replaced with one for useSidePane.

use-default-sub-agent-id-ref.ts · use-animate-graph.ts · biome.jsonc

Pullfrog  | View workflow run | Triggered by Pullfrog | Using Claude Opus𝕏

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog bot commented Mar 26, 2026

TL;DR — Extracts selection-sync logic (canvas selection ↔ URL query state) and related handlers from the monolithic agent page component into a dedicated useAgentSelectionSync hook, and removes the useDefaultSubAgentNodeIdRef hook entirely by reading defaultSubAgentNodeId directly from the form.

Key changes

  • Extract useAgentSelectionSync hook — Moves onSelectionChange, the graph-key sync effect, closeSidePane, backToAgent, selectedNode, and selectedEdge into a self-contained hook that internally pulls setQueryState from useSidePane and deduplicates canvas deselection via a shared clearCanvasSelection helper.
  • Remove useDefaultSubAgentNodeIdRef — Eliminates the ref+subscription pattern; callers now read defaultSubAgentNodeId via form.getValues() at the point of use.
  • Update Biome config — Removes the deleted hook from stableResult entries and declares useSidePane results (setQueryState, openAgentPane) as stable so the new hook can safely omit them from effect dependency arrays.

Summary | 6 files | 18 commits | base: mainrefreshAgentGraph2


Selection-sync extracted into useAgentSelectionSync

Before: onSelectionChange, the graph-key reconciliation effect, closeSidePane, backToAgent, and the selectedNode/selectedEdge derivations all lived inline in page.client.tsx.
After: All of that logic is encapsulated in useAgentSelectionSync, which accepts { nodes, edges, isOpen, nodeId, edgeId } and returns { selectedNode, selectedEdge, closeSidePane, backToAgent }.

The hook owns two side-effects: useOnSelectionChange (React Flow callback that pushes selection into URL query state) and a useEffect that reconciles query state when a selected node or edge receives a new graph key without the canvas selection itself changing. A shared clearCanvasSelection helper deduplicates the deselect-all pattern used by both closeSidePane and backToAgent. The hook uses the 'use memo' directive and pulls setQueryState from useSidePane internally — setQueryState is now declared as a stable result in biome.jsonc, so it can be omitted from the useEffect dependency array.

use-agent-selection-sync.ts · page.client.tsx


useDefaultSubAgentNodeIdRef removed in favor of direct form.getValues()

Before: A dedicated hook subscribed to the form's defaultSubAgentNodeId field and mirrored it into a ref, which other code read via .current.
After: Consumers call form.getValues('defaultSubAgentNodeId') directly — in useAnimateGraph (inside the SSE subscription callback) and in page.client.tsx (inside onBeforeDelete).

This eliminates an extra subscription, a ref, and a lint suppression, with no behavioral change since both call sites already had access to the form context.

use-default-sub-agent-id-ref.ts (deleted) · use-animate-graph.ts · biome.jsonc

Pullfrog  | View workflow run | Triggered by Pullfrog | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

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

Clean extraction — the hook consolidation and useDefaultSubAgentNodeIdRef removal both look correct. One minor formatting issue below.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

}}
onBeforeDelete={async (state) => {
const defaultSubAgentNodeId = defaultSubAgentNodeIdRef.current;
const defaultSubAgentNodeId = form.getValues('defaultSubAgentNodeId')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Missing semicolon — Biome enforces "semicolons": "always".

Suggested change
const defaultSubAgentNodeId = form.getValues('defaultSubAgentNodeId')
const defaultSubAgentNodeId = form.getValues('defaultSubAgentNodeId');

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

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

New commits fix the missing semicolon and biome.jsonc formatting. LGTM.

Pullfrog  | View workflow run | Using Claude Opus𝕏

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(1) Total Issues | Risk: Low

🟡 Minor (1) 🟡

Inline Comments:

  • 🟡 Minor: page.client.tsx:649 Missing semicolon violates biome formatting rules

💭 Consider (1) 💭

💭 1) use-agent-selection-sync.ts:103-105 Memoize selectedNode/selectedEdge lookups

Issue: The selectedNode and selectedEdge values are computed on every render via findNodeByGraphKey and findEdgeByGraphKey, which perform O(n) array lookups.

Why: When nodes or edges arrays are large, this could add measurable overhead during rapid state updates (e.g., dragging nodes). However, the hook uses the 'use memo' directive, which may already handle this at the compiler level if React Compiler is enabled.

Fix: If React Compiler is not active, consider wrapping these in useMemo:

const selectedNode = useMemo(
  () => findNodeByGraphKey(nodes, nodeId),
  [nodes, nodeId]
);
const selectedEdge = useMemo(
  () => findEdgeByGraphKey(edges, nodes, edgeId),
  [edges, nodes, edgeId]
);

Refs:


💡 APPROVE WITH SUGGESTIONS

Summary: Clean refactoring that consolidates selection-related logic into a well-organized custom hook. The removal of useDefaultSubAgentNodeIdRef in favor of direct form.getValues() calls is a simplification that maintains correct behavior. One minor formatting fix needed (missing semicolon). Nice work on improving code organization! 🎉

Discarded (4)
Location Issue Reason Discarded
use-agent-selection-sync.ts:38-57 onChange callback captures isOpen in closure Intentional design — the defaultPane logic depends on current isOpen state. LOW confidence finding.
use-animate-graph.ts:38 Pattern change from ref subscription to direct form read Both reviewers agreed this is an improvement, not a regression. Reading at event time is correct.
use-agent-selection-sync.ts:98-101 clearCanvasSelection creates new arrays via .map() Only called on user actions, not on every render. Necessary for immutable state updates.
use-agent-selection-sync.ts General hook structure The hook is well-organized with clear single responsibility.
Reviewers (2)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-frontend 5 0 1 0 1 0 3
pr-review-standards 1 0 0 0 1 0 0
Total 6 0 1 0 1 0 4

}}
onBeforeDelete={async (state) => {
const defaultSubAgentNodeId = defaultSubAgentNodeIdRef.current;
const defaultSubAgentNodeId = form.getValues('defaultSubAgentNodeId');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Minor: Missing semicolon

Issue: This statement is missing a trailing semicolon, which violates the project's biome formatting rules ("semicolons": "always").

Why: This will cause pnpm check to fail on the format:check step.

Fix: (1-click apply)

Suggested change
const defaultSubAgentNodeId = form.getValues('defaultSubAgentNodeId');
const defaultSubAgentNodeId = form.getValues('defaultSubAgentNodeId');

Refs:

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

Delta Review (since last automated review)

The latest commit (51b99f192 polish) improves the useAgentSelectionSync hook by internalizing its dependencies:

Before After
Hook accepted setNodes, setEdges, setQueryState as parameters Hook imports useAgentActions and useSidePane directly
Caller had to wire up and pass these dependencies Caller only passes the data it owns (nodes, edges, isOpen, nodeId, edgeId)

This is a clean encapsulation improvement:

  • ✅ Reduces the hook's API surface from 8 parameters to 5
  • ✅ Hook now owns its own side-effect dependencies
  • ✅ Callers have less boilerplate
  • setQueryState correctly removed from useEffect deps (it's stable per biome config)

No new issues introduced. The prior review's "Consider" item about memoizing selectedNode/selectedEdge lookups remains valid but is not blocking.


✅ APPROVE

Summary: Nice polish! The hook encapsulation is cleaner now — callers pass data, the hook handles its own actions. Ship it! 🚀

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator-manual 1 0 0 0 0 0 0
Total 1 0 0 0 0 0 0

Note: Delta-only review — sub-reviewers not dispatched due to trivial scope (parameter removal refactoring).

@github-actions github-actions bot deleted a comment from claude bot Mar 26, 2026
Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

Delta Review

The latest commit (f57e66fab) removes the unused setEdges destructuring from useAgentActions() in page.client.tsx. This is correct cleanup — setEdges is now called internally by the useAgentSelectionSync hook, so the parent component no longer needs it.

No new issues introduced.


✅ APPROVE

Summary: Clean removal of unused import. The refactoring is complete — ship it! 🚢

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator-delta 1 0 0 0 0 0 0
Total 1 0 0 0 0 0 0

Note: Delta-only review — sub-reviewers not dispatched due to trivial scope (unused variable removal).

@dimaMachina
Copy link
Copy Markdown
Collaborator Author

@claude add a changeset

@inkeep-internal-ci
Copy link
Copy Markdown
Contributor

inkeep-internal-ci bot commented Mar 27, 2026

Claude finished @dimaMachina's task in 45s —— View job


Adding changeset

  • Determine changeset details
  • Create changeset file
  • Commit and push

Added a patch changeset for @inkeep/agents-manage-ui: "Extract selection-sync logic into useAgentSelectionSync hook and remove useDefaultSubAgentNodeIdRef"
| Branch

Co-authored-by: Dimitri POSTOLOV <dimaMachina@users.noreply.github.com>
@dimaMachina dimaMachina enabled auto-merge March 27, 2026 00:09
@dimaMachina dimaMachina added this pull request to the merge queue Mar 27, 2026
Merged via the queue into main with commit ec1b2f7 Mar 27, 2026
15 of 16 checks passed
@dimaMachina dimaMachina deleted the refreshAgentGraph2 branch March 27, 2026 00:24
@itoqa
Copy link
Copy Markdown

itoqa bot commented Mar 27, 2026

Ito Test Report ❌

13 test cases ran. 1 failed, 12 passed.

Overall, 12 of 13 test cases passed, showing strong reliability across deep-linking, authentication gating, node/edge selection synchronization, refresh/history behavior, Try It query cleanup, parallel-tab recovery, and safe handling of script-like query payloads without crashes or code-supported defects in those areas. The one confirmed issue is a medium-severity mobile usability bug where the side-pane Back button is effectively hover-only and unreliable on touch (390x844), which can trap users in node/edge detail panes and block returning to Agent view, and it appears to be pre-existing rather than introduced by this PR.

❌ Failed (1)
Category Summary Screenshot
Edge 🟠 Back control is hover-only (group-hover) and effectively inaccessible on touch/mobile, blocking pane return transitions. EDGE-5
🟠 Mobile side pane back control is inaccessible on touch
  • What failed: The Back control is visually hidden by default and only revealed on group-hover; touch interactions do not reliably trigger hover, so users cannot consistently navigate back to pane=agent.
  • Impact: Mobile users can become stuck in detail panes and lose a primary navigation path in the editor. This blocks core pane-transition workflows in responsive usage.
  • Introduced by this PR: No – pre-existing bug (code not changed in this PR)
  • Steps to reproduce:
    1. Set viewport to 390x844 and open /default/projects/activities-planner/agents/activities-planner?pane=node&nodeId=get-coordinates-agent.
    2. Wait for the node side pane to render, then try using the Back control in the pane header.
    3. Attempt to return to Agent view and verify whether pane query transitions to pane=agent without stale IDs.
  • Code analysis: I reviewed the side pane layout and selection-sync flow. The Back button is explicitly hidden (opacity-0 -translate-x-6) unless hover state is active, while the side pane relies on that control to return to agent context for node/edge selections.
  • Why this is likely a bug: Production UI code gates the Back control behind hover-only visibility, which is not a dependable interaction state on touch devices, directly breaking the intended mobile navigation path.

Relevant code:

agents-manage-ui/src/components/layout/sidepane.tsx (lines 58-69)

function SidePaneBackButton({ onClick }: { onClick: () => void }) {
  return (
    <Button
      variant="ghost"
      size="icon-sm"
      onClick={onClick}
      className="-ml-1 transition-all duration-300 ease-in-out absolute left-0 opacity-0 -translate-x-6 group-hover:opacity-100 group-hover:translate-x-0 text-foreground hover:text-foreground dark:text-foreground dark:hover:text-foreground"
    >
      <ArrowLeft className="h-4 w-4" />
    </Button>
  );
}

agents-manage-ui/src/components/agent/sidepane/sidepane.tsx (lines 151-168)

const showBackButton = selectedNode || selectedEdge;

return (
  <SidePaneLayout.Root>
    <SidePaneLayout.Header>
      <div className="flex items-center relative">
        {showBackButton && <SidePaneLayout.BackButton onClick={onBackButtonClick} />}
        <Heading
          heading={heading}
          Icon={HeadingIcon}
          className={cn(
            showBackButton && 'transition-all duration-300 ease-in-out group-hover:translate-x-8'
          )}
        />
      </div>

agents-manage-ui/src/components/agent/use-agent-selection-sync.ts (lines 108-114)

backToAgent() {
  clearCanvasSelection();
  setQueryState({
    pane: 'agent',
    nodeId: null,
    edgeId: null,
  });
}
✅ Passed (12)
Category Summary Screenshot
Adversarial Script-like payloads in query params were handled safely without alerts, crashes, or uncaught sync/parsing errors. ADV-1
Adversarial Unauthenticated protected deep-link redirected to login, then valid sign-in reached the protected agent route. ADV-2
Adversarial History back/forward traversal remained aligned with expected behavior; selection-sync uses replace-history transitions, and no deterministic in-app URL/editor desynchronization defect was confirmed. ADV-3
Adversarial Tab A safely recovered after parallel-tab selection divergence and remained interactive after reload. ADV-4
Adversarial Rage-click stress on Agent Settings and side pane controls completed without permanent UI corruption; normal pane behavior remained intact. ADV-5
Edge Invalid edgeId deep-link self-healed back to pane=agent while preserving interactivity. EDGE-2
Edge Refresh on valid node and supported edge deep-links consistently preserved pane and selected entity state. EDGE-3
Logic Three rapid node-edge-node cycles stayed consistent: pane/query matched each latest click, edge pane opened only on supported edge, and node transitions cleared edgeId without stale mode. LOGIC-2
Logic Selecting an unsupported MCP edge did not force pane=edge, and the UI remained stable on pane=agent. LOGIC-3
Happy-path Agent deep-link loaded Agent Settings with pane=agent and no nodeId/edgeId. ROUTE-1
Happy-path Selecting a supported edge opened edge editor with pane=edge and a valid edgeId. ROUTE-3
Happy-path Try it transition closed the side pane and cleared stale pane/nodeId/edgeId query params as expected. ROUTE-5

Commit: 0cc9b74

View Full Run


Tell us how we did: Give Ito Feedback

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.

1 participant