Files
web-cad/ROADMAP.md
T

741 lines
88 KiB
Markdown
Raw Normal View 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](./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):
```ts
// 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)
```ts
// 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:
```ts
/**
* 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:
```ts
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 bestehendem `blockId`, `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:
```ts
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): CADElement` — `dist` 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:
```ts
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:
```ts
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):
```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:
```ts
/**
* 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:
```ts
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)
- [x] 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)*
- [x] 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)*
- [x] D7 Chamfer, Explode, Align, Lengthen *(2026-08-29 verifiziert: chamferTool/lengthenTool/explodeTool/alignTool in core-modify per Code-Grep, alle im tools-Array registriert)*
- [x] 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)
- [ ] 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
- [ ] 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
- [x] 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
- [x] 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)*
- [x] F2 UI LibraryPanel (Baum, Suche, Favoriten, Thumbnail grid), DragDrop→Canvas insert instance
- [x] 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)*
- [x] 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)*
- [x] 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)*
- [x] 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)*
- [x] 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)*
- [x] 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
- [x] 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)*
- [x] 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)*
- [x] 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)*
- [x] 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)
## 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-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/<blockId>.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 23:11:24 +02:00
| 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 00:43:51 +02:00
| 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 00:49:15 +02:00
| 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 00:54:09 +02:00
| 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:<id>'); 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 08:45:18 +02:00
| 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 08:54:26 +02:00
| 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 10:02:54 +02:00
| 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 18:37:09 +02:00
| 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 12:58:36 +02:00
| 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 18:52:53 +02:00
| 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 22:46:51 +02:00
| 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 22:49:09 +02:00
| 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 23:10:55 +02:00
| 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 07:31:15 +02:00
| 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 07:34:58 +02:00
| 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 12:25:49 +02:00
| 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 10:07:35 +02:00
| 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: **G5** (Browser-Print stylesheet: @media print nur Layout). Danach: Rest-D-Tools (D1D4/D9/D10-path/D11/D12), Phase H (Modi/Templates/Rollen/i18n/Flächen), Phase I (Release).*
*Status 2026-08-29: Phasen B+A+C+D+E+F komplett, G1G4 fertig (Layouts: Datenmodell, Bar, Titelblock, PDF-Export), BUG-1-Kollisionsklasse geschlossen. FE 580/580 Tests, BE 272/272, tsc/Build grün. Alle Commits auf Forgejo main.*