From 69141d604af96a764376cf9fc703833780955e87 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Thu, 27 Aug 2026 08:36:30 +0200 Subject: [PATCH] task(A4.14): make v2 tool path the default (verified e2e without flag) --- .../playwright-tests/phase-a-verify.test.ts | 104 ++++++++++++++++++ frontend/src/App.tsx | 5 +- frontend/src/components/CanvasArea.tsx | 8 +- frontend/src/components/RibbonBar.tsx | 6 +- frontend/src/kernel/featureFlags.ts | 15 +++ 5 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 frontend/playwright-tests/phase-a-verify.test.ts create mode 100644 frontend/src/kernel/featureFlags.ts diff --git a/frontend/playwright-tests/phase-a-verify.test.ts b/frontend/playwright-tests/phase-a-verify.test.ts new file mode 100644 index 0000000..1bade7c --- /dev/null +++ b/frontend/playwright-tests/phase-a-verify.test.ts @@ -0,0 +1,104 @@ +/** + * Phase-A-Verifikation (ROADMAP Übergabepunkt): + * Beweist mit echtem Chromium bei gesetzttem VITE_TOOL_DISPATCHER=v2: + * 1. Ribbon-V2-Sektion rendert die registrierten V2-Werkzeuge + * 2. Werkzeugwechsel über den v2tool:-Kanal funktioniert + * 3. Zeichnen + Ctrl+Z/Y laufen crashfrei durch den V2-Pfad + * + * Läuft gegen die bereits gestarteten Dev-Server (reuseExistingServer). + */ +import { test, expect, chromium } from '@playwright/test'; + +const API_URL = process.env.API_URL || 'http://localhost:3001'; +const BASE_URL = process.env.FRONTEND_URL || 'http://localhost:5173'; + +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, + }); + return { status: res.status, body: await res.json() }; +} + +test('Phase-A: V2-Pfad end-to-end (Ribbon-Sektion, Toolwechsel, Zeichnen+Undo crashfrei)', async () => { + const browser = await chromium.launch({ headless: true }); + const page = await browser.newPage({ viewport: { width: 1600, height: 1000 } }); + + // ── Auth & Navigation (Muster aus diagnose-editor.test.ts) ── + const email = `pa-verify-${Date.now()}@example.com`; + const reg = await api('POST', '/api/auth/register', { email, password: 'Test1234!', name: 'PA' }); + expect(reg.status).toBe(201); + const token = reg.body.session.token; + + const proj = await api('POST', '/api/projects', { name: `PA-${Date.now()}`, description: 'x' }, token); + expect(proj.status).toBe(201); + + await page.goto(`${BASE_URL}/`); + await page.evaluate((t) => localStorage.setItem('auth_token', t), token); + await page.reload(); + + await page.waitForSelector('.dashboard-project-card', { timeout: 15000 }); + await page.locator('.dashboard-project-card').first().click(); + await page.waitForSelector('canvas', { timeout: 15000 }); + await page.waitForTimeout(1200); // Engines mounten lassen + + // ── Fehlerkanäle sammeln — V2-Pfad darf KEINE erzeugen ── + const pageErrors: string[] = []; + const consoleErrors: string[] = []; + page.on('pageerror', (err) => pageErrors.push(err.message)); + page.on('console', (msg) => { + if (msg.type() === 'error') consoleErrors.push(msg.text()); + }); + + // ── 1) Ribbon: Zeichenflächen-Tab öffnen und V2-Sektion prüfen ── + await page.locator('.ribbon-tab', { hasText: 'Zeichenfläche' }).first().click(); + await page.waitForTimeout(300); + + const v2Section = page.locator('text=V2:').first(); + await expect(v2Section).toBeVisible(); + + // Registrierte V2-Buttons müssen vorhanden sein (alle Registry-Tools): + const v2ButtonGroup = page.locator('[data-v2tool]'); + const buttonCount = await v2ButtonGroup.count(); + expect(buttonCount).toBeGreaterThanOrEqual(22); + + // ── 2) Toolwechsel: Linie über V2-Button aktivieren ── + await page.locator('[data-v2tool="line"]').first().click(); + await page.waitForTimeout(300); + + // ── 3) Zeichnen auf dem Canvas (Zug in der Mitte) ── + const canvas = page.locator('canvas').first(); + const box = await canvas.boundingBox(); + expect(box).not.toBeNull(); + const cx = box!.x + box!.width / 2; + const cy = box!.y + box!.height / 2; + + await page.mouse.move(cx - 100, cy - 50); + await page.mouse.down(); + await page.mouse.move(cx + 100, cy + 50, { steps: 8 }); + await page.mouse.up(); + await page.waitForTimeout(400); + + // ── 4) Undo/Redo Shortcuts crashfrei ── + await page.keyboard.press('Control+z'); + await page.waitForTimeout(300); + await page.keyboard.press('Control+y'); + await page.waitForTimeout(300); + + // Kein Seitenfehler durch den gesamten V2-Ablauf: + expect(pageErrors).toEqual([]); + // Konsolenfehler nur tolerieren, wenn sie nichts mit dem V2-Pfad zu tun haben: + const v2Related = consoleErrors.filter((e) => /dispatcher|v2tool|undo/i.test(e)); + expect(v2Related).toEqual([]); + + // ── 5) Sichtbelege ── + await page.screenshot({ path: 'playwright-tests/screenshots/phase-a-canvas.png', fullPage: false }); + await page.keyboard.press('Control+z'); + await page.waitForTimeout(250); + await page.screenshot({ path: 'playwright-tests/screenshots/phase-a-after-undo.png', fullPage: false }); + + await browser.close(); +}); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 755b20d..5e8fca4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -19,6 +19,7 @@ import MobileDrawers from './components/MobileDrawers'; import BackgroundImport from './components/BackgroundImport'; import HistoryPanel from './components/HistoryPanel'; import V2HistoryPanel from './components/V2HistoryPanel'; +import { V2_TOOLS_ENABLED } from './kernel/featureFlags'; import SettingsModal from './components/SettingsModal'; import InlineTextEditor from './components/InlineTextEditor'; import { BackgroundService, type BackgroundConfig } from './services/backgroundService'; @@ -1574,7 +1575,7 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack onTabChange={setActiveRibbonTab} onAction={handleRibbonAction} onIsV2ToolActive={(toolId: string) => - import.meta.env.VITE_TOOL_DISPATCHER === 'v2' && + V2_TOOLS_ENABLED && activeTool === toolId && !!pluginRegistry.getToolV2(toolId) } @@ -1743,7 +1744,7 @@ const CADEditor: React.FC = ({ projectId, token, onNavigateBack onApply={handleBgApply} backgroundService={bgServiceRef.current} /> - {historyPanelOpen && (import.meta.env.VITE_TOOL_DISPATCHER === 'v2' ? ( + {historyPanelOpen && (V2_TOOLS_ENABLED ? ( setHistoryPanelOpen(false)} /> diff --git a/frontend/src/components/CanvasArea.tsx b/frontend/src/components/CanvasArea.tsx index b7f7980..32779a7 100644 --- a/frontend/src/components/CanvasArea.tsx +++ b/frontend/src/components/CanvasArea.tsx @@ -17,6 +17,7 @@ import { logger } from '../utils/logger'; import { CADDocument } from '../kernel/document/CADDocument'; import { YjsDocument } from '../crdt/YjsDocument'; import { setActiveDocument, getActiveDocument } from '../kernel/document/documentService'; +import { V2_TOOLS_ENABLED } from '../kernel/featureFlags'; import { pluginRegistry } from '../plugins'; const CanvasArea: React.FC = ({ @@ -53,10 +54,9 @@ const CanvasArea: React.FC = ({ canvas, zoomPan, renderEngine, snapEngine, selectionEngine, spatialIndex, layerManager, ); - // ── Task A2/A4: V2-Tool-Dispatcher hinter Feature-Flag ── - // Flag VITE_TOOL_DISPATCHER=v2: aktiviert den InteractionDispatcher UND - // verdrahtet ihn mit einem lokalen CADDocument + SelectionBridge (Task A4.1). - if (import.meta.env.VITE_TOOL_DISPATCHER === 'v2') { + // ── Task A2/A4.14: V2-Pfad ist seit Verifikation STANDARD — Opt-out via + // VITE_TOOL_DISPATCHER=legacy. Siehe kernel/featureFlags.ts. + if (V2_TOOLS_ENABLED) { const ydoc = new YjsDocument(); ydoc.data.layers.set('layer-0', { id: 'layer-0', name: 'Standard', visible: true, locked: false, diff --git a/frontend/src/components/RibbonBar.tsx b/frontend/src/components/RibbonBar.tsx index 7cb73cc..543f272 100644 --- a/frontend/src/components/RibbonBar.tsx +++ b/frontend/src/components/RibbonBar.tsx @@ -1,6 +1,7 @@ import React from 'react'; import type { RibbonBarProps, RibbonTab } from '../types/ui.types'; import { pluginRegistry } from '../plugins'; +import { V2_TOOLS_ENABLED } from '../kernel/featureFlags'; const tabs: Array<{ id: RibbonTab; label: string; svg: React.ReactNode }> = [ { id: 'start', label: 'Start', svg: }, @@ -38,8 +39,8 @@ const RibbonBar: React.FC = ({
- {/* ===== V2 TOOLS (Task A4.13, nur bei aktivem Dispatcher-Flag) ===== */} - {activeTab === 'canvas' && import.meta.env.VITE_TOOL_DISPATCHER === 'v2' && ( + {/* ===== V2 TOOLS (Task A4.13/A4.14: Standard aktiv — Opt-out via VITE_TOOL_DISPATCHER=legacy) ===== */} + {activeTab === 'canvas' && V2_TOOLS_ENABLED && (
= ({ return (