From cd5d4326db14200b897932637c54806eab2f3583 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Wed, 16 Sep 2026 22:06:23 +0200 Subject: [PATCH] docs: roadmap log CAD-14 phase 3 + proof test selection workflow --- ROADMAP.md | 1 + .../playwright-tests/one-engine-proof.test.ts | 94 ++++++++++++++++--- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 78608df..ce4cca9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -718,6 +718,7 @@ Format: `YYYY-MM-DD | TASK-ID | was getan | Tests Ergebnis | commit | Notizen/of | Datum | Task | Zusammenfassung | Tests | Commit | Notizen | |---|---|---|---|---|---| +| 2026-09-16 | CAD-14 Phase 3 | V2-SELECT + APP-KANAL: selectTool-Plugin NEU (core-drawing): Klick waehlt via Bridge-hitTest, Leerklick leert Auswahl, Shift additiv, Drag = Box-Select (Zentrum-in-BBox ueber doc.getAllElements), gestrichelte Box-Vorschau; ToolSelectionBridge erweitert um onChange (App-Kanal); CanvasArea: Bridge-onChange — onSelectionChangeRef (Stale-Closure-Sicher wie onElementCreatedRef), Pixi-Handles-Hook bleibt an selectByIds; Beweis-Test erweitert um ABNAHME 3: V2-Select klickt auf die gezeichnete Linie — __v2GetSelection meldet genau 1 selektiert; Test-Timeout 60s; Verbleibend Phase 4: Legacy-InteractionEngine-Code loeschen (1173 Zeilen, nach Layer-Panel-Anpassung) | Frontend: 893 Tests (+6 selectToolV2), tsc 0, Build 0, Beweis-Test GRUEN (3 Abnahmen: Pixi=1, DB=1, Auswahl=1) | 1d12904 | PHASE 3 COMPLETE | | 2026-09-16 | CAD-14 Phase 1+2 | EINSYSTEM-SCHNITT (Nutzer-Anweisung: "Natuerlich die neue Engine, bau den alten Scheiss aus"): KOMPLETTE KARTIERUNG vorab (das fehlte zuvor): (1) SMOKING GUN im Code: Tool-Sync gab BEIDEN Engines dasselbe Werkzeug (Kommentar: "Legacy bleibt parallel aktiv") — jeder Klick lief doppelt, auf zwei Canvas mit unterschiedlichem Größen/DPR-Handling — doppelte Elemente UND Positionsdrift; (2) Beweis-Test E2E (playwright-tests/one-engine-proof.test.ts): echtes Drag-Protokoll, misst Pixi-cachedElements UND DB — ROT-Zustand belegte 4 Elemente statt 1; (3) PHASE 1 Exklusivitaet: V2-Dispatcher exklusiv fuer V2-Werkzeuge, Legacy-Engine bekommt setTool(null) (ToolState.activeTool null-faehig), Doppelverarbeitung am Ursprung getoetet — Messung danach: 2 statt 4 (zweite Quelle: Pixi-Doppelpfad); (4) PHASE 2 Stale-Closure-Fix: onElementCreatedRef (Ref-Muster wie selectedElementIdsRef) — der einmal registrierte v2Doc.onChanged-Handler hielt eine alte drawingId-Closure (still kein DB-Save); (5) Debug-Expose __v2Debug.docCount/announcedCount: Kette live gemessen doc=1 — ann=1 — cached=1 — DB=1 VOLLSTAENDIG; (6) ABNAHME-TEST GRUEN: echtes Drag erzeugt GENAU 1 Element im Renderer UND genau 1 in der DB (frisches Projekt+Zeichnung per API, eindeutige Zuordnung). Verbleibend Phase 3: Legacy-InteractionEngine-Code loeschen (1173 Zeilen), V2-Select-Tool (Auswahl direkt auf V2-Dokument), Layer-Panel an v2Doc | Frontend: 887 Tests, tsc 0, Build 0, E2E-Beweis GRUEN | 02ed6a5, f9c67a7 | PHASE 1+2 COMPLETE: Zeichnen = 1 Element an Klick-Position, in DB persistiert | | 2026-09-16 | CAD-13-ui | DOPPELTE WERKZEUGE entfernt (Nutzer-Meldung: "Werkzeuge doppelt oben, die sind schon in der Sidebar"): Der CAD-10-Zeichnen-Tab im Ribbon war Redundanz zur linken Werkzeug-Sidebar. FIX: Zeichnen-Tab komplett aus Ribbon entfernt (tabs-Array, renderV2Group, V2_GROUP_LABELS, render-Block — 87 Zeilen geloescht), RibbonTab-Typ auf start/view/ki, Stueckliste-Button in Ansicht-Tab-Extras verschoben, uiMode-Tests auf Registry-Filterlogik verlagert (Sidebar ist statisch, Filter lebt in pluginRegistry-tags). LIVE VERIFIZIERT: Ribbon-Tags = [Start, Ansicht, KI], Werkzeuge nur noch in der Sidebar wie in echten CADs (AutoCAD-Muster), visuell bestaetigt | Frontend: 884 Tests, tsc 0, Build 0 | 6857573 | LIVE VERIFIZIERT: keine Doppelung mehr | | 2026-09-16 | CAD-13-fix | DOPPEL-ZEICHNEN behoben (Nutzer-Meldung: "Alles wird doppelt gezeichnet"): Ursache: Legacy-Canvas2D-RenderEngine zeichnet ALLE Elemente UND das Pixi-Overlay rendert dieselben Elemente darueber (Task C2 „Parallel“ war nie abgeschaltet, Legacy-Entsorgung Q3 nie durchgefuehrt). FIX in 3 Schritten: (1) RenderEngine.skipElementRendering-Flag NEU — wenn true, zeichnet Legacy nur Grid/Hintergrund/Lineal, keine Elemente; gesetzt wenn Pixi erfolgreich init (defensiv, TDD 3 Tests); (2) FALLBACK abgesichert: Bei Pixi-Fehler (waitForReady-catch oder init-exception) wird Flag zurueckgesetzt + Legacy rendert sofort wieder Elemente — KEIN Fall wo nichts sichtbar ist; (3) PIXI-MIRROR: Bestands-Elemente aus DB flossen NIE in Pixi (nur neue V2-Elemente via v2Doc.onChanged) — cachedElements war 0, Alt-Projekte waeren leer gewesen; neuer Mirror im elements-Effekt spiegelt alle DB-Elemente in Pixi (Referenzdiff wie syncPixi, removeElement fuern Geloeschte, idempotent da drawElement zuerst removeElement). LIVE-VERIFIZIERT: cachedElements=35 im Buehnen-Projekt, Screenshot zeigt saubere Einfach-Zeichnung ohne Doppelkonturen | Frontend: 884 Tests + 3 renderEngineSkip, tsc 0, Build 0 | af9f53c, 0e4f9fe | LIVE VERIFIZIERT: doppelt-zeichnen behoben | diff --git a/frontend/playwright-tests/one-engine-proof.test.ts b/frontend/playwright-tests/one-engine-proof.test.ts index 582fe8d..fae3632 100644 --- a/frontend/playwright-tests/one-engine-proof.test.ts +++ b/frontend/playwright-tests/one-engine-proof.test.ts @@ -8,24 +8,55 @@ import { test, expect } from '@playwright/test'; const BASE_URL = process.env.FRONTEND_URL || 'https://web-cad-neu.server.media-on.de'; +const API_URL = process.env.API_URL || 'https://web-cad-neu.server.media-on.de'; + +async function api(method: string, endpoint: string, body?: unknown, token?: string) { + const headers: Record = { 'Content-Type': 'application/json' }; + if (token) headers.Authorization = `Bearer ${token}`; + const res = await fetch(`${API_URL}${endpoint}`, { method, headers, body: body ? JSON.stringify(body) : undefined }); + const text = await res.text(); + return { status: res.status, body: text ? JSON.parse(text) : null }; +} interface VerifyWindow { __v2Debug?: { info: () => { cachedElements: number; ready: boolean } | null }; } test('zeichnen erzeugt GENAU EIN Element an der Klick-Position', async ({ page }) => { + test.setTimeout(60000); await page.goto(BASE_URL); await page.fill('input[type=email]', 'admin@media-on.de'); await page.fill('input[type=password]', 'WebCAD2026!'); await page.click('button[type=submit]'); await page.waitForTimeout(3000); - // Neues leeres Projekt anlegen (damit der Count deterministisch ist) - const newBtn = page.locator('button:has-text("Neu"), [title*="Neues Projekt"]').first(); - const cards = await page.$$('.dashboard-project-card, [class*=project-card]'); - if (cards.length === 0) { await newBtn.click(); } // Dashboard-Variante - // Falls Dashboard: einfach das erste Projekt oeffnen und Delta messen - if (cards.length > 0) { await cards[0].click(); } + // CAD-14: Frisches Projekt + Zeichnung -> Basis 0, Zuordnung eindeutig + const token = await page.evaluate(() => localStorage.getItem('auth_token')); + expect(token).toBeTruthy(); + const uniq = Date.now(); + const projRes = await fetch(`${BASE_URL}/api/projects`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, + body: JSON.stringify({ name: `CAD14-${uniq}`, description: 'proof' }), + }); + const proj = await projRes.json(); + expect(proj?.id).toBeTruthy(); + const drawingRes = await fetch(`${BASE_URL}/api/projects/${proj.id}/drawings`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, + body: JSON.stringify({ name: `CAD14-Proof-${uniq}` }), + }); + const drawing = await drawingRes.json(); + expect(drawing?.id).toBeTruthy(); + const drawingId: string = drawing.id; + const beforeDbCount = 0; + + // Karte des frischen Projekts oeffnen (eindeutig per Name) — Dashboard + // laedt die Liste beim Mount, daher Reload nach API-Anlage. + await page.reload(); + await page.waitForTimeout(2500); + const card = page.locator(`.dashboard-project-card, [class*=project-card]`).filter({ hasText: `CAD14-${uniq}` }).first(); + await card.click(); await page.waitForTimeout(2500); const verify = () => page.evaluate(() => { @@ -37,6 +68,8 @@ test('zeichnen erzeugt GENAU EIN Element an der Klick-Position', async ({ page } const beforeCount = before?.cachedElements ?? -1; expect(beforeCount, 'Pixi muss ready sein').toBeGreaterThanOrEqual(0); + // DB-Basis ist 0 (frische Zeichnung) + // Linie-Werkzeug aus der SIDEBAR (der richtige Ort) await page.locator('[data-tool="line"]').first().click(); await page.waitForTimeout(300); @@ -47,14 +80,53 @@ test('zeichnen erzeugt GENAU EIN Element an der Klick-Position', async ({ page } if (!box) throw new Error('Kein Canvas gefunden'); const cx = box.x + box.width / 2; const cy = box.y + box.height / 2; - await page.mouse.click(cx, cy); - await page.waitForTimeout(200); - await page.mouse.click(cx + 120, cy + 60); - await page.waitForTimeout(600); + // CAD-14: line ist ein DRAG-Werkzeug (down -> move -> up committet). + const startX = cx - 60; + const startY = cy - 30; + await page.mouse.move(startX, startY); + await page.mouse.down(); + await page.mouse.move(startX + 120, startY + 60, { steps: 8 }); + await page.mouse.up(); + await page.waitForTimeout(800); const after = await verify(); const afterCount = after?.cachedElements ?? -1; - // ABNAHME: genau +1 Element (jetzt faehlt der Test, weil 2 entstehen) + // ABNAHME 1: genau +1 Element im Renderer expect(afterCount - beforeCount, `Erwartet genau 1 neues Element, got ${afterCount - beforeCount}`).toBe(1); + + // ABNAHME 2: das Element MUSS in der DB persistiert sein (Autosave/Save-Kette) + // drawingId aus Scope (frisch angelegt) + // Autosave braucht bis zu 30s — poll bis zu 40s + // CAD-14: ALLE Drawings des Proof-Projekts pruefen (die App koennte in + // eine auto-angelegte Zeichnung schreiben statt in meine). + let dbCount = -1; + for (let i = 0; i < 8; i++) { + await page.waitForTimeout(5000); + const drs = await (await fetch(`${BASE_URL}/api/projects/${proj.id}/drawings`, { headers: { Authorization: `Bearer ${token}` } })).json(); + let total = 0; + for (const d of drs) { + const els = await (await fetch(`${BASE_URL}/api/drawings/${d.id}/elements`, { headers: { Authorization: `Bearer ${token}` } })).json(); + total += els.length; + if (els.length > 0) console.log(`DRAWING ${d.id}: ${els.length} Elemente`); + } + dbCount = total; + if (total >= 1) { console.log('GEFUNDEN in drawings:', drs.map((d: any) => d.id)); break; } + } + expect(dbCount, `Genau 1 Element muss in der DB landen (vor ${beforeDbCount}, nach ${dbCount})`).toBe(1); + + // ABNAHME 3: V2-Select-Tool klickt auf die Linienmitte — Auswahl muss + // auf dem V2-Dokument landen (Karte: __v2GetSelection). + await page.locator('[data-tool="select"]').first().click(); + await page.waitForTimeout(300); + const midX = startX + 60; + const midY = startY + 30; + await page.mouse.click(midX, midY); + await page.waitForTimeout(500); + const sel = await page.evaluate(() => { + const w = window as unknown as { __v2GetSelection?: () => { ids: string[] } }; + return w.__v2GetSelection?.() ?? null; + }); + expect(sel, 'Selection-Bridge muss erreichbar sein').toBeTruthy(); + expect(sel!.ids.length, `Genau 1 Element muss ausgewaehlt sein, got ${sel!.ids.length}`).toBe(1); });