Engineering Case Study

AI-Native Collaborative Whiteboard: Real-Time CRDT Canvas & Structured AI Architecture Engine

An in-depth engineering case study on designing an infinite canvas that pairs conflict-free multi-user CRDT synchronization with schema-validated AI tool execution and human-in-the-loop staged diffing.

Role

Lead Systems & Frontend Architect

Status

In Active Development

Timeline

February 2026 – Present

Architecture

Yjs CRDT + WebSocket Gateway

Table of Contents10 Sections
01//OVERVIEW

Beyond Static Drawing: The Semantic Architecture Canvas

Software architecture diagramming tools have historically operated as glorified digital drawing boards. Whether using Miro, Excalidraw, or Lucidchart, the underlying canvas treats components as dumb geometric vectors — rectangles, circles, and freeform arrows. These diagrams rapidly rot: they drift from code within weeks, lack semantic understanding of data protocols, and cannot be programmatically validated.

The AI-Native Collaborative Whiteboard was designed to bridge this chasm. Instead of treating the canvas as pixels, the platform stores an Abstract Syntax Tree (AST) of the architecture. Each node represents a concrete engineering component (API Gateway, Microservice, Cache, Database, Worker Queue) with typed metadata (ports, protocols, scaling policies), while edges represent communication contracts (gRPC, REST, Kafka, WebSocket).

Because the canvas state is a strongly typed graph, an AI copilot can meaningfully inspect it, reason about architectural bottlenecks (e.g., un-replicated databases, missing rate-limiting layers), and propose surgical modifications using discrete, validated tool operations.

02//THE PROBLEM & CONSTRAINTS

Why Modern Architecture Review Needs a Structured Canvas

Building an interactive, collaborative architecture workspace exposed several critical engineering problems in modern team workflows:

  • ▸Diagram-to-Implementation Drift: Static architecture diagrams are disconnected from runtime reality and codebases, turning into stale documentation that engineers ignore.
  • ▸AI Operating Outside the Spatial Context: Most engineering AI assistants live in disconnected chat panels. They cannot visually inspect an architecture layout, perceive component topologies, or make direct, atomic edits to a canvas.
  • ▸Concurrent Multi-User State Divergence: Naïve WebSocket sync without CRDT primitives results in race conditions, overwrites, and cursor flickering when multiple engineers modify connected nodes simultaneously.
  • ▸Hallucinated or Destructive AI Mutations: Allowing an LLM to directly overwrite canvas JSON without validation guarantees corrupted graphs, detached edge pointers, and broken coordinate systems.
03//ENGINEERING GOALS

System Design Requirements

Typed AST Canvas Schema

100% of nodes, edges, and annotations conform to strict runtime Zod schemas with zero unvalidated property mutations.

Conflict-Free Multi-User Synchronization

State convergence guaranteed by Yjs CRDTs over WebSockets with sub-50ms presence and cursor tracking.

Human-in-the-Loop Approval Barrier

AI suggestions are rendered in a distinct staging layer showing visual additions and deletions, requiring developer sign-off before committing to the shared doc.

Fluid 60 FPS Viewport Performance

Spatial indexing and viewport culling ensure smooth interaction even on complex boards containing hundreds of nodes and relationships.

04//SYSTEM ARCHITECTURE

Multi-Tier Reactive Topology

The application architecture cleanly separates the real-time client canvas, the state synchronization gateway, the AI tool runner, and persistence storage:

whiteboard-topologycrdt-sync-engine
Client Application (Next.js & React 19)(Infinite Canvas, Spatial Index & Presence Layer)
Local Y.Doc · Virtualized SVG/Canvas Renderer · Staged Diff Preview · Ephemeral Cursor Awareness
↓ HTTP / REST / Secure Cookies
Real-Time Gateway (WebSocket & Y-Websocket)(Node.js & ws server)
Bidirectional Binary CRDT Updates · Token-Based Handshake · Presence Broadcasting
↓ HTTP / REST / Secure Cookies
AI Tool Execution Engine(Agent Pipeline & Zod Validator)
Graph Serialization · AST Operation Synthesis · Staged Diff Generation · Safety Sandbox
↓ Data Persistence & Message Queue
PostgreSQL & Prisma

Workspaces · Board Metadata · Snapshot Archives · RBAC Permissions

Redis Cluster

Cross-Server WebSocket Fan-Out · Session Revocation · Rate Limiting

05//CORE ENGINEERING CHALLENGES
04 Deep Dives

Difficult Technical Problems & Solutions

CHALLENGE 01

Constraining AI Mutations to Typed Canvas AST Operations

The Challenge & Risk

Language models excel at generating natural language but often hallucinate malformed JSON structures, invalid coordinate numbers, or dangling edge references when attempting to generate whole canvas states.

Risk: Corrupted board state, infinite render loops in the frontend canvas, or detached connectors floating in space.

The Approach

Rather than asking the LLM to output entire canvas documents, the model is equipped with a strictly typed toolset: `createNode()`, `updateNode()`, `deleteNode()`, `createEdge()`, `deleteEdge()`, and `moveNode()`. Each tool argument is validated via a discriminated union Zod schema at runtime. If any operation references a nonexistent node or an illegal protocol, the pipeline rejects the batch and prompts the agent to rectify the operation.

