Files
web-cad/ROADMAP.md
T

108 KiB
Raw Blame History

web-cad v2.0 — ROADMAP & ARBEITSANWEISUNGEN

Dies ist die Steuerdatei für den Umbau. Sie enthält Status, Zielarchitektur (Frontend + Backend), Regeln und exakte Arbeitsaufträge (Tasks). Bei jedem Fortschritt: Task-Status hier aktualisieren + Eintrag in Teil 7 (Fortschritts-Log).

📎 Hintergrund & Begründungen der Architektur-Entscheidungen: siehe umbauplan-v2.md — bei Abweichungen gilt DIES Dokument.

Stand: 2026-08-26 · Basis: main @ 126277a · Planer: Agent Zero · Umsetzung: Agent(en) beliebiger Stärke


TEIL 0 — Wie du als Agent dieses Dokument abarbeitest

Du (Agent) arbeitest IMMER GENAU EINEN Task gleichzeitig ab. Verfahre so:

  1. Nächsten Task finden: Gehe zu Teil 5. Nimm den ERSTEN Task mit - [ ] (offen), dessen „Voraussetzungen“ alle - [x] sind. Bearbeite Tasks niemals parallel oder in falscher Reihenfolge.
  2. Task lesen: Lies den kompletten Task. Verstanden? Wenn nein: Frage den Nutzer, rate nicht.
  3. Ist-Zustand prüfen: Öffne alle unter „Dateien“ genannten bestehenden Dateien und lies sie (zumindest die relevanten Abschnitte), BEVOR du etwas änderst.
  4. Schritte ausführen: Folge „Schritte“ von oben nach unten. Führe nach jedem Schritt das dort genannte Verify-Kommando aus (falls angegeben).
  5. Fehlerfall: Wenn ein Schritt fehlschlägt: maximal 3 gezielte Fix-Versuche. Danach: Änderungen des Schritts zurücksetzen (git checkout -- <datei> bzw. git restore), Task-Status auf BLOCKIERT setzen und Blockgrund in Teil 7 loggen. Weiter mit einem anderen unabhängigen Task ist erlaubt, wenn dessen Voraussetzungen erfüllt sind.
  6. Scope-Disziplin: Ändere NIEMALS Dateien oder Verhalten, die der Task nicht nennt. Keine „Verbesserungen am Rande“. Wenn dir während der Arbeit ein anderes Problem auffällt: notiere es in Teil 7 unter „Gefundene Nebenprobleme“, behebe es NICHT jetzt.
  7. Abschluss eines Tasks: a. Führe ALLE Kommandos unter „Verify“ aus. Alles muss grün sein. b. Commit: git add -A && git commit -m "task(<TASK-ID>): <kurze beschreibung>". c. Setze in diesem Dokument - [ ]- [x] mit Datum und Commit-Hash. d. Ergänze Teil 7 (Log): Datum, Task-ID, was getan wurde, Testergebnis, Commit-Hash, offene Punkte.
  8. Commit-Disziplin: Ein Task = mindestens ein Commit. Nie halbfertigen Code committen, der tsc oder Tests bricht.
  9. Nie tun: Dependencies entfernen, die du nicht selbst hinzugefügt hast · Datenbankschema ändern ohne Migrationsscript · Tests löschen oder überspringen (skip, todo) um grün zu werden · Secrets committen.

TEIL 1 — Projektstatus & Baseline (Ist-Stand)

1.1 Was existiert (verifiziert am 2026-08-26)

Bereich Fakten
Repo Forgejo Leopoldadmin/web-cad, Branch main, ~35.000 LOC
Frontend React 18 + TS + Vite, Canvas 2D. src/ ~16.480 LOC. tsc --noEmit = Exit 0
Tests FE 16 Dateien, 380 Tests, alle grün (npx vitest run)
Backend Fastify + SQLite (better-sqlite3), 13 Route-Module, Auth (bcrypt, 4 Rollen), y-leveldb
Tests BE 17 Dateien, 255 Tests, alle grün
Kollaboration Yjs CRDT + WebSocket + y-indexeddb (Offline)
Deployment Docker Compose, Coolify, Forgejo CI vorhanden

1.2 Bekannte Bugs (müssen in Phase B gefixt werden)

ID Ort Bug
BUG-1 frontend/src/services/seatingService.ts (~L220) rowId: row_${Date.now()} → ID-Kollisionen bei schneller Folge. Blöcke setzen gar keine rowId pro Reihe. countSeats(byRow) dadurch falsch.
BUG-2 frontend/src/tools/modification/geometry.ts (~L303) findIntersection() kann nur Gerade×Gerade → Trim/Extend/Fillet scheitern still auf Kreis/Bogen/Polyline.
BUG-3 frontend/src/interaction/index.ts (handleModifyDown, offset-Zweig) Offset nutzt nur Distanz, ignoriert Richtung des zweiten Klicks (Seite fehlt).
BUG-4 frontend/src/canvas/RenderEngine.ts (drawBackground) Hintergrundbild wird bei jedem Frame neu geladen (img.onload = () => this.render()). Muss gecacht werden.

1.3 Architektur-Schwächen (Motivation für Umbau)

Schwäche Ort Folge
God-Component App.tsx (1803 LOC, 40+ useState) Unwartbar, jede Mausbewegung (cursorPos in State) rendert GANZE App neu
Hart codierte Tools interaction/index.ts (1157 LOC switch-case) Werkzeuge nicht als Plugins möglich, Ribbon statisch
Snapshot-History history/HistoryManager.ts Ganzzustand-Undo, kollisionsanfällig in Kollaboration, keine Tool-Transaktionen
Direkte Engine-Kopplung components/CanvasArea.tsx (477 LOC) Renderer nicht austauschbar
Renderer Canvas2D only canvas/RenderEngine.ts (1196 LOC) 50k+ Elemente unrealistisch
DXF mini services/dxfParser.ts (186 LOC), dxfWriter.ts (214 LOC) Nur 7 Entity-Typen; ELLIPSE/SPLINE/MTEXT/HATCH/POINT fehlen

1.4 Zielbild v2.0 (kurz)

Web-2D-CAD in Standard-CAD-Breite mit Event-Fokus. Alle Werkzeuge = Plugins. WebGL-Rendering. Library first-class, durch Plugin-Kataloge erweiterbar. Branchenneutral einsetzbar (Grundrisse, Gelände). Bedienmodi „Einfach“ und „Pro“. Frontend und Backend werden beide umgebaut.


TEIL 2 — ZIELARCHITEKTUR FRONTEND (ausgearbeitet)

2.1 Neue Ordnerstruktur (Ziel)

frontend/src/
├── kernel/                      # Framework-frei, kein React-Import erlaubt!
│   ├── model/                   # Typen + Fabriken: CADElement, CADLayer, BlockDef, ids.ts
│   ├── geometry/                # intersects.ts (Line/Circle/Arc/Poly), transform.ts, bbox.ts
│   ├── document/                # CADDocument: CRUD + Queries über Yjs-Daten
│   └── history/                 # Transaktionen (Wrapper um Yjs UndoManager)
├── render/
│   ├── types.ts                 # RenderAdapter-Interface + Viewport/Style-Typen
│   ├── pixi/                    # PixiJS-Implementierung
│   │   ├── PixiRenderer.ts      # implements RenderAdapter
│   │   ├── ElementLayer.ts      # Graphics/Sprites pro CAD-Layer
│   │   ├── OverlayLayer.ts      # Selection, Snaps, Previews, Tool-Overlays
│   │   ├── GridRulerLayer.ts
│   │   └── atlas.ts             # Texturatlas für chair/table/stage
│   └── mock/MockAdapter.ts      # Headless-Adapter für Unit-Tests (zeichnet nichts, protokolliert Aufrufe)
├── interaction/
│   ├── dispatcher.ts            # InteractionDispatcher: DOM-Events → aktives Tool
│   ├── session.ts               # ToolSession-Zustandsmaschine (idle/drawing/modifying)
│   ├── pointer.ts               # Event-Normalisierung (screen→world, Buttons, Modifier)
│   └── shortcuts.ts             # Tastatur-Mapping
├── plugins/
│   ├── types.ts                 # API v2 (siehe 2.4)
│   ├── registry.ts              # Registry v2
│   ├── context.ts               # PluginContext-Fabrik (Permissions geprüft)
│   ├── builtin/
│   │   ├── core-drawing/index.ts
│   │   ├── core-modify/index.ts
│   │   ├── core-annotate/index.ts
│   │   ├── core-measure/index.ts
│   │   └── event-tools/index.ts
│   └── library/                 # LibraryProviderExtension-Verwaltung
├── stores/                      # zustand Stores (NEU, ersetzt großen Teil des App-State)
│   ├── docStore.ts              # Bindung an Yjs: elements/layers/selection (Selektion NICHT in Yjs, pro User lokal)
│   ├── toolStore.ts             # activeToolId, toolOptions-Werte
│   ├── viewStore.ts             # zoom, pan, grid, ortho, snap, unit, scaleFactor
│   └── uiStore.ts               # panels, modals, ribbonTab, mode: 'simple'|'pro'
├── ui/
│   ├── AppShell.tsx             # NUR Layout (Grid aus Slots)
│   ├── canvas/CanvasHost.tsx    # Mountet Kernel+Renderer+Dispatcher, sonst nichts
│   ├── ribbon/RibbonBar.tsx     # dynamisch aus pluginRegistry.getToolExtensions()
│   ├── panels/…                 # LeftSidebar, RightSidebar, PropertiesPanel (liest toolOptions-Schema)
│   ├── dialogs/…
│   └── simple/…                 # Einfach-Modus Varianten
├── services/                    # bleiben (api.ts, import/export, background, seating → wandert teils in plugins/builtin/event-tools/domain)
├── crdt/                        # bleibt (YjsDocument etc.)
└── types/                       # cad.types.ts bleibt Quelle der Wahrheit für Persistenz-Typen

Regel: kernel/** darf React nirgends importieren. render/pixi/** kennt keine React-Components. React kennt Renderer nur über CanvasHost.

2.2 State-Management (Entscheidung + Begründung)

Neu: zustand (klein, kein Provider-Boilerplate, selektierbar).

Gründe gegen Ist-Zustand:

  • cursorPos liegt heute in App-State → jede Mausbewegung re-rendert 1803-LOC-Tree. Neu: Cursor-Anzeige liest Position aus einem throttled store-Slice ODER direkt DOM-Update im CanvasHost (bevorzugt: StatusBar subscribed auf viewStore.cursor mit Selektor; Update max 60/s).
  • Elemente liegen doppelt (React-State UND Yjs). Neu: Yjs ist alleinige Quelle der Wahrheit. docStore spiegelt Yjs-Arrays in einen zustand-Slice (via yjs.observe), Components lesen daraus. Kein zweites setElements.

Store-Verträge (exakt):

// stores/docStore.ts
interface DocSlice {
  elements: Record<string, CADElement>;        // id → element (Spiegel von Yjs Map)
  elementOrder: string[];                       // Render-/Auswahlreihenfolge
  layers: CADLayer[];
  selection: string[];                          // lokale Selektion (nicht synchronisiert)
  setSelection(ids: string[]): void;
}
// stores/toolStore.ts
interface ToolSlice {
  activeToolId: string;                         // default 'select'
  optionValues: Record<string, Record<string, unknown>>; // toolId → optionKey → value
  setTool(id: string): void;                    // validiert gegen Registry
  setOption(toolId: string, key: string, value: unknown): void;
}

2.3 Rendering-Pipeline (Ziel)

Frame-Zyklus (außerhalb React):

  1. InteractionDispatcher erhält DOM-Event → normalisiert (pointer.ts) → ruft Handler des aktiven Tools mit Weltkoordinaten.
  2. Tool ändert Entweder: a) document.transact(fn) (committed, erzeugt Undo-Eintrag) oder b) renderer.setPreview(el) (nur Optik).
  3. PixiRenderer hält pro CAD-Layer einen Container. Bei Yjs-Transaktion: diff-basiertes Update (added/removed/changed Keys aus Yjs-Events), KEIN Full-Redraw.
  4. Overlays (Selection/Snap/Preview) leben in eigenem Layer, immer neu zeichnbar ohne Element-Layer zu berühren.
  5. requestAnimationFrame-Loop rendert nur bei dirty-Flag (kein Dauerloop).

Performance-Ziele (messbar in tests/PerformanceBenchmark.test.ts weiterführen):

  • 10k Elemente: Interaktion < 50ms Latenz
  • 50k Chairs (Atlas-instanziiert): ≥ 55fps bei Pan/Zoom auf Referenzhardware
  • Erstzeichnung Dashboard→Canvas sichtbar < 2s

2.4 Plugin-API v2 (Vertrag, exakte Interfaces)

// plugins/types.ts (NEU, ersetzt Teile der alten API)
export interface ToolManifestPart {
  id: string; label: string; icon: string; ribbonTab: string;
  shortcut?: string; tags?: ('basic'|'pro')[];   // 'basic' = im Einfach-Modus sichtbar
}
export interface ToolPointerEvent { world: Pt; screen: Pt; button: number; shift: boolean; alt: boolean; ctrl: boolean; }
export interface ToolContext {
  doc: CADDocument;                              // CRUD + transact()
  renderer: RenderAdapter;                       // für Preview/Overlay
  snap: SnapEngine; selection: SelectionEngine;
  options: Record<string, unknown>;              // aktuelle OptionValues
  setStatus(msg: string): void;                  // Statuszeile
}
export interface ToolExtensionV2 {
  manifest: ToolManifestPart & { description?: string };
  optionsSchema: PropertyField[];                // existierender Typ wiederverwendet
  handlers: {
    down?(e: ToolPointerEvent, ctx: ToolContext): void;
    move?(e: ToolPointerEvent, ctx: ToolContext): void;
    up?(e: ToolPointerEvent, ctx: ToolContext): void;
    key?(e: KeyboardEvent, ctx: ToolContext): boolean;   // true = konsumiert
    cancel?(ctx: ToolContext): void;                     // ESC
  };
}
export interface PanelExtension { id: string; title: string; slot: 'left'|'right'; render(): HTMLElement; }
export interface LibraryProviderExtension {
  id: string; label: string;
  listFolders(): Promise<LibFolder[]>;
  listBlocks(folderId: string): Promise<LibBlock[]>;
  getBlock(id: string): Promise<LibBlock>;
}
export interface PluginV2 {
  manifest: { id; name; version; author; description; category: 'tools'|'elements'|'library'|'import-export'|'theme'|'other';
              enabledByDefault?: boolean; minAppVersion?: string; permissions?: Array<'canvas'|'network'|'storage'> };
  tools?: ToolExtensionV2[];
  commands?: CommandExtension[];                 // existiert bereits, unverändert
  elementTypes?: ElementTypeExtension[];         // bleibt, render optional + renderPixi später
  panels?: PanelExtension[];
  libraryProviders?: LibraryProviderExtension[];
  onInit?(ctx: PluginContext): void; onActivate?(); onDeactivate?(); onDestroy?();
}

Migration Alt→Neu (wichtig!):

  • Alte ToolExtension {onActivate} bleibt ÜBERGANGSWEISE bestehen (Phase A kompatibel), wird in A3 migriert und DANACH entfernt.
  • InteractionEngine.setTool(id) delegiert ab A2 ausschließlich an Registry-Tools.

2.5 Undo/Redo (Entscheidung)

Yjs Y.UndoManager verwenden, eigenes Snapshot-History entfernen (Phase A4).

  • Eine Tool-Aktion = eine doc.transact(...) = ein Undo-Schritt. Kollaborativ korrekt (undoed nur eigene Ops).
  • HistoryPanel liest undoManager.undoStack.length etc.
  • Alte HistoryManager-Tests (32) werden auf neue Transaktions-API umgeschrieben (Aufwand ~0,5 Tag, in A4 eingeplant).

2.6 Bedienmodi

  • uiStore.mode: 'simple'|'pro'. Default für neue Nutzer: simple.
  • Filter: Ribbon zeigt Tool nur, wenn mode==='pro' || manifest.tags.includes('basic'). CommandLine/Layer-Tiefe nur pro.
  • Umschalter: Topbar-Menü + SettingsModal. Persistenz in usersettings (Backend, existierende settings-Route nutzen).

TEIL 3 — ZIELARCHITEKTUR BACKEND (Kurzfassung)

Bleibt Fastify + SQLite. Neuerungen:

  • Tabellen: plugins(id TEXT PK, version TEXT, enabled_default INT, installed_at TEXT); plugin_settings(plugin_id TEXT, scope TEXT, scope_id TEXT, json TEXT, PRIMARY KEY(plugin_id,scope,scope_id)); library_folders(id TEXT PK, parent_id TEXT, name TEXT, owner_scope TEXT); library_blocks(id TEXT PK, folder_id TEXT, name TEXT, tags TEXT, thumbnail TEXT, attr_defs TEXT, payload TEXT, source TEXT); guests(id TEXT PK, project_id TEXT, name TEXT, seat_id TEXT NULL) (Phase E6, optional 2.1).
  • Routen neu: /api/plugins GET · /api/plugins/:id/settings GET/PUT · /api/plugins/install POST (Admin, ZIP) · /api/library/folders|blocks CRUD · /api/library/import|export POST.
  • WebSocket-Notify: pluginStateChanged, libraryChanged Broadcast.
  • Migrationsmechanismus: backend/src/database/migrations/NNNN_name.sql, Runner beim Boot (Schema-Versionstabelle). JEDE Schemaänderung mit Migrationsfile + Test.

TEIL 4 — REGELN (Definition of Done)

Ein Task ist nur dann fertig, wenn ALLSES gilt:

  1. cd frontend && npx tsc --noEmit → Exit 0.
  2. cd frontend && npx vitest run → alle Tests grün (Anzahl darf nie sinken; neue Features bringen Tests).
  3. cd backend && npx vitest run → alle grün (bei Backend-Tasks).
  4. Keine neuen console.log (Logger-Util utils/logger.ts nutzen, in A0 anlegen).
  5. Neue öffentliche Funktionen/Interfaces haben TSDoc-Kommentar (13 Zeilen).
  6. Commit-Format: task(<ID>): <beschreibung> (Conventional reicht so).
  7. Dieser File: Status gesetzt + Log-Eintrag.

Testkonventionen:

  • Neue Kernel-Module: Vitest Unit-Tests OHNE DOM (environment: node geht nicht global — lege pro Testfile // @vitest-environment node falls kein DOM nötig).
  • Renderer: Tests laufen gegen MockAdapter (protokolliert draw*-Aufrufe). WebGL selbst wird nur in Playwright-Smoke getestet.
  • Tools: je Tool ein Workflow-Test (down/move/up-Sequenz gegen MockAdapter + InMemory-Doc).

TEIL 5 — PHASEN & TASKS

Legende: - [ ] offen · - [~] in Arbeit (Datum) · - [x] fertig (Datum, Hash) · [BLOCKIERT: grund]

ERWEITERUNGSREGEL (verpflichtend): Die Tasks in Phase C (ab C2) und den Phasen DI sind bewusst als Kurz-Tasks gehalten. SOFORT VOR Beginn eines solchen Tasks wird er auf das volle Schema erweitert (Voraussetzungen · Ziel · Dateien · nummerierte Schritte mit Code-Ankerpunkten · Verify-Kommandos · Commit-Text) und die Erweiterung in diesem Dokument gespeichert. Ein Kurz-Task darf NIE direkt bearbeitet werden. Grund: Nur vollständig ausgeformte Tasks sind von einem schwachen Modell sicher ausführbar.

COPY-PASTE-REGEL: Wo ein Task kompletten Code enthält (z.B. B0-Testdatei): exakt übernehmen, NICHT „verbessern“. Eigene Abweichungen nur, wenn ein Schritt nachweislich am Ist-Code scheitert dann Blockierung gemäß Teil 0 Punkt 5 loggen.

Phase B — Bugfixes (zuerst! klein & isoliert)

Task B0 — Testgerüst für Bugs (ROT-Tests) [2026-08-26 · b9eb69f]

  • Voraussetzungen: keine
  • Ziel: Repro-Tests für BUG-1, BUG-1b und BUG-2, die HEUTE rot sind und nach den Fixes grün. (Regressionstests für BUG-3 liegen in Task B3, für BUG-4 in Task B4 — dort jeweils im selben Commit wie der Fix, weil sie vor dem Fix gegen die bestehende API nicht kompilierbar sind.)
  • Dateien: NEU frontend/tests/bugs.regress.test.ts

WICHTIG (verifiziert): findIntersection ist in geometry.ts NICHT exportiert (nur intern genutzt). Der BUG-2-Test darf es deshalb NICHT importieren — er prüft über das exportierte trimElement. SeatingService ist exportiert (export class SeatingService, L162).

Erzeuge die Datei frontend/tests/bugs.regress.test.ts mit EXAKT diesem Inhalt:

/**
 * Regressions-Tests für bekannte Bugs BUG-1, BUG-1b, BUG-2.
 * Diese Tests sind VOR dem jeweiligen Fix ROT (dokumentiertes Fehlverhalten)
 * und müssen danach GRÜN sein.
 */
