Files
web-cad/docs/architecture.md
T

69 KiB
Raw Blame History

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:

interface CADYjsDoc {
  projectMeta: Y.Map<{          // Project-level metadata
    name: string,
    units: 'mm' | 'cm' | 'm' | 'inches' | 'feet',
    createdAt: string,
    updatedAt: string,
  }>
  layers: Y.Map<string, Y.Map<{ // layerId -> layer data
    name: string,
    visible: boolean,
    locked: boolean,
    color: string,
    lineType: string,
    transparency: number,
  }>>
  elements: Y.Array<CADElement> // Ordered array of all elements
  blocks: Y.Map<string, Y.Array<CADElement>> // blockId -> block elements
  blocksInstances: Y.Array<BlockInstance>     // Block placements in drawing
}

2.1.4 Undo/Redo System

  • History stack: Array of Yjs update bytes (using Y.encodeStateAsUpdate)
  • Stack size: Configurable (default: 100)
  • Each undo/redo: Apply/reverse Yjs update to Y.Doc
  • KI operations: Appear as single entries in undo history (atomic)
  • Group operations: Drag-and-drop produces single undo entry (debounced)

2.1.5 PWA / Offline Support

Feature Implementation
Service Worker vite-plugin-pwa (Workbox-based, MIT)
Caching App shell (cache-first), API data (stale-while-revalidate)
IndexedDB idb library (MIT) for offline element cache
Offline sync On reconnect: Yjs CRDT merge + WebSocket re-sync
Install prompt manifest.json with icons, theme color, standalone display

2.1.6 i18n

  • Framework: i18next (MIT) + react-i18next (MIT)
  • Initial languages: German (de), English (en)
  • Translation files: /src/locales/{de,en}/translation.json
  • Architecture separation: Architecture strings are separate from UI strings

2.2 Backend (Node.js + Fastify)

Technology: Node.js 22 LTS + Fastify 5 + TypeScript 5
License: MIT (Fastify), MIT (Node.js)
Role: API server, WebSocket server, KI proxy, persistence, auth

2.2.1 REST API Layer (Fastify)