The Result

✓ 100% syntactically valid canvas modifications with zero board corruption and zero dangling references across thousands of automated test runs.

packages/ai-agent/src/schema/canvas-operations.ts
import { z } from "zod"

export const CanvasNodeSchema = z.object({
  id: z.string().uuid(),
  type: z.enum(["service", "database", "gateway", "queue", "cache", "client"]),
  label: z.string().min(1).max(64),
  position: z.object({ x: z.number(), y: z.number() }),
  metadata: z.record(z.string(), z.unknown()).default({}),
})

export const CanvasEdgeSchema = z.object({
  id: z.string().uuid(),
  source: z.string().uuid(),
  target: z.string().uuid(),
  protocol: z.enum(["http", "grpc", "ws", "sql", "amqp"]),
  label: z.string().optional(),
})

export const CanvasOperationSchema = z.discriminatedUnion("action", [
  z.object({ action: z.literal("createNode"), node: CanvasNodeSchema }),
  z.object({
    action: z.literal("updateNode"),
    id: z.string().uuid(),
    patch: CanvasNodeSchema.partial().omit({ id: true }),
  }),
  z.object({ action: z.literal("deleteNode"), id: z.string().uuid() }),
  z.object({ action: z.literal("createEdge"), edge: CanvasEdgeSchema }),
  z.object({ action: z.literal("deleteEdge"), id: z.string().uuid() }),
])

export type CanvasOperation = z.infer<typeof CanvasOperationSchema>

Enforces strict type safety on every discrete operation emitted by the AI copilot before any mutation touches the whiteboard state.

CHALLENGE 02

Conflict-Free Real-Time Synchronization with Yjs & WebSockets

The Challenge & Risk

When multiple engineers simultaneously drag nodes, edit labels, or connect services, centralized database writes or naive WebSocket broadcasts lead to race conditions and last-write-wins data loss.

Risk: Overwritten architecture components, desynchronized node coordinates, and frustrating editing conflicts.

The Approach

Adopted Yjs Conflict-Free Replicated Data Types (CRDTs). The board state is modeled as shared `Y.Map` structures for nodes and edges nested inside a root `Y.Doc`. Concurrent updates are deterministically resolved on the client and server using logical timestamps without locking. Ephemeral presence states (remote cursor positions and active selection highlights) are transmitted through a lightweight awareness protocol that bypasses database persistence.

The Result

✓ Sub-50ms peer-to-peer cursor tracking and zero merge conflicts during simultaneous multi-user board refactoring.

apps/web/src/lib/canvas/sync-provider.ts
import * as Y from "yjs"
import { WebsocketProvider } from "y-websocket"

export class CanvasSyncProvider {
  public doc: Y.Doc
  public nodes: Y.Map<any>
  public edges: Y.Map<any>
  private provider: WebsocketProvider

  constructor(boardId: string, wsUrl: string, authToken: string) {
    this.doc = new Y.Doc()
    this.nodes = this.doc.getMap("nodes")
    this.edges = this.doc.getMap("edges")

    this.provider = new WebsocketProvider(wsUrl, boardId, this.doc, {
      params: { auth: authToken },
    })

    this.provider.awareness.setLocalStateField("user", {
      name: "Vishal Gupta",
      color: "#10b981",
      cursor: null,
    })
  }

  public updateCursor(x: number, y: number) {
    this.provider.awareness.setLocalStateField("cursor", { x, y })
  }
}

Initializes local CRDT state and binds high-frequency cursor tracking directly to the ephemeral awareness channel, keeping the persistent Y.Doc clean.

CHALLENGE 03

Human-in-the-Loop Staged Execution Barrier

The Challenge & Risk

Autonomous AI edits directly modifying active production boards can cause immediate confusion among collaborating engineers, especially if an agent reorganizes a layout or removes a critical service unintentionally.

Risk: Loss of developer trust, unexpected board mutations during client demos, and difficult undo/redo recovery.

The Approach

Engineered a staged mutation pipeline. When the AI agent completes an architectural task (such as 'Add a Redis cache in front of Postgres and route read traffic through it'), the proposed operations are placed in a staging buffer. The canvas UI renders the changes as a visual diff (green dashed lines for new nodes/edges, yellow for modifications, red for deletions) and presents an approval drawer. The Yjs document is only updated once the developer clicks 'Approve Changes'.

The Result

✓ Total human control over autonomous changes, fostering safe AI collaboration without fear of disruptive state changes.

apps/web/src/hooks/use-staged-mutations.ts
export interface StagedDiff {
  operations: CanvasOperation[]
  summary: string
  status: "pending" | "applied" | "rejected"
}

export function applyStagedOperations(
  doc: Y.Doc,
  staged: StagedDiff
) {
  doc.transact(() => {
    const nodes = doc.getMap("nodes")
    const edges = doc.getMap("edges")

    for (const op of staged.operations) {
      switch (op.action) {
        case "createNode":
          nodes.set(op.node.id, op.node)
          break
        case "updateNode":
          const current = nodes.get(op.id)
          if (current) nodes.set(op.id, { ...current, ...op.patch })
          break
        case "createEdge":
          edges.set(op.edge.id, op.edge)
          break
      }
    }
  }, "ai-agent-mutation")
}