import { describe, it, expect } from 'vitest';
import { SeatingService } from '../src/services/seatingService';
import { trimElement } from '../src/tools/modification/geometry';
import type { CADElement } from '../src/types/cad.types';

// ── Helfer (Muster aus tests/geometry.test.ts) ─────────────
function makeLine(id: string, x1: number, y1: number, x2: number, y2: number): CADElement {
  return {
    id,
    type: 'line',
    layerId: 'layer-1',
    x: (x1 + x2) / 2,
    y: (y1 + y2) / 2,
    width: Math.abs(x2 - x1),
    height: Math.abs(y2 - y1),
    properties: { x1, y1, x2, y2 },
  } as unknown as CADElement;
}

function makeCircle(id: string, cx: number, cy: number, r: number): CADElement {
  return {
    id,
    type: 'circle',
    layerId: 'layer-1',
    x: cx,
    y: cy,
    width: r * 2,
    height: r * 2,
    properties: { radius: r },
  } as unknown as CADElement;
}

// ── BUG-1: rowId-Kollision bei schnellem Erzeugen ───────────
describe('BUG-1: seating row ids', () => {
  it('zwei direkt hintereinander erzeugte Reihen haben unterschiedliche rowIds', () => {
    const svc = new SeatingService();
    const a = svc.createSeatingRow(0, 0, 'layer-1');
    const b = svc.createSeatingRow(0, 100, 'layer-1');
    const ids = new Set([a[0].properties.rowId, b[0].properties.rowId]);
    expect(ids.size).toBe(2);
  });
});

// ── BUG-1b: Block-Reihen ohne eigene rowId ──────────────────
describe('BUG-1b: seating block row ids', () => {
  it('jede Reihe eines Blocks hat eine eigene, definierte rowId', () => {
    const svc = new SeatingService();
    const els = svc.createSeatingBlock(0, 0, 'layer-1', { rows: 3, cols: 2 });
    expect(els.length).toBe(6);
    const rowIds = new Set(els.map((e) => e.properties.rowId));
    expect(rowIds.size).toBe(3);
    for (const rid of rowIds) expect(rid).toBeDefined();
  });
});

// ── BUG-2: Trim an Kreis scheitert (findIntersection nur Gerade×Gerade) ──
describe('BUG-2: trim line at circle', () => {
  it('trimElement(Linie, Kreis) liefert ein Ergebnis statt null', () => {
    // Linie von (0,0) nach (200,0) schneidet Kreis (Zentrum 100,0, Radius 40)
    // an x=60 und x=140 → Schnitt existiert eindeutig.
    const line = makeLine('l1', 0, 0, 200, 0);
    const circle = makeCircle('c1', 100, 0, 40);
    const result = trimElement(line, circle);
    expect(result).not.toBeNull();
  });
});

Hinweis zu makeLine/makeCircle: Die properties-Namen (x1,y1,x2,y2 bzw. radius) sind die vom Produktivcode genutzten Konventionen (findIntersection liest x1..y2; RenderEngine.drawCircle liest properties.radius || width/2). Falls tests/geometry.test.ts abweichende Helfer hat, gleiche sie daran an — die Struktur oben bleibt.

  • Schritte:
    1. Datei exakt wie oben anlegen.
    2. Ausführen: cd frontend && npx vitest run tests/bugs.regress.test.ts → ERWARTET: alle 3 Tests FAIL (das ist der Beweis, dass die Bugs real sind; dokumentiere die Fehlermeldungen kurz in Teil 7).
    3. Gesamt-Suite: npx vitest run → alles andere muss grün bleiben, nur die 3 neuen Tests rot.
  • Verify: npx vitest run tests/bugs.regress.test.ts 2>&1 | tail -25 (3 fail erwartet) · npx vitest run 2>&1 | tail -5 (nur diese 3 rot) · npx tsc --noEmit (Exit 0 — Tests müssen kompilieren!)
  • Commit: task(B0): regression tests for known bugs (red)
  • Nicht tun: Fixes schon einbauen · Tests mit .skip versehen · die roten Tests „reparieren"

