Skip to content

Improve the editing experience with policy agent#8164

Merged
lucanovera merged 23 commits into
mainfrom
ENG-3573-FE-Animate-and-transition-the-editing-experience-with-agent-policy-builder
May 14, 2026
Merged

Improve the editing experience with policy agent#8164
lucanovera merged 23 commits into
mainfrom
ENG-3573-FE-Animate-and-transition-the-editing-experience-with-agent-policy-builder

Conversation

@lucanovera
Copy link
Copy Markdown
Contributor

@lucanovera lucanovera commented May 12, 2026

Ticket ENG-3573

Description Of Changes

Animates the access-policy editing experience when the policy agent proposes a YAML diff. The React Flow canvas now plays a three-phase transition that shows the previous and proposed states side-by-side, fades the old state out, and pulse-highlights added / modified / removed nodes. A new "Policy agent is working" indicator surfaces this state in two places so the user can tell something is happening even when the relevant nodes are off-screen.

Tested alongside fidesplus#3558 — that PR backs the policy-update payload (added / changed / removed IDs) consumed by this transition. Reviewers should check out both branches together.

Code Changes

  • Added a phase machine (ghost-holdsettling → cleanup) in AccessPolicyEditor.tsx that drives the diff transition when the agent returns a PolicyUpdate.
  • Added tagNodesWithDiff / buildUnionGraph helpers in policy-yaml.ts to tag React Flow nodes with diffStatus-added / -modified / -removed classes and merge old + new graphs during ghost-hold.
  • Added DiffViewportController (inside AccessPolicyEditor.tsx) to fit the viewport on the affected sub-graph at each phase boundary.
  • Added pulse / fade keyframes for added / modified / removed nodes in AccessPolicyEditor.module.scss.
  • Added timing constants DIFF_HOLD_MS = 3000, DIFF_HIGHLIGHT_MS = 6500, DIFF_FIT_DURATION_MS = 800 in constants.ts.
  • Added new shared component PolicyAgentWorking.tsx — a pulsating square + "Policy agent is working" label — with default and small size variants.
  • Mounted the indicator at the top center of the React Flow canvas (default size) via <Panel position="top-center"> while a transition is in flight.
  • Replaced the chat bubble's "The policy was updated" footer on the most recent applied message with <PolicyAgentWorking size="small" /> during the transition; it flips back to the green check + "The policy was updated" when the transition ends. Older applied messages always show the check.
  • Extended the agent-chat.slice PolicyUpdate type with added / changed / removed ID arrays and updated MSW handlers in agent-chat-handlers.ts.
  • Added unit coverage for the new YAML / diff helpers in utils.test.ts.

Steps to Confirm

  1. Check out this branch of fides and fidesplus#3558 — both are required for the agent to return a populated PolicyUpdate.
  2. Start the stack with the LLM classifier enabled (the chat panel is gated on detection_discovery.llm_classifier_enabled).
  3. In clients/admin-ui run npm run dev and log in
  4. Open or create an access policy and make sure the agent chat panel is visible (toggle at the top of the editor).
  5. Send a prompt that produces a YAML diff — e.g. "Add a geo_location constraint that blocks EU traffic".
  6. Confirm:
    • The "Policy agent is working" indicator (default size, pulsating square) appears at the top center of the React Flow canvas as soon as the response arrives.
    • The new agent chat bubble shows the small indicator instead of the green check + "The policy was updated".
    • Ghost / removed nodes hang around for ~3s, then fade; added / modified nodes pulse green / amber.
    • After ~6.5s the indicator disappears from both the canvas and the chat bubble, and the bubble flips to the green check + "The policy was updated".
    • The viewport pans / zooms to fit the affected sub-graph (not the whole policy).
  7. Send a second prompt while the first transition is still running — the previous chat bubble should immediately show the green check (its transition was cancelled), and the indicator should re-attach to the new bubble.
  8. Send a prompt that the agent answers without modifying the policy (no policy_update in the response) — no indicator should appear and no footer should be added to the chat bubble.
  9. Toggle to Code mode while a transition is in flight — the transition should be cancelled cleanly with no ghosts left in the canvas.
  10. Run npm run typecheck and npm run test -- access-policies in clients/admin-ui — both should pass.

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Add a db-migration This indicates that a change includes a database migration label to the entry if your change includes a DB migration
    • Add a high-risk This issue suggests changes that have a high-probability of breaking existing code label to the entry if your change includes a high-risk change (i.e. potential for performance impact or unexpected regression) that should be flagged
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • All UX related changes have been reviewed by a designer
    • No UX review needed
  • Followup issues:
    • Followup issues created
    • No followup issues
  • Database migrations:
    • No migrations
  • Documentation:
    • No documentation updates required

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 12, 2026

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

Project Deployment Actions Updated (UTC)
fides-plus-nightly Ready Ready Preview, Comment May 14, 2026 8:25pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
fides-privacy-center Ignored Ignored May 14, 2026 8:25pm

Request Review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Title Lines Statements Branches Functions
admin-ui Coverage: 9%
6.89% (3151/45705) 6.26% (1655/26425) 4.78% (647/13523)
fides-js Coverage: 78%
79.17% (1977/2497) 66.25% (1249/1885) 73.31% (349/476)
privacy-center Coverage: 85%
82.53% (364/441) 79.74% (189/237) 74.07% (60/81)

