Skip to content

fix: validate API graph shape and add cycle guards + error boundary a…#143

Merged
DataDave-Dev merged 1 commit into
DataDave-Dev:mainfrom
YasserYG8:fix/graph-validation-error-boundary
Jun 28, 2026
Merged

fix: validate API graph shape and add cycle guards + error boundary a…#143
DataDave-Dev merged 1 commit into
DataDave-Dev:mainfrom
YasserYG8:fix/graph-validation-error-boundary

Conversation

@YasserYG8

@YasserYG8 YasserYG8 commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Title fix: validate API graph shape and add cycle guards + error boundary around diagram

Description

This PR resolves Issue #45 by implementing robust API payload validation and visual guards around diagram rendering. Previously, malformed or cyclic graph structures returned by the backend would throw or cause unbounded recursion, freezing or crashing the entire client application.

Key Changes

  1. API Validation (Schema Guard):

    • Imported and integrated the Zod graphSchema validation inside CodeWorkspace.tsx for /api/analyze responses.
    • Prevents raw unsafe object casting by validating the data structure before updating the application state.
  2. Hierarchical Cycle & Infinite Loop Guards:

    • Implemented visited-set cycle tracking inside the layout recursion functions (sizeOf, ancestorIn, topAncestor, and depthOf) in Diagram.tsx.
    • Prevents infinite loops or stack overflow errors when resolving cyclic parents (e.g., circular module nesting).
  3. React Error Boundary:

    • Created a reusable ErrorBoundary.tsx component.
    • Wrapped the exported <Diagram> component to catch any layout-calculation or render-time crash, presenting a clean fallback error card with a "Try Again" option without breaking the rest of the application.
  4. Testing & Environment Configurations:

    • Wrote test suites in Diagram.test.ts to verify cycle-detection safety.
    • Adjusted vitest.config.ts to stub out CSS file resolution during unit testing, avoiding Node-side PostCSS environment errors.

Verification / Testing Done

  • Automated Tests: All unit tests (pnpm test) pass cleanly.
  • Type Checking: TypeScript compilation (pnpm typecheck) succeeds with no warnings.
  • Manual Verification: Simulated circular graph inputs; verified that the app no longer freezes and the error boundary catches crashes gracefully.

Summary by CodeRabbit

  • New Features

    • Added safer diagram rendering with automatic error handling and a retry option if the diagram fails to load.
    • Improved code analysis results validation to prevent broken graph data from being shown.
  • Bug Fixes

    • Prevented infinite loops and stack overflows in diagram layout for cyclic relationships.
    • Added tests covering normal layouts and cycle handling to improve reliability.
  • Chores

    • Updated test setup to better handle CSS imports during automated testing.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds cycle-safe visited-Set guards to ancestorIn, sizeOf, topAncestor, and depthOf in Diagram.tsx to prevent infinite loops on cyclic parent references. Introduces a new ErrorBoundary component wrapping the diagram render. Validates /api/analyze responses using graphSchema.safeParse in CodeWorkspace. Exports layout and adds Vitest tests and CSS mock infrastructure.

Changes

Cycle protection, ErrorBoundary, and API validation

Layer / File(s) Summary
ErrorBoundary component
src/components/ui/ErrorBoundary.tsx, src/components/ui/Diagram.tsx
New ErrorBoundary class component with fallback UI and "Try Again" reset; imported and applied to wrap ReactFlowProvider/DiagramFlow in the exported Diagram.
Cycle-safe layout traversal
src/components/ui/Diagram.tsx
layout is now exported. ancestorIn, sizeOf (with post-recursion cleanup), topAncestor, and depthOf each add Set-based visited tracking to return null or default sizes instead of looping infinitely on cyclic parent chains.
API response validation
src/components/ui/CodeWorkspace.tsx
Imports graphSchema and replaces the direct JSON cast with safeParse; throws "Invalid graph data returned by the API." on failure and sets graph state from validated data on success.
Layout tests and Vitest CSS mock
src/components/ui/Diagram.test.ts, src/test/mocks/styleMock.*, vitest.config.ts
Two Vitest tests verify layout handles a linear parent chain and terminates within 1 s on a cyclic A↔B graph. A styleMock.js empty-object export and a reactflow/dist/style.css alias in vitest.config.ts let tests resolve CSS imports.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • DataDave-Dev/weftmap#1: Introduced the /api/analyze response handling in CodeWorkspace that this PR now adds graphSchema.safeParse validation on top of.
  • DataDave-Dev/weftmap#106: Modified Diagram.tsx rendering structure with ReactFlowProvider/DiagramFlow, which this PR wraps in ErrorBoundary.

