From 0a12e5e06db68bf2d63976ca790b86b5862991ef Mon Sep 17 00:00:00 2001 From: Leopoldadmin Date: Fri, 19 Jun 2026 22:20:22 +0000 Subject: [PATCH] Update architecture.md with full content - Phase 2 architecture design --- docs/architecture.md | 1504 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1503 insertions(+), 1 deletion(-) diff --git a/docs/architecture.md b/docs/architecture.md index bd38c88..1ff4680 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1 +1,1503 @@ -§§include(/tmp/architecture.md) \ No newline at end of file +# 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 layer data + name: string, + visible: boolean, + locked: boolean, + color: string, + lineType: string, + transparency: number, + }>> + elements: Y.Array // Ordered array of all elements + blocks: Y.Map> // blockId -> block elements + blocksInstances: Y.Array // 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(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): 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; + 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