Files
web-cad/docs/architecture.md
T

1504 lines
69 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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