Inspiration

Architecture discussions live in two worlds: messy whiteboard sketches where ideas are born, and rigid diagram tools where they go to die. Miro is great for sticky notes but terrible for system design. D2 is amazing for code-driven diagrams but lives in a lonely editor. We kept losing context switching between them during design reviews.

Eunoia — Greek for "beautiful thinking" — is our attempt to unify them. One infinite canvas where freehand brainstorming and structured D2 modeling coexist.

What it does

Eunoia is a real-time collaborative whiteboard built for engineering teams:

  • Infinite 60 FPS canvas — SVG with grid-cell spatial culling, handles 3,000+ shapes while panning/zooming smoothly
  • D2 code-to-canvas — write D2 in an embedded Monaco editor and it compiles to fully interactive native vector elements, not flat images. Differential reconciliation keeps edits instant (<500ms for 50 nodes)
  • Real-time multiplayer — Yjs CRDTs for state sync with sub-50ms broadcast, live cursors via Redis pub/sub, per-user undo/redo
  • Zero work loss — in-memory Y.Docs with debounced, fflate-compressed snapshots persisted to PostgreSQL BYTEA, plus history + one-click restore
  • Engineering-native — shapes, magnetic connectors, pressure-sensitive ink, transform gizmos, R2 image uploads, room links, password-locked rooms

How we built it

Frontend: Next.js 16 (React 19, App Router), Tailwind v4, sketch-theme UI. Canvas is layered SVG (background / scene / interactive) with rbush culling. D2 authoring in Monaco with custom syntax highlighting + debounced compile pipeline.

Sync server: Elysia + ws on Bun/Node. Each room is a live Y.Doc — WebSocket sync via y-protocols, awareness for presence, JSON telemetry for cursors. Redis pub/sub fans out cursors across instances. Snapshots are debounced, compressed, and pruned (newest + newest-K + 30-day window).

Persistence & infra: PostgreSQL + Prisma (rooms, snapshots, image metadata only — bytes live in Cloudflare R2 via presigned PUT + HEAD verify). Go D2-compiler service bundles dagre / elk / tala in-process, tier-gated server-side. Zod validates every HTTP, WebSocket, and compiler boundary.

Challenges we ran into

  • Performance at scale: naive SVG rendering died at ~500 shapes. We built a culling + layered pipeline to hold 60 FPS at 3,000+.
  • D2 reconciliation: full re-renders flickered on every keystroke. Switching to AST-to-native diffing fixed it.
  • Sync correctness: interleaving CRDT updates, awareness, and cursor telemetry over one socket caused race conditions. We separated data channels and validated telemetry strictly (close 1008 on malformed).
  • Persistence safety: avoiding DB write storms while guaranteeing crash recovery took careful debounce + force-flush-on-restore logic (peers dropped with 4100 to reload cleanly).

Accomplishments that we're proud of

  • 60 FPS pan/zoom at 3,000+ vector shapes
  • Sub-50ms peer sync in local benchmarks
  • D2 as first-class canvas citizens, not embedded PNGs
  • True server persistence — close the tab, your board survives
  • Full open-core stack running on docker-compose up

What we learned

CRDTs are magical until you need presence, undo, and history on top — then product decisions matter more than algorithms. We also learned that code-first diagramming clicks instantly with engineers: give them text, diffs, and versioning, and they actually keep diagrams up to date. And Zod at every boundary saved us more debugging hours than any other choice.

What's next for Eunoia

Pro/Enterprise SaaS (ELK + TALA layouts), AI generation from prompt → D2 → canvas, workspaces/RBAC + audit log, version branching for diagrams, offline-first editing with conflict UI, and Figma / GitHub / Terraform importers. Open-sourcing Community Edition to grow the D2 ecosystem.

Built With

Share this project:

Updates

Submission history