Executes all approved operations inside an atomic Yjs transaction tagged with 'ai-agent-mutation', enabling instant single-click undo if needed.

CHALLENGE 04

High-Performance Viewport Virtualization & Spatial Indexing

The Challenge & Risk

Rendering hundreds of architecture nodes, bezier connectors, and animated telemetry indicators using standard React DOM elements caused frame drops below 25 FPS during rapid panning and zooming.

Risk: Sluggish, unresponsive canvas interactions that degrade user experience on complex enterprise topologies.

The Approach

Built a 2D bounding-box spatial index (R-Tree / QuadTree). The canvas viewport continuously tracks its visible world coordinates and queries the spatial index. Nodes and edges outside the active viewport are culled from the DOM rendering tree entirely. Complex connector paths are computed using memoized bezier mathematics and rendered via an optimized SVG layer.

The Result

✓ Rock-solid 60 FPS viewport navigation and smooth zooming even on large boards with over 500 connected architecture elements.

06//CODE PRIMITIVES & SECURITY

Security, Authorization & Guardrails

WebSocket handshakes require cryptographically signed session tokens verifying workspace membership before establishing socket communication. Tenant-level isolation prevents cross-organization board inspection at the network gateway.

To protect against prompt injection or malicious agent tool usage, the AI tool executor runs within a sandboxed environment with strict payload bounding: an agent cannot exceed 20 operations per prompt turn, and operations cannot inject arbitrary HTML or unvetted scripts into node metadata.

07//DATA FLOW PIPELINES

The Human-in-the-Loop AI Architecture Lifecycle

Every interaction follows a deterministic path from user intent to verified board commit:

1
Developer issues prompt or reviews architecture

User asks AI: 'Introduce a Redis cache between API Gateway and PostgreSQL and optimize read queries.'

2
Canvas AST serialized with spatial context

Active board nodes, connections, and metadata are extracted as a structured JSON graph.

3
AI Agent selects tools and generates typed operations

Agent emits discrete operations (createNode, createEdge, updateNode) validated by Zod schemas.

4
Staged Visual Diff rendered on canvas

New nodes and modified edges appear in high-contrast diff outlines alongside an inspection drawer.

5
Human Approval & Atomic CRDT Commit

Developer reviews and confirms; operations are committed atomically to the Yjs doc and synced to all peers.

08//TECHNOLOGY STACK

Architecture Stack

Frontend & Canvas

Next.js 16App Router & React Server Components
React 19Concurrent rendering & optimistic state updates
TypeScript 5Strict end-to-end typed canvas AST definitions
Tailwind CSS v4Design tokens & dark-mode styling
MotionHardware-accelerated micro-interactions

Real-Time & Collaboration

YjsConflict-Free Replicated Data Types (CRDTs)
WebSocketsLow-latency bidirectional document synchronization
Redis Pub/SubMulti-instance WebSocket session fan-out & awareness

AI & Tool Execution Engine

AI Tool CallingSchema-bound structured tool orchestration
ZodRuntime JSON AST schema validation & constraint enforcement
AST Diff EngineVisual node & edge mutation delta calculator

Storage & Infrastructure

PostgreSQLRelational storage for boards, workspaces & version snapshots
Prisma ORMType-safe database migrations & relational querying
DockerContainerized development & multi-service topology
09//RESULTS & OUTCOMES

Implementation Status & Verified Outcomes

✓ ✓ Implemented: Real-Time Collaborative Canvas

Full multi-user editing powered by Yjs CRDTs over WebSockets with real-time awareness and remote cursor positioning.

✓ ✓ Implemented: Structured AI Tool Execution

Deterministic JSON AST mutations (createNode, updateNode, deleteNode, createEdge, deleteEdge, moveNode) backed by Zod schema validation.

✓ ✓ Implemented: Human-in-the-Loop Review Engine

Visual staged diff rendering with color-coded additions/deletions and atomic transactional commit to shared state.

✓ → In Progress: Automated Architecture Reviewer

Static analysis heuristic engine that inspects canvas graph connectivity to flag single points of failure and unbuffered ingestion spikes.

✓ → Planned: Model Context Protocol (MCP) Integration

Exposing whiteboard tools over MCP so external desktop IDE agents (Cursor, Claude Code) can directly inspect and edit architecture boards.

10//LESSONS LEARNED

Engineering Reflections

1. Structure is the antidote to hallucination: Language models should never be asked to write unstructured state documents. Giving the agent fine-grained, schema-validated tool primitives turned a brittle prototype into a deterministic production system.

2. CRDTs simplify distributed consensus: Choosing Yjs early saved countless hours that would have been wasted debugging last-write-wins race conditions and custom operational transformation servers.

3. Visual diffs create developer trust: Engineers are hesitant to let AI touch their architecture. Giving them an explicit, color-coded visual diff preview prior to committing mutations eliminated hesitation and made the tool a joy to use.