Skip to content

feat: improve role graph layout for policy updates#364

Merged
hsluoyz merged 1 commit intomasterfrom
copilot/improve-role-inheritance-graph-again
Dec 11, 2025
Merged

feat: improve role graph layout for policy updates#364
hsluoyz merged 1 commit intomasterfrom
copilot/improve-role-inheritance-graph-again

Conversation

Copy link
Contributor

Copilot AI commented Dec 10, 2025

The Role Inheritance Graph was re-parsing and re-rendering on every keystroke when editing policies, causing constant flashing and position resets that disrupted editing workflow.

Changes

  • Debounced policy parsing with 500ms delay to batch rapid edits
  • Structural change detection to skip updates when policy structure hasn't changed
  • Incremental DOM updates using D3's enter/update/exit pattern instead of full graph resets
  • Position preservation to maintain node locations between updates for smooth transitions
  • Optimized lookups with Map-based O(1) position retrieval instead of O(n²) array searches

Implementation

Debouncing with Change Detection

// Before: Immediate re-render on every change
useEffect(() => {
  const parser = new PolicyInheritanceParser();
  parser.parsePolicy(policy);
  // ... update state
}, [policy]);

// After: Debounced with structural comparison
const POLICY_UPDATE_DEBOUNCE_MS = 500;
const previousPolicyRef = useRef<string>('');

useEffect(() => {
  const timer = setTimeout(() => {
    if (policy !== previousPolicyRef.current) {
      parsePolicyData(policy);
      previousPolicyRef.current = policy;
    }
  }, POLICY_UPDATE_DEBOUNCE_MS);
  
  return () => clearTimeout(timer);
}, [policy, parsePolicyData]);

Incremental DOM Updates

// Before: Full reset removes all elements
svg.selectAll('*').remove();

// After: D3 data join pattern for incremental updates
const nodes = nodesGroup
  .selectAll('g.node')
  .data(allNodes, (d) => d.id);

nodes.exit().remove();  // Remove only deleted nodes
const nodesEnter = nodes.enter().append('g');  // Add only new nodes
const nodesAll = nodesEnter.merge(nodes);  // Update all nodes

Position Preservation

// Map-based O(1) lookup for previous positions
const previousNodesMap = new Map(
  previousNodesRef.current.map((n) => [n.id, n])
);

allNodes.forEach((node) => {
  const previousNode = previousNodesMap.get(node.id);
  if (previousNode) {
    // Preserve existing positions
    node.x = previousNode.x;
    node.y = previousNode.y;
  } else {
    // Calculate position for new nodes only
    node.x = calculateX();
    node.y = calculateY();
  }
});

Key Improvements

  1. No flashing - Graph stays stable during typing
  2. Incremental updates - Only changed elements are added/removed/updated:
    • Add only new nodes/edges when policy lines are created
    • Remove only specific nodes/edges when policy lines are deleted
    • Update only modified elements instead of full redraw
  3. Smooth transitions - Node positions preserved between updates
  4. Better performance - Reduced DOM manipulations and optimized lookups

Screenshot

Before: Graph resets on every character typed (red circles highlight affected areas)

After: Stable editing with incremental updates

Original prompt

This section details on the original issue you should resolve

<issue_title>[feature] avoid resetting the entire role inheritance graph on every policy change</issue_title>
<issue_description>Problem:
When editing the Policy section, the Role Inheritance Graph is fully resets after every single keystroke— even if the change is as small as typing or deleting a single character.
Currently, the graph is completely re-rendered regardless of whether the edit affects only one node/edge or the entire policy, which causes constant flashing/resets and disrupts the user's workflow.
This frequent resetting negatively impacts user experience.

Expected Behavior:

  • The graph should not refresh on every keystroke, but only when updates are meaningful.
  • Partial update instead of full reset (only update changed edge/node).
  • Consider applying debounced rendering (e.g. only update after the user stops typing for ~300–800ms).

In the below screenshot, the problematic parts are circled in red:
Image</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@netlify
Copy link

netlify bot commented Dec 10, 2025

Deploy Preview for casbin-editor ready!

Name Link
🔨 Latest commit 7d8c3c3
🔍 Latest deploy log https://app.netlify.com/projects/casbin-editor/deploys/693a948c9b4d87000891fe04
😎 Deploy Preview https://deploy-preview-364--casbin-editor.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@CLAassistant
Copy link

CLAassistant commented Dec 10, 2025

CLA assistant check
All committers have signed the CLA.

Copilot AI changed the title [WIP] Improve role inheritance graph rendering on policy edits Debounce Role Inheritance Graph updates on policy edits Dec 10, 2025
Copilot AI requested a review from tjyyy3 December 10, 2025 13:09
@hsluoyz hsluoyz marked this pull request as ready for review December 10, 2025 14:07
@tjyyy3
Copy link
Contributor

tjyyy3 commented Dec 10, 2025

@copilot
The debounce improvement is helpful, but currently the graph is still being fully reset on each keystroke (add/delete/update). What we expect instead is incremental updates rather than a full redraw.

  1. Update only the specific element when editing (instead of resetting all nodes)
  2. Add only the new node/edge when something is created
  3. Remove only the corresponding node/edge when deleted

Copy link
Contributor Author

Copilot AI commented Dec 10, 2025

@copilot
The debounce improvement is helpful, but currently the graph is still being fully reset on each keystroke (add/delete/update). What we expect instead is incremental updates rather than a fu...

Implemented incremental DOM updates using D3's enter/update/exit pattern in commit f762d2d.

The graph now:

  1. Only adds new nodes/edges when policy lines are created
  2. Only removes specific nodes/edges when policy lines are deleted
  3. Only updates changed elements instead of full redraw
  4. Preserves node positions between updates for smooth transitions

Changes include:

  • D3 data join pattern for links, nodes, labels, and markers
  • Conditional removal - only full reset on dimension/theme change
  • Position preservation using previousNodesRef for existing nodes
  • Reuse of container groups instead of recreation

Copilot AI changed the title Debounce Role Inheritance Graph updates on policy edits Implement incremental updates for Role Inheritance Graph to avoid full resets Dec 10, 2025
@tjyyy3 tjyyy3 force-pushed the copilot/improve-role-inheritance-graph-again branch from bad0f5f to 7d8c3c3 Compare December 11, 2025 09:53
@hsluoyz hsluoyz changed the title Implement incremental updates for Role Inheritance Graph to avoid full resets feat: implement incremental updates for Role Inheritance Graph to avoid full resets Dec 11, 2025
@hsluoyz hsluoyz changed the title feat: implement incremental updates for Role Inheritance Graph to avoid full resets feat: improve role graph layout for policy updates Dec 11, 2025
@hsluoyz hsluoyz merged commit b712961 into master Dec 11, 2025
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] avoid resetting the entire role inheritance graph on every policy change

4 participants