Constraining Gemini 2.5 to Geometry-Validated Diagram Schemas
Language models excel at understanding system topologies conceptually, but generate malformed geometry, illegal coordinates, and overlapping bounding boxes when outputting raw canvas elements directly.
Risk: Overlapping nodes, unreadable text collisions, broken connectors, or Excalidraw canvas runtime rendering crashes.
Created a dual-stage generation pipeline using Google Gemini 2.5 (@google/genai) and Zod (lib/validate.ts). Gemini outputs a high-level structured JSON graph specifying nodes, dimensions, labels, and connections. A validation layer verifies geometry bounds, ensures minimum margins between nodes to prevent overlapping, validates connector endpoints, and passes the clean schema to RenderAIDiagram.tsx to generate native Excalidraw elements.
Zero canvas rendering exceptions and cleanly spaced, legible architecture diagrams, flowcharts, and mindmaps across all supported prompt categories.
import { z } from "zod";
export const DiagramNodeSchema = z.object({
id: z.string(),
type: z.enum(["rectangle", "ellipse", "diamond", "text"]),
label: z.string().min(1).max(120),
x: z.number().min(0).max(4000),
y: z.number().min(0).max(4000),
width: z.number().min(60).max(600),
height: z.number().min(40).max(400),
backgroundColor: z.string().default("transparent"),
strokeColor: z.string().default("#1e1e1e"),
});
export const DiagramEdgeSchema = z.object({
id: z.string(),
fromNodeId: z.string(),
toNodeId: z.string(),
label: z.string().optional(),
style: z.enum(["arrow", "line"]).default("arrow"),
});
export const AIDiagramSchema = z.object({
title: z.string(),
type: z.enum(["architecture", "flowchart", "mindmap"]),
nodes: z.array(DiagramNodeSchema).min(1).max(50),
edges: z.array(DiagramEdgeSchema).default([]),
});
export function validateAndAdjustGeometry(diagram: z.infer<typeof AIDiagramSchema>) {
const validated = AIDiagramSchema.parse(diagram);
const nodeMap = new Map(validated.nodes.map((n) => [n.id, n]));
// Verify all edge references exist to prevent orphan pointers
const validEdges = validated.edges.filter(
(edge) => nodeMap.has(edge.fromNodeId) && nodeMap.has(edge.toNodeId)
);
return { ...validated, edges: validEdges };
}Enforces strict type safety and spatial integrity on every diagram emitted by Gemini 2.5 before passing to RenderAIDiagram.tsx.