@lucanovera
Copy link
Copy Markdown
Contributor Author

/code-review

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.

Code Review: PR #8164 — Animated transition & "Policy agent is working" indicator

This is a well-structured, well-tested implementation. The three-phase state machine (ghost-hold → settling → cleanup), the YAML diff tagging helpers, and the new PolicyAgentWorking component all follow clear patterns. Test coverage for the new policy-yaml.ts helpers is thorough.

Summary of findings

Medium

  • proOptions={{ hideAttribution: true }} (AccessPolicyEditor.tsx:852) — This is a React Flow Pro-only prop. Using it without a valid Pro license is a ToS violation. Confirm the license or remove it before merge.

Minor

  • Hardcoded #faad14 (AccessPolicyEditor.module.scss:80) — The diffStatus-modified rule uses a raw hex instead of a design token, unlike the added/removed peers. Should reference --fidesui-color-warning or equivalent.
  • Accessibility gap in PolicyAgentWorking (PolicyAgentWorking.tsx:10) — The component has no role="status" / aria-live, so screen readers won't be notified when the indicator appears or when it swaps from the green check. Adding role="status" to the wrapper is a one-liner fix.
  • Ghost constraint edge topology (policy-yaml.ts:641) — Constraint ghost edges always attach to newFirstCondition, which is structurally wrong in multi-condition policies. This is acceptable for a transient visual, but deserves a comment explaining the known simplification.
  • Empty discriminator in nodeContentId (policy-yaml.ts:503) — Unconfigured constraint nodes produce constraint:consent: (empty trailing segment), which causes id collisions between multiple unconfigured constraints. Consider returning null when the discriminator field is empty.

What looks good

  • Timer cleanup on unmount and on mode switch is handled correctly.
  • React 18 automatic batching means the two state updates inside each timeout (setPendingTransition + setSyncKey) render atomically, so the syncKey effect always sees the intended pendingTransition phase — no race.
  • DiffViewportController correctly guards against re-firing fitView for the same {phase, epoch} via lastSeenRef, and defers until all nodes are measured.
  • The YAML serialization guard (if (!onYamlChange || pendingTransition) return) correctly prevents ghost nodes from being written back into yamlValue during the hold phase.
  • Unit tests for nodeContentId, tagNodesWithDiff, and buildUnionGraph cover the main paths including ghost node propagation to incident edges.

🔬 Codegraph: connected (49858 nodes)

💡 Write /code-review in a comment to re-run this review.

id: `e-ghost-${newActionNode.id}-${ghostId}`,
source: newActionNode.id,
target: ghostId,
type: "labeledEdge",
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.

clients/admin-ui/src/features/access-policies/policy-yaml.ts:641

Ghost constraint edges always wire to newFirstCondition (the first condition node found in the new graph), regardless of which condition actually owned the constraint in the old graph. In a policy that has multiple condition nodes this produces structurally incorrect ghost edges.

For the brief ghost-hold phase this is probably acceptable (the user just needs a visual hint that something is disappearing), but it's worth a comment here explaining the intentional simplification and its limitation:

// Simplified: always attach the ghost constraint to the first condition in
// the new graph. Multi-condition topologies may show structurally incorrect
// edges during the ghost-hold phase, but this is a transient visual only.

const { property } = node.data as ConditionNodeData;
return property ? `condition:${property}` : null;
}
if (node.type === "constraintNode") {
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.

clients/admin-ui/src/features/access-policies/policy-yaml.ts:503

When privacyNoticeKey (or geoField / dataFlowDirection) is undefined, the discriminator becomes an empty string, producing ids like constraint:consent:. If a user has dragged in two unconfigured consent constraints, both will map to the same content id and both will receive the same diff highlight — even if only one was actually changed. This is an edge case, but worth noting in the JSDoc or guarding with a null return:

if (data.constraintType === ConstraintType.CONSENT) {
  return data.privacyNoticeKey
    ? `constraint:consent:${data.privacyNoticeKey}`
    : null; // not identifiable until configured
}

@lucanovera lucanovera marked this pull request as ready for review May 14, 2026 16:49
@lucanovera lucanovera requested a review from a team as a code owner May 14, 2026 16:49
@lucanovera lucanovera requested review from gilluminate and kruulik and removed request for a team and gilluminate May 14, 2026 16:49
Comment thread clients/admin-ui/src/features/access-policies/PolicyAgentWorking.module.scss Outdated
Comment thread clients/admin-ui/src/features/access-policies/PolicyAgentWorking.module.scss Outdated
Comment thread clients/admin-ui/src/features/access-policies/PolicyAgentWorking.tsx Outdated
Comment thread clients/admin-ui/src/features/access-policies/AccessPolicyEditor.tsx Outdated
Copy link
Copy Markdown
Contributor

@kruulik kruulik left a comment

Choose a reason for hiding this comment

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

Looks good - code comments are nits and can be addressed later.

@lucanovera lucanovera added this pull request to the merge queue May 14, 2026
Merged via the queue into main with commit caa7791 May 14, 2026
50 of 51 checks passed
@lucanovera lucanovera deleted the ENG-3573-FE-Animate-and-transition-the-editing-experience-with-agent-policy-builder branch May 14, 2026 20:39
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.

2 participants