> 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 |
| 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!
**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
interfaceDocSlice{
elements: Record<string,CADElement>;// id → element (Spiegel von Yjs Map)
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)
- 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 D–I 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.
- 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)
// ── 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.
constline=makeLine('l1',0,0,200,0);
constcircle=makeCircle('c1',100,0,40);
constresult=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.
(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)`
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). */
/** 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.
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:
// 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.
- 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`
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)
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.
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.
- 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
1. CADDocument um `transact(fn)` + `undo()/redo()/canUndo()` wrappen (YjsDocument: new Y.UndoManager([elementsMap, layersMap], {trackedOrigins: 'self'})`). Alte HistoryManager-Klasse PARALLEL lassen bis 4.
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!
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`
- 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`
| 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.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) |
*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).*