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
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
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:
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 Structure:
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)
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 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)
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
2.3 Plugin System Architecture
2.3.1 Plugin Lifecycle
2.3.2 Plugin Manifest
2.3.3 Plugin Isolation
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
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
5.2 rbush Spatial Index
5.3 Zoom / Pan
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
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
- User goes offline → Service Worker detects → UI shows Offline indicator → Yjs updates queued locally (IndexedDB)
- User continues editing → changes applied to local Yjs Doc → IndexedDB persists → Canvas renders from local state
- 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
7.2 Context Injection
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
- App Startup: Scan /plugins directory, read manifests, validate schema
- Registration: Check API version, check dependencies, register in SQLite
- Activation: Load entry point, create API proxy, verify permissions, init plugin, register UI/hooks
- Execution: Hooks fire on events, UI components rendered, API calls through proxy, errors caught
- Deactivation: Call deactivate(), remove UI, unregister hooks, clean state
- Uninstall: Deactivate, remove from DB, delete files
8.2 Plugin API Surface
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
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)
9.4 Data Persistence
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
- Exact domain name: TBD (proposed: cad.media-on.de) - needs DNS confirmation
- KI model default: Depends on user endpoint - no default model forced
- Plugin distribution: Local file upload vs marketplace URL - initial: local upload
- Email service for password reset: SMTP configuration needed
- Backup strategy: SQLite backup schedule and Yjs LevelDB backup - needs ops plan
- 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
Ready for Implementation: No - awaiting quality review and UI design phase