┌─────────────────────────────────────────┐
│           Fastify Server (port 3001)     │
│  ┌──────────┐  ┌───────────────────────┐ │
│  │ CORS     │  │ Rate Limiting         │ │
│  │ Plugin   │  │ (@fastify/rate-limit) │ │
│  │          │  │ 100 req/min per user  │ │
│  └──────────┘  └───────────────────────┘ │
│  ┌──────────┐  ┌───────────────────────┐ │
│  │ JWT/Auth │  │ OpenAPI/Swagger       │ │
│  │ Plugin   │  │ (@fastify/swagger)    │ │
│  │ (cookie) │  │ Auto-generated docs   │ │
│  └──────────┘  └───────────────────────┘ │
│  ┌──────────────────────────────────────┐│
│  │         Route Groups                 ││
│  │  /api/auth/*      → AuthController   ││
│  │  /api/projects/*  → ProjectController││
│  │  /api/layers/*    → LayerController  ││
│  │  /api/elements/*  → ElementController││
│  │  /api/blocks/*    → BlockController  ││
│  │  /api/import/*    → ImportController ││
│  │  /api/export/*    → ExportController ││
│  │  /api/plugins/*   → PluginController ││
│  │  /api/ai/*        → AIController     ││
│  │  /api/users/*     → UserController   ││
│  │  /api/settings/*  → SettingsController││
│  └──────────────────────────────────────┘│
└─────────────────────────────────────────┘

API-First Design Principles:

  • All functionality accessible via REST API
  • OpenAPI/Swagger auto-generated from Fastify schemas
  • UI is one API client among potentially many
  • External integrations (Event-Management, CRM) use same API
  • Webhooks for project events (export finished, project created, etc.)

2.2.2 WebSocket Server (y-websocket)

┌─────────────────────────────────────────┐
│      WebSocket Server (port 3001/ws)     │
│  ┌─────────────────────────────────────┐ │
│  │  y-websocket server                 │ │
│  │  ├── Room: project:{projectId}      │ │
│  │  │   ├── User A (Planner)           │ │
│  │  │   ├── User B (Planner)           │ │
│  │  │   └── User C (Viewer)            │ │
│  │  ├── Room: project:{projectId}      │ │
│  │  │   └── User D (Planner)           │ │
│  │  └── ...                            │ │
│  └─────────────────────────────────────┘ │
│  ┌─────────────────────────────────────┐ │
│  │  y-leveldb persistence              │ │
│  │  /data/yjs/{projectId}.ldb          │ │
│  │  Single source of truth for CRDT    │ │
│  └─────────────────────────────────────┘ │
│  ┌─────────────────────────────────────┐ │
│  │  Awareness (cursor positions,       │ │
│  │  active user list, selections)      │ │
│  └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘

WebSocket Protocol:

  • y-websocket protocol: sync-step1, sync-step2, awareness update
  • Authentication: JWT token in WebSocket connection query param or header
  • Room isolation: each project has its own Yjs document room
  • Permission enforcement: Planner (CRUD), Betrachter (read-only), Admin (full), Gast (read-only)

2.2.3 KI Copilot Service (Backend)

┌──────────────────────────────────────────────────┐
│           KI Copilot Service (Backend)            │
│                                                    │
│  User Message (text/speech)                        │
│       ↓                                            │
│  ┌─────────────────────────────────────────────┐  │
│  │  Context Injection                           │  │
│  │  - Active selection (elements)              │  │
│  │  - Current zoom/viewport                     │  │
│  │  - Active layer                              │  │
│  │  - Recent actions (from undo history)        │  │
│  │  - Current CAD state summary                 │  │
│  └─────────────────────────────────────────────┘  │
│       ↓                                            │
│  ┌─────────────────────────────────────────────┐  │
│  │  System Prompt Construction                  │  │
│  │  - CAD domain instructions                   │  │
│  │  - Available CAD functions (from registry)   │  │
│  │  - Guardrails (safety rules)                 │  │
│  └─────────────────────────────────────────────┘  │
│       ↓                                            │
│  ┌─────────────────────────────────────────────┐  │
│  │  OpenAI-compatible API Call                  │  │
│  │  POST {base_url}/chat/completions            │  │
│  │  Body: { model, messages, tools, ... }       │  │
│  └─────────────────────────────────────────────┘  │
│       ↓                                            │
│  ┌─────────────────────────────────────────────┐  │
│  │  Response Processing                         │  │
│  │  - If tool_calls: execute via CAD-Function   │  │
│  │    Registry → return result to LLM → loop    │  │
│  │  - If text: return to user                   │  │
│  └─────────────────────────────────────────────┘  │
│       ↓                                            │
│  ┌─────────────────────────────────────────────┐  │
│  │  Guardrail Validation                        │  │
│  │  - Check: no destructive ops without confirm │  │
│  │  - Check: element count limits               │  │
│  │  - Check: valid parameters                   │  │
│  └─────────────────────────────────────────────┘  │
│       ↓                                            │
│  Function Execution → Canvas Update → User Feedback│
└──────────────────────────────────────────────────┘

CAD-Function-Registry (OpenAI Function Calling Schema):

// 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

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

-- 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

import RBush from 'rbush';

interface CADItem {
  minX: number; minY: number;
  maxX: number; maxY: number;
  elementId: string;
  elementType: string;
  layerId: string;
}

const spatialIndex = new RBush<CADItem>(9);

// Insert on element create
spatialIndex.insert({ minX, minY, maxX, maxY, elementId, elementType, layerId });

// Query viewport (render culling)
const visibleItems = spatialIndex.search({
  minX: viewportLeft, minY: viewportTop,
  maxX: viewportRight, maxY: viewportBottom
});
// O(log n + k) where k = results

// Query point (hit testing)
const hits = spatialIndex.search({ minX: x, minY: y, maxX: x, maxY: y });

5.3 Zoom / Pan

// 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

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

interface PluginAPI {
  canvas: {
    addElement(element: CADElement): string;
    removeElement(id: string): boolean;
    modifyElement(id: string, changes: Partial<CADElement>): boolean;
    getElement(id: string): CADElement | null;
    getElements(filter: ElementFilter): CADElement[];
    getSelection(): string[];
    setSelection(ids: string[]): void;
    getViewport(): ViewportState;
    setViewport(zoom: number, panX: number, panY: number): void;
  };
  layers: {
    create(name: string, options?: LayerOptions): string;
    delete(id: string): boolean;
    toggle(id: string): void;
    setActive(id: string): void;
    list(): Layer[];
  };
  blocks: {
    create(name: string, elementIds: string[]): string;
    insert(blockId: string, x: number, y: number, options?: InsertOptions): string;
    explode(instanceId: string): boolean;
  };
  api: {
    call(endpoint: string, method: string, body?: object): Promise<any>;
    getProject(): ProjectInfo;
  };
  storage: {
    get(key: string): any;
    set(key: string, value: any): void;
    remove(key: string): void;
  };
  ui: {
    registerRibbonTab(tab: RibbonTabDef): void;
    registerSidePanelTab(tab: SidePanelTabDef): void;
    registerCommand(cmd: CommandDef): void;
    registerContextMenuItem(item: ContextMenuItemDef): void;
    showDialog(component: string, props?: object): void;
    showToast(message: string, type?: string): void;
  };
  events: {
    on(event: string, handler: Function): void;
    off(event: string, handler: Function): void;
    emit(event: string, data?: any): void;
  };
  ai: {
    registerFunction(def: FunctionDefinition): void;
    unregisterFunction(name: string): void;
  };
}

8.3 Plugin UI Integration Points

  • Ribbon Bar: [Core Tabs] [Plugin Tabs...]
  • Side Panel: [Blocks] [Layers] [Properties] [KI] [Plugin Tabs...]
  • Command Line: core commands + plugin commands
  • Context Menu: core actions + plugin actions

9. Deployment Architecture

9.1 Docker Container Setup

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