# 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