Files
web-cad/ROADMAP.md
T
2026-08-27 18:52:53 +02:00

66 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)
  • D2 Spline (CV-basierte Bézier, Speicherung Kontrollpunkte, Render als interpolierte Polyline mit Glättung)
  • D3 Point, XLine, Ray (construction-Layer-Tag properties.construction:true, Standard-Layer 'defpoints' auto-anlegen)
  • D4 MTEXT (Mehrzeilig, , fontSize/bold/italic Properties; Editor = InlineTextEditor erweitern)
  • D5 Stretch (Fenster selektiert Endpunkte; verschiebt nur getroffene Vertices)
  • D6 Break/Join (Break: Element an Punkt splitten; Join: kollineare lines→polyline)
  • D7 Chamfer, Explode, Align, Lengthen
  • D8 PolylineEditor (Vertex add/del, segment→arc glätten)
  • D9 GripEditing (select-Tool erweitern: Handles an Endpunkten/Centern, drag=modify in Transaktion)
  • D10 Arrays: array-rect (rows,cols,dx,dy), array-polar (cx,count,angleSum,rotate), array-path (entlang polyline/arc) — JEDES als Dialog+Vorschau; Tests mit Zähl-Assertions
  • D11 Snaps erweitern (SnapEngine + Optionen): tangent, perpendicular, quadrant, center, from-offset (Basispunkt+Eingabefeld dx/dy)
  • D12 BlockAttribute: BlockDef.attrDefs[], Instanz attrValues; PropertiesPanel rendert Formular; RenderAdapter zeichnet Attr-Texte; (DXF ATTDEF/ATTR erst F)

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

Phase F — Library & Import/Export

  • F1 Backend library_folders/library_blocks + Migrationsfile + Tests (CRUD, scopes)
  • F2 UI LibraryPanel (Baum, Suche, Favoriten, Thumbnail grid), DragDrop→Canvas insert instance
  • F3 „Als Block speichern“ (Auswahl→Block, Thumbnail client-seitig canvas.toDataURL klein)
  • F4 LibraryProviderExtension anbinden: builtin-catalog JSONs (event/architecture/landscape Startersets je ~15-30 Blöcke), user-library, global-library
  • F5 DXF Parser erweitern: ELLIPSE, SPLINE(grad≤3→Polyline approx), MTEXT, POINT, HATCH(basic solid/lines), ATTDEF/ATTR, INSERT→Blockinstanz; Fehlerreport (Liste skipped entities)
  • F6 DXF Writer erweitern: BLOCKS+INSERTS, ATTR; Version AC1009 (R12) + AC1015 Ziel
  • F7 .wcadlib Paket (zip: manifest.json, blocks.json, thumbs/) Import+Export (Backend endpoint + UI Dialog)
  • F8 SVG-Import als Block (svg→Path→Polyline approximation, simple subset: path/rect/circle/ellipse/line/polygon)

Phase G — Layouts & Plot

  • G1 Datenmodell layouts (Yjs Map: layoutId→{name, viewport{center,scale}, frameBlockRef}), Backend persistence folgt automatisch via drawing json
  • G2 LayoutBar UI + Viewport-Widget (zeigt Modellbereich skaliert, klickbar zoom-to)
  • G3 Titelblock-Block mit Attributen (Projekt/Datum/Maßstab/Autor) auto-insert
  • G4 PDF-Export je Layout im Ausgabemaßstab (pdf-lib; Linienbreiten aus Layern; monochrome Option)
  • G5 Browser-Print stylesheet (@media print: nur Layout)

Phase H — Modi & Branchenneutralität

  • H1 simple/pro Mode (uiStore.mode, Ribbon-Filter tags basic, CommandLine hide, Settings persist)
  • H2 Projekt-Templates (Halle/OpenAir/Saal/Messe/Buero/Garten): JSON Presets (grid unit, layers, enabled plugins, library folders seed)
  • H3 Rollen: library-admin (global write) vs user (own folders) — an bestehendes auth anbinden, Backend guard
  • H4 i18n Grundlage: strings.ts zentrale Maps de/en, UI-Texte ersetzen (mechanisch, Teilautomatisierbar)
  • H5 Flächenberechnung Polygon (m²) Info im PropertiesPanel + measure-area Tool

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

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: Phase-A-Verifikation (Playwright/manuell: Flag VITE_TOOL_DISPATCHER=v2, Ribbon-V2-Sektion sichtbar, Undo/Redo-Shortcuts, V2HistoryPanel). Danach Legacy-Entsorgung (interaction/index.ts <300 LOC) erst NACH grüner Verifikation. Anschließend Phase C (PixiJS/WebGL).