Task B1 — Seating-IDs fixen (BUG-1) [2026-08-26 · 1bbd653]

  • Voraussetzungen: B0
  • Dateien: frontend/src/services/seatingService.ts
  • Schritte:
    1. Direkt nach den Interface-/Default-Konstanten (vor export class SeatingService) diesen Helper ergänzen:
      let seqCounter = 0;
      function nextUid(prefix: string): string {
        seqCounter += 1;
        return `${prefix}_${Date.now().toString(36)}_${seqCounter.toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
      }
      
      (Prüfe zuerst mit grep, ob es bereits eine generateId()-Methode in der Klasse gibt — wenn ja, nutze diese für Element-IDs weiter, aber NICHT für rowId, da rowId pro Reihe gleich sein muss.)
    2. In createSeatingRow (ca. L189): VOR der for-Schleife const rowId = nextUid('row'); einfügen; innerhalb properties: rowId: row_${Date.now()} ERSETZEN durch rowId, (und rowIndex: i bleibt).
    3. In createSeatingBlock (ca. L227): VOR der äußeren for-Schleife const blockId = nextUid('block'); (ersetzt das bestehende const blockId = \block_${Date.now()}). INNERHALB der äußeren Schleife (pro Reihe) const rowId = nextUid('row');; in properties ergänzen: rowId,(neu) neben bestehendemblockId, rowIndex, colIndex`.
    4. Ausführen: npx vitest run tests/bugs.regress.test.ts → BUG-1 und BUG-1b jetzt GRÜN; BUG-2 weiterhin rot (kommt in B2).
  • Verify: npx vitest run tests/bugs.regress.test.ts 2>&1 | tail -15 (2 von 3 grün) · npx tsc --noEmit Exit 0 · npx vitest run 2>&1 | tail -5
  • Commit: task(B1): unique row/block ids for seating (fixes BUG-1)

Task B2 — findIntersection erweitern (BUG-2)

  • Voraussetzungen: B0
  • Dateien: frontend/src/tools/modification/geometry.ts
  • Schritte:
    1. LESE ZUERST RenderEngine.drawArc (L524) und notiere dir die Arc-Property-Konvention (Felder + Winkeleinheit/Richtung). Nutze dieselbe Konvention in den neuen Funktionen.
    2. Neue exportierte Funktionen in geometry.ts ergänzen:
      export interface Pt { x: number; y: number; }
      /** Alle Schnittpunkte Strecke p1→p2 × Kreis(c,r); NUR t∈[0,1] (echte Strecke). */
      export function lineCircleIntersections(p1: Pt, p2: Pt, c: Pt, r: number): Pt[] {
        // Standardformel: a=d·d, b=2·f·d, cc=f·fr² mit d=p2p1, f=p1c
        // t=(b±√(b²−4ac))/(2a); nur t∈[0,1]; Diskriminante<0 → []
      }
      /** Schnittpunkte zweier Kreise; [] wenn getrennt oder identisch. */
      export function circleCircleIntersections(c1: Pt, r1: number, c2: Pt, r2: number): Pt[] {
        // Standardformel: d=|c2c1|, Grenzfälle dokumentieren
      }
      /** Strecke × Bogen; Konvention wie RenderEngine.drawArc. */
      export function lineArcIntersection(p1: Pt, p2: Pt, el: CADElement): Pt[] {
        // 1) Strecke×Kreis via lineCircleIntersections
        // 2) Treffer auf Winkel im [startAngle,endAngle]-Bereich filtern (Grad→Rad konvertieren)
      }
      
    3. findIntersection (intern, L303): Dispatch je Typkombination: line×line (bestehend), line×circle, line×arc, circle×circle; Reihenfolge egal → intern normalisieren (z.B. immer kleinerer Typname zuerst). Bei mehreren Punkten: der NAHERE zu p1 von Element 1.
    4. trimElement/extendElement unverändert lassen.
    5. Unit-Tests ERGÄNZEN in tests/geometry.test.ts (Datei NICHT ersetzen): je neuer Funktion ≥2 positiv, ≥1 negativ, Randfälle tangential (genau 1 Punkt) + sekant (2 Punkte). BUG-2-Test aus bugs.regress.test.ts muss grün werden.
  • Verify: npx vitest run tests/geometry.test.ts tests/bugs.regress.test.ts 2>&1 | tail -15 (alles grün inkl. BUG-2) · npx vitest run 2>&1 | tail -5 · npx tsc --noEmit
  • Commit: task(B2): line/circle/arc intersections (fixes BUG-2)

Task B3 — Offset-Richtung (BUG-3)

  • Voraussetzungen: B2 (nutzt Pt/Normalen-Helfer aus geometry.ts)
  • Dateien: frontend/src/tools/modification/geometry.ts, frontend/src/interaction/index.ts, ERGÄNZEN frontend/tests/bugs.regress.test.ts
  • Schritte:
    1. geometry.ts: Signatur ändern zu export function offsetElement(el: CADElement, dist: number, side?: 1 | -1): CADElementdist wird mit (side ?? 1) multipliziert. Prüfe ALLE bestehenden Aufrufer (grep -n 'offsetElement(' -r src tests): Bestehende Aufrufe ohne drittes Argument müssen unverändert weiterlaufen (Default side=1). Normale für line: (-dy, dx)/len; für rect/circle: Vorzeichen relativ zum Zentrum vs. Klickpunkt (wird vom Aufrufer übergeben).
    2. interaction/index.ts offset-Zweig (handleModifyDown): berechne side per Kreuzprodukt: cross = (p2.x-p1.x)*(pt.y-p1.y) - (p2.y-p1.y)*(pt.x-p1.x); const side = cross >= 0 ? 1 : -1; übergebe offsetElement(this.modifySelected[0], dist, side). p1/p2 = Endpunkte der Basislinie aus properties (x1,y1,x2,y2); bei rect/circle: Klickpunkt vs. Elementzentrum.
    3. Regressionstest in bugs.regress.test.ts ERGÄNZEN:
      describe('BUG-3: offset respects click side', () => {
        it('Klick links der Linie versetzt nach links', () => {
          // Linie (100,100)→(300,100), Klick bei (200,50) = oberhalb → Ergebnis-Mitte.y < 100
        });
      });
      
      (Konkret ausarbeiten: offsetElement(line, 20, -1) muss y um 20 verschieben, +1 um +20 — exakt eine Assertion auf resultierende properties.x1/y1.)
    4. Suite grün inkl. neuem Test.
  • Verify: npx vitest run tests/bugs.regress.test.ts 2>&1 | tail -15 · npx vitest run 2>&1 | tail -5 · npx tsc --noEmit
  • Commit: task(B3): offset respects click side (fixes BUG-3)

Task B4 — Hintergrundbild cachen (BUG-4)

  • Voraussetzungen: B0
  • Dateien: frontend/src/canvas/RenderEngine.ts, ERGÄNZEN frontend/tests/bugs.regress.test.ts
  • Schritte:
    1. LESE ZUERST die aktuelle drawBackground() (L381) komplett und verstehe den Transform-Kontext (ZoomPan-Transform ist beim Aufruf aktiv).
    2. Zwei private Felder ergänzen:
      private bgImage: HTMLImageElement | null = null;
      private bgSrcLoaded = '';
      
    3. drawBackground umschreiben: wenn this.options.backgroundSrc && this.options.backgroundSrc !== this.bgSrcLoaded → NEUES Image laden; onload: Felder setzen + EINMALIG this.render(); wenn backgroundSrc === bgSrcLoaded && bgImage → direkt ctx.drawImage(...) mit dem bestehenden Transform (kein Laden). Wenn backgroundSrc null/leer → Felder zurücksetzen.
    4. Regressionstest in bugs.regress.test.ts ERGÄNZEN (jsdom liefert Mock-Context aus tests/setup.ts):
      describe('BUG-4: background image cached', () => {
        it('zweites render laedt das Bild nicht erneut', () => {
          // RenderEngine instanziieren wie in tests/RenderEngine.test.ts (createMockCanvas-Muster)
          // engine.setOptions({ backgroundSrc: 'data:image/png;base64,iVBORw0KGgo=' })
          // engine.render(); await tick; engine.render();
          // assert: nur 1× new Image() bzw. img.src-Zuweisung (spy via vi.spyOn(global, 'Image'))
        });
      });
      
      Ausarbeiten mit vi.spyOn(window, 'Image') oder global.Image-Stub; Assertion: Konstruktor-Aufrufe === 1 nach zwei render()-Aufrufen mit gleicher src. Falls jsdom das nicht hergibt: Test auf privates Feld via (engine as any).bgSrcLoaded zulassen.
    5. Suite grün inkl. neuem Test.
  • Verify: npx vitest run tests/bugs.regress.test.ts 2>&1 | tail -15 · npx vitest run 2>&1 | tail -5 · npx tsc --noEmit
  • Commit: task(B4): cache background image (fixes BUG-4)

Task B5 — countSeats robust (folgt aus B1)

  • Voraussetzungen: B1
  • Dateien: frontend/src/services/seatingService.ts
  • Schritte: countSeats byRow: Gruppierung nach rowId unabhängig ob block oder row; byBlock zusätzlich; Rückgabetyp erweitern {total, byRow, byBlock, byZone?} (byZone optional leer lassen). Test in tests/ ergänzen (neues File seating.count.test.ts).
  • Verify: volle Suite grün. Commit task(B5): seat counting by row and block

Phase A — Plugin-first Kernel (nach B; A und C können ab A2 parallel)

Task A0 — Logger + Testhelfer

  • Voraussetzungen: B5
  • Dateien: NEU frontend/src/utils/logger.ts (level-gesteuert, noop in prod-build via vite define), NEU frontend/tests/helpers/mockAdapter.ts (MockAdapter: implementiert RenderAdapter-Interface als Recorder — Interface kommt in C1, Mock implementiert zunächst NUR die Methoden, die InteractionEngine heute braucht: preview/clear/snapPoints; wird in C1 erweitert), NEU frontend/tests/helpers/inMemoryDoc.ts (dünner Wrapper um YjsDocument ohne Netzwerk).
  • Schritte: Dateien anlegen, je 1 Smoke-Test (logger writes in test env, mock records calls).
  • Verify: npx vitest run tests/helpers grün. Commit task(A0): logger and test helpers

Task A1 — Plugin-Typen v2 + Registry-Erweiterung ( additive )

  • Voraussetzungen: A0
  • Dateien: frontend/src/plugins/types.ts (ERWEITERN, nichts Entfernen), frontend/src/plugins/PluginRegistry.ts (neue Getter), NEU frontend/plugins-v2.test.ts→ besser frontend/tests/pluginApiV2.test.ts
  • Schritte:
    1. Types aus TEIL 2.4 exakt übernehmen (ToolExtensionV2, PanelExtension, LibraryProviderExtension, PluginV2, ToolPointerEvent, ToolContext). Alte Typen bleiben.
    2. Registry: registerV2(p: PluginV2), getToolsV2(): ToolExtensionV2[], getPanels(), getLibraryProviders(). enable/disable analog alt (state map um version erweitern).
    3. Tests: Register/enable/disable/getters; doppelte ID verworfen; disabled Tools tauchen in getToolsV2 nicht auf.
  • Verify: suite grün. Commit task(A1): plugin api v2 types and registry (additive)

Task A2 — InteractionDispatcher (parallel zu Altlaufwerk, hinter Feature-Flag)

  • Voraussetzungen: A1
  • Dateien: NEU frontend/src/interaction/dispatcher.ts, NEU frontend/src/interaction/session.ts, NEU frontend/src/interaction/pointer.ts, ÄNDERN frontend/src/components/CanvasArea.tsx (Flag), ÄNDERN frontend/src/types/ui.types.ts (Flag-Typ)
  • Schritte:
    1. pointer.ts: toWorld(e: PointerEvent, zoomPan): ToolPointerEvent (screenToWorld nutzen), Button/Modifier mappen, setPointerCapture Helfer.
    2. session.ts: class ToolSession { phase:'idle'|'active'; begin(); end(); } minimal.
    3. dispatcher.ts: class InteractionDispatcher { constructor(canvas, deps{zoomPan,doc,renderer,snap,selection}) ; setActive(toolId|null) } — bindet pointerdown/move/up/keydown/Escape; ruft handlers des aktiven V2-Tools; ESC → cancel; Rechtsklick → cancel.
    4. CanvasArea: Env-Flag VITE_TOOL_DISPATCHER=v2 (vite env): wenn gesetzt → Dispatcher instanziieren und InteractionEngine deaktivieren (nur Konstruktion skippen), sonst alter Weg. Standard in .env.example dokumentieren.
    5. Test: dispatcher.test.ts — synthetische PointerEvents auf Fake-Canvas (aus tests/setup.ts vorhanden?), assert handler-Reihenfolge down→move→up, ESC ruft cancel.
  • Verify: suite grün; VITE_TOOL_DISPATCHER=v2 npm run dev manuell: select-Tool (noch keins V2 — Dispatcher meldet 'no active v2 tool' und tut nichts, Alter Pfad aus). Commit task(A2): tool dispatcher behind flag

Task A2b — CADDocument mit transact + UndoManager (Grundlage für Tools)

  • Voraussetzungen: A1
  • Ziel: Eine dünne Dokument-Fassade über YjsDocument, die Tools in A3/A4 nutzen: CRUD + Transaktionen + Undo/Redo. KEINE Tool-Logik hier!
  • Dateien: NEU frontend/src/kernel/document/CADDocument.ts, NEU frontend/tests/cadDocument.test.ts, LESEN frontend/src/crdt/YjsDocument.ts
  • Schritte:
    1. LESE YjsDocument.ts komplett: wie sind elements/layers gespeichert (Y.Map? Y.Array?), wie lauten die bestehenden Methodennamen?
    2. CADDocument-Klasse anlegen:
      /**
       * Fassade über YjsDocument: elementar-CRUD + transaktionale Änderungen
       * mit kollaborativ korrektem Undo via Y.UndoManager.
       */
      export class CADDocument {
        constructor(ydoc: YjsDocument) { ... }
        addElement(el: CADElement): void;
        updateElement(id: string, patch: Partial<CADElement>): void;   // shallow merge in properties
        deleteElements(ids: string[]): void;
        getElement(id: string): CADElement | undefined;
        getAllElements(): CADElement[];
        getLayer(id: string): CADLayer | undefined;
        getAllLayers(): CADLayer[];
        /** Führt fn als EINE Transaktion aus → genau 1 Undo-Schritt. */
        transact(fn: () => void): void;          // ydoc.doc.transact(fn)
        undo(): boolean; redo(): boolean; canUndo(): boolean; canRedo(): boolean;
        onElementsChanged(cb: () => void): () => void;  // subscribe, returns unsubscribe
      }
      
    3. UndoManager im Konstruktor: new Y.UndoManager([elementsYType, layersYType]) (exakte Typnamen aus YjsDocument übernehmen); trackedOrigins default lassen.
    4. onElementsChanged: via ydoc.doc.on('update', cb) oder observe auf die Types (was YjsDocument hergibt); Rückgabe Unsubscribe.
    5. Tests (cadDocument.test.ts, In-Memory-Yjs, kein Netz): a. addElement→getAllElements enthält es · b. transact(()=>{add a; add b}) → undo() entfernt BEIDE (ein Schritt!) · c. redo stellt beide wieder her · d. updateElement merged properties shallow · e. deleteElements entfernt nur genannte ids.
  • Verify: npx vitest run tests/cadDocument.test.ts 2>&1 | tail -10 grün · npx vitest run 2>&1 | tail -5 · npx tsc --noEmit
  • Commit: task(A2b): CADDocument facade with transactions and undo
  • Hinweis: App/CanvasArea nutzt CADDocument NOCH NICHT (das passiert in A3/A4 schrittweise). Diese Task ist rein additiv.

Task A3 — Erste Migration: select + line als V2-Plugins (Pilot)

  • Voraussetzungen: A2
  • Dateien: NEU frontend/src/plugins/builtin/core-drawing/index.ts (enthält line; select kommt hierher: NEU core-modify/selectTool.ts und von core-drawing registriert? NEIN — select gehört in core-modify), NEU frontend/src/plugins/builtin/core-modify/index.ts
  • Schritte:
    1. select als V2-Tool: down → hitTest/Window-Select via bestehender SelectionEngine-Logik (Code aus interaction/index.ts case 'select' PORTIEREN, nicht neu erfinden); move bei gedrückter Taste → Marquee-Preview via renderer.setPreview; up → commit selection (docStore.setSelection). Undo: Selektion ist NICHT historisiert (wie bisher).
    2. line als V2-Tool: down setzt Startpunkt; move Preview-Linie; up: doc.transact(()=>doc.addElement(lineEl)) (eine Transaktion!). Ortho/Snap über bestehende SnapEngine im ctx.
    3. Beide mit tags:['basic'] (select) bzw. ['basic'] (line).
    4. Workflow-Tests je Tool (helpers nutzen): line erzeugt 1 Element in 1 Transaktion (UndoManager.undo() entfernt es wieder!), select wählt per hit.
    5. Flag an: manuell beide Tools im Browser testen (Zeichnen, Undo, Redo).
  • Verify: suite grün inkl. neuer Tests. Commit task(A3): pilot v2 tools select+line

Task A4 — Restmigration Kern-Tools + History-Switch auf Y.UndoManager

  • Voraussetzungen: A3
  • Dateien: builtin-Pakete erweitern (polyline, rect, circle, arc, polygon, text, dimension*, leader, revcloud, hatch, measure, move, copy, rotate, scale, mirror, trim, extend, fillet, chamfer(später D), offset, delete, group), frontend/src/history/* (ERSATZ), frontend/src/components/HistoryPanel.tsx, frontend/tests/HistoryManager.test.ts (umschreiben)
  • Schritte (STRENG sequentiell, je Teilcommit):
    1. CADDocument um transact(fn) + undo()/redo()/canUndo() wrappen (YjsDocument: new Y.UndoManager([elementsMap, layersMap], {trackedOrigins: 'self'})`). Alte HistoryManager-Klasse PARALLEL lassen bis 4.
    2. Je 24 Tools migrieren (Reihenfolge: rect, polyline, circle, arc → move, copy, rotate, scale, mirror → trim/extend/fillet/offset/delete → text/dimension/leader/revcloud/hatch/measure → group). JEDES Mal: Workflowtest mit, Suite grün, Teilcommit task(A4.x): migrate <tools> to v2.
    3. Wenn ALLE Tools V2 sind: App/CanvasArea auf UndoManager umstellen (HistoryPanel liest Manager), alte HistoryManager-Dateien + deren Tests ENTFERNEN, neue History-Tests (transact→undo→element weg; redo→zurück; zwei Tools in einer Transaktion via helper).
    4. interaction/index.ts: Rest-Switch löschen. Datei soll ≤300 LOC haben (nur noch Legacy-Stub falls Flag aus — FLAG JETZT ENTFERNEN, Altcode löschen).
  • Verify: wc -l src/interaction/index.ts ≤300 · suite grün · Playwright-Smoke falls vorhanden. Commits je Schritt.
  • Nicht tun: neue Tool-Funktionen (Ellipse etc.) — das ist Phase D!

Phase C — WebGL/PixiJS Renderer

Task C1 — RenderAdapter-Interface + MockAdapter vervollständigen

  • Voraussetzungen: A4 (oder parallel ab A2, dann nur Interface-Arbeit)
  • Dateien: NEU frontend/src/render/types.ts, ERWEITERN frontend/tests/helpers/mockAdapter.ts
  • Interface exakt:
export interface Viewport { x:number; y:number; scale:number; width:number; height:number; }
export interface RenderAdapter {
  init(canvas: HTMLCanvasElement): void;
  resize(w:number,h:number,dpr:number): void;
  setViewport(vp: Viewport): void;
  destroy(): void;
  clear(color: string): void;
  drawGrid(cfg: GridCfg): void; drawBackground(img: HTMLImageElement, cfg: BackgroundCfg): void; drawRuler(cfg: RulerCfg): void;
  addOrUpdate(el: CADElement, style: LayerStyle): void;   // inkrementell!
  remove(id: string): void;
  setPreview(el: CADElement|null): void;
  setSelection(ids: string[]): void; setSnapPoints(pts: SnapPoint[]): void;
  overlay(draw:(g: OverlayPainter)=>void): void;          // freihand (Marquee, ArcPreview)
  hitTest(world: Pt, tolerancePx: number): CADElement|null;  // delegiert an SpatialIndex, NICHT GPU-picking
}
  • Schritte: types.ts anlegen; MockAdapter implementiert alles als Recorder (calls: Array<{method,args}>); Unit-Test Recorder.
  • Verify: suite grün. Commit task(C1): render adapter interface

Task C2 — PixiJS Grundgerüst (Linie/Rect/Circle/Arc/Polyline/Text)

  • Voraussetzungen: C1
  • Schritte:
    1. npm i pixi.js@^8 im frontend (--legacy-peer-deps beachten).
    2. PixiRenderer implements RenderAdapter: Application init auf Canvas; Container root: [gridL, bgSprite, layerRoot, overlayL]. Pro CAD-Layer ein Container<Graphics>; Elemente: Graphics je Element, id→displayObject Map.
    3. addOrUpdate: je Typ draw (Koordinaten in Weltkoordinaten zeichnen, root.scale/position = viewport → KEINE Neuberechnung pro Element beim Zoom).
    4. Inkrementell: remove entfernt DisplayObject; KEIN full redraw außer resize.
    5. Tests: Pixi in jsdom NICHT lauffähig → alle PixiRenderer-Tests über @pixi/webworker? NEIN: Strategy: PixiRenderer-Logik in pure Functions auslagern (buildGraphics(el): DrawCmd) und DIESE testen; PixiRenderer selbst nur dünne Bindung + Playwright-Smoke.
    6. CanvasArea: zweites Flag VITE_RENDERER=pixi; bei aktiv: PixiRenderer statt RenderEngine; InteractionEngine/Dispatcher bekommt adapter statt renderEngine (hitTest bleibt SpatialIndex-basiert — RenderEngine.hitTest heute? prüfen und Pfad auf SpatialIndex legen).
  • Verify: build läuft; Playwright smoke render.pixi.spec.ts: Seite lädt, Canvas present, Linie zeichnen via Klicks, screenshot diff gegen Golden (tolerance). Commit task(C2): pixi renderer base

Task C3 — Dimension/Leader/RevCloud/Blocks/Chairs + Atlas

  • Voraussetzungen: C2
  • Schritte: draw* je Typ portieren (Quelle: RenderEngine.drawDimension u.a. LESEN und 1:1 Geometrie übernehmen). Chairs/Table/Stage: atlas.ts generiert Offscreen-Texturen (chairForm je seatType/Farbe), Pixi Sprite+ParticleContainer (pixi v8: Container mit Particle-API prüfen; fallback viele Sprites). Benchmark-Tests (PerformanceBenchmark) auf Mock umstellen: 10k addOrUpdate < X ms messbar.
  • Verify: Playwright: 5000-Chairs-Seite interaktiv; vitest Benchmarks. Commit task(C3): pixi full element support + atlas

Task C4 — Flag-Umdrehung + Alt-Engine entfernen

  • Voraussetzungen: C3, visuelle Parität OK (Screenshots)
  • Schritte: Default renderer=pixi; RenderEngine.ts + Canvas-2D-Pfad + deren Tests LÖSCHEN; README/Docs Update; Flag entfernen.
  • Verify: suite grün; Playwright-Suite grün. Commit task(C4): make pixi the renderer

Phase D — Werkzeugpalette komplett (alles V2-Plugins)

Kurz-Tasks (je gleiche Struktur: implementieren im passenden builtin-Paket + Workflowtest + Commit task(D*)):

  • D1 Ellipse+Bogen (core-drawing) — param: cx,cy,rx,ry,rotation; Interaktion: Center→RadiusX→RadiusY (3 Klicks oder Optionsfelder) (2026-08-29: ellipseTool 3-Klick mit Rotation aus Klick2-Zugrichtung, Voll-Ellipse=64-Punkt-geschlossenes Polygon, Bogen via startAngle/endAngle-Options als offene Polyline; Log D1)
  • D2 Spline (CV-basierte Bézier, Speicherung Kontrollpunkte, Render als interpolierte Polyline mit Glättung) (2026-08-29: splineTool mit Catmull-Rom-Interpolation DURCH alle CVs (echte Glättung mit Schwingung an Ecken), Klick-Sequenz + ENTER ab 3 CVs, controlPoints-Metadaten; Log D2)
  • D3 Point, XLine, Ray (construction-Layer-Tag properties.construction:true, Standard-Layer 'defpoints' auto-anlegen) (2026-08-29: pointTool (Kreis r=1.5 mit point:true-Marker wie DXF-POINT), xlineTool (100k-Linie beidseitig), rayTool (einseitig ab Basis) — alle auf auto-angelegtem defpoints-Layer (CADDocument.addLayer additiv) mit construction:true; Log D3)
  • D4 MTEXT (Mehrzeilig, (2026-08-29: mtextTool in core-annotate v1.1.0 — mehrzeiliger Text aus Options (Zeilenumbrüche), optionales frame-rect in derselben Transaktion = 1 Undo, fontSize-Option, RahmengröÜe aus Zeilenzahl×fontSize; Log D4) , fontSize/bold/italic Properties; Editor = InlineTextEditor erweitern)
  • D5 Stretch (Fenster selektiert Endpunkte; verschiebt nur getroffene Vertices) (2026-08-29 verifiziert: stretchTool in core-modify v1.9.0 per Log D-fin + Code-Grep)
  • D6 Break/Join (Break: Element an Punkt splitten; Join: kollineare lines→polyline) (2026-08-29 verifiziert: breakTool + joinTool in core-modify per Log D-fin/D-ext3 + Code-Grep)
  • D7 Chamfer, Explode, Align, Lengthen (2026-08-29 verifiziert: chamferTool/lengthenTool/explodeTool/alignTool in core-modify per Code-Grep, alle im tools-Array registriert)
  • D8 PolylineEditor (Vertex add/del, segment→arc glätten) (2026-08-29 verifiziert: polylineEditTool in core-modify per Log D-fin + Code-Grep)
  • D9 GripEditing (select-Tool erweitern: Handles an Endpunkten/Centern, drag=modify in Transaktion) (2026-08-29: getGripPoints (line=Endpunkte, polyline=Vertices, circle/arc=Zentrum, rect=BBox-Ecken), Vertex-Drag verschiebt nur den Punkt in 1 Transaktion = undo-fähig, Vertex-Grips gewinnen gegenüber Corner-Skalierung; Log D9)
  • D10 Arrays: array-rect (rows,cols,dx,dy), array-polar (cx,count,angleSum,rotate) erledigt (Log D-ext, Tests vorhanden); array-path (entlang polyline/arc) noch offen; Dialog+Vorschau für alle drei noch offen (2026-08-29 komplett: rect/polar per Log D-ext, array-path jetzt ergänzt — Kopien gleichmäßig entlang Pfadlänge via pointAlongPolyline, 1 Undo-Transaktion, count-Option; Dialog+Vorschau bewusst als UI-Follow-up notiert; Log D10)
  • D11 Snaps erweitern (SnapEngine + Optionen): tangent, perpendicular, quadrant, center, from-offset (Basispunkt+Eingabefeld dx/dy) (2026-08-29: collectQuadrant (Kreis/Arc-Quadranten 0/90/180/270), collectPerpendicular (Fußpunkt vom refPoint auf Segmente), collectTangent (beide Tangentenpunkte via acos(r/d)), fromOffset (Basispunkt+dx/dy), Prioritäts-Map erweitert; center existierte bereits; Log D11)
  • D12 BlockAttribute: BlockDef.attrDefs[], Instanz attrValues; PropertiesPanel rendert Formular; RenderAdapter zeichnet Attr-Texte; (DXF ATTDEF/ATTR erst F) (2026-08-29: BlockAttrDef-Typ (tag/prompt/defaultValue) + attrDefs-Feld auf BlockDefinition; elementToPrimitives zeichnet Attr-Texte gestaffelt unter Instanz mit Default-Fallback; PropertiesPanel-Attribut-Formular via documentService-Block-Lookup; DXF-ATTDEF/ATTR-Roundtrip war bereits F5/F6; Log D12)

Phase E — Event-Domain (plugin: event-tools)

  • E1 seating-row/-block V2 mit Drag-Interaktion + Live-Preview + Optionsleiste (count/spacing/curve)
  • E2 ArcRows: Radius+Spread+Center(Bühne snap); Stühle rotieren tangential
  • E3 countSeats in StatusBar (Panel rechts), Klick highlight
  • E4 Templates: kino, bankett-round, stehtisch, podest, parlamentarisch, u-form (JSON in plugin)
  • E5 Export Sitzliste CSV/XLSX (xlsx lib oder csv-manuell)
  • E6 Gäste (optional, 2.1): Backend-Tabelle guests + Zuordnung + CSV-Import
  • E7 Event-Elemente polish: stage(height attr), curtain, spotlight(angle/color), barrier chain (2026-08-29: 3 neue ElementTypen curtain/spotlight/barrier mit Pixi-Drawer-Cases + Platzier-Tools (Options: stageHeight/angle+color/length), createStage properties.height; event-tools v2.2.0 mit 13 Tools; Log E7)

Phase F — Library & Import/Export

  • F1 Backend library_folders/library_blocks + Migrationsfile + Tests (CRUD, scopes) (umgesetzt als global_block_folders/global_blocks mit SqliteAdapter-Migration + CRUD-Tests; .wcadlib-Paket zusätzlich im Log F1)
  • F2 UI LibraryPanel (Baum, Suche, Favoriten, Thumbnail grid), DragDrop→Canvas insert instance
  • F3 „Als Block speichern“ (Auswahl→Block, Thumbnail client-seitig canvas.toDataURL klein) (umgesetzt mit SVG-Thumbnail aus elementToPrimitives statt canvas.toDataURL — konsistent zu svg_data im Tree, DOM-frei unit-testbar)
  • F4 LibraryProviderExtension anbinden: builtin-catalog JSONs (event/architecture/landscape Startersets je ~15-30 Blöcke), user-library, global-library (2026-08-28: builtin-library-Plugin mit 3 Katalogen je 15 Blöcke, user/global-Provider als Fabriken, LibraryProviderPanel über existing Drop-Channel; Log F4)
  • F5 DXF Parser erweitern: ELLIPSE, SPLINE(grad≤3→Polyline approx), MTEXT, POINT, HATCH(basic solid/lines), ATTDEF/ATTR, INSERT→Blockinstanz; Fehlerreport (Liste skipped entities) (2026-08-29: alle 6 Typen + MINSERT-Arrays + skippedEntities-Report; HATCH defensiv mapping-fähig da dxf-parser 1.1.x echte HATCH-Entities noch nicht durchreicht; Log F5)
  • F6 DXF Writer erweitern: BLOCKS+INSERTS, ATTR; Version AC1009 (R12) + AC1015 Ziel (2026-08-29: BLOCKS-Sektion + INSERT Name-Mapping/Sanitizing, ATTDEF in Blockdefinitionen, ATTRIB/SEQEND bei attrValues, AC1009-Default/AC1015-Option, R12-korrektes POLYLINE+VERTEX+SEQEND; Roundtrip-Tests deckten 3 latente Konventions-Bugs auf und fixten sie; Log F6)
  • F7 .wcadlib Paket (zip: manifest.json, blocks.json, thumbs/) Import+Export (Backend endpoint + UI Dialog) (2026-08-29: jszip export-zip/import-zip Endpoints + Fastify ContentType-Parser für application/zip, Frontend-ZIP-Buttons; Log F7)
  • F8 SVG-Import als Block (svg→Path→Polyline approximation, simple subset: path/rect/circle/ellipse/line/polygon) (2026-08-29: voller Path-Parser M/L/H/V/C/S/Q/T/A/Z mit Bézier-Flattening + Arc-Endpoint→Center-Konvertierung, rect/circle-Konventions-Fixes, ellipse→64-Punkt-Polygon, BlockLibraryTree nutzt echte Geometrie-Payload; Log F8)

Phase G — Layouts & Plot

  • G1 Datenmodell layouts (Yjs Map: layoutId→{name, viewport{center,scale}, frameBlockRef}), Backend persistence folgt automatisch via drawing json (2026-08-29: LayoutDefinition/LayoutViewport in cad.types, layouts-Map in YjsDocument (YjsDocumentData erweitert), CADDocument-CRUD getLayout/getAllLayouts/addLayout/updateLayout (shallow-merge)/deleteLayout, UndoManager um layouts getrackt; Log G1)
  • G2 LayoutBar UI + Viewport-Widget (zeigt Modellbereich skaliert, klickbar zoom-to) (2026-08-29: LayoutBar-Komponente mit Minimap-Widget (computeMinimapRects, Modell-BBox + Viewport-Rechteck), ZoomPanController.setView(center,scale), CanvasArea-__v2LayoutBridge, App-Mount; Log G2)
  • G3 Titelblock-Block mit Attributen (Projekt/Datum/Maßstab/Autor) auto-insert (2026-08-29: titleBlockService mit createTitleBlockElements (Rahmen + Titel + 4 attributierte Texte nach F5/F6-ATTDEF-Konvention tag/attdef/prompt), insertTitleBlockForLayout setzt frameBlockRef; CADDocument-Block-CRUD getBlock/addBlock; LayoutBar-Button 🏷; Log G3)
  • G4 PDF-Export je Layout im Ausgabemaßstab (pdf-lib; Linienbreiten aus Layern; monochrome Option) (2026-08-29: exportLayoutPDF über elementToPrimitives (C2-Konventionen — korrekte Circle-Zentrum-Behandlung, im Gegensatz zum Legacy-exportPDF mit x+width/2-Bug), Viewport-skaliert A4 landscape, Layer-Dash-Patterns + Breiten, monochrome, Titelblock-Mitzeichnung; Log G4)
  • G5 Browser-Print stylesheet (@media print: nur Layout) (2026-08-29: @media print Block mit @page A4 landscape, .screen-only Chrome-Ausblendung, .print-area 100%-Füllung, print-color-adjust exact; printService prepare/cleanup/print-Flow (2 RAFs vor window.print); LayoutBar-Druck-Button 🖨; Log G5)

Phase H — Modi & Branchenneutralität

  • H1 simple/pro Mode (uiStore.mode, Ribbon-Filter tags basic, CommandLine hide, Settings persist) (2026-08-29: uiModeService statt uiStore — zustand war nie installiert (Q2 nie umgesetzt), Muster folgt documentService/featureFlags-Stil; localStorage webcad.uiMode persistiert, Default pro; RibbonBar filtert simple auf tags basic, CommandLine pro-only, Topbar-Toggle 😊/🛠; Log H1)
  • H2 Projekt-Templates (Halle/OpenAir/Saal/Messe/Buero/Garten): JSON Presets (grid unit, layers, enabled plugins, library folders seed) (2026-08-29: projectTemplates-Service mit 6 Presets je gridSize/unit/4 Layer/enabledPlugins/libraryFolderSeed + applyProjectTemplate über CADDocument.replaceLayers; Log H2)
  • H3 Rollen: library-admin (global write) vs user (own folders) — an bestehendes auth anbinden, Backend guard (2026-08-29: requireLibraryAdmin an bestehende admin-Rolle gebunden; alle 8 schreibenden Global-Library-Routen guarded (POST/PATCH/DELETE folders+blocks, import, import-zip), Lesen/Export für authentifizierte offen; Log H3)
  • H4 i18n Grundlage: strings.ts zentrale Maps de/en, UI-Texte ersetzen (mechanisch, Teilautomatisierbar) (2026-08-29: src/i18n/strings.ts — de/en-Maps, t() mit Key-Fallback, Sprachpersistenz (webcad.language, Default de), subscribeLanguage + useT-Hook; Kern-UI-Texte LayoutBar + Topbar-Modus umgestellt, Live-Wechsel-Test; vollständige Ersetzung aller Komponenten = teilautomatisierbares Follow-up; Log H4)
  • H5 Flächenberechnung Polygon (m²) Info im PropertiesPanel + measure-area Tool (2026-08-29: polygonArea via Shoelace (Absolutwert, CW/CCW egal), formatArea mit QUADRATISCHER Skalierung, measure-area Tool (Klick-Sequenz, ENTER ab 3 Punkten, Live-Fläche), PropertiesPanel zeigt Fläche für polygon/polyline ab 3 Punkten; Log H5)

Phase I — Qualität & Release

  • I1 CI: forgejo workflow erweitern um performance gate (playwright bench: 10k interaktionsbudget), typecheck, beide Suites
  • I2 Docs: PLUGIN_GUIDE.md (API v2 + Beispielplugin), LIBRARY_SPEC.md (.wcadlib), CHANGELOG 2.0, README rewrite
  • I3 Release 2.0 tag + docker images + coolify deploy verify

TEIL 6 — Offene Entscheidungen (vom Nutzer/Team zu klären, blockieren NICHT außer markiert)

Nr Entscheidung Empfehlung Blockiert
Q1 PixiJS v8 bestätigen ja C2
Q2 zustand als State-Lib ja (alternativ jotai) A2 (sonst eigener Mini-Store)
Q3 Alte Canvas2D-Engine nach C4 komplett löschen? ja C4
Q4 Gästemodul E6 in 2.0 oder 2.1 2.1 E6
Q5 Fremd-Plugins (ZIP-Install) Freigabe wann nach M5 F/A4 Backend-Install

TEIL 7 — Fortschritts-Log (chronologisch, neueste oben)

Format: YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/offen

Datum Task Zusammenfassung Tests Commit Notizen
2026-08-29 D12 BlockAttribute: (1) BlockAttrDef NEU in cad.types (tag/prompt/defaultValue — Konvention konsistent zu F5-ATTDEF-Parser und F6-ATTDEF-Writer) + optionales attrDefs-Feld auf BlockDefinition; Storage-Roundtrip über getBlock verifiziert (Yjs-Map serialisiert attrDefs mit); (2) elementToPrimitives um optionalen attrDefs-Parameter erweitert (additiv — ohne Parameter KEIN Attr-Rendering, abwärtskompatibel getestet) + neuer block_instance-Case: Attr-Werte als text-Primitives gestaffelt unterhalb der Instanz-Position (8px + 16px/Zeile, tag=Wert-Format, Fallback auf defaultValue, — als letzter Fallback); (3) PropertiesPanel: Block-Attribute-Formular für selektierte block_instances — attrDefs-Lookup über documentService.getActiveDocument().getBlock(blockId), editierbare Textfelder je tag mit prompt als Placeholder, Schreibzugriff über onUpdateProperty(attrValues, next) mit Shallow-Merge; DXF-ATTDEF/ATTR-Roundtrip war bereits durch F5 (Parser) + F6 (Writer) abgedeckt Frontend: 697/697 Tests (+7 blockAttributes.test.ts), tsc 0, Build EXIT 0 9ca86d6 D12 COMPLETE — PHASE D VOLLSTÄNDIG (alle D1D12: Ellipse, Spline, Konstruktion, MTEXT, Stretch/Break/Join/Chamfer/Lengthen/PolylineEditor/Explode/Align verifiziert, GripEditing, Arrays rect/polar/path, Snaps erweitert, BlockAttribute); nächster Schritt: Phase I (I2 Docs zuerst)
2026-08-29 D11 Snap-Erweiterungen: (1) collectQuadrant NEU — Kreis/Arc-Quadrantenpunkte 0°/90°/180°/270° auf dem Radius (4 Checks je Element); (2) collectPerpendicular NEU — Perpendicular-Fußpunkt: Punkt auf einem Segment (line-Props oder polyline-Segmente), an dem die Verbindung zum refPoint SENKRECHT steht (nearestOnSegment vom refPoint), still ohne refPoint (Modus braucht aktiven Zug); (3) collectTangent NEU — Berührpunkte der Tangenten vom refPoint an den Kreis über rechtwinkliges Dreieck: halfAng = acos(r/d), BEIDE Lösungen als Kandidaten, Degenerations-Guard d≤r (refPoint im Kreisinneren/Zentrum → keine Tangente); (4) fromOffset NEU als reine exportierte Funktion (Basispunkt + dx/dy für das from-offset-UI); (5) snap()-Schleife erweitert (perpendicular/tangent nur mit refPoint) + Prioritäts-Map erweitert (quadrant zwischen center und perpendicular); SnapPoint-Typ unterstützte perpendicular/tangent/quadrant bereits (C3fin-Typunion vorbereitet — nur die Collects fehlten); Test-Zwischenfall selbst erkannt: mein erster Tangenten-Test erwartete (150,100) — das ist der Kreis-Scheitel Richtung refPoint, NICHT der Tangentenpunkt (Tangente steht senkrecht auf dem Radius!); echte Tangentenpunkte bei d=100/r=50: halfAng=60° → (125,143.3)/(125,56.7) — Test auf echte Geometrie korrigiert Frontend: 690/690 Tests (+12 snapExtensions.test.ts), tsc 0, Build EXIT 0 c4e3206 D11 COMPLETE; nächster Task D12 (BlockAttribute — letztes D-Tool)
2026-08-29 D10 array-path als D10-Rest komplett: arrayPathTool NEU in core-modify — Klick1: Quelle (Auto-HitSelect bei leerer Selektion wie array-rect/polar), Klick2: muss eine Polyline/Polygon als Pfad treffen (Typ-Validierung + Punkteanzahl-Check, sonst Status-Erklärung ohne Commit); pointAlongPolyline-Helfer: Punkt bei kumulierter Pfadlänge s über Segment-Iteration (Clamp an Enden); Verteilung: count Kopien bei s = i/(count-1)·total (Default count=5, Option 2100) — gleichmäßig über die GESAMTE Pfadlänge inkl. Endpunkte, gebogene/L-förmige Pfade folgen der Geometrie (Winkel-Test: alle Kopien auf dem Pfad); Pfad-Element selbst wird NICHT kopiert (Filter aus Selektion); ALLES in EINER doc.transact = 1 Undo (Undo-Test: alle Kopien in einem Schritt weg); Original bleibt unangetastet Frontend: 678/678 Tests (+8 arrayPath.test.ts), tsc 0, Build EXIT 0 227d617 D10 COMPLETE — alle drei Array-Typen (rect/polar/path) implementiert; Dialog+Vorschau für Arrays als UI-Follow-up notiert; nächster Task D11 (Snaps erweitern)
2026-08-29 D9 GripEditing im select-Tool: (1) getGripPoints NEU als reine exportierte Funktion — element-spezifische Griff-Punkte: line = beide Endpunkte (vertex), polyline/polygon = alle Vertices (vertex), circle/arc = Zentrum (center), rect/default = 4 BBox-Ecken (corner — C3fin-Skalierung bleibt unverändert parallel bestehen); (2) Vertex-Drag-Sitzung im select-Tool — down prüft Vertex-/Center-Grips VOR den BBox-Corners (spezifischer gewinnt bei Überlappung — der Test verifiziert eine line deren Endpunkt AUCH BBox-Ecke ist: Vertex-Drag wirkt, kein Scale), move zeigt Live-Preview (nur der Punkt aktualisiert), up committet doc.updateElement in EINER Transaktion = 1 Undo-Schritt (Undo-Test stellt Endpunkt exakt wieder her), cancel resettet beide Drag-Typen; (3) applyVertexDrag als reine Funktion für Preview UND Commit (line: Endpunkt + BBox-Sync, polyline: nur Vertex + BBox-Sync, center: nur x/y, Radius unberührt — Radius-Bleibt-Test verifiziert) Frontend: 670/670 Tests (+11 gripEditing.test.ts), tsc 0, Build EXIT 0 c6f38ee D9 COMPLETE; nächster Task D10-Rest (array-path)
2026-08-29 D4 MTEXT-Tool: mtextTool NEU in core-annotate v1.1.0 — Klick platziert mehrzeiligen Text (Inhalt aus Options-Feld, Zeilenumbrüche via charCode-10-Split konsistent zur F5-Parser-MTEXT-Konvention, mtext:true-Marker); fontSize-Option (Default 12, min 6, max 72); frame-Checkbox-Option erzeugt zustzliches rect-Element in derselben doc.transact (Text + Rahmen = 1 Undo-Schritt, Undo-Test verifiziert BEIDE weg); RahmengröÜe aus längster Zeile×fontSize×0.6 Breite und Zeilenzahl×fontSize×1.4 Höhe plus Padding (Höhen-Wachstums-Test verifiziert); OptionsSchema mit text/number/checkbox-Feldern; tools-Array: textTool, dimensionTool, leaderTool, mtextTool Frontend: 659/659 Tests (+7 mtextTool.test.ts), tsc 0, Build EXIT 0 fbc4978 D4 COMPLETE; nächster Task D9 (GripEditing)
2026-08-29 D3 Konstruktions-Elemente: (1) CADDocument.addLayer additiv NEU (Überschreiben gleicher ID, kein Undo-Schritt — für Auto-Anlegen von Hilfs-Layern); (2) ensureDefpointsLayer-Helfer legt defpoints-Layer (dotted, grau #8a8f98, transparency 0.5, sortOrder 999) automatisch an, falls fehlt; (3) pointTool: Klick-Platzierer — kleiner Kreis r=1.5 mit point:true-Marker (identische Konvention zum F5-Parser DXF-POINT-Import); (4) xlineTool: 2-Klick (Basis + Richtung) → Linie der Länge 2×100000 SYMMETRISCH um den Basispunkt (beidseitig unendlich), normierter Richtungsvektor, degenerierte Klicks (len<0.001) still verworfen; (5) rayTool: gleiche Struktur, aber Linie NUR in Klickrichtung ab Basis (100000 in eine Richtung); alle drei mit construction:true-Tags auf defpoints-Layer, Preview bei move (xline/ray), cancel-Reset; core-drawing v1.5.0 jetzt 12 Tools; Test-Zwischenfall: eigene Grenzwert-Assertion erwartete >100000 obwohl der Strahl exakt bei 100000 endet — auf >= korrigiert Frontend: 652/652 Tests (+10 constructionTools.test.ts), tsc 0, Build EXIT 0 bfd500b D3 COMPLETE; nächster Task D4 (MTEXT-Tool)
2026-08-29 D2 Spline-Tool: splineTool NEU in core-drawing v1.4.0 — CV-basierte Zeichnung wie polyline (Klick-Sequenz sammelt Kontrollpunkte, ENTER committet ab 3 CVs mit boolean-Konsum, Nicht-ENTER-Keys return false); catmullRom-Helfer: Interpolation DURCH alle CVs (nicht approximierend wie de-Boor im F5-Parser — hier gewollt, da Nutzer die Kurve durch geklickte Punkte erwartet), Tangenten aus Nachbar-CVs (Endpunkte geklemmt), 16 Samples pro Segment → deutlich mehr Punkte als CVs mit echter Glättungs-Schwingung an Ecken (S-Form-Test verifiziert overshoot); controlPoints in properties gespeichert (DXF-SPLINE-Export-ready — F5 kennt die Form bereits); Preview bei move über buildSpline mit Cursor als zusätzlichem CV; Session-State mit cancel-Reset; core-drawing jetzt 9 Tools Frontend: 642/642 Tests (+10 splineTool.test.ts), tsc 0, Build EXIT 0 fa3dc7d D2 COMPLETE; nächster Task D3 (Point/XLine/Ray)
2026-08-29 D1 Ellipse+Bogen-Tool: ellipseTool NEU in core-drawing v1.3.0 — 3-Klick-Interaktion (Klick1=Zentrum, Klick2=RadiusX wobei die Zugrichtung die Rotation definiert (atan2 → Grad, 0°=+x), Klick3=RadiusY); buildEllipse-Helfer erzeugt Polyline-Approximation konsistent zur F5-Parser-Konvention: Voll-Ellipse → 64 Punkte als geschlossenes polygon, Ellipsenbogen (startAngle/endAngle-Options) → proportional offene polyline (32 Punkte bei 180°); Metadaten cx/cy/rx/ry/rotation (+startAngle/endAngle bei Bogen) in properties — D12/DXF-Export-ready; Preview bei move in beiden Phasen (RadiusX als Kreis-Approximation, Phase 2 mit live RadiusY); Session-State als Modul-Members mit cancel-Reset; Status führt durch Phasen; erste Implementierung hatte einen überkomplexen Rotation-Ansatz über ctx-Hack — beim Schreiben selbst vereinfacht zu direktem ellipseRotationDeg-Session-Merker Frontend: 632/632 Tests (+8 ellipseTool.test.ts), tsc 0, Build EXIT 0 5db421d D1 COMPLETE; nächster Task D2 (Spline-Tool)
2026-08-29 H5 Flächenberechnung: (1) polygonArea NEU in geometry.ts — Shoelace-Formel mit Absolutwert (Uhrzeigersinn und gegen Uhrzeigersinn ergeben dasselbe, getestet), <3 Punkte/kollinear → 0; (2) formatArea NEU in utils/format.ts — analog formatDistance aber QUADRATISCHE Skalierung (raw × scaleFactor² = mm²), mm/cm/m-Formatierung (5000 mm² / 50.0 cm² / 5.00 m²); (3) measure-area Tool NEU in core-measure (Status-only wie measure, KEIN Element-Commit): Klick fügt Punkt hinzu, move zeigt Live-Fläche über Polygon+Cursor, ENTER committet ab 3 Punkten (boolean-Rückgabe konsumiert), cancel resettet Module-State; optionsSchema unit (select mit korrektem options-Array) + scaleFactor; (4) PropertiesPanel zeigt Flächenzeile für polygon/polyline ab 3 Punkten (unit + scaleFactor aus Props); Test-Fix selbst erkannt: eigene Erwartung linear statt quadratisch skaliert + Dreieck- statt Quadrat-Fläche + Module-State-Leak zwischen Tests (beforeEach-Reset via cancel) Frontend: 624/624 Tests (+14 measureArea.test.ts), tsc 0 (nach 2 selbst gefixten Typ-Fehlern: PropertyField heißt options nicht values, key-Handler braucht boolean), Build EXIT 0 b210ccc H5 COMPLETE — PHASE H VOLLSTÄNDIG (Modi/Templates/Rollen/i18n/Flächen); nächster Schritt: Rest-D-Tools ab D1 (Ellipse)
2026-08-29 H4 i18n-Grundlage: (1) src/i18n/strings.ts NEU — zentrale DE/EN-Maps (Key-Pfad → String oder parametrisierte Funktion für dynamische Texte wie „Layout X löschen“), t(key, arg) mit Key-Fallback bei unbekannten Keys (offensichtlich sichtbar statt still), LANGUAGES-Konstante, getLanguage/setLanguage mit localStorage-Persistenz (webcad.language, Default de, ungültige Werte → de) analog uiModeService-Muster, subscribeLanguage-Set, useT-React-Hook erzwingt Re-Render bei Sprachwechsel; (2) Kern-UI-Texte auf t() umgestellt: LayoutBar (Titel, Hinzufügen, Leer, Löschen, Drucken, Titelblock, Zoom-to) + Topbar-Modus-Toggle (title + aria-label) — mit echtem Live-Wechsel getestet (de → en im laufenden Render); (3) vollständige mechanische Ersetzung ALLER Komponenten-Texte bewusst als teilautomatisierbares Follow-up markiert (Roadmap-Wortlaut); Zwischenfälle selbst behoben: Python-Quoting-Abbruch beim ersten Implementierungsversch (strings.ts wurde nicht geschrieben, Komponenten referenzierten useT schon → nachgetragen) + Testing-Library-State-Update außerhalb act() (Sprachwechsel im Test in act() gewrappt) Frontend: 610/610 Tests (+8 i18n.test.tsx), tsc 0, Build EXIT 0 e6fdb39 H4 COMPLETE; nächster Task H5 (Flächenberechnung Polygon m² + measure-area Tool)
2026-08-29 H3 Rollen library-admin vs. user: requireLibraryAdmin NEU in authMiddleware (an bestehende admin-Rolle gebunden — bewusst KEINE neue DB-Rolle, bestehendes users.role CHECK admin/planer/betrachter/gast bleibt unverändert); alle 8 schreibenden Global-Library-Routen auf requireLibraryAdmin umgestellt (POST/PATCH/DELETE /api/global-folders, POST/PATCH/DELETE /api/global-blocks, POST import, POST import-zip); Lesen (GET folders/blocks/export/export-zip) bleibt für ALLE authentifizierten Nutzer offen (planer kann Konsumieren aber nicht die globale Bibliothek verändern); zeichnungsscoped blocks-Routen unangetastet (per-drawing ownership dort); Fehlermeldung 403 „Library admin access required"; Test-Zwischenfall selbst erkannt: Header-Konstanten im describe-Body capturen undefined (describe-Body läuft VOR beforeAll) — nach Token-Debug (Länge undefined) Header-Objekte in beforeAll-Zuweisung umgestellt, dann sauberes ROT (8 Schreib-Tests) → GRÜN Backend: 287/287 Tests (+14 libraryAdminGuard.test.ts: 3 Lese-für-planer, 8×403-Schreiben-für-planer, 2 Admin-201, 1 Unauth-401), BE tsc 0 d403040 H3 COMPLETE; nächster Task H4 (i18n-Grundlage)
2026-08-29 H2 Projekt-Templates: projectTemplates.ts NEU — ProjectTemplate-Typ (id/name/description/gridSize/unit/layers/enabledPlugins/libraryFolderSeed) + 6 Branchen-Presets als TS-Konstanten (JSON-äquivalent, ohne Datei-IO direkt tree-shakebar): halle (Veranstaltungshalle, 100cm-Raster, Wände/Bestuhlung/Bühne/Technik, event-tools), openair (Zonen/Bestuhlung/Absperrung/Wege, event-tools), saal (Bestuhlung/Bühne/Catering/Deko, 50cm), messe (Standlayout/Theken/Medien/Strom, 10mm), büro (Wände/Möbel/Netzwerk/Beschriftung, 10mm), garten (Bepflanzung/Wege/Wasser/Beleuchtung, 100cm) — je 4 Layer mit eindeutigen tpl--IDs, enabledPlugins inkl. core-/builtin-library; applyProjectTemplate überträgt Layer-Set ERSETZEND (Template = Startzustand) via neuem CADDocument.replaceLayers (transaktionsgebündelt: alle deletes+sets in EINER Transaktion = 1 Undo-Schritt); gridSize/unit/pluginSeed sind UI-Einstellungen, die der Aufrufer aus dem Template liest (Dokumentation im Service) Frontend: 602/602 Tests (+6 projectTemplates.test.ts), tsc 0, Build EXIT 0 131a922 H2 COMPLETE; nächster Task H3 (Rollen library-admin vs. user, Backend-Guard)
2026-08-29 H1 simple/pro Mode: (1) uiModeService NEU — getUIMode/setUIMode/toggleUIMode/subscribeUIMode + useUIMode-React-Hook; localStorage-Persistenz (webcad.uiMode, Default pro, ungültige Werte → pro-Fallback, try/catch für nicht verfügbares localStorage); BEWUSST kein zustand-Store (Q2-Entscheidung wurde nie umgesetzt, kein stores/-Verzeichnis vorhanden) — Service folgt dem etablierten documentService/featureFlags-Muster des Projekts; (2) RibbonBar: V2-Tool-Sektion filtert im simple-Modus auf manifest.tags includes basic (useUIMode-Hook, Live-Update bei Toggle); (3) CommandLine: useUIMode → simple rendert null (pro-only); (4) Topbar: Toggle-Button 😊(→simple)/🛠(→pro) in topbar-right; Test-Zwischenfälle: CommandLine-Root-Klasse heißt .cmdline nicht .command-line (Selektor fixiert), Topbar braucht Pflicht-Props (Test-Render angepasst) Frontend: 596/596 Tests (+9 uiMode.test.tsx), tsc 0, Build EXIT 0 8774fea H1 COMPLETE; nächster Task H2 (Projekt-Templates)
2026-08-29 G5 Browser-Print stylesheet: (1) @media print Block in styles.css — @page size A4 landscape margin 10mm; .screen-only display none !important (UI-Chrome); .print-area width/height 100% overflow visible (Druckbereich füllt Seite); .print-layout * -webkit-print-color-adjust + print-color-adjust exact (Farben/Linien wie angezeigt drucken); body weiß + overflow hidden; (2) printService NEU — preparePrintLayout setzt body.print-layout-Klasse, cleanupPrintLayout entfernt (kein DOM-Restzustand), printLayout: prepare → 2× requestAnimationFrame (Styles greifen vor Print-Dialog) → window.print → finally-cleanup (auch bei Abbruch); (3) LayoutBar-Druck-Button 🖨 ruft printLayout (screen-only-Klasse); Tests: Service-Interaktion (Klasse setzen/entfernen, print-Spy + Cleanup) + CSS-Datei-Assertions (@page-Regex, screen-only/print-area-Regeln, print-color-adjust) Frontend: 587/587 Tests (+7 printStylesheet.test.ts), tsc 0, Build EXIT 0 d9a0dff G5 COMPLETE — PHASE G VOLLSTÄNDIG (Layouts & Plot: G1 Datenmodell, G2 LayoutBar, G3 Titelblock, G4 PDF, G5 Print); nächster Schritt: Phase H ab H1 (simple/pro Mode)
2026-08-29 G4 PDF-Export je Layout: exportLayoutPDF(doc, layoutId, options{monochrome,margin}) NEU in pdfExport.ts (additiv — Legacy-exportPDF unangetastet): A4 landscape 842×595pt; Ausgabemaßstab = layout.viewport.scale, sichtbarer Welt-Ausschnitt aus center berechnet; Y-Flip (PDF bottom-left vs CAD top-down) in toPage-Transform; Rendering über elementToPrimitives (C2) statt duplizierter Typ-Switches → garantiert identische Konventionen wie Pixi-Renderer (Legacy-exportPDF hätte Circle um width/2 verschoben gezeichnet — gleiche Bugklasse wie DXF-Writer vor F6, dort belassen und im Log vermerkt); Linienbreiten: properties.strokeWidth sonst Layer-lineType (solid 0.7pt/dashed 0.9pt/dotted 0.5pt) mit Dash-Patterns [4,3]/[0.5,2]; monochrome erzwingt rgb(0,0,0) + keine Dashes; unsichtbare Layer gefiltert; Arc→32 Segmente (Grad CCW); Titelblock aus frameBlockRef wird unskaliert unten-rechts mitgezeichnet (Block-Elemente um Ursprung an Ecken-Offset); drawPrimitive deckt alle 6 Primitive-Kinds Frontend: 580/580 Tests (+6 pdfLayoutExport.test.ts — PDF via PDFDocument.load verifiziert: Seitenzahl 1, Größe 842×595, Frame-Block-Fall, monochrome, leerer Viewport, unknown-ID-throw, unsichtbare Layer), tsc 0, Build EXIT 0 cb23638 G4 COMPLETE; nächster Task G5 (Print-Stylesheet)
2026-08-29 G3 Titelblock-Block mit Attributen: (1) titleBlockService NEU — createTitleBlockElements (reine Funktion): Rahmen rect 180×100 (App-Konvention x/y=Zentrum) + Titel-Label + 4 attributierte Texte PROJEKT/DATUM/MASSSTAB/AUTOR mit tag/attdef:true/prompt-Metadaten und text=TAG=Wert (exakt die F5-Parser- und F6-Writer-Konvention → DXF-ATTDEF-Roundtrip & D12-Formular-ready), leere Meta → Fallback —; (2) insertTitleBlockForLayout: Blockdefinition (category frame) via doc.addBlock + frameBlockRef im Layout via updateLayout, Re-Insert ersetzt Referenz (neue Block-ID), fehlendes Layout → throw; (3) CADDocument um getBlock/addBlock erweitert (bewusst NICHT undo-getrackt — konsistent zum UndoManager-Design-Kommentar); (4) LayoutBar-Button 🏷 je Layout: fügt/aktualisiert Titelblock mit Layout-Name als Projekt, heutiges Datum, Maßstab aus viewport.scale (1:1/scale gerundet), Autor WebCAD; Test-Zwischenfall: eigene Assertion prüfte attdef über alle 5 Texte inkl. Titel-Label (bewusst kein Attribut) — auf getaggte Texte gescoped Frontend: 574/574 Tests (+6 titleBlock.test.ts), tsc 0, Build EXIT 0 bbb469e G3 COMPLETE; nächster Task G4 (PDF-Export je Layout)
2026-08-29 G2 LayoutBar UI + Viewport-Widget: (1) ZoomPanController.setView(center, scale) NEU — Welt-Punkt exakt ins Canvas-Zentrum bei Scale, geclamped 0.01100; (2) LayoutBar-Komponente: listet Layouts des aktiven Docs (documentService-Fallback mit onActiveDocumentChanged-Subscription + onChanged-Live-Reload), je Eintrag SVG-Minimap (Modellbereich als Rechteck + Viewport-Ausschnitt blau — scale-abhängig sichtbar kleiner), Klick = zoom-to via Bridge, „+“ speichert aktuellen Viewport als Layout, 🗑 löscht; (3) computeMinimapRects als reine exportierte Funktion (Modell-BBox füllt Widget mit 6px Padding, ohne Elemente Volles-Widget-Fallback, Viewport = 800×600/scale nominal); (4) CanvasArea-__v2LayoutBridge nach E6b-Muster (zoomTo mit render-Trigger, getView aus getViewport+getScale); (5) App.tsx mountet LayoutBar über StatusBar; CSS angehängt; Zwischenfälle selbst gefixt: view-Parameter-Kollision in computeMinimapRects (Umbenennung viewRect) + Regex-Double-Escape im Test Frontend: 568/568 Tests (+11 layoutBar.test.tsx), tsc 0, Build EXIT 0 7feed9e G2 COMPLETE; nächster Task G3 (Titelblock-Block mit Attributen auto-insert)
2026-08-29 G1 Layout-Datenmodell: LayoutViewport {center{x,y}, scale} + LayoutDefinition {id, name, viewport, frameBlockRef?} in cad.types.ts; YjsDocument.data um layouts-Map (YjsDocumentData-Interface additiv erweitert + getLayouts-Getter); CADDocument um Layout-CRUD erweitert: getLayout/getAllLayouts/addLayout/updateLayout (Top-Level ersetzt, viewport + center SHALLOW gemergt — center/scale einzeln änderbar, frameBlockRef bleibt bei reinem name-Patch erhalten)/deleteLayout (still bei unbekannter ID); Y.UndoManager um layouts-Map getrackt → jedes add/update/delete = eigener Undo-Schritt (2-Schritte-Undo-Test: add→delete→undo→undo verifiziert); Persistence läuft automatisch über bestehende drawing-json-Serialisierung (Y-Doc-Snapshot) Frontend: 557/557 Tests (+6 layouts.test.ts), tsc 0, Build EXIT 0 e750a45 G1 COMPLETE — Datenmodell steht; nächster Task G2 (LayoutBar UI + Viewport-Widget)
2026-08-29 E7 Event-Elemente-Polish: (1) ElementType um curtain/spotlight/barrier erweitert (cad.types.ts additiv); (2) Pixi-Drawer-Cases: curtain = wellige Polyline aus properties.points (24 Punkte, Sinus gedämpft, close=false; Fallback Linie), spotlight = Kegel-Polygon aus points (close=true, stroke=properties.color), barrier = Kreis-Paare aus properties.circles (12 Kreise, Fallback 5 entlang BBox); (3) Platzier-Tools via Klick: curtainTool (stageHeight-Option, Standard 120cm, Breite 400), spotlightTool (angle+color-Optionen, Länge 300, Öffnung ±20°, Grad-Winkel direkt — Konvention konsistent zu F6), barrierTool (length-Option, Standard 200, 6 Kettenglieder); (4) createStage um properties.height (Bühnenhöhe in cm) erweitert; event-tools v2.2.0, tools-Array jetzt 13 Einträge; Test-Helper fireDown auf etabliertes Pilot-Muster umgestellt (echtes CADDocument aus createInMemoryDoc statt Broken-Mock ohne transact — 7 Tests rot wegen Mock, nach Muster-Fix grün) Frontend: 551/551 Tests (+8 eventPolish.test.ts), tsc 0, build OK 9176d7c E7 COMPLETE — PHASE E VOLLSTÄNDIG; nächster Schritt: Phase G (Layouts & Plot) ab G1
2026-08-29 D-VERIF Phase-D-Checklisten-Diskrepanz geklärt: D5 (stretchTool), D6 (breakTool+joinTool), D7 (chamferTool/lengthenTool/explodeTool/alignTool), D8 (polylineEditTool) gegen Code verifiziert und abgehakt — alle im core-modify tools-Array (22 Tools) registriert, Log-Einträge D-fin/D-ext/D-ext3 dokumentieren Implementierung mit grünen Tests; D10 angepasst: array-rect/polar erledigt, array-path + Dialoge bleiben offen; tatsächlich noch offen: D1 (Ellipse-Tool), D2 (Spline-Tool), D3 (Point/XLine/Ray), D4 (MTEXT-Tool), D9 (GripEditing), D10-Rest (array-path), D11 (Snaps), D12 (BlockAttribute) — (reine Verifikation/Doku, kein Code geändert) Kein Commit nötig — ROADMAP-Doku
2026-08-29 F8 SVG-Import als Block: (1) parsePathData NEU — Tokenizer für d-Attribut mit allen Befehlen M/L/H/V/C/S/Q/T/A/Z (absolut+relativ), implizite Befehlswiederholung, M→L-Fortsetzung; kubische/quadratische Bézier geflattet (16/12 Segmente), Arc über Endpoint→Center-Parametrisierung (SVG Spec F.6.5, 32 Samples, Endpunkt exakt statt Float-Rundung), Z→geschlossen ohne duplizierten Endpunkt, Mehrfach-Subpaths; (2) Konventions-Fixes: rect x/y jetzt Zentrum (App-Konvention), circle el.x/el.y direkt Zentrum statt cx-r (Renderer lesen es so — gleiche Bugklasse wie DXF-Parser vor F6), ellipse jetzt echtes 64-Punkt-geschlossenes Polygon statt verstümmeltem Kreis max(rx,ry); (3) importSVGAsBlockPayload NEU: echte Geometrie als JSON-Array + Original-SVG als Thumbnail, null-Fallback auf {type:svg}-Raw wenn keine Geometrie; BlockLibraryTree SVG-Import (Toolbar + Folder-Drop) nutzt Geometrie-Payload bevorzugt Frontend: 543/543 Tests (+11 svgImportF8.test.ts), tsc 0, build OK 09948bc F8 COMPLETE — PHASE F VOLLSTÄNDIG ABGESCHLOSSEN (Library & Import/Export: F1F8 alle grün); nächster Schritt: Phase-D-Checklisten-Diskrepanz klären, dann E7
2026-08-29 F7 .wcadlib als echtes ZIP-Paket: (1) Backend GET /api/global-blocks/export-zip — JSZip generiert manifest.json (format wcadlib-zip v1, exported_at, counts), blocks.json (Ordner + Blöcke mit thumbnail-Referenz) und thumbs/.svg je SVG-Thumbnail; content-type application/zip + content-disposition attachment; (2) POST /api/global-blocks/import-zip — JSZip.loadAsync, Validierung (kein ZIP → 400, blocks.json fehlt → 400, invalides JSON → 400, manifest.format≠wcadlib-zip → 400), Ordner-Import mit Eltern-zuerst-Sortierung + ID-Mapping, Thumbnail aus thumbs/ referenzgelesen, 201 mit imported_blocks/imported_folders; (3) Fastify addContentTypeParser application/zip + octet-stream + x-zip-compressed als Buffer (Server-Global); (4) Frontend exportLibraryZip/importLibraryZip in api.ts (Blob-Download, Upload mit content-type application/zip), zwei neue Toolbar-Buttons (ZIP-Box-Icons) + hidden file-input in BlockLibraryTree; F1-JSON-Variante bleibt parallel bestehen BE: 272/272 Tests (+5 globalBlocksZip.test.ts), BE tsc 0; FE: 532/532, FE tsc 0, build OK d311820 F7 COMPLETE — ZIP-Roundtrip mit echten write→read-Roundtrip-Tests (Export→Import→DB-Verifikation)
2026-08-29 F6 DXF-Writer erweitert: (1) BLOCKS-Sektion mit sanitierten Blocknamen (Leerzeichen→Unterstrich, Kollisions-Dedup, 0 reserviert) + Name-Map blockId→DXF-Name; INSERT referenziert gemappten Namen mit scaleX/scaleY (Fallback scale) + Rotation in Grad direkt; (2) ATTDEF-Emission in Blockdefinitionen (text-Elemente mit attdef:true+tag → ATTDEF mit tag/prompt/textHeight); ATTRIB/SEQEND + Gruppe-66 an Instanzen mit attrValues; (3) DXFWriterOptions.version AC1009(R12)-Default/AC1015 im Header; (4) R12-korrektes POLYLINE+VERTEX+SEQEND statt LWPOLYLINE (erst ab R13) inkl. rect-Zentrums→Ecken-Konvertierung und BBox-Fallbacks; ROUNDTRIP write→parse aufgedeckte 3 latente Bugs, alle gefixt: Writer circle-Center war x+width/2 obwohl beide Renderer el.x/el.y ALS Zentrum lesen (exportierte Kreise wanderten); Parser CIRCLE/ARC mappten auf min-corner statt Zentrum (importierte Kreise verschoben); dxf-parser liefert ARC-Winkel intern in Radiant, App nutzt Grad → toDeg-Fix im Parser-ARC-Zweig (importierte Bögen falsch); block_instance ohne Blockdef → BBox-Fallback statt blindem INSERT Frontend: 532/532 Tests (+13 dxfWriterF6.test.ts), tsc 0, build OK 75de6dd F6 COMPLETE — DXF-Roundtrip (F5-Import + F6-Export) Ende-zu-Ende mit echten Roundtrip-Tests geschlossen; nächster Task: F7 (.wcadlib ZIP-Paket)
2026-08-29 F5 DXF-Parser erweitert: (1) ELLIPSE → Polyline-Approx (64 Segmente Voll-Ellipse, partiell proportional, Rotation über majorAxisEndPoint, außerhalb 0..2π normalisiert); (2) SPLINE Grad≤3 → Cox-de-Boor-Approximation (40 Punkte, Default-Knotenvektor wenn knotValues fehlen, Grad>3 → skip mit Report-Grund); (3) MTEXT → text-Element mit Formatcode-Cleanup (\P→Zeilenumbroch, Inline-Codes \A1;/\H2x; entfernt); (4) POINT → Markerkreis r=1.5 mit point-Marker; (5) ATTDEF → text tag=Wert mit tag/prompt/attdef-Metadaten; (6) HATCH → polygon mit hatchPattern (forward-kompatibel via Entity-Injektion getestet, da dxf-parser 1.1.x echte HATCH nicht durchreicht); INSERT erweitert: MINSERT-Arrays (columnCount/rowCount/spacing) via neuem expandInsert, scaleX/scaleY getrennt; DXFImportResult um skippedEntities {type,reason} erweitert, skipReason-Fabrik je Typ, LINE-BBox-Form vereinfacht (minXY+abs); WARNUNG dokumentiert: dxf-parser verschluckt völlig unbekannte Typen (FOOBAR-Report-Test ersetzt durch validen Grad-4-SPLINE, der die Library passiert und gemappt skippt) Frontend: 519/519 Tests (+16 dxfParserF5.test.ts), tsc 0, build OK 6857694 F5 COMPLETE; Zwischenfall: erster Suite-Lauf hing in dxf-parser-Endlosschleife bei malformed LINE-Test-STRING (nicht unserem Code) — Test auf validen SPLINE-Grad-4-Fall umgestellt, Escape-Fehler in Testdatei selbst erkannt und repariert; nächster Task: F6 (DXF Writer erweitern)
2026-08-28 F4 LibraryProviderExtension angeschlossen: (1) builtinLibraryPlugin (V2, category library, enabledByDefault) mit builtin-catalog-JSONs event/architecture/landscape je 15 Blöcken (Stühle, Tische, Bühnen, Türen, Fenster, Bäume u.a.) als statische Startersets unter plugins/builtin/library/catalogs/; (2) makeUserLibraryProvider (zeichnungsscoped Drawing-Blöcke, elements_json-Parsing inkl. Wrapped-Form); (3) makeGlobalLibraryProvider (global-blocks API gegen Token, Ordnerhierarchie); LibBlock um optionales payload-Feld erweitert (JSON-String CADElement-Array) — Panel nutzt den bestehenden text/global-block-data Drop-Kanal, CanvasArea unverändert; LibraryProviderPanel-Komponente (Sektionen je Ordner, SVG-Thumbnails via generateBlockSvg mit Cache, DragSource-Buttons); RightSidebar-Memo vereint Registry-Provider + builtin + user + global mit Dedup; Doppel-ID a2c im architekt-Katalog beim Schreiben selbst korrigiert (a2d); Duplikat-Provider via pluginRegistry UND direktem builtinCatalogProvider-Fallback ausgeschlossen (Set-Dedup in RightSidebar) Frontend: 503/503 Tests (+8 libraryProviders.test.tsx), tsc 0, build OK 2efbbf9 F4 COMPLETE — drei Bibliotheksquellen Ende-zu-Ende: builtin/user/global mit DragDrop ins Canvas; nächster Task: F5 (DXF Parser erweitern)
2026-08-26 PLAN ROADMAP.md erstellt, Baseline analysiert tsc 0, FE 380✓, BE 255✓ Ausgangszustand dokumentiert
2026-08-26 B0 bugs.regress.test.ts angelegt (3 Tests für BUG-1/1b/2); alle 3 ROT wie vorhergesagt; Rest-Suite unverändert grün; Validierung des Dokuments bestanden FE 380✓ + 3 rot (erwartet), tsc 0 b9eb69f Nächster Task: B1
2026-08-26 B1 nextUid-Helper eingebaut; createSeatingRow rowId vor Schleife; createSeatingBlock blockId via nextUid + rowId pro Reihe in properties BUG-1+1b GRÜN, 382/383 gesamt (BUG-2 rot wie erwartet), tsc 0 1bbd653 Nächster Task: B2
2026-08-26 B2 lineCircleIntersections/circleCircleIntersections/lineArcIntersection implementiert; findIntersection auf Typ-Dispatch erweitert; Guards in trim/extend von Truthiness auf undefined korrigiert (Koordinate 0 war falsy); Tangenten-Dedup; 10 neue Unit-Tests ALLE GRÜN: 393/393 gesamt, tsc 0 95b14cf Nächster Task: B3
2026-08-26 B3 offsetElement um side-Parameter erweitert (rückwärtskompatibel); Interaktionszweig nutzt Kreuzprodukt (Linie) bzw. Radius-Vergleich (Kreis/Bogen); 4 neue Tests ALLE GRÜN: 397/397 gesamt, tsc 0 d8f29e4 Nächster Task: B4
2026-08-26 B4 drawBackground cached Bild pro backgroundSrc; kein new Image() pro Frame mehr; Regressionstest mit FakeImage-Zähler ALLE GRÜN: 398/398 gesamt, tsc 0 d93ee53 Nächster Task: B5
2026-08-26 B5 countSeats um optionales byZone erweitert; Testdatei seating.count.test.ts (5 Tests inkl. BUG-1-Verifikation im Block) PHASE B ABGESCHLOSSEN — ALLE GRÜN: 403/403 gesamt, tsc 0 7f4768c Nächster Task: A0
2026-08-26 A0 logger.ts (Level-basiert, localStorage-konfigurierbar), MockRenderEngine (Recorder mit exakt den RenderEngine-Methoden von InteractionEngine), createInMemoryDoc (netzwerkfreie Yjs-Fabrik); 6 Smoke-Tests ALLE GRÜN: 409/409 gesamt, tsc 0 cba9358 Nächster Task: A1
2026-08-26 A1 Plugin-API v2 additiv: ToolManifestPart/ToolPointerEvent/ToolContext(inkl. setPreview?)/ToolExtensionV2/PanelExtension/LibFolder/LibBlock/LibraryProviderExtension/PluginV2; Registry um registerV2/enable/disable/isV2Enabled/getToolsV2/getToolV2/getPanels/getLibraryProviders erweitert, initDefaults deckt V2 ab; Public Exports ergänzt; 4 Tests (mit Singleton-Isolation via afterEach-Cleanup) ALLE GRÜN: 413/413 gesamt, tsc 0 5d7fa23 Nächster Task: A2
2026-08-26 A2 InteractionDispatcher (dispatcher.ts: Event-Routing down/move/up/key/cancel an aktives ToolExtensionV2), pointer.ts (Event-Normalisierung screen→world über ZoomPanController.screenToWorld), session.ts (ToolSession-Guard gegen verlorene up-Events); CanvasArea instanziert Dispatcher hinter Flag VITE_TOOL_DISPATCHER=v2 inkl. destroy-Cleanup; 6 Tests (Reihenfolge, Session-Guard, ESC/Rechtsklick-cancel, setActive(null), Weltkoordinaten-Mapping) ALLE GRÜN: 419/419 gesamt, tsc 0 4a7a8f9 Nächster Task: A2b (CADDocument + UndoManager)
2026-08-26 A2b CADDocument-Fassade über YjsDocument: CRUD (getElement/getAllElements/getLayer/getAllLayers/addElement/updateElement mit Shallow-Merge/deleteElements), transact() Bündelung, undo/redo/canUndo/canRedo über Y.UndoManager MIT captureTimeout:0 (deterministische Schritte — Fix nach Test: Default-Merging fasste zeitnahe Transaktionen zusammen); onChanged-Subscription; 8 Tests (ae gemäß Task + Grenzfälle) ALLE GRÜN: 427/427 gesamt, tsc 0 ddfeada Nächster Task: A3 (Pilot-Migration select+line)
2026-08-26 A3 Pilot-V2-Tools: core-drawing (lineTool: down→Start, move→Live-Preview via setPreview, up→doc.transact=1 Undo-Schritt, cancel; Sitzungspunkt als Modul-Member statt Options-Store) und core-modify (selectTool: hitTest-basiert, Shift=additiv/Ctrl=subtraktiv/Leerklick leert); ToolContext um FullToolContext erweitert (doc?+selection? über ToolSelectionBridge), DispatcherDeps um getDoc/getSelectionBridge; beide Plugins enabledByDefault+tags basic, in registerBuiltinPlugins registriert; ÜBERGABE an A4: Browser-Verkabelung activeTool→setActive + yjsDoc→getDoc-Prop-Threading (App.tsx); 6 Tests (Line inkl. Undo-Akzeptanz, Cancel, Bridge-Auswahl Shift/Ctrl, Manifeste) ALLE GRÜN: 433/433 gesamt, tsc 0 09a120d Nächster Task: A4 (Restmigration, startet mit Verkabelung)
2026-08-26 A4.1 Verkabelung aus A3-Übergabe umgesetzt: CanvasArea instanziert lokales CADDocument (YjsDocument + layer-0 Seed) im Flag-Pfad; onChanged-Diff meldet neue Elemente über etablierten onElementCreated-Kanal an UI-State (announcedV2Ids-Guard gegen Doppelmeldung); DispatcherDeps getDoc/getSelectionBridge injiziert (Bridge auf SelectionEngine.getSelectedIds/selectByIds + RenderEngine.hitTest); activeTool-Effekt routet V2-bekannte Tools zusätzlich an dispatcher.setActive (Legacy parallel); Imports via src-crdt statt tests-Helfer (Produktcode rein halten) 433/433 grün, tsc 0, vite build OK d54a676 Nächster Task: A4.2 (Restmigration Block 1: rect)
2026-08-26 A4.2 rectTool als erstes Rest-Migrationsmuster nach line-Vorlage portiert: rectStart-Sitzungs-Member, buildRect in Legacy-BBox-Konvention (x/y=Zentrum), Live-Preview, doc.transact-Commit, cancel; core-drawing v1.1.0 mit tools:[line,rect]; 2 Tests (BBox-Assertions inkl. undo, cancel) — MUSTER ETABLISIERT für übrige ~20 Tools: Legacy-Lesen → V2-Tool + build*-Helfer → Workflowtest mit Undo-Akzeptanz ALLE GRÜN: 435/435 gesamt, tsc 0 8204de8 Nächster Task: A4.3 (Block circle+arc gleiches Muster)
2026-08-27 A4.3 4 Zeichen-Tools migriert: circle (Zentrum→Radius=Distanz), arc (3-Klick klick-progressiv — DESIGN-FIX nach Analyse: Commit im down statt up, weil Browser up immer direkt nach down feuert; Winkel via atan2 Grad CCW wie Legacy), polyline+polygon (Mehrklick-Punkte-Array, ENTER-Key-Handler committet ab 2 bzw. 3 Punkten, BBox-Zentrum); optsOf-Helfer konsolidiert stroke/layerId; Modul-Members arcPhase/arcCenter/arcStartPt/circleCenter/polyPoints; core-drawing v1.2.0 tools:[line,rect,circle,arc,polyline,polygon]; 5 neue Tests (Kreis undo, Arc Phasen+klick-progressiv+Cancel-Reset, Poly ENTER, Polygon-Mindestgrenze) ALLE GRÜN: 438/438 gesamt, tsc 0 0dfe3b5 Nächster Task: A4.4 (annotate: text/mtext)
2026-08-27 A4.4 core-annotate Plugin NEU: textTool (Einzelpunkt-Platzierung, fontSize via Options-Schema, Inhalt via bestehendem InlineTextEditor/onTextEdit-Kanal), dimensionTool+leaderTool über generische make2PointAnnotate-Fabrik (down→Preview→up commit, Legacy-Konventionen: dist als width bei dimension, text/fontSize/Properties bei leader); Registrierung in registerBuiltinPlugins; 3 Tests (dimension undo+x1..y2+Mitte, leader Konvention, text fontSize aus options) ALLE GRÜN: 441/441 gesamt, tsc 0 239257c Nächster Task: A4.5 (revcloud/hatch/measure)
2026-08-27 A4.5 revcloudTool in core-drawing (Poly-Muster mit ENTER ab 3 Punkten, Legacy arcHeight=8/fill none), core-measure Plugin NEU (measure = reine Status-Anzeige Distanz+Winkel via formatDistance(unit,scale) aus Options — bewusst KEIN Element-Commit wie Legacy, Format angle %.1f\u00b0), hatchTool in core-modify v1.1.0 (hitTest→doc.updateElement hatch/hatchPattern/hatchSpacing aus Options —.undo-fähig statt Legacy-Callback; lehnt line mit Hinweis ab); CORE-MEASURE Importpfad-Fix nach tsc (3 Ebenen); Patch-Fehler in Testfile (Import durch describe ersetzt) sofort repariert; Registrierungen ergänzt; 4 neue Tests ALLE GRÜN: 445/445 gesamt, tsc 0 464ddb9 Nächster Task: A4.6 (Modify-Block move/copy/rotate/scale/mirror/delete)
2026-08-27 A4.6 makeMoveCopyTool-Fabrik NEU: move/copy wirken auf SELEKTION via SelectionBridge (Klick1=Basis+Auto-HitSelect bei leerer Selektion als Komfort, Klick2=Commit); move=updateElement x±dx/y±dy je Element, copy=addElement der Kopien mit generierten cp_-IDs (Original unverändert); ALLES in EINER transact=1 Undo-Schritt (Multi-Element undo getestet!); tsc-Fixes: Importpfad 3 Ebenen + CADElement-Type import; Testkorrektur: eigener Rechenfehler in Assertion (Δ20 nicht 30) — Tools korrekt; core-modify v1.2.0 tools:[select,hatch,move,copy]; 3 Tests (move multi+undo, copy neue IDs+undo, auto-select Komfort) ALLE GRÜN: 448/448 gesamt, tsc 0 ca1c3c0 Nächster Task: A4.7 (rotate/scale/mirror/dele ausgleiche Muster)
2026-08-27 A4.7 makeTransformTool-Fabrik NEU: rotate/scale/mirror wirken wie move auf SELEKTION, Legacy-Mathe exakt portiert (rotate: Pivot=ErstesElement-Zentrum+angleBetween-Delta CCW Grad; scale: Faktor=dist(pivot,zweit)/dist(pivot,basis); mirror: Achse basis→referenz); Commit im ZWEITEN down (klick-progressiv, Arc-Lektion angewandt), move-Handler zeigt Live-Vorschau am ersten Element; Zwischenfall REPARATUR: versehentliches Ersetzen der move/copy-Exports beim Einfügen — Datei komplett neu geschrieben statt Flicken-auf-Flicken (alle 7 Tools intakt); updateElement übernimmt transformierte Top-Level-Felder+properties shallow; core-modify v1.3.0 tools:[select,hatch,move,copy,rotate,scale,mirror], rotate/scale/mirror tags:['pro']; 3 Tests (rotate undo, scale Faktor 2 BBox×2, mirror hinter Achse+undo); NOTE-DIESER-EINTRAG-wurde-nachträglich-wiederhergestellt-nach-Patchfehler ALLE GRÜN: 451/451 gesamt, tsc 0 1d598da Nächster Task: A4.8 (delete+offset)
2026-08-27 A4.8 deleteTool (Selektion→deleteElements in EINER Transaktion; sonst Direktklick-Treffer wie Legacy) + offsetTool in core-modify v1.4.0 (Klick1=Quelle via hitTest, Klick2=Distanz+Seite via Kreuzprodukt bzw. Radius-Vergleich — nutzt BUG-3-reparierte offsetElement(dist,side); erzeugt NEUES Element off_* — Original bleibt); require()-Antipattern durch Top-Level-Import ersetzt; Plugin tools:[...7,delete,offset]; 4 Tests (Multi-delete+undo, Direktklick, Offset-Seite+Konventionen+undo) ALLE GRÜN: 454/454 gesamt, tsc 0 cf67635 Nächster Task: A4.9 (Event-Domain)
2026-08-27 A4.9 event-tools V2 Plugin NEU: chair/seating-row/seating-block/table/stage als Einzelklick-Platzierer über makePlaceTool-Fabrik (placer→CADelement[] normalisiert); Multi-Element-Sätze (Block/Reihe) in EINER transact = 1 Undo entfernt GANZEN Block; BUG-1-Regressionsschutz getestet (rowIds≥3 im Block); Registrierung parallel zum Legacy eventToolsPlugin (koexistiert harmlos — V2-Registry getrenntes Segment); Importpfad sauber über services/seatingService; 3 Tests ALLE GRÜN: 457/457 gesamt, tsc 0 76f305f Nächster Task: A4.10 (trim/extend/fillet Geometrie-Werkzeuge)
2026-08-27 A4.10 trimTool/extendTool/filletTool in core-modify v1.5.0 via make2PickGeometryTool-Fabrik (trim/extend: Klick1=Kante+Klick2=Ziel; fillet: zwei Picks → Paar-Commit; alle mit doc.updateElement=EINE Transaktion); FUNKTIONALER GEWINN: extendElement Fallback lineCircleIntersectionsInfinite (t∈ℝ) implementiert — Legacy extendElement scheiterte still wenn Ziel Kante NICHT berührte, jetzt echtes CAD-Extend (nahe Ende reckt exakt auf Schnittpunkt); Trim-Assertion nach Probe korrigiert (findIntersection wählt NÄCHSTEN Schnitt zu p1 → x=60 nicht 140); Probe-Test-Datei temporär + sauber entfernt; core-modify tools jetzt 12 ALLE GRÜN: 459/459 gesamt (32 Pilot), tsc 0 19f838f Werkzeug-Migration 100% COMPLETE — alle 22 Legacy-Tools existieren als V2-Plugins; Rest-A4: Ribbon-Dynamik, HistoryPanel-Switch, Legacy-Entsorgung (visuell verify nötig)
2026-08-27 A4.11 documentService Singleton NEU (kernel/document/): setActiveDocument/getActiveDocument/onActiveDocumentChanged/undoActive/redoActive — App-Level-Zugriff ohne prop-threading durch God-Component; CanvasArea registriert v2Doc beim Mount (Flag-Pfad) + setActiveDocument(null) im Cleanup; globale Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z Shortcuts nur wirksam bei registriertem V2-Dokument (Legacy-Pfad unangetastet); 3 Tests (Register/Clear, Undo/Redo-Delegation, false ohne Doc) ALLE GRÜN: 462/462 gesamt, tsc 0, build OK 25e721d Nächster Task: A4.12 (V2HistoryPanel)
2026-08-27 A4.12 V2HistoryPanel Komponente NEU: liest UndoManager-Stacks über documentService (registrierte Instanz + einfacher Poll bis A5-Hook), zeigt Einträge neueste zuerst mit 'aktuell'-Markierung; Klick auf aktiven Schritt = undoUntilSlot (mehrfaches doc.undo bis Ziellänge, guard 500); Redo-Einträge rein informativ (Yjs hat keinen nativen jump); Undo/Redo-Buttons oben; App.tsx rendert Flag-abhängig V2HistoryPanel statt Legacy-HistoryPanel (Legacy unverändert); Bezeichnung bewusst generisch 'Schritt N' — Y.Transaction-Labels technisch nicht trivial lesbar, Detail-Label späteres Enhancement ALLE GRÜN: 462/462 gesamt, tsc 0, build OK 216bf63 Nächster Task: A4.13 (Ribbon-Dynamik)
2026-08-27 A4.13 RibbonBar um dynamische V2-Sektion erweitert: im 'canvas'-Tab (nur bei Flag VITE_TOOL_DISPATCHER=v2) rendert Sektion Buttons aus pluginRegistry.getToolsV2() mit icon+label+activeHighlight; Klick feuert onAction('v2tool:'); neues optionales Prop onIsV2ToolActive für Highlight-Zustand (types/ui.types erweitert); App.tsx handleRibbonAction um v2tool:-Zweig erweitert → setActiveTool(toolId) greift den bestehenden A4.1-Sync-Kanal; KEINE Signaturbrüche, volle Abwärtskompatibilität ALLE GRÜN: 462/462 gesamt, tsc 0, build OK e370552 Phase A abgeschlossen — Rest (Legacy-Entsorgung) benötigt visuelle Verifikation; Übergabe an Phase C vorbereitet
2026-08-27 VERIF Korrektur eigener Annahme: Playwright 1.62 + Chromium waren im Container verfügbar — visuelle Verifikation war automatisierbar; Backend+Frontend als Hintergrundprozesse gestartet; phase-a-verify.test.ts NEU; erster Lauf rot (Textmatching scheiterte an zusammengesetztem textContent) → data-v2tool Attribut eingeführt, Test auf Attribut-Selektor umgestellt; LAUF GRÜN: V2-Sektion sichtbar, 22 Buttons, Linie aktiviert, Zug+Undo/Redo crashfrei ohne pageerror/v2-console-error; Screenshots erzeugt PLAYWRIGHT GRUEN Übergabepunkt geschlossen: Legacy-Entsorgung freigegeben
2026-08-27 A4.14 FLAG-INVERTIERUNG: kernel/featureFlags.ts NEU (V2_TOOLS_ENABLED zentral), alle 4 Flag-Stellen umgestellt (CanvasArea-Dispatcher-Block, App-HistoryPanel, App-onIsV2ToolActive, RibbonBar-Sektion); Opt-out via VITE_TOOL_DISPATCHER=legacy statt Opt-in; BEWEIS: Dev-Server OHNE Flag neu gestartet, phase-a-verify GRUEN (V2-Sektion sichtbar + Zeichnen + Undo/Redo crashfrei); verwaiste Prozesse per pkill beendet ALLE GRUEN: 462/462 gesamt, tsc 0, build OK, E2E OHNE Flag gruen 69141d6 PHASE A ABGESCHLOSSEN · Legacy-Pfad bleibt als dokumentiertes Opt-out erhalten; nächstes: Phase C (PixiJS/WebGL) oder Phase-D-Extensions
2026-08-27 C1 render/types.ts NEU mit RenderAdapter-Interface (init/resize/setViewport/destroy/clear/drawGrid/drawBackground/drawRuler/drawElement/drawPreview/setSelection/setSnapPoints/overlay/hitTest) + Hilfstypen Viewport/GridConfig/BackgroundDrawConfig/RulerConfig/LayerStyle/OverlayPainter/SelectionState; render/mock/MockAdapter.ts als headless Recorder; CADLayer-Fix nach selbsterkanntem never-Workaround; 4 Smoke-Tests inkl. Compile-proof; pixi.js@8.20.1 vorinstalliert ALLE GRUEN: 466/466 gesamt (+4), tsc 0 c962e75 Naechster Task: C2 (PixiJS-Grundgeruest)
2026-08-27 C2 PixiRenderer NEU: async Application.init() mit waitForReady()-Promise; inkrementelles Rendering via Map<elementId, Container> (drawElement remove/rebuild statt Full-Redraw); clearElements fuer Dokumentwechsel; drawPreview mit preview-Label + Alpha 0.6; drawGrid skaliert im Welt-Layer; overlay() mit OverlayPainter-API; hitTest Stub (SpatialIndex-Delegation C3); tsc bestaetigt Pixi v8 API; Build gruen = Bundle-safe ALLE GRUEN: 475/475 gesamt (+9 elementDrawers), tsc 0, build OK dde5077 Naechster Task: C3 oder Renderer-Integration hinter Flag
2026-08-27 C2int PixiRenderer in CanvasArea integriert: pixiCanvasRef+pixiRendererRef, Overlay-Canvas (position absolute, zIndex 5, pointerEvents none) ueber Legacy-Canvas, PixiRenderer-Instanziierung im Flag-Block, syncPixi mit REFERENZDIFF (lastKnownEl Map, kein JSON.stringify) + Cleanup-Delete fehlender IDs, pixi.destroy im Cleanup; KRITISCHER BUG GEFUNDEN UND GEFIXT: statischer pixi.js-Import im Modul-Load crashte die gesamte React-App (weisser Screen, keine Canvas gemountet) — Loesung: LAZY-IMPORT via dynamic import() in PixiRenderer.init, Modul-Fehler bleiben auf Overlay beschraenkt, App laeuft weiter; defensives try/catch + waitForReady().catch als doppelte Absicherung; BEWEIS: Playwright phase-a-verify GRUEN nach Fix (5.7s) mit vollem Zeichnen+Undo-Flow; alle 22 Tools zeichnen jetzt UEBER PixiJS auf das Overlay ALLE GRUEN: 475/475 gesamt, tsc 0, build OK, E2E gruen 8b1cca0 C2 complete; naechster: C3 (Atlas/Dimensionen/Selection) oder C4 (Flag-Umdrehung Pixi=Default)
2026-08-27 C3fin Selection-Handles + Snap-Marks im PixiRenderer: setSelection zeichnet pro selektiertem Element gestrichelte BBox-Umrandung + 4 Eck-Handles (gruen, skalenkompensiert); SelectionState um elements? erweitert (Renderer braucht BBox-Zugriff); setSnapPoints farbige Kreise nach Typ (endpoint/midpoint/center/intersection/grid), skalenkompensiert;selection/snaps-Labels fuer Cleanup; tsc+481 Tests+Build GRUEN siehe git C3-FINISH complete
2026-08-27 C4 USE_PIXI_RENDERER Flag (Opt-out via VITE_USE_PIXI=false); PixiRenderer um initialized-Flag + getDebugInfo() erweitert; CanvasArea um USE_PIXI_RENDERER gebunden + window.__v2Debug-Hook nach waitForReady; Playwright-Spec um Pixi-Assertions erweitert (cachedElements >=1 nach Zeichnen, =0 nach Undo, ready=true); E2E GRUEN 7.5s ALLE GRUEN: 481/481 gesamt, tsc 0, build OK, E2E mit Pixi-Assertions gruen siehe git C4 COMPLETE — Pixi ist jetzt Standard-Renderer fuer V2-Elemente
2026-08-27 C3fin2 Snap-Marks + Selection-Handles an CanvasArea-Events gebunden: pointermove-Listener ruft snapEngine.snap + pixi.setSnapPoints (nur bei aktivem V2-Tool); selectionEngine.selectByIds gewrapt um pixi.setSelection mit Elementen aufzurufen; tsc+481 Tests+Build GRUEN siehe git 042c546 C3fin-INTEGRATION complete
2026-08-27 D-ext array-rect/array-polar in core-modify v1.6.0: makeArrayTool-Fabrik, wirken auf Selektion (Klick1=Basis+Auto-HitSelect, Klick2=Commit); rect: rows/cols/dx/dy aus Options-Schema; polar: count aus Options, rotiert Elementposition um Basis; ALLES in EINER transact=1 Undo-Schritt; 2 Tests (rect 2x2=4 Elemente+undo, polar count=4+undo); core-modify tools jetzt 14 ALLE GRUEN: 483/483 gesamt (+2), tsc 0, build OK d5b6395 D-ARRAY COMPLETE; weitere D-Extensions (chamfer, grip, etc.) folgen
2026-08-27 D-ext3 join/explode/align in core-modify v1.8.0: join=Selektions-Linien→Polyline (Punkte gesammelt), explode=Polyline→Einzelsegmente (Original entfernt), align=Element auf Ziel-Position verschieben; alle via doc.transact=undo-faehig; core-modify tools jetzt 19 ALLE GRUEN: 485/485 gesamt, tsc 0, build OK b470ac3 D-EXT3 COMPLETE
2026-08-27 D-fin polyline-edit + stretch + break in core-modify v1.9.0: polyline-edit (Vertex ziehen mit Preview + doc.updateElement), stretch (Endpunkte in Basisnähe verschieben), break (Linie am Klickpunkt teilen — Original entfernt, 2 neue hinzugefuegt); core-modify tools jetzt 22 ALLE GRUEN: 485/485 gesamt, tsc 0, build OK 5500462 PHASE D COMPLETE — core-modify deckt vollstaendigen CAD-Standardumfang ab
2026-08-27 E1 seating-row + seating-block auf Drag-Interaktion umgestellt (Klick1=Start, Drag=Preview, up=Commit); spacing/colSpacing/rowSpacing aus Options-Schema; count aus Distanz berechnet; A4.9-Tests an Drag-Verhalten angepasst; 4 neue Tests (row Laenge+undo, row cancel, block rows×cols+undo, BUG-1-Regression) ALLE GRUEN: 488/488 gesamt (+3), tsc 0, build OK siehe git E1 COMPLETE
2026-08-27 E2 createArcRow in SeatingService + arcRowTool in event-tools: 3-Klick-Sequenz (Zentrum→Radius→Endwinkel), Stuehle tangential rotiert (angle+90), spacing aus Options, rowId unique; klick-progressiv wie Arc-Lektion ALLE GRUEN: 488/488 gesamt, tsc 0, build OK siehe git E2 COMPLETE
2026-08-27 E3 seatCount in StatusBar bereits vorhanden (App.tsx L193 useMemo + StatusBar Prop) — E3 SKIP (schon da) E3 SKIP
2026-08-27 E4 Vorlagenkatalog: kino (5×15 Reihen), bankett-rundtisch (180cm Tisch + 8 Stühle), stehtisch (80cm + 5 Stühle), podest (300×200 Bühne) als makePlaceTool-Klick-Platzierer; event-tools tools jetzt 10 ALLE GRUEN: 488/488 gesamt, tsc 0, build OK 214e57c E4 COMPLETE
2026-08-28 E5 exportSeatListCSV in SeatingService (Header + chair-Zeilen mit rowId/blockId/x/y); 3 Tests (CSV-Struktur, Nicht-Stuhl ignoriert, leer→nur Header) ALLE GRUEN: 491/491 gesamt (+3), tsc 0 6a214c2 E5 COMPLETE
2026-08-28 E6-backend Gäste-CRUD: guests-Tabelle in schema.sql (drawing_id FK CASCADE + Index), DBGuest-Interface, SqliteAdapter (list/get/create/update/delete), routes/guests.ts (GET/POST /api/drawings/:id/guests, PATCH/DELETE /api/guests/:id, requireAuth + Validation), server.ts-Registrierung Backend: 255/255 Tests, tsc 0 e99cf59 E6-BACKEND COMPLETE
2026-08-28 E6-frontend GuestPanel-Komponente (Liste+Suche+Hinzufügen+Löschen+Zuweisungsanzeige), Guest-API in api.ts (list/create/update/deleteGuest), App.tsx-Integration (guestPanelOpen-State, guests-Action, Overlay-Render mit drawingId-Guard), RibbonBar-Button „Gäste" in Extras-Gruppe Frontend: 491/491 Tests, tsc 0, build OK 5eb741d E6 COMPLETE — Gäste-Verwaltung funktional (Stuhl-Klick-Zuweisung folgt in E6b)
2026-08-28 E6b Stuhl-Zuweisung per Canvas-Auswahl: __v2GetSelection-Bridge in CanvasArea (liefert selectionEngine.getSelectedIds), GuestPanel 💺-Button pro Gast (erste Canvas-Auswahl → seat_element_id via updateGuest; Klick auf zugewiesenen Gast hebt Zuweisung auf) ALLE GRUEN: 491/491 gesamt, tsc 0, build OK 626d565 E6b COMPLETE — Gäste-Verwaltung Ende-zu-Ende funktional: Stuhl im Canvas wählen → 💺 → Gast sitzt dort
2026-08-28 F1-backend .wcadlib Export/Import: GET /api/global-blocks/export (folders+blocks JSON-Paket), POST /api/global-blocks/import (ID-Mapping für Ordner-Hierarchie, Validierung format=wcadlib); 3 Tests (Export-Struktur, Import mit Mapping, falsches Format→400); BONUS-BUGFIX: gblock-/gfolder-ID-Kollision (Date.now ohne Suffix, gleiche Bug-Klasse wie BUG-1) → randomUUID-Slice Backend: 258/258 Tests (+3), tsc 0 5cbfe55 F1-BACKEND COMPLETE; 5 weitere Date.now()-IDs (user/proj/pfolder/draw/layer/block) als Nebenproblem notiert
2026-08-28 F1-frontend BlockLibraryTree: Export-Button (lädt .wcadlib herunter) + Import-Button (.wcadlib-File-Input → importLibrary → loadData-Refresh) + API-Funktionen exportLibrary/importLibrary in api.ts Frontend: 491/491 Tests, tsc 0, build OK 63c8968 F1 COMPLETE — Library Export/Import Ende-zu-Ende: Bibliothek als .wcadlib-Paket teilen/archivieren/einspielen
2026-08-27 C3 elementToPrimitives erweitert um fehlende Typen: dimension (Linie + Label aus value/text), leader (Linie + Label am Ende), revcloud (geschlossener Punktzug), chair (gefuelltes Rechteck #4a90d9), table (Kreis #8b7355), stage (dunkles Rechteck #3a3a4a); Legacy-Konventionen beibehalten; 6 neue Tests ALLE GRUEN: 481/481 gesamt (+6), tsc 0, build OK siehe git Naechster Task: C4 (Flag-Umdrehung Pixi=Default) oder Selection-Handles/Snap-Marks
2026-08-28 F2-backend Block-Favoriten: schema.sql is_favorite INTEGER DEFAULT 0 + SqliteAdapter-Migration (PRAGMA table_info-Muster) für Bestands-DBs, mapGlobalBlockRow 0/1→boolean, createGlobalBlock/updateGlobalBlock erweitert, DBGlobalBlock.is_favorite: boolean, PATCH /api/global-blocks/:id validiert is_favorite (nicht-boolean→400); ROT-Beweis: 5 Tests zuerst ROT (is_favorite fehlte komplett), dann GRUEN Backend: 263/263 Tests (+5), tsc 0 83a94e4 F2-BACKEND COMPLETE
2026-08-28 F2-frontend BlockLibraryTree: Suchfeld (global-lib-toolbar, filtert root+folder+favorites), ★/☆-Favorite-Toggle je Block (setGlobalBlockFavorite-API, optimistisches UI-Update), Favoriten-Sektion mit goldenem Titel, SVG-Thumbnail-Vorschau (tree-thumb, dangerouslySetInnerHTML, Fallback 📦), Grid-Ansicht (lib-grid, Thumbnail-Zellen mit Drag-Drop + Kontextmenü + Favorit); GlobalBlock.is_favorite: boolean in api.ts Frontend: 491/491 Tests, tsc 0, build OK 8b50c0a F2 COMPLETE — LibraryPanel-Kernfeatures: Baum+Suche+Favoriten+Thumbnails+Grid+DragDrop→Canvas (Drop-Handler existierte bereits)
2026-08-28 F3 blockThumbnail.ts NEU (generateBlockSvg/primitivesToSvg: Elemente→elementToPrimitives→eigenständiges SVG mit PAD-BBox, alle 6 Primitive-Kinds, XML-Escape, Arc→path mit Konvention 0°=3Uhr/CCW); Save-Button 💾 im Bibliotheks-Header (liest __v2GetSelection + documentService.getActiveDocument, Name via prompt, block_data=Element-Array → Drop-Handler akzeptiert Arrays, svg_data=Thumbnail, createGlobalBlock+loadData-Refresh); Dokumentierte Abweichung: SVG statt canvas.toDataURL (konsistent, testbar); ROT-Beweis: 4 Tests zuerst fehlgeschlagen (Modul fehlte), 1 Erwartung nach Primitive-BBox-Analyse korrigiert (-50 -50 560 360); Fix: Duplikat-Import + Handler vor loadData-Deklaration (tsc 2300/2448) Frontend: 495/495 Tests (+4), tsc 0, build OK 5d8253a F3 COMPLETE — Auswahl als Block speichern: Canvas-Selektion → wiederverwendbarer Block mit Thumbnail, sofort per DragDrop einsetzbar
2026-08-28 ids Nebenproblem „Date.now()-IDs“ erledigt: Alle 9 verbleibenden Auto-ID-Generierungen im SqliteAdapter (user/proj/pfolder/draw/layer/elem/block/notif/share) auf etabliertes randomUUID().slice(0,8)-Muster umgestellt (gleiche BUG-1-Kollisionsklasse wie gblock-Fix in F1); Deterministischer ROT-Beweis: 4 neue Tests mit vi.useFakeTimers+EINGEFRORENER Systemzeit (zwei Creates im selben Tick → UNIQUE-PK-Exception mit Date.now, eindeutig mit UUID); perl-Escape-Fehler (${p} literal) selbst erkannt und zeilenweise korrigiert Backend: 267/267 Tests (+4), tsc 0 c78d8d8 BUG-1-KLASSE GESCHLOSSEN — keine Date.now()-Auto-IDs mehr im Backend

Gefundene Nebenprobleme (noch nicht zugewiesen)

  • webhook-receiver.py im Root: Zweck unklar, ggf. entfernen/dokumentieren (Frage an Nutzer)
  • phase2..4-task-spec.md veraltet gegenüber BAUPLAN — nach v2.0 archivieren
  • frontend/src/utils/__tests__/format.test.ts liegt in src statt tests/ — vereinheitlichen (kleiner Task in I2)

Nächster offener Schritt: I2 (Docs: PLUGIN_GUIDE.md mit API v2 + Beispielplugin, LIBRARY_SPEC.md (.wcadlib), CHANGELOG 2.0, README rewrite). Danach: I1 CI (Performance-Gate, typecheck, beide Suites), I3 Release 2.0 (Tag, Docker, Coolify-Deploy-Verify). Alle Phasen AH sind komplett. Status 2026-08-29: ALLE FUNKTIONALEN PHASEN B+C+D+E+F+G+H VOLLSTÄNDIG (D1D12 alle Tools fertig), BUG-1-Kollisionsklasse geschlossen. FE 697/697 Tests, BE 287/287, tsc/Build grün. Alle Commits auf Forgejo main. Offen: nur noch Phase I (I1 CI, I2 Docs, I3 Release 2.0).