9.2 KiB
Architecture Overview
1. Executive Summary
Zui is a node-based visual editor for composing configs, templates, variables, and AI-generated content. The frontend is a React 18 SPA backed by a thin Express API for AI agent calls. All graph data is persisted in localStorage; the backend is stateless.
2. Technology Stack
| Layer | Technology |
|---|---|
| Frontend framework | React 18, TypeScript (strict), Vite 7 |
| Graph canvas | @xyflow/react v12 (React Flow) |
| State management | Zustand 5 (command/reducer pattern) |
| Styling | Tailwind CSS + Shadcn UI (@radix-ui/*) |
| Rich text | BlockNote 0.36 with custom blocks |
| Templating | Nunjucks |
| Graph traversal | graphology + graphology-traversal |
| Routing | React Router v6 |
| Backend | Node.js 20+, TypeScript, Express 4 |
| AI SDK | Vercel AI SDK v4 + @ai-sdk/openai |
| Testing | Vitest, React Testing Library |
3. Project Structure
frontend/src/
├── main.tsx # Entry: registers node types & config types, mounts app
├── app/
│ ├── canvas/ # CanvasPage, canvasStore (Zustand), canvasStore.test.ts
│ ├── kosmos/ # KosmosPage, sidebar, AI settings, KosmosContext
│ └── recollections/
│ ├── RecollectionsPage # Workspace list
│ ├── RecollectionLayout # Shared layout (sidebar + outlet)
│ ├── flux/ # Flux view (graph canvas)
│ ├── logos/ # Logos view (BlockNote rich text)
│ └── katalogos/ # Katalogos view (artifact gallery)
├── components/
│ ├── graph/ # AnimatedEdge, BaseNode, handles, keyboard shortcuts
│ ├── nodes/ # One folder per node type
│ └── ui/ # Shadcn primitives
├── hooks/ # useGraphStateWithHistory, useResizeHeight, …
└── lib/
└── graph/
├── nodeRegistry.ts # Mutable plugin registry
├── registerBuiltinNodes.tsx
├── nodeTypeBuilder.ts # Fluent builder for node descriptors
├── configTypes.ts # Output type registry (plantuml, markdown, wireframe)
├── rendering.ts # 3-step rendering pipeline contract
├── state.ts # Graph state types
└── canvasStore/ # Reducer + selectors
backend/src/
├── index.ts # Express app, CORS, routes, error handler
├── routes/agentRoutes.ts # POST /api/agent, POST /api/agent/stream
├── services/agentService.ts # LLM call logic (Vercel AI SDK)
├── repositories/cacheRepository.ts
├── models/index.ts
└── middleware/rateLimiter.ts
4. Key Architectural Patterns
4.1 Command / Reducer Store (canvasStore)
The Zustand canvas store uses a pure reducer (canvasStoreReducer) with a discriminated union of CanvasCommand types. All mutations go through dispatch(command). In dev mode every command is logged to the console.
The store has three slices:
graph— nodes and edges (the React Flow state)path— trigger pulse for edge animationui— renaming state, fullscreen node, pending connection
4.2 Delta-based Undo / Redo
useGraphStateWithHistory computes inverse deltas (only the nodes/edges that changed) rather than snapshotting the full graph. Maximum 100 history entries. Drag operations commit a pre-drag snapshot on onNodeDragStop.
4.3 Node Plugin Registry
Node types are registered via a mutable registry in nodeRegistry.ts. The built-ins are registered at app init from registerBuiltinNodes.tsx. Adding a new node type requires:
- Implement a React component for the node.
- Create a descriptor using
NodeTypeBuilder. - Call
registerNodeType(descriptor)— no edits to core code.
4.4 Fluent Builder for Node Descriptors
NodeTypeBuilder provides a type-safe method-chaining API (idPrefix, withInputOutput, classification, allowedSourceTypes, help, menu, withFullscreen, sourceRenderingLogic, …). Required fields are validated at .build().
4.5 Three-Step Rendering Pipeline
Defined as an explicit contract in rendering.ts:
- Resolve — source node (Config/Agent) produces
{ resolved, outputTypeId, reasoning? } - Render — output type handler converts the resolved string (PlantUML → Kroki SVG, Markdown → HTML, Wireframe → SVG)
- Display — the Rendering node shows an image viewport or scrollable HTML
4.6 Node Classification System
Four metaphorical classes drive the create menu grouping:
| Class | Node type | Metaphor |
|---|---|---|
psyche |
Variable | source / input |
physis |
Data (CSV) | physical data |
pneuma |
Config / Render | transformation |
archon |
Agent | AI / LLM |
4.7 Recollections (Workspaces)
Each workspace (recollection) is stored in localStorage:
| Key | Content |
|---|---|
zui_platform_recollections |
Metadata index (id, name, icon, timestamps) |
zui_graph_<id> |
Flux graph (nodes + edges) |
zui_logos_<id> |
BlockNote rich text document |
zui_render_cache_<id> |
Rendered artifact cache |
Legacy key prefix emanations is migrated to recollections on first load.
4.8 User-configurable AI Connection
KosmosContext persists aiConnection (provider, baseURL, model, apiKey) in localStorage under zui_ai_connection. The Agent node forwards this to the backend with each request, so users can switch between OpenAI and local LLMs (LM Studio, Ollama) from the UI without env variable changes.
4.9 Diagram Rendering via Kroki
PlantUML diagrams are rendered by proxying to https://kroki.io via /api/kroki/plantuml/svg. This proxy is configured in both the Vite dev server and Nginx production config. Timeout: 15 seconds.
5. Data Flow
User interaction
→ dispatch(CanvasCommand) [canvasStore reducer]
→ Zustand state update [graph / path / ui slices]
→ React re-render [React Flow nodes/edges]
Rendering node triggered
→ Resolve source node [Config/Agent → resolved string]
→ Render output type [plantuml/markdown/wireframe]
→ Kroki proxy (PlantUML) [GET /api/kroki/plantuml/svg]
→ Agent backend (AI) [POST /api/agent or /api/agent/stream]
→ Display artifact [image / HTML / SVG viewport]
Agent node (streaming)
→ POST /api/agent/stream [Express → agentService]
→ Vercel AI SDK streams SSE [backend → frontend]
→ useStreamingContent hook consumes [updates node data]
6. Backend Architecture
The backend is intentionally thin and stateless. It has three responsibilities:
- Proxy AI calls — forwards prompts to the configured LLM (OpenAI or OpenAI-compatible).
- Stream responses —
POST /api/agent/streamusespipeTextStreamToResponsefrom the Vercel AI SDK. - Health check —
GET /healthfor Docker/orchestration readiness probes.
The InMemoryCache and rateLimiter middleware are implemented but not yet wired into the agent routes.
7. Contribution Workflow
- Fork the repository and clone locally.
- Install dependencies:
cd frontend && npm installandcd backend && npm install. - Run the dev servers (see README).
- Create a feature branch:
git checkout -b feat/your-feature. - Follow the Coding Standards (see CONTRIBUTING.md).
- Submit a Pull Request with a descriptive title, linked issue(s), updated tests, and documentation changes if relevant.
PR Review Checklist
- Readability: clear naming, minimal nesting
- TypeScript: no
any, strict types throughout - Performance: no unnecessary re-renders, proper memoization
- Accessibility: semantic HTML, ARIA attributes where needed
- Security: no hardcoded secrets, input validated at boundaries
8. Coding Standards
- TypeScript: strict mode (
strict,noImplicitAny,noUnusedLocals). - Naming: PascalCase for components, camelCase for functions/variables, UPPER_SNAKE_CASE for constants.
- File structure: domain-grouped under
src/app/<domain>/andsrc/components/<category>/. - Formatting: Prettier via
frontend/prettier.config.cjs; line length ≤ 120. - Comments: JSDoc for public APIs; inline only where logic is non-obvious.
9. Quick Reference for AI Agents
| Topic | Location |
|---|---|
| Entry point | frontend/src/main.tsx |
| Canvas store | frontend/src/app/canvas/canvasStore.ts |
| Store reducer | frontend/src/app/canvas/canvasStoreReducer.ts |
| Node registry | frontend/src/lib/graph/nodeRegistry.ts |
| Node builder | frontend/src/lib/graph/nodeTypeBuilder.ts |
| Rendering pipeline | frontend/src/lib/graph/rendering.ts |
| Built-in node registration | frontend/src/lib/graph/registerBuiltinNodes.tsx |
| AI settings context | frontend/src/app/kosmos/KosmosContext.tsx |
| Backend entry | backend/src/index.ts |
| Agent service | backend/src/services/agentService.ts |
This document is a living reference; update it when architectural decisions change.