2026-06-19 22:20:22 +00:00
# Architecture Specification – web-cad
**Project: ** web-cad – Web-based 2D-CAD for Event Seating Plans
**Phase: ** 2 (Architecture)
**Date: ** 2026-06-19
**Status: ** Draft v1
**Based on: ** Requirements Specification v5 (2026-06-19)
---
## 1. System Architecture Overview
### 1.1 High-Level Architecture Diagram
```
┌─────────────────────────────────────────────────────────────────────────┐
│ Browser (Client) │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Frontend Container (PWA – React + Vite) │ │
│ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │ │
│ │ │ Canvas 2D │ │ React UI │ │ KI Copilot Panel │ │ │
│ │ │ Renderer │ │ (Ribbons, │ │ (Chat/Command Interface) │ │ │
│ │ │ + rbush │ │ Side Panel, │ │ → sends to Backend │ │ │
│ │ │ Spatial IDX │ │ Command Line)│ │ ← receives function │ │ │
│ │ │ │ │ │ │ calls & results │ │ │
│ │ └──────┬──────┘ └──────┬───────┘ └────────────┬─────────────┘ │ │
│ │ ┌──────┴──────────────────┴──────────────────────┴─────────────┐ │ │
│ │ │ Application State Layer │ │ │
│ │ │ ┌──────────┐ ┌────────────┐ ┌───────────┐ ┌───────────┐ │ │ │
│ │ │ │ Yjs Doc │ │ Undo/Redo │ │ Plugin │ │ IndexedDB │ │ │ │
│ │ │ │ (CRDT) │ │ History │ │ Runtime │ │ (Offline) │ │ │ │
│ │ │ └────┬─────┘ └────────────┘ └───────────┘ └───────────┘ │ │ │
│ │ └───────┼───────────────────────────────────────────────────────┘ │ │
│ └──────────┼─────────────────────────────────────────────────────────┘ │
└─────────────┼─────────────────────────────────────────────────────────────┘
│ WebSocket (y-websocket) │ REST API (HTTPS)
│ │
┌─────────────┼───────────────────────────────────┼─────────────────────────┐
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ CAD-Backend Container (Node.js + Fastify) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────────────────┐ │ │
│ │ │ WebSocket │ │ REST API │ │ KI Copilot Service │ │ │
│ │ │ Server │ │ (Fastify + │ │ (OpenAI-compatible │ │ │
│ │ │ (y-websocket │ │ OpenAPI/ │ │ endpoint proxy + │ │ │
│ │ │ + y-leveldb)│ │ Swagger) │ │ Function Calling) │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └────────────┬──────────────┘ │ │
│ │ ┌──────┴──────────────────┴──────────────────────┴───────────────┐ │ │
│ │ │ Backend Core Services │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │ │ │
│ │ │ │ Auth │ │ Project │ │ Plugin │ │ CAD-Function │ │ │ │
│ │ │ │ Service │ │ Service │ │ Registry │ │ Registry │ │ │ │
│ │ │ │ (bcrypt/ │ │ (CRUD, │ │ (manifest │ │ (for LLM │ │ │ │
│ │ │ │ argon2) │ │ export) │ │ mgmt) │ │ tool use) │ │ │ │
│ │ │ └────┬─────┘ └────┬─────┘ └───────────┘ └──────────────┘ │ │ │
│ │ └───────┼─────────────┼─────────────────────────────────────────┘ │ │
│ │ ┌───────┴─────────────┴──────────────────────────────────────────┐ │ │
│ │ │ SQLite (better-sqlite3) + LevelDB (Yjs persistence) │ │ │
│ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ projects | layers | elements | users | sessions | │ │ │ │
│ │ │ │ plugins | versions | settings | units_config │ │ │ │
│ │ │ └────────────────────────────────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ External Services │ │
│ │ ┌──────────────────────┐ ┌──────────────────────────────────────┐ │ │
│ │ │ OpenAI-compatible │ │ Docker Volume (data persistence) │ │ │
│ │ │ LLM API (user-prov.) │ │ /data/db (SQLite) + /data/yjs │ │ │
│ │ └──────────────────────┘ └──────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
│
│ Deployed via Coolify on coolify-01
│ Domain: cad.media-on.de
│ SSL: Let's Encrypt via Traefik
```
### 1.2 Container Architecture
| Container | Technology | Port | Purpose |
|-----------|-----------|------|---------|
| **cad-frontend ** | Nginx + React static build | 8080 | Serves PWA, static assets, Service Worker |
| **cad-backend ** | Node.js + Fastify | 3001 | REST API, WebSocket server, KI proxy, SQLite |
Both containers deployed via Coolify on coolify-01 (46.225.91.159).
TLS terminated by Traefik (Let's Encrypt).
### 1.3 Data Flow Overview
```
User Action → Canvas Event → Yjs Doc Update → WebSocket → y-leveldb (persist)
↓
React UI re-render
rbush index rebuild (incremental)
Canvas 2D paint (requestAnimationFrame)
User Input (KI) → Frontend KI Panel → Backend KI Service → OpenAI-compat API
← Function Call response ←
→ CAD-Function-Registry executes → Canvas
← Result feedback ← User
External Client → REST API → Fastify route → Service layer → SQLite
↓
OpenAPI/Swagger docs
```
---
## 2. Component Design
### 2.1 Frontend (React + Vite PWA)
**Technology: ** React 19 + Vite 6 + TypeScript 5
**License: ** MIT
**Role: ** API client, rendering engine, user interaction, offline support
#### 2.1.1 Canvas Renderer
| Sub-component | Responsibility | Technology |
|---------------|---------------|------------|
| **RenderEngine ** | Main render loop, requestAnimationFrame, double-buffering | Canvas 2D API |
| **SpatialIndex ** | Bounding box queries, hit testing, viewport culling | rbush (MIT) |
| **LayerManager ** | Layer rendering order, visibility, lock states | Internal |
| **ZoomPanController ** | Affine transforms, zoom-to-fit, zoom-to-window | Canvas transforms |
| **SnapEngine ** | Snap-to-grid, snap-to-endpoint, snap-to-midpoint, polar tracking | Internal |
| **SelectionEngine ** | Single/window/crossing/fence selection, quick-select | Internal + rbush |
**Rendering Pipeline: **
```
1. Calculate viewport transform (pan offset, zoom factor)
2. Query rbush for elements intersecting viewport bbox
3. Sort by layer order (locked layers last for overlay)
4. For each visible layer:
a. Set layer alpha (if partially transparent)
b. For each element in layer (from rbush result):
- Apply element transform (position, rotation, scale)
- Stroke/fill based on element type (line, circle, polyline, etc.)
- Render selection highlight if selected
- Render handles/grips if in edit mode
5. Render overlays: snap markers, cursor crosshair, selection box, grid
6. requestAnimationFrame → repeat if dirty
```
**Performance Strategy for 50k+ elements: **
- rbush spatial index: only render elements within viewport bbox (viewport culling)
- Dirty rectangle tracking: only repaint changed regions when possible
- Layer-based culling: skip invisible/locked layers entirely
- Batch rendering: group elements by type for fewer state changes
- Off-screen canvas for static layers (grid, background image)
- Incremental rbush updates on element add/remove/modify
#### 2.1.2 React UI Layer
| Component | Description |
|-----------|-------------|
| **RibbonBar ** | Top toolbar with CAD functions (Draw, Modify, Annotate, etc.) |
| **SidePanel ** | Right panel with tabs: Blocks, Layers, Properties, KI Copilot |
| **CommandLine ** | Bottom command input bar with autocomplete |
| **StatusBar ** | Bottom status: coordinates, snap toggles, active layer, units |
| **CanvasArea ** | Main canvas with zoom/pan controls |
| **ModalDialogs ** | Settings, import/export, version history, permissions |
| **ContextMenu ** | Right-click context menus |
| **BlockEditor ** | In-place block editing within drawing context |
**WCAG 2.1 AA compliance: ** All UI elements except Canvas are WCAG 2.1 AA compliant.
Canvas has inherent barriers (visual drawing); Command Line provides text alternative.
#### 2.1.3 Yjs CRDT Client
```
┌───────────────────────────────────────┐
│ Yjs Document (Y.Doc) │
│ ┌─────────┐ ┌─────────────────────┐ │
│ │ Y.Map │ │ Y.Array │ │
│ │ project │ │ elements │ │
│ │ meta │ │ (CRDT array of │ │
│ │ │ │ element objects) │ │
│ └─────────┘ └─────────────────────┘ │
│ ┌─────────┐ ┌─────────────────────┐ │
│ │ Y.Map │ │ Y.Map │ │
│ │ layers │ │ blocks │ │
│ └─────────┘ └─────────────────────┘ │
└───────────────┬───────────────────────┘
│ y-websocket provider
▼
WebSocket Server (backend)
```
**Yjs Document Structure: **
``` typescript
interface CADYjsDoc {
projectMeta : Y.Map < { // Project-level metadata
name : string ,
units : 'mm' | 'cm' | 'm' | 'inches' | 'feet' ,
createdAt : string ,
updatedAt : string ,
} >
layers : Y.Map < string , Y.Map < { // layerId -> layer data
name : string ,
visible : boolean ,
locked : boolean ,
color : string ,
lineType : string ,
transparency : number ,
} > >
elements : Y.Array < CADElement > // Ordered array of all elements
blocks : Y.Map < string , Y.Array < CADElement > > // blockId -> block elements
blocksInstances : Y.Array < BlockInstance > // Block placements in drawing
}
```
#### 2.1.4 Undo/Redo System
- **History stack:** Array of Yjs update bytes (using Y.encodeStateAsUpdate)
- **Stack size:** Configurable (default: 100)
- **Each undo/redo:** Apply/reverse Yjs update to Y.Doc
- **KI operations:** Appear as single entries in undo history (atomic)
- **Group operations:** Drag-and-drop produces single undo entry (debounced)
#### 2.1.5 PWA / Offline Support
| Feature | Implementation |
|---------|---------------|
| Service Worker | vite-plugin-pwa (Workbox-based, MIT) |
| Caching | App shell (cache-first), API data (stale-while-revalidate) |
| IndexedDB | idb library (MIT) for offline element cache |
| Offline sync | On reconnect: Yjs CRDT merge + WebSocket re-sync |
| Install prompt | manifest.json with icons, theme color, standalone display |
#### 2.1.6 i18n
- **Framework:** i18next (MIT) + react-i18next (MIT)
- **Initial languages:** German (de), English (en)
- **Translation files:** /src/locales/{de,en}/translation.json
- **Architecture separation:** Architecture strings are separate from UI strings
### 2.2 Backend (Node.js + Fastify)
**Technology: ** Node.js 22 LTS + Fastify 5 + TypeScript 5
**License: ** MIT (Fastify), MIT (Node.js)
**Role: ** API server, WebSocket server, KI proxy, persistence, auth
#### 2.2.1 REST API Layer (Fastify)
```
┌─────────────────────────────────────────┐
│ Fastify Server (port 3001) │
│ ┌──────────┐ ┌───────────────────────┐ │
│ │ CORS │ │ Rate Limiting │ │
│ │ Plugin │ │ (@fastify/rate-limit) │ │
│ │ │ │ 100 req/min per user │ │
│ └──────────┘ └───────────────────────┘ │
│ ┌──────────┐ ┌───────────────────────┐ │
│ │ JWT/Auth │ │ OpenAPI/Swagger │ │
│ │ Plugin │ │ (@fastify/swagger) │ │
│ │ (cookie) │ │ Auto-generated docs │ │
│ └──────────┘ └───────────────────────┘ │
│ ┌──────────────────────────────────────┐│
│ │ Route Groups ││
│ │ /api/auth/* → AuthController ││
│ │ /api/projects/* → ProjectController││
│ │ /api/layers/* → LayerController ││
│ │ /api/elements/* → ElementController││
│ │ /api/blocks/* → BlockController ││
│ │ /api/import/* → ImportController ││
│ │ /api/export/* → ExportController ││
│ │ /api/plugins/* → PluginController ││
│ │ /api/ai/* → AIController ││
│ │ /api/users/* → UserController ││
│ │ /api/settings/* → SettingsController││
│ └──────────────────────────────────────┘│
└─────────────────────────────────────────┘
```
**API-First Design Principles: **
- All functionality accessible via REST API
- OpenAPI/Swagger auto-generated from Fastify schemas
- UI is one API client among potentially many
- External integrations (Event-Management, CRM) use same API
- Webhooks for project events (export finished, project created, etc.)
#### 2.2.2 WebSocket Server (y-websocket)
```
┌─────────────────────────────────────────┐
│ WebSocket Server (port 3001/ws) │
│ ┌─────────────────────────────────────┐ │
│ │ y-websocket server │ │
│ │ ├── Room: project:{projectId} │ │
│ │ │ ├── User A (Planner) │ │
│ │ │ ├── User B (Planner) │ │
│ │ │ └── User C (Viewer) │ │
│ │ ├── Room: project:{projectId} │ │
│ │ │ └── User D (Planner) │ │
│ │ └── ... │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ y-leveldb persistence │ │
│ │ /data/yjs/{projectId}.ldb │ │
│ │ Single source of truth for CRDT │ │
│ └─────────────────────────────────────┘ │
│ ┌─────────────────────────────────────┐ │
│ │ Awareness (cursor positions, │ │
│ │ active user list, selections) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
**WebSocket Protocol: **
- y-websocket protocol: sync-step1, sync-step2, awareness update
- Authentication: JWT token in WebSocket connection query param or header
- Room isolation: each project has its own Yjs document room
- Permission enforcement: Planner (CRUD), Betrachter (read-only), Admin (full), Gast (read-only)
#### 2.2.3 KI Copilot Service (Backend)
```
┌──────────────────────────────────────────────────┐
│ KI Copilot Service (Backend) │
│ │
│ User Message (text/speech) │
│ ↓ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Context Injection │ │
│ │ - Active selection (elements) │ │
│ │ - Current zoom/viewport │ │
│ │ - Active layer │ │
│ │ - Recent actions (from undo history) │ │
│ │ - Current CAD state summary │ │
│ └─────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────┐ │
│ │ System Prompt Construction │ │
│ │ - CAD domain instructions │ │
│ │ - Available CAD functions (from registry) │ │
│ │ - Guardrails (safety rules) │ │
│ └─────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────┐ │
│ │ OpenAI-compatible API Call │ │
│ │ POST {base_url}/chat/completions │ │
│ │ Body: { model, messages, tools, ... } │ │
│ └─────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Response Processing │ │
│ │ - If tool_calls: execute via CAD-Function │ │
│ │ Registry → return result to LLM → loop │ │
│ │ - If text: return to user │ │
│ └─────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Guardrail Validation │ │
│ │ - Check: no destructive ops without confirm │ │
│ │ - Check: element count limits │ │
│ │ - Check: valid parameters │ │
│ └─────────────────────────────────────────────┘ │
│ ↓ │
│ Function Execution → Canvas Update → User Feedback│
└──────────────────────────────────────────────────┘
```
**CAD-Function-Registry (OpenAI Function Calling Schema): **
``` typescript
// Example function definitions (OpenAI tool schema)
const cadFunctions = [
{
type : "function" ,
function : {
name : "draw_line" ,
description : "Draw a line from point A to point B" ,
parameters : {
type : "object" ,
properties : {
x1 : { type : "number" , description : "Start X coordinate" } ,
y1 : { type : "number" , description : "Start Y coordinate" } ,
x2 : { type : "number" , description : "End X coordinate" } ,
y2 : { type : "number" , description : "End Y coordinate" } ,
layer : { type : "string" , description : "Target layer name" }
} ,
required : [ "x1" , "y1" , "x2" , "y2" ]
}
}
} ,
{
type : "function" ,
function : {
name : "draw_circle" ,
description : "Draw a circle at center with radius" ,
parameters : {
type : "object" ,
properties : {
cx : { type : "number" , description : "Center X" } ,
cy : { type : "number" , description : "Center Y" } ,
radius : { type : "number" , description : "Circle radius" } ,
layer : { type : "string" , description : "Target layer name" }
} ,
required : [ "cx" , "cy" , "radius" ]
}
}
} ,
// ... draw_polyline, draw_polygon, draw_rect, draw_arc,
// draw_text, draw_dimension,
// move_elements, copy_elements, rotate_elements,
// scale_elements, mirror_elements, trim_elements,
// delete_elements, select_elements,
// create_layer, set_active_layer, toggle_layer,
// create_block, insert_block, explode_block,
// place_seating_row, place_seating_block,
// export_dxf, export_svg, export_pdf,
// import_dxf, import_svg,
// zoom_to, zoom_to_fit, set_snap,
// undo, redo, save
]
```
**Guardrails: **
- Destructive operations (delete all, clear) require user confirmation
- Element creation limit: max 10,000 elements per KI operation
- Parameter validation: coordinates within project bounds, radius > 0
- No direct DOM manipulation: KI operates through function registry only
- Rate limiting: max 20 function calls per KI conversation turn
- Error handling: if function fails, return error to LLM for self-correction
#### 2.2.4 Auth Service
```
┌─────────────────────────────────────────────┐
│ Auth Service │
│ ┌──────────┐ ┌──────────────────────────┐ │
│ │ Register │ │ Login │ │
│ │ (email + │ │ (email + pass) │ │
│ │ pass) │ │ → bcrypt/argon2 │ │
│ │ │ │ → JWT (cookie) │ │
│ └──────────┘ └──────────────────────────┘ │
│ ┌──────────┐ ┌──────────────────────────┐ │
│ │ Reset │ │ Session │ │
│ │ (email │ │ (HTTP-only cookie, CSRF) │ │
│ │ link) │ │ │ │
│ └──────────┘ └──────────────────────────┘ │
│ ┌──────────┐ ┌──────────────────────────┐ │
│ │ User │ │ Role Mgmt │ │
│ │ Mgmt │ │ (Admin/Betrachter/ │ │
│ │ (Admin) │ │ Planner/Gast) │ │
│ └──────────┘ └──────────────────────────┘ │
└─────────────────────────────────────────────┘
```
### 2.3 Plugin System Architecture
#### 2.3.1 Plugin Lifecycle
```
Plugin Discovery → Manifest Validation → Registration → Installation
↓
Activation ←─── Loading ←─────┘
↓ ↑
Execution Deactivation
↓ ↑
UI Integration Uninstall
```
#### 2.3.2 Plugin Manifest
``` typescript
interface PluginManifest {
name : string ; // Unique plugin identifier
version : string ; // Semver version
displayName : string ; // Human-readable name
description : string ; // Plugin description
author : string ; // Author name
license : string ; // License identifier
main : string ; // Entry point (JS module)
apiVersion : string ; // Required CAD API version
permissions : PluginPermission [ ] ; // Requested permissions
ui : PluginUIConfig ; // UI integration config
hooks : PluginHooks ; // API hook registrations
dependencies : string [ ] ; // Other plugin dependencies
}
interface PluginPermission {
type : 'canvas' | 'api' | 'storage' | 'network' | 'clipboard' ;
access : 'read' | 'write' | 'read-write' ;
scope? : string ; // Optional scope limitation
}
interface PluginUIConfig {
ribbonTab ? : { label : string ; icon : string ; tools : ToolDef [ ] } ;
sidePanelTab ? : { label : string ; icon : string ; component : string } ;
commandLineCommands ? : { command : string ; handler : string } [ ] ;
contextMenuItems ? : { label : string ; action : string } [ ] ;
}
interface PluginHooks {
onElementCreate? : string ; // Hook function name
onElementModify? : string ;
onElementDelete? : string ;
onProjectOpen? : string ;
onProjectSave? : string ;
onExport? : string ;
onImport? : string ;
onSelectionChange? : string ;
onLayerChange? : string ;
}
```
#### 2.3.3 Plugin Isolation
```
┌───────────────────────────────────────────────┐
│ Plugin Sandbox (try-catch + API gating) │
│ ┌───────────────────────────────────────────┐ │
│ │ Plugin Code (runs in main thread) │ │
│ │ ├── Access only via Plugin API proxy │ │
│ │ ├── No direct access to React internals │ │
│ │ ├── No direct access to Yjs document │ │
│ │ └── No direct access to SQLite │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ Plugin API (controlled interface) │ │ │
│ │ │ ├── canvas.addElement() │ │ │
│ │ │ ├── canvas.removeElement() │ │ │
│ │ │ ├── canvas.getSelection() │ │ │
│ │ │ ├── layers.create() / .toggle() │ │ │
│ │ │ ├── blocks.create() / .insert() │ │ │
│ │ │ ├── api.call(endpoint, params) │ │ │
│ │ │ ├── storage.get() / .set() │ │ │
│ │ │ ├── ui.registerTab() │ │ │
│ │ │ ├── ui.registerCommand() │ │ │
│ │ │ └── events.on() / .emit() │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────┐ │
│ │ Error Boundary (plugin crashes don't │ │
│ │ affect core or other plugins) │ │
│ └───────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
```
**Isolation Strategy: **
- **No direct imports:** Plugins interact via a controlled Plugin API proxy
- **Error boundaries:** Each plugin wrapped in try-catch; failures logged, not fatal
- **Permission enforcement:** API proxy checks manifest permissions before each call
- **No shared state:** Plugins have their own state namespace
- **UI isolation:** Plugin UI components rendered in isolated containers
- **Registration:** Plugins registered via manifest; loaded lazily on activation
#### 2.3.4 Plugin API Hooks
| Hook | Trigger | Plugin Can |
|------|---------|------------|
| `onElementCreate` | Element added | Validate, modify, reject |
| `onElementModify` | Element changed | Validate, modify |
| `onElementDelete` | Element removed | Validate, reject |
| `onProjectOpen` | Project loaded | Initialize, add data |
| `onProjectSave` | Project saved | Pre-save hook |
| `onExport` | Export triggered | Transform export data |
| `onImport` | Import triggered | Transform import data |
| `onSelectionChange` | Selection changed | React to selection |
| `onLayerChange` | Layer toggled | React to layer state |
---
## 3. Data Model
### 3.1 SQLite Schema
``` sql
-- Projects table
CREATE TABLE projects (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
name TEXT NOT NULL ,
description TEXT ,
owner_id TEXT NOT NULL REFERENCES users ( id ) ,
units TEXT NOT NULL DEFAULT ' mm ' ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
updated_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
thumbnail_path TEXT
) ;
-- Users table
CREATE TABLE users (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
email TEXT UNIQUE NOT NULL ,
password_hash TEXT NOT NULL ,
display_name TEXT NOT NULL ,
role TEXT NOT NULL DEFAULT ' planner ' ,
is_active INTEGER NOT NULL DEFAULT 1 ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
updated_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Sessions table
CREATE TABLE sessions (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
user_id TEXT NOT NULL REFERENCES users ( id ) ,
token_hash TEXT UNIQUE NOT NULL ,
expires_at TEXT NOT NULL ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
ip_address TEXT ,
user_agent TEXT
) ;
-- Project members (collaboration permissions)
CREATE TABLE project_members (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
project_id TEXT NOT NULL REFERENCES projects ( id ) ON DELETE CASCADE ,
user_id TEXT NOT NULL REFERENCES users ( id ) ,
role TEXT NOT NULL DEFAULT ' betrachter ' ,
invited_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
accepted_at TEXT ,
UNIQUE ( project_id , user_id )
) ;
-- Layers (persisted as project configuration; live data in Yjs)
CREATE TABLE layers (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
project_id TEXT NOT NULL REFERENCES projects ( id ) ON DELETE CASCADE ,
name TEXT NOT NULL ,
visible INTEGER NOT NULL DEFAULT 1 ,
locked INTEGER NOT NULL DEFAULT 0 ,
color TEXT ,
line_type TEXT ,
transparency REAL NOT NULL DEFAULT 0 . 0 ,
sort_order INTEGER NOT NULL DEFAULT 0 ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Elements (persisted as snapshots; live data in Yjs CRDT)
CREATE TABLE elements (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
project_id TEXT NOT NULL REFERENCES projects ( id ) ON DELETE CASCADE ,
layer_id TEXT REFERENCES layers ( id ) ,
element_type TEXT NOT NULL ,
geometry TEXT NOT NULL ,
style TEXT ,
metadata TEXT ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
updated_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Blocks (block definitions)
CREATE TABLE blocks (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
project_id TEXT NOT NULL REFERENCES projects ( id ) ON DELETE CASCADE ,
name TEXT UNIQUE NOT NULL ,
base_element_ids TEXT NOT NULL ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Block instances (placements in drawing)
CREATE TABLE block_instances (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
project_id TEXT NOT NULL REFERENCES projects ( id ) ON DELETE CASCADE ,
block_id TEXT NOT NULL REFERENCES blocks ( id ) ON DELETE CASCADE ,
layer_id TEXT REFERENCES layers ( id ) ,
position_x REAL NOT NULL ,
position_y REAL NOT NULL ,
rotation REAL NOT NULL DEFAULT 0 . 0 ,
scale_x REAL NOT NULL DEFAULT 1 . 0 ,
scale_y REAL NOT NULL DEFAULT 1 . 0 ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Background images / floor plans
CREATE TABLE background_images (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
project_id TEXT NOT NULL REFERENCES projects ( id ) ON DELETE CASCADE ,
file_path TEXT NOT NULL ,
position_x REAL NOT NULL DEFAULT 0 . 0 ,
position_y REAL NOT NULL DEFAULT 0 . 0 ,
scale REAL NOT NULL DEFAULT 1 . 0 ,
rotation REAL NOT NULL DEFAULT 0 . 0 ,
opacity REAL NOT NULL DEFAULT 1 . 0 ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Version history (snapshots)
CREATE TABLE versions (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
project_id TEXT NOT NULL REFERENCES projects ( id ) ON DELETE CASCADE ,
label TEXT ,
description TEXT ,
snapshot_data BLOB NOT NULL ,
created_by TEXT REFERENCES users ( id ) ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Plugins registry
CREATE TABLE plugins (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
name TEXT UNIQUE NOT NULL ,
version TEXT NOT NULL ,
display_name TEXT NOT NULL ,
description TEXT ,
manifest TEXT NOT NULL ,
is_active INTEGER NOT NULL DEFAULT 0 ,
installed_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
activated_at TEXT
) ;
-- Settings (per user and global)
CREATE TABLE settings (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
user_id TEXT REFERENCES users ( id ) ,
key TEXT NOT NULL ,
value TEXT NOT NULL ,
updated_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
UNIQUE ( user_id , key )
) ;
-- KI configuration
CREATE TABLE ai_config (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
scope TEXT NOT NULL ,
project_id TEXT REFERENCES projects ( id ) ,
api_base_url TEXT NOT NULL ,
api_key_encrypted TEXT NOT NULL ,
model TEXT NOT NULL DEFAULT ' gpt-4o ' ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) ) ,
updated_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Webhooks
CREATE TABLE webhooks (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
project_id TEXT REFERENCES projects ( id ) ON DELETE CASCADE ,
url TEXT NOT NULL ,
event TEXT NOT NULL ,
is_active INTEGER NOT NULL DEFAULT 1 ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Audit log
CREATE TABLE audit_log (
id TEXT PRIMARY KEY DEFAULT ( lower ( hex ( randomblob ( 16 ) ) ) ) ,
user_id TEXT REFERENCES users ( id ) ,
project_id TEXT REFERENCES projects ( id ) ,
action TEXT NOT NULL ,
details TEXT ,
ip_address TEXT ,
created_at TEXT NOT NULL DEFAULT ( datetime ( ' now ' ) )
) ;
-- Indexes
CREATE INDEX idx_elements_project ON elements ( project_id ) ;
CREATE INDEX idx_elements_layer ON elements ( layer_id ) ;
CREATE INDEX idx_elements_type ON elements ( project_id , element_type ) ;
CREATE INDEX idx_layers_project ON layers ( project_id ) ;
CREATE INDEX idx_blocks_project ON blocks ( project_id ) ;
CREATE INDEX idx_versions_project ON versions ( project_id ) ;
CREATE INDEX idx_sessions_user ON sessions ( user_id ) ;
CREATE INDEX idx_sessions_token ON sessions ( token_hash ) ;
CREATE INDEX idx_project_members_project ON project_members ( project_id ) ;
CREATE INDEX idx_project_members_user ON project_members ( user_id ) ;
CREATE INDEX idx_settings_user_key ON settings ( user_id , key ) ;
CREATE INDEX idx_audit_log_user ON audit_log ( user_id ) ;
CREATE INDEX idx_audit_log_project ON audit_log ( project_id ) ;
```
### 3.2 Entity Relationship Diagram
```
users ────< project_members >──── projects
│ │
│ ├──< layers
│ ├──< elements
│ ├──< blocks ──< block_instances
│ ├──< background_images
│ ├──< versions
│ ├──< webhooks
│ └──< ai_config (project scope)
│
├──< sessions
├──< settings
└──< audit_log
plugins (standalone registry)
settings (user_id nullable for global)
ai_config (scope: global or project)
```
### 3.3 Database Abstraction Layer
- All database access through a DatabaseInterface abstract class
- Query builder uses standard SQL compatible with both SQLite and PostgreSQL
- No SQLite-specific extensions in core queries
- Migration scripts provided for PostgreSQL/MySQL transition
- SQLite Adapter (default) → PostgreSQL Adapter (future)
---
## 4. API Design
### 4.1 REST API Endpoints
#### Authentication
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| POST | /api/auth/register | Register new user | None |
| POST | /api/auth/login | Login, returns session cookie | None |
| POST | /api/auth/logout | Logout, invalidate session | Required |
| POST | /api/auth/reset-password | Request password reset email | None |
| POST | /api/auth/reset-password/confirm | Reset password with token | None |
| GET | /api/auth/me | Get current user info | Required |
#### Projects
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | /api/projects | List user projects | Required |
| POST | /api/projects | Create new project | Required |
| GET | /api/projects/:id | Get project details | Required |
| PUT | /api/projects/:id | Update project metadata | Planner+ |
| DELETE | /api/projects/:id | Delete project | Admin |
| GET | /api/projects/:id/members | List project members | Member |
| POST | /api/projects/:id/members | Invite member | Admin |
| PUT | /api/projects/:id/members/:userId | Update member role | Admin |
| DELETE | /api/projects/:id/members/:userId | Remove member | Admin |
| GET | /api/projects/:id/versions | List versions | Member |
| POST | /api/projects/:id/versions | Create version snapshot | Planner+ |
| GET | /api/projects/:id/versions/:vId | Get version details | Member |
| POST | /api/projects/:id/versions/:vId/restore | Restore version | Planner+ |
#### Layers
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | /api/projects/:id/layers | List layers | Member |
| POST | /api/projects/:id/layers | Create layer | Planner+ |
| PUT | /api/projects/:id/layers/:layerId | Update layer | Planner+ |
| DELETE | /api/projects/:id/layers/:layerId | Delete layer | Planner+ |
| PUT | /api/projects/:id/layers/reorder | Reorder layers | Planner+ |
#### Elements
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | /api/projects/:id/elements | List elements (filtered) | Member |
| POST | /api/projects/:id/elements | Create element | Planner+ |
| PUT | /api/projects/:id/elements/:elemId | Update element | Planner+ |
| DELETE | /api/projects/:id/elements/:elemId | Delete element | Planner+ |
| POST | /api/projects/:id/elements/batch | Batch operations | Planner+ |
#### Blocks
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | /api/projects/:id/blocks | List block definitions | Member |
| POST | /api/projects/:id/blocks | Create block definition | Planner+ |
| PUT | /api/projects/:id/blocks/:blockId | Update block definition | Planner+ |
| DELETE | /api/projects/:id/blocks/:blockId | Delete block definition | Planner+ |
| POST | /api/projects/:id/blocks/:blockId/insert | Insert block instance | Planner+ |
#### Import / Export
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| POST | /api/projects/:id/import/dxf | Import DXF file | Planner+ |
| POST | /api/projects/:id/import/svg | Import SVG file | Planner+ |
| GET | /api/projects/:id/export/dxf | Export as DXF | Member |
| GET | /api/projects/:id/export/svg | Export as SVG | Member |
| GET | /api/projects/:id/export/pdf | Export as PDF | Member |
| GET | /api/projects/:id/export/png | Export as PNG | Member |
| GET | /api/projects/:id/export/json | Export project as JSON | Member |
#### KI Copilot
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| POST | /api/ai/chat | Send message to KI copilot | Required |
| GET | /api/ai/functions | List available CAD functions | Required |
| POST | /api/ai/config | Set KI API config (admin) | Admin |
| GET | /api/ai/config | Get KI API config (admin) | Admin |
| GET | /api/ai/health | Check KI endpoint connectivity | Admin |
#### Plugins
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | /api/plugins | List installed plugins | Required |
| POST | /api/plugins/install | Install plugin from package | Admin |
| PUT | /api/plugins/:id/activate | Activate plugin | Admin |
| PUT | /api/plugins/:id/deactivate | Deactivate plugin | Admin |
| DELETE | /api/plugins/:id | Uninstall plugin | Admin |
| GET | /api/plugins/:id/manifest | Get plugin manifest | Required |
#### Users (Admin only)
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | /api/users | List all users | Admin |
| PUT | /api/users/:id | Update user role | Admin |
| DELETE | /api/users/:id | Delete user | Admin |
#### Settings
| Method | Path | Description | Auth |
|--------|------|-------------|------|
| GET | /api/settings | Get user settings | Required |
| PUT | /api/settings | Update user settings | Required |
| GET | /api/settings/global | Get global settings | Admin |
| PUT | /api/settings/global | Update global settings | Admin |
### 4.2 OpenAPI / Swagger
- Auto-generated via @fastify/swagger and @fastify/swagger -ui
- Available at /api/docs (Swagger UI) and /api/openapi.json (spec)
- All routes have TypeScript schema definitions (params, querystring, body, response)
- OpenAPI 3.0 specification
- External clients can auto-generate SDKs from the spec
### 4.3 WebSocket API
| Event | Direction | Description |
|-------|-----------|-------------|
| sync-step1 | Client to Server | Initial sync request |
| sync-step2 | Server to Client | Sync response with missing updates |
| update | Bi-directional | Yjs document update bytes |
| awareness | Bi-directional | Cursor position, user info |
| permission-denied | Server to Client | User lacks permission for action |
---
## 5. Rendering Pipeline
### 5.1 Canvas 2D + rbush Architecture
```
1. Viewport Calculation (panX, panY, zoom, canvasWidth x canvasHeight)
2. Spatial Query: rbush.search(viewportBBox) → only visible elements
3. Layer Sorting: sort by sortOrder, skip invisible layers
4. Canvas Transform: ctx.save(), ctx.translate(panX, panY), ctx.scale(zoom, zoom)
5. Background Rendering: grid + background image (offscreen canvas, cached)
6. Element Rendering: for each element in sorted results:
- Apply element transform (pos, rot, scale)
- Set stroke/fill from element style or layer
- Render by type: line, circle, polyline, polygon, rect, arc, text, dimension, block_instance
- Render selection highlight if selected
- Render edit handles if in edit mode
7. Overlay Rendering: snap markers, cursor crosshair, selection box, multi-user cursors
8. Cleanup: ctx.restore(), mark canvas clean
```
### 5.2 rbush Spatial Index
``` typescript
import RBush from 'rbush' ;
interface CADItem {
minX : number ; minY : number ;
maxX : number ; maxY : number ;
elementId : string ;
elementType : string ;
layerId : string ;
}
const spatialIndex = new RBush < CADItem > ( 9 ) ;
// Insert on element create
spatialIndex . insert ( { minX , minY , maxX , maxY , elementId , elementType , layerId } ) ;
// Query viewport (render culling)
const visibleItems = spatialIndex . search ( {
minX : viewportLeft , minY : viewportTop ,
maxX : viewportRight , maxY : viewportBottom
} ) ;
// O(log n + k) where k = results
// Query point (hit testing)
const hits = spatialIndex . search ( { minX : x , minY : y , maxX : x , maxY : y } ) ;
```
### 5.3 Zoom / Pan
``` typescript
// screen to world
function screenToWorld ( screenX , screenY ) {
return { x : ( screenX - panX ) / zoom , y : ( screenY - panY ) / zoom } ;
}
// world to screen
function worldToScreen ( worldX , worldY ) {
return { x : worldX * zoom + panX , y : worldY * zoom + panY } ;
}
// Zoom limits
const MIN_ZOOM = 0.001 ;
const MAX_ZOOM = 1000 ;
```
### 5.4 Performance Optimization
| Technique | Impact | Implementation |
|-----------|--------|----------------|
| Viewport culling | Only render visible elements | rbush search on viewport bbox |
| Layer culling | Skip invisible/locked layers | Filter before render |
| Off-screen canvas | Cache static layers | grid/background to OffscreenCanvas |
| Dirty rect tracking | Only repaint changed regions | Track changed bboxes, clip region |
| Incremental index updates | Avoid full rbush rebuild | Insert/remove on element change |
| Batch rendering | Fewer canvas state changes | Group by stroke/fill style |
| requestAnimationFrame | Sync with browser repaint | 60fps cap, skip if not dirty |
| Device pixel ratio | Crisp rendering on HiDPI | Scale canvas by devicePixelRatio |
---
## 6. Multi-User Architecture (Yjs CRDT)
### 6.1 Synchronization Flow
```
User A draws line → Yjs Doc update → Y.encodeStateAsUpdate()
→ WebSocket send (update bytes)
→ y-websocket Server: receives, broadcasts to B/C, persists to y-leveldb
→ User B receives update → Y.applyUpdate(Yjs Doc)
→ React re-render → rbush index update → Canvas repaint
```
### 6.2 Conflict Resolution (CRDT)
- Commutative: Updates can be applied in any order
- Associative: Grouping of updates does not matter
- Idempotent: Applying the same update twice has no additional effect
- No conflicts: CRDTs guarantee convergence without conflict resolution
- Element-level merge: concurrent changes to different properties of same element merge automatically
- Array order: Yjs Y.Array uses insertion-based CRDT; deterministic order based on client ID
### 6.3 Permission Enforcement
| Role | Yjs Access | API Access |
|------|-----------|------------|
| Planner | Read + Write (full CRDT) | All CRUD operations |
| Betrachter | Read-only (observe only) | GET only |
| Admin | Read + Write | All + user management |
| Gast | Read-only (temporary) | GET only, limited scope |
### 6.4 Offline Synchronization
1. User goes offline → Service Worker detects → UI shows Offline indicator → Yjs updates queued locally (IndexedDB)
2. User continues editing → changes applied to local Yjs Doc → IndexedDB persists → Canvas renders from local state
3. User reconnects → WebSocket re-establishes → y-websocket sync exchanges missing updates → CRDT merge converges → UI shows Online
### 6.5 Scalability (Future: Redis Pub/Sub)
Initial: single server with all WebSocket connections.
Future: multiple servers with Redis Pub/Sub for cross-server broadcast.
---
## 7. KI Copilot Architecture
### 7.1 Architecture Pattern
```
Frontend KI Panel → Backend KI Service → OpenAI-compatible API Endpoint
↓
Context Injection (selection, zoom, layer, recent actions)
↓
System Prompt + Function Definitions
↓
API Call (POST /chat/completions with tools)
↓
Response Processing:
- tool_calls → CAD-Function-Registry executes → Canvas update
- text → return to user
↓
Guardrail Validation (destructive ops, limits, parameter validation)
↓
Function Execution → Canvas Update → User Feedback
```
### 7.2 Context Injection
``` typescript
interface KIContext {
activeLayer : { id : string ; name : string } ;
currentZoom : number ;
viewportBBox : { minX : number ; minY : number ; maxX : number ; maxY : number } ;
selectedElements : Array < { id : string ; type : string ; geometry : object ; layer : string } > ;
recentActions : Array < { action : string ; params : object } > ;
projectName : string ;
layerCount : number ;
elementCount : number ;
blockCount : number ;
units : string ;
model : string ;
apiBaseUrl : string ;
}
```
### 7.3 Guardrails
| Guardrail | Implementation |
|-----------|----------------|
| Destructive operation confirmation | Functions like delete_elements with all=true require confirm: true param |
| Element count limit | Max 10,000 elements per function call |
| Parameter validation | Coordinates within bounds, radius > 0, valid layer names |
| No direct DOM access | LLM can only interact through registered functions |
| Rate limiting | Max 20 function calls per conversation turn |
| Error recovery | Function errors returned to LLM for self-correction (max 3 retries) |
| Audit logging | All KI operations logged in audit_log table |
| Undo integration | All KI operations appear in undo history as atomic entries |
---
## 8. Plugin System Architecture (Detailed)
### 8.1 Plugin Loading Sequence
1. App Startup: Scan /plugins directory, read manifests, validate schema
2. Registration: Check API version, check dependencies, register in SQLite
3. Activation: Load entry point, create API proxy, verify permissions, init plugin, register UI/hooks
4. Execution: Hooks fire on events, UI components rendered, API calls through proxy, errors caught
5. Deactivation: Call deactivate(), remove UI, unregister hooks, clean state
6. Uninstall: Deactivate, remove from DB, delete files
### 8.2 Plugin API Surface
``` typescript
interface PluginAPI {
canvas : {
addElement ( element : CADElement ) : string ;
removeElement ( id : string ) : boolean ;
modifyElement ( id : string , changes : Partial < CADElement > ) : boolean ;
getElement ( id : string ) : CADElement | null ;
getElements ( filter : ElementFilter ) : CADElement [ ] ;
getSelection ( ) : string [ ] ;
setSelection ( ids : string [ ] ) : void ;
getViewport ( ) : ViewportState ;
setViewport ( zoom : number , panX : number , panY : number ) : void ;
} ;
layers : {
create ( name : string , options? : LayerOptions ) : string ;
delete ( id : string ) : boolean ;
toggle ( id : string ) : void ;
setActive ( id : string ) : void ;
list ( ) : Layer [ ] ;
} ;
blocks : {
create ( name : string , elementIds : string [ ] ) : string ;
insert ( blockId : string , x : number , y : number , options? : InsertOptions ) : string ;
explode ( instanceId : string ) : boolean ;
} ;
api : {
call ( endpoint : string , method : string , body? : object ) : Promise < any > ;
getProject ( ) : ProjectInfo ;
} ;
storage : {
get ( key : string ) : any ;
set ( key : string , value : any ) : void ;
remove ( key : string ) : void ;
} ;
ui : {
registerRibbonTab ( tab : RibbonTabDef ) : void ;
registerSidePanelTab ( tab : SidePanelTabDef ) : void ;
registerCommand ( cmd : CommandDef ) : void ;
registerContextMenuItem ( item : ContextMenuItemDef ) : void ;
showDialog ( component : string , props? : object ) : void ;
showToast ( message : string , type ? : string ) : void ;
} ;
events : {
on ( event : string , handler : Function ) : void ;
off ( event : string , handler : Function ) : void ;
emit ( event : string , data? : any ) : void ;
} ;
ai : {
registerFunction ( def : FunctionDefinition ) : void ;
unregisterFunction ( name : string ) : void ;
} ;
}
```
### 8.3 Plugin UI Integration Points
- Ribbon Bar: [Core Tabs] [Plugin Tabs...]
- Side Panel: [Blocks] [Layers] [Properties] [KI] [Plugin Tabs...]
- Command Line: core commands + plugin commands
- Context Menu: core actions + plugin actions
---
## 9. Deployment Architecture
### 9.1 Docker Container Setup
``` yaml
version : '3.8'
services :
cad-backend :
build : ./backend
container_name : cad-backend
restart : unless-stopped
ports :
- "3001:3001"
volumes :
- cad-data:/data
environment :
- NODE_ENV=production
- PORT=3001
- JWT_SECRET=${JWT_SECRET}
- AI_API_URL=${AI_API_URL}
- AI_API_KEY=${AI_API_KEY}
- AI_MODEL=${AI_MODEL}
- DATABASE_PATH=/data/db/cad.sqlite
- YJS_PERSISTENCE_PATH=/data/yjs
- CORS_ORIGIN=https://cad.media-on.de
- RATE_LIMIT_MAX=100
- RATE_LIMIT_WINDOW=60000
healthcheck :
test : [ "CMD" , "curl" , "-f" , "http://localhost:3001/api/health" ]
interval : 30s
timeout : 10s
retries : 3
cad-frontend :
build : ./frontend
container_name : cad-frontend
restart : unless-stopped
ports :
- "8080:80"
depends_on :
- cad-backend
environment :
- API_URL=https://cad.media-on.de/api
- WS_URL=wss://cad.media-on.de/ws
volumes :
cad-data :
```
### 9.2 Coolify Deployment
| Aspect | Configuration |
|--------|---------------|
| Server | coolify-01 (46.225.91.159) |
| Domain | cad.media-on.de (TBD) |
| SSL | Let's Encrypt via Traefik |
| Containers | 2 (cad-backend + cad-frontend) via docker-compose |
| Volume | Docker volume cad-data for SQLite + Yjs + uploads |
| Environment | Secrets via Coolify environment variables |
| KI API | External OpenAI-compatible endpoint via env vars |
| WebSocket | Traefik WebSocket proxy support |
| Scaling | Initial: single server. Future: Redis Pub/Sub |
### 9.3 Reverse Proxy (Traefik)
```
User Browser → Traefik (TLS) → /api/* → cad-backend:3001 (REST)
→ /ws → cad-backend:3001 (WebSocket upgrade)
→ /* → cad-frontend:8080 (static PWA)
```
### 9.4 Data Persistence
```
/data/
├── db/cad.sqlite # SQLite database
├── yjs/ # Yjs LevelDB persistence per project
├── uploads/backgrounds/ # Uploaded background images
├── uploads/imports/ # Imported DXF/SVG files
└── plugins/installed/ # Installed plugin packages
```
---
## 10. Security Concept
### 10.1 Authentication
- Method: Email + Password
- Password hashing: argon2 (preferred) or bcrypt (fallback)
- Session: JWT in HTTP-only, Secure, SameSite=Strict cookie
- CSRF: Double-submit token via @fastify/csrf
- Session timeout: Configurable, default 24 hours
- Password reset: Time-limited token via email link
### 10.2 Authorization (RBAC)
| Role | Project Access | User Mgmt | KI Config | Plugin Mgmt |
|------|---------------|-----------|-----------|-------------|
| Admin | Full (all) | Full | Full | Full |
| Planner | CRUD (own + shared) | None | None | None |
| Betrachter | Read-only | None | None | None |
| Gast | Read-only (temp) | None | None | None |
### 10.3 API Rate Limiting
- Per user (authenticated): 100 req/min
- Per IP (unauthenticated): 20 req/min
- KI API calls: 20 req/min
- WebSocket messages: 100 updates/min
- On limit exceeded: HTTP 429 with Retry-After header
### 10.4 Data Security
- Transport: TLS 1.3 via Traefik
- Passwords: argon2/bcrypt hash (never plaintext)
- KI API key: AES-256 encrypted in database
- Input validation: Fastify schema validation on all routes
- Import validation: DXF/SVG/PDF schema validation, size limits, sanitize
- SQL injection: Parameterized queries only (better-sqlite3)
- XSS: React auto-escaping, CSP headers via @fastify/helmet
- CORS: Configured for specific origin (cad.media-on.de)
### 10.5 GDPR Compliance
- Data export: User can export all personal data (JSON)
- Data deletion: User can delete account + all associated data
- Audit log: All data access logged
- Data minimization: Only collect email + display name
- Consent: Registration requires explicit consent checkbox
- Retention: Audit logs 90 days, sessions purged on expiry
### 10.6 KI Safety
- Destructive ops: require confirm flag; prompt user if missing
- Element limits: Max 10,000 elements per KI operation
- Parameter bounds: Validate all function parameters before execution
- Function whitelist: Only registered CAD functions callable
- Rate limiting: Max 20 function calls per conversation turn
- Audit trail: All KI operations logged
---
## 11. Technology Stack
### 11.1 Stack Summary with Licenses
| Layer | Technology | License | Justification |
|-------|-----------|---------|---------------|
| Frontend Framework | React 19 + TypeScript 5 | MIT | Mature ecosystem, component model, hooks |
| Build Tool | Vite 6 | MIT | Fast HMR, optimized builds, PWA plugin |
| Rendering | Canvas 2D + rbush | MIT | Browser-native, 50k+ elements @ 60fps |
| WebGL (optional) | regl or Three.js | MIT | Reserved for very large scenes |
| CRDT | Yjs + y-websocket + y-leveldb | MIT | Conflict-free sync, WebSocket transport |
| Offline | y-indexeddb + vite-plugin-pwa | MIT | IndexedDB persistence, Workbox PWA |
| i18n | i18next + react-i18next | MIT | Industry standard, namespace support |
| Backend | Fastify 5 + TypeScript 5 | MIT | High performance, schema validation, OpenAPI |
| Runtime | Node.js 22 LTS | MIT | Long-term support, ecosystem |
| WebSocket | ws + y-websocket | MIT | Standard WebSocket, y-websocket protocol |
| Database | SQLite (better-sqlite3) | Public Domain/MIT | Embedded, zero-config, WAL mode |
| Auth | argon2 + @fastify/cookie + @fastify/jwt | MIT | Secure hashing, JWT sessions |
| API Docs | @fastify/swagger | MIT | Auto-generated OpenAPI 3.0 |
| Rate Limiting | @fastify/rate -limit | MIT | Configurable rate limiting |
| Security | @fastify/helmet + @fastify/cors | MIT | Security headers, CORS |
| DXF | dxf-parser + dxf-writer | MIT | Open-source DXF parsing/writing |
| DWG (optional) | libredwg | GPL | Optional DWG support |
| PDF | pdf-lib | MIT | Client/server PDF generation |
| SVG | Native browser API | MIT | SVG import/export |
| KI Client | OpenAI SDK or native fetch | MIT | OpenAI-compatible API client |
| Validation | Zod | MIT | TypeScript-first schema validation |
| Testing | Vitest + Playwright | MIT | Unit/integration + E2E testing |
| Container | Docker | Apache 2.0 | Standard container runtime |
| Deployment | Coolify | AGPL-3.0 | Self-hosted deployment platform |
| Reverse Proxy | Traefik | MIT | TLS, WebSocket proxy, Let's Encrypt |
### 11.2 ADRs (Architecture Decision Records)
#### ADR-001: Canvas 2D over WebGL as Primary Renderer
- Status: Accepted
- Decision: Canvas 2D with rbush spatial index as primary renderer
- Rationale: Browser-native, no GPU dependency, with rbush viewport culling only visible elements rendered. WebGL reserved as optional hybrid for extremely large scenes.
- Alternatives: WebGL only (higher complexity, GPU dependency), SVG (DOM overhead at >3000 elements)
#### ADR-002: Yjs CRDT over Operational Transform
- Status: Accepted
- Decision: Yjs CRDT with y-websocket transport
- Rationale: CRDTs guarantee convergence without central conflict resolution. MIT-licensed, well-maintained. Works offline and resyncs automatically.
- Alternatives: OT (requires central server), Custom sync (too risky)
#### ADR-003: React over Vue/Svelte for Frontend
- Status: Accepted
- Decision: React 19 + TypeScript
- Rationale: Largest ecosystem for complex UI, hooks model, excellent TypeScript support.
- Alternatives: Vue (smaller CAD ecosystem), Svelte (smaller ecosystem)
#### ADR-004: Node.js + Fastify over Python + FastAPI
- Status: Accepted
- Decision: Node.js 22 + Fastify 5
- Rationale: Yjs is JavaScript; Node.js backend shares types. y-websocket server is Node-native. Single language across stack. Fastify has built-in schema validation and OpenAPI generation.
- Alternatives: Python + FastAPI (Yjs requires Node.js anyway), Deno (less maturity)
#### ADR-005: SQLite over PostgreSQL for Initial Version
- Status: Accepted
- Decision: SQLite with better-sqlite3, abstracted via DatabaseInterface
- Rationale: Zero config, embedded, single-file. Perfect for initial single-server. Abstraction enables future migration.
- Alternatives: PostgreSQL (requires separate container), MySQL (similar overhead)
#### ADR-006: API-First Design
- Status: Accepted
- Decision: Backend is purely an API server; UI is one API client
- Rationale: All functionality via documented REST API. External integrations use same API. Webhooks for events.
#### ADR-007: Plugin System with API Proxy Isolation
- Status: Accepted
- Decision: Plugins run in main thread with API proxy isolation and error boundaries
- Rationale: Full sandbox (Web Workers) limits Canvas/DOM access. API proxy gives controlled access with permission enforcement.
- Alternatives: Web Worker (too restrictive), iframe (complex messaging)
#### ADR-008: OpenAI-compatible Endpoint for KI Copilot
- Status: Accepted
- Decision: Use OpenAI-compatible API endpoint (user-provided base_url + api_key)
- Rationale: Supports OpenAI, OpenRouter, Ollama, vLLM, llama.cpp. No lock-in. Function Calling is standard.
#### ADR-009: 2 Docker Containers (Backend + Frontend)
- Status: Accepted
- Decision: 2 containers: cad-backend (Node.js) + cad-frontend (Nginx static)
- Rationale: Separation of concerns, independent scaling, frontend CDN-cacheable.
---
## 12. Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Canvas 2D performance at 50k+ elements | Medium | High | rbush spatial index, viewport culling, dirty rect tracking; benchmark early |
| Yjs CRDT complexity for complex CAD data | Medium | Medium | Use simple Y.Map/Y.Array structures; test concurrent editing |
| KI API endpoint reliability (external) | Medium | Medium | Timeout handling, fallback to manual, retry logic |
| KI function calling errors | Medium | Medium | Guardrails, parameter validation, error feedback, max 3 retries |
| Plugin system destabilizing core | Low | High | API proxy isolation, error boundaries, permission enforcement |
| DXF/DWG compatibility issues | Medium | Medium | dxf-parser (MIT), test with real files, DWG optional |
| Browser memory with large projects | Medium | Medium | Virtualized rendering, element pagination, IndexedDB offloading |
| Cross-browser Canvas rendering differences | Low | Low | Regression tests, visual tolerance checks |
| SQLite concurrent write contention | Low | Medium | WAL mode, write queue, batch operations |
| Offline merge complexity | Low | Low | Yjs CRDT guarantees convergence; merge report for transparency |
---
## 13. Non-Functional Requirements Mapping
| NFR | Architecture Response |
|-----|---------------------|
| NF-PERF-01 (50k+ @ 60fps) | Canvas 2D + rbush viewport culling + dirty rect tracking |
| NF-PERF-02 (Lighthouse >= 80) | Vite optimization, code splitting, lazy loading, PWA caching |
| NF-PERF-03 (<= 500MB memory) | Spatial index, virtualized rendering, IndexedDB offloading |
| NF-PERF-04 (< 1s collab latency) | WebSocket + Yjs binary updates, server-side broadcast |
| NF-PERF-05 (KI < 3s) | Streaming responses, context injection optimization |
| NF-SEC-01 (Auth) | argon2/bcrypt, HTTP-only cookies, CSRF protection |
| NF-SEC-02 (RBAC) | Server-side role enforcement on all routes and WebSocket |
| NF-SEC-03 (TLS) | Traefik + Let's Encrypt |
| NF-SEC-04 (Input validation) | Zod schemas + Fastify validation |
| NF-SEC-05 (KI safety) | Guardrails, function validation, audit logging |
| NF-SEC-06 (GDPR) | Data export, deletion, audit log, data minimization |
| NF-SEC-07 (Rate limiting) | @fastify/rate -limit, 100 req/min, HTTP 429 |
| NF-A11Y-01 (WCAG 2.1 AA) | All UI except Canvas; Command Line as alternative |
| NF-DEP-01 (Docker) | Dockerfile + docker-compose.yml |
| NF-DEP-02 (Coolify) | Coolify deployment on coolify-01 |
| NF-DEP-03 (Env vars) | All secrets via environment variables |
| NF-DEP-04 (SQLite) | SQLite default, DatabaseInterface for migration |
| NF-DEP-05 (KI external) | KI API via env vars, no local model |
| NF-PLAT-01 (Browser) | Chrome, Firefox, Edge, Safari (latest 2) |
| NF-PLAT-02 (WebAssembly) | Not required; native JS/Canvas sufficient |
| NF-PLAT-03 (Web Speech API) | Optional voice input, browser-native |
| NF-PLAT-04 (PWA) | vite-plugin-pwa, manifest.json, service worker |
| NF-OSS-01 (Open Source) | AGPL-3.0 or MIT for web-cad; all deps OSS |
| NF-OSS-02 (OSS components) | All libraries MIT/Apache/BSD/ISC/LGPL/AGPL |
| NF-OSS-03 (License compat) | All licenses compatible |
| NF-OSS-04 (DXF) | dxf-parser + dxf-writer (MIT) |
| NF-OSS-05 (DWG optional) | libredwg (GPL) if needed |
| NF-OSS-06 (KI offsite) | User-provided OpenAI-compatible endpoint |
---
## 14. Open Design Questions
1. Exact domain name: TBD (proposed: cad.media-on.de) - needs DNS confirmation
2. KI model default: Depends on user endpoint - no default model forced
3. Plugin distribution: Local file upload vs marketplace URL - initial: local upload
4. Email service for password reset: SMTP configuration needed
5. Backup strategy: SQLite backup schedule and Yjs LevelDB backup - needs ops plan
6. Monitoring/logging: Log aggregation strategy - initial: container logs
---
## 15. Handoff
### Design Status
- Architecture complete and documented
- All major components designed (Frontend, Backend, KI Copilot, Plugin System, Rendering)
- 9 ADRs with rationale and alternatives
- Data model with 14 tables defined
- REST API with 50+ endpoints documented
- Deployment architecture for 2 containers on Coolify
- Security concept with auth, RBAC, rate limiting, GDPR, KI safety
- Risk assessment with 10 identified risks and mitigations
### Task Count
- Estimated 25-30 implementation tasks (see task_graph.json)
- Tasks sequenced with dependencies
- Each task sized for one implementation block
### Risks
- Canvas 2D performance at 50k+ elements (mitigated by rbush, needs early benchmark)
- KI API reliability (external dependency, mitigated by timeout/retry/fallback)
- Plugin system stability (mitigated by isolation and error boundaries)
- DXF compatibility (mitigated by dxf-parser, needs real-file testing)
### Open Design Questions
- See Section 14 (6 items)
### Ready for Implementation: No - awaiting quality review and UI design phase