Poem

🐇 Hoppity-hop through a graph gone awry,
Cycles and loops made the layout just die!
But now with a Set, we track where we've been,
No infinite hops past nodes already seen.
And if something breaks while the diagram draws,
ErrorBoundary catches with soft furry paws! 🌿

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is informative but does not follow the required template and omits the 'Tipo' and checklist sections. Rewrite it using the template headings ('Qué hace', 'Tipo', 'Checklist') and include the required checkbox items and verification details.
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: graph validation, cycle guards, and an error boundary.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/ui/CodeWorkspace.tsx`:
- Around line 302-306: The graph validation in CodeWorkspace is still too
permissive because graphSchema uses a loose shape and the result is force-cast
to Graph, allowing invalid node/edge fields like parent and layout metadata to
slip through. Tighten graphSchema so it fully matches the GraphNode and
GraphEdge contract used by Diagram, remove the .loose() behavior for fields that
must be validated, and update the setGraph(parsed.data ...) path so parsed.data
is accepted by TypeScript without any cast.

In `@src/components/ui/Diagram.tsx`:
- Around line 680-684: The Diagram error boundary currently keeps its fallback
state across different inputs, so a failure in DiagramFlow can persist even when
CodeWorkspace passes a new valid graph. Update the ErrorBoundary usage in
Diagram by tying it to the current graph input with a key derived from graph (or
a stable graph id/hash if available) so the boundary remounts and resets when
the graph changes.

In `@src/components/ui/ErrorBoundary.tsx`:
- Around line 29-71: The ErrorBoundary fallback in ErrorBoundary should reset
when the wrapped diagram data changes, not only when the user clicks Try Again.
Add a reset mechanism such as a resetKeys prop and clear hasError/error when
those keys change in componentDidUpdate or via getDerivedStateFromProps, so new
valid input can recover automatically. Update the ErrorBoundary usage in Diagram
to pass a stable key derived from the graph/input data, and keep the existing
fallback render path and render() behavior intact.
- Around line 53-55: The heading in ErrorBoundary’s render fallback is using a
nonstandard Tailwind class, so update the h3 styling from text-md to text-base
in the ErrorBoundary component to match the default Tailwind scale and ensure
the intended font size is applied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f65acc97-6040-4afd-b3e4-ebadffd8c049

📥 Commits

Reviewing files that changed from the base of the PR and between 656cd67 and 3e75f1a.

📒 Files selected for processing (7)
  • src/components/ui/CodeWorkspace.tsx
  • src/components/ui/Diagram.test.ts
  • src/components/ui/Diagram.tsx
  • src/components/ui/ErrorBoundary.tsx
  • src/test/mocks/styleMock.css
  • src/test/mocks/styleMock.js
  • vitest.config.ts

Comment on lines +302 to +306
const parsed = graphSchema.safeParse(data);
if (!parsed.success) {
throw new Error("Invalid graph data returned by the API.");
}
setGraph(parsed.data as Graph);

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The new validation still lets malformed graph shapes cross the API boundary.

graphSchema only checks a minimal subset of node/edge fields and allows everything else through with .loose(), so invalid parent/layout fields can still reach Diagram. Line 306 then hides that drift again with as Graph. Please align the schema with the full GraphNode/GraphEdge contract and let parsed.data type-check without a cast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/CodeWorkspace.tsx` around lines 302 - 306, The graph
validation in CodeWorkspace is still too permissive because graphSchema uses a
loose shape and the result is force-cast to Graph, allowing invalid node/edge
fields like parent and layout metadata to slip through. Tighten graphSchema so
it fully matches the GraphNode and GraphEdge contract used by Diagram, remove
the .loose() behavior for fields that must be validated, and update the
setGraph(parsed.data ...) path so parsed.data is accepted by TypeScript without
any cast.

Comment on lines +680 to +684
<ErrorBoundary>
<ReactFlowProvider>
<DiagramFlow {...props} />
</ReactFlowProvider>
</ErrorBoundary>

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Tie the boundary to graph so it recovers on new input.

Because ErrorBoundary does not reset on prop changes (see ErrorBoundary.tsx), a render failure for one graph will keep showing the fallback even after CodeWorkspace passes a different valid graph. Adding a key derived from the graph forces a remount (and fresh boundary state) whenever the input changes — a minimal fix until/unless resetKeys is added.

Proposed fix
 export default function Diagram(props: { graph: Graph; emptyLabel: string }) {
   return (
-    <ErrorBoundary>
+    <ErrorBoundary key={props.graph.nodes.length + ":" + props.graph.edges.length}>
       <ReactFlowProvider>
         <DiagramFlow {...props} />
       </ReactFlowProvider>
     </ErrorBoundary>
   );
 }

A more robust key would be a stable hash/id of the graph if one is available.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<ErrorBoundary>
<ReactFlowProvider>
<DiagramFlow {...props} />
</ReactFlowProvider>
</ErrorBoundary>
<ErrorBoundary key={props.graph.nodes.length + ":" + props.graph.edges.length}>
<ReactFlowProvider>
<DiagramFlow {...props} />
</ReactFlowProvider>
</ErrorBoundary>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/Diagram.tsx` around lines 680 - 684, The Diagram error
boundary currently keeps its fallback state across different inputs, so a
failure in DiagramFlow can persist even when CodeWorkspace passes a new valid
graph. Update the ErrorBoundary usage in Diagram by tying it to the current
graph input with a key derived from graph (or a stable graph id/hash if
available) so the boundary remounts and resets when the graph changes.

Comment on lines +29 to +71
public render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="flex h-full w-full flex-col items-center justify-center p-6 text-center bg-[#f6f7f9] dark:bg-[#0b0d12]">
<div className="max-w-md rounded-xl border border-red-100 dark:border-red-950 bg-white dark:bg-[#12151c] p-6 shadow-sm">
<div className="mx-auto mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-red-100 dark:bg-red-950 text-red-600 dark:text-red-400">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
</div>
<h3 className="text-md font-semibold text-[#0f172a] dark:text-[#e6e9ef] mb-1.5">
Failed to render diagram
</h3>
<p className="text-[13px] leading-relaxed text-[#475569] dark:text-[#9aa6b8] mb-4">
The diagram contains malformed or cyclic data and could not be displayed correctly.
</p>
<button
onClick={() => this.setState({ hasError: false, error: null })}
className="rounded-lg bg-[#4f46e5] px-3.5 py-2 text-xs font-medium text-white hover:bg-[#4338ca] transition-colors"
>
Try Again
</button>
</div>
</div>
);
}

return this.props.children;
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Boundary never resets when the failing input changes.

hasError only clears via the "Try Again" button. There is no getDerivedStateFromProps/resetKeys mechanism, so:

  • After an error, supplying a new (valid) graph to the wrapped Diagram keeps the fallback UI shown until the user clicks "Try Again".
  • "Try Again" merely re-renders the same children; if the underlying error is deterministic, it immediately re-throws and returns to the fallback.

Consider accepting a resetKeys prop and clearing error state in componentDidUpdate when a key changes, so the boundary recovers automatically when the offending data is replaced. The wrap site in Diagram.tsx (Lines 680-684) is the consumer affected by this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/ErrorBoundary.tsx` around lines 29 - 71, The ErrorBoundary
fallback in ErrorBoundary should reset when the wrapped diagram data changes,
not only when the user clicks Try Again. Add a reset mechanism such as a
resetKeys prop and clear hasError/error when those keys change in
componentDidUpdate or via getDerivedStateFromProps, so new valid input can
recover automatically. Update the ErrorBoundary usage in Diagram to pass a
stable key derived from the graph/input data, and keep the existing fallback
render path and render() behavior intact.

Comment on lines +53 to +55
<h3 className="text-md font-semibold text-[#0f172a] dark:text-[#e6e9ef] mb-1.5">
Failed to render diagram
</h3>

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Tailwind CSS v4 include a text-md font-size utility by default?

💡 Result:

No, Tailwind CSS v4 does not include a text-md font-size utility class by default [1][2]. In Tailwind CSS, the standard utility for the base (medium) font size is text-base [1][3]. While other utilities follow an -md suffix convention (such as rounded-md or shadow-md), the font-size utility uses text-base to represent the base, default text size [3][4]. You can add a text-md utility if desired by defining it in your project's CSS using the @theme directive [5][6]: @theme { --text-md: 1rem; --text-md--line-height: 1.5rem; }

Citations:


Use text-base instead of text-md. Tailwind doesn’t define text-md by default, so this heading won’t get the intended font size unless a custom token is added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/ErrorBoundary.tsx` around lines 53 - 55, The heading in
ErrorBoundary’s render fallback is using a nonstandard Tailwind class, so update
the h3 styling from text-md to text-base in the ErrorBoundary component to match
the default Tailwind scale and ensure the intended font size is applied.

@DataDave-Dev DataDave-Dev merged commit 0eadbe6 into DataDave-Dev:main Jun 28, 2026
2 of 3 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.

2 participants