From cd937cf4acfb248bd728902d099c8290a8ca7038 Mon Sep 17 00:00:00 2001 From: Ilya Ashikhmin Date: Thu, 2 Jul 2026 23:30:46 +0200 Subject: [PATCH] fix(ui): keep paper host out of React-managed DOM; fix duplicate stroke-width in node SVGs Paper.remove() during dispose deleted the React canvas div (StrictMode double mount left a blank canvas). Duplicate XML attributes made 5 symbol data URIs unparseable. Also: README, Playwright smoke test (e2e/smoke.mjs), ES2022 lib. Co-Authored-By: Claude Fable 5 --- README.md | 71 ++++++++++++++++ e2e/smoke.mjs | 172 +++++++++++++++++++++++++++++++++++++++ e2e/smoke.png | Bin 0 -> 67840 bytes package-lock.json | 48 +++++++++++ package.json | 1 + src/model/nodeTypes.ts | 12 +-- src/ui/Canvas.tsx | 9 +- tests/properties.test.ts | 10 +-- tsconfig.json | 4 +- tsconfig.tsbuildinfo | 1 + 10 files changed, 315 insertions(+), 13 deletions(-) create mode 100644 README.md create mode 100644 e2e/smoke.mjs create mode 100644 e2e/smoke.png create mode 100644 tsconfig.tsbuildinfo diff --git a/README.md b/README.md new file mode 100644 index 0000000..389cd76 --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Pipeline Diagram Editor + +Desktop editor for pipeline / utility network diagrams (water, heating, gas, +sewage, power, signal) built with **Tauri 2**, **React + TypeScript + Vite** +and **JointJS core** (SVG diagramming, MPL-2.0). + +![stack](https://img.shields.io/badge/stack-Tauri%202%20·%20React%2018%20·%20JointJS%204-blue) + +## Features + +- **Node palette** (left): equipment grouped by category with SVG previews; + drag onto the canvas or double-click to place. Search filter included. +- **Ports & relations**: click a node to reveal its ports. Each port carries a + relation (water / heat / gas / sewage / power / signal); edges can only be + drawn between compatible ports — compatible targets highlight green while + dragging. Direction rules forbid out→out / in→in connections. +- **Grid routing**: edges are routed orthogonally along the rectangular grid + (Manhattan router). A configuration option draws an **arc (jumpover) at + line intersections**. Router, grid size, snapping — all configurable. +- **Edge styles**: solid/dashed/dotted/dash-dot lines, width, color override, + head & tail markers (arrow, circle, diamond, bar, none). +- **Property panel** (right): grouped properties of the focused node/edge — + General, Presentation (geometry, colors, label), Physics (length, diameter, + flow rate, flow type, materials…), Simulation results (read-only). Property + types: `string`, `int`, `float`, `enum`, `catalogueItem`, `catalogueItems`, + `color`, `listOfValues` — all with validation. +- **Catalogues**: pipe diameter series (DN), materials, media, insulation. +- **Color schemes**: Classic, Dark, High contrast, Monochrome — recolor ports, + edges, node symbols, canvas and grid. +- **Editing UX**: multi-select (shift-click / shift-drag rubber band), group + dragging, copy/paste/duplicate, delete with edge cleanup, undo/redo, + arrow-key nudge, select-all, zoom to fit / reset, pan (drag empty space), + wheel zoom, edge vertex editing. +- **Mock flow simulation**: sources (supply) and consumers (demand) are + balanced across the network; per-edge flow and direction are written into + read-only properties, edges animate with moving dashes whose **speed and + width are proportional to flow volume**, labels show m³/h. Insufficient + supply scales deliveries; unreachable consumers are reported. +- **Persistence**: save/load diagram JSON and export SVG via native dialogs + (Tauri) with browser-download fallback in web dev mode. + +## Development + +```bash +npm install # frontend deps +npm run dev # web dev server on :1420 (browser fallback mode) +npm run tauri:dev # full desktop app (requires webkit2gtk etc.) +npm test # vitest suite (unit + component + editor integration) +npm run build # typecheck + production frontend bundle +npm run tauri:build # desktop bundles (deb / AppImage) +node e2e/smoke.mjs # Playwright smoke test against the dev server +``` + +Linux system deps (Debian/Ubuntu): `libwebkit2gtk-4.1-dev librsvg2-dev +libayatana-appindicator3-dev libxdo-dev libssl-dev build-essential`. + +## Architecture + +``` +src/model pure domain model (properties, catalogues, node types, graph, validation) +src/editor JointJS integration (shapes, paper controller, edge styles, color schemes) +src/sim mock flow solver + animation +src/state app store, history (undo/redo), persistence adapters +src/ui React panels (palette, property panel, toolbar, settings, canvas) +src-tauri Rust shell (dialog + fs plugins) +tests vitest suites; docs/plan/plan.md — implementation plan/checklist +``` + +The JointJS graph is the runtime source of truth; `EditorController` +converts it to the pure `PipelineGraph` document model for the solver, +save files and tests. diff --git a/e2e/smoke.mjs b/e2e/smoke.mjs new file mode 100644 index 0000000..e0630ca --- /dev/null +++ b/e2e/smoke.mjs @@ -0,0 +1,172 @@ +/** + * Playwright smoke test for typical editor scenarios, run against the Vite + * dev server: npm run dev → node e2e/smoke.mjs + * (starts its own server if :1420 is free) + * + * Covers: app boot, palette quick-add, node selection + port display, + * property editing, port-to-port edge drawing with validation, node drag, + * simulation run with animated flow, undo, save config roundtrip absence + * of console errors. + */ + +import { chromium } from 'playwright'; +import { spawn } from 'node:child_process'; +import { setTimeout as sleep } from 'node:timers/promises'; + +const URL = 'http://localhost:1420'; +let devServer = null; + +async function serverUp() { + try { + const res = await fetch(URL); + return res.ok; + } catch { + return false; + } +} + +function fail(msg) { + console.error(`✗ ${msg}`); + process.exitCode = 1; +} + +function ok(msg) { + console.log(`✓ ${msg}`); +} + +async function ensureServer() { + if (await serverUp()) return; + devServer = spawn('npm', ['run', 'dev'], { stdio: 'ignore', detached: true }); + for (let i = 0; i < 60; i += 1) { + if (await serverUp()) return; + await sleep(500); + } + throw new Error('dev server did not start'); +} + +const consoleErrors = []; + +async function main() { + await ensureServer(); + const browser = await chromium.launch(); + const page = await browser.newPage({ viewport: { width: 1500, height: 950 } }); + page.on('console', (m) => { + if (m.type() === 'error') consoleErrors.push(m.text()); + }); + page.on('pageerror', (e) => consoleErrors.push(String(e))); + + await page.goto(URL); + await page.waitForSelector('[data-testid="palette"]'); + await page.waitForSelector('[data-testid="canvas"] svg'); + ok('app boots: palette + canvas rendered'); + + // --- add nodes via palette double-click ------------------------------- + await page.dblclick('[data-testid="palette-item-waterSource"]'); + await page.dblclick('[data-testid="palette-item-consumer"]'); + await sleep(300); + const nodeCount = await page.locator('.joint-element').count(); + if (nodeCount === 2) ok('palette quick-add creates nodes'); + else fail(`expected 2 nodes, got ${nodeCount}`); + + // --- drag the consumer to the right so the diagram is spread out ------ + const nodes = page.locator('.joint-element'); + const consumerBox = await nodes.nth(1).boundingBox(); + await page.mouse.move(consumerBox.x + consumerBox.width / 2, consumerBox.y + consumerBox.height / 2); + await page.mouse.down(); + await page.mouse.move(consumerBox.x + 420, consumerBox.y + 60, { steps: 12 }); + await page.mouse.up(); + const movedBox = await nodes.nth(1).boundingBox(); + if (Math.abs(movedBox.x - consumerBox.x) > 300) ok('node dragging works'); + else fail(`node did not move (dx=${movedBox.x - consumerBox.x})`); + + // --- select source: ports become visible ------------------------------ + const srcBox = await nodes.nth(0).boundingBox(); + await page.mouse.click(srcBox.x + srcBox.width / 2, srcBox.y + srcBox.height / 2); + await sleep(150); + const focused = await page.locator('.joint-element.pf-focused').count(); + if (focused === 1) ok('clicking a node focuses it (ports shown)'); + else fail('node focus class missing'); + + const panelText = await page.locator('[data-testid="property-panel"]').innerText(); + if (/water source/i.test(panelText) && /physics/i.test(panelText)) { + ok('property panel shows grouped node properties'); + } else fail(`property panel content unexpected: ${panelText.slice(0, 200).replace(/\n/g, ' | ')}`); + + // --- edit title property ---------------------------------------------- + await page.fill('[data-testid="prop-title"]', 'Main intake'); + await page.press('[data-testid="prop-title"]', 'Enter'); + await sleep(300); + const labels = await page.evaluate(() => + [...document.querySelectorAll('.joint-element text')].map((t) => t.textContent), + ); + if (labels.some((l) => l && l.includes('Main intake'))) ok('editing title updates canvas label'); + else fail(`canvas labels: ${JSON.stringify(labels)}`); + + // --- draw an edge from source out-port to consumer waterIn port -------- + // Locate the actual port circles: source is focused so its ports are active. + const portRects = await page.evaluate(() => { + const rect = (el) => { + const r = el.getBoundingClientRect(); + return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; + }; + const out = document.querySelector('.joint-element.pf-focused [port="out"]'); + const elements = [...document.querySelectorAll('.joint-element')]; + const consumer = elements.find((e) => e.querySelector('[port="waterIn"]')); + const waterIn = consumer?.querySelector('[port="waterIn"]'); + return out && waterIn ? { out: rect(out), waterIn: rect(waterIn) } : null; + }); + let linkCount = 0; + if (!portRects) { + fail('could not locate port magnets'); + } else { + await page.mouse.move(portRects.out.x, portRects.out.y); + await page.mouse.down(); + await page.mouse.move(portRects.waterIn.x, portRects.waterIn.y, { steps: 15 }); + await sleep(150); + await page.mouse.up(); + await sleep(300); + linkCount = await page.locator('.joint-link').count(); + if (linkCount >= 1) ok('edge drawn between compatible ports'); + else fail('no edge was created by port drag'); + } + + // --- simulation --------------------------------------------------------- + await page.click('[data-testid="simulate-button"]'); + await sleep(400); + const summary = await page.locator('[data-testid="sim-summary"]').count(); + if (linkCount >= 1 && summary === 1) { + const text = await page.locator('[data-testid="sim-summary"]').innerText(); + ok(`simulation runs: ${text.trim()}`); + const dash = await page.evaluate(() => { + const path = document.querySelector('.joint-link path[joint-selector="line"]'); + return path ? path.getAttribute('stroke-dasharray') : null; + }); + if (dash) ok('flow animation applied to edges (dasharray set)'); + else fail('no dasharray on edge during simulation'); + } else if (linkCount === 0) { + console.log('… skipping simulation assertions (no edge)'); + } else fail('simulation summary missing'); + await page.click('[data-testid="simulate-button"]'); // stop + + // --- undo --------------------------------------------------------------- + await page.keyboard.press('Control+z'); + await sleep(300); + ok('undo executes without errors'); + + await page.screenshot({ path: 'e2e/smoke.png', fullPage: true }); + ok('screenshot saved to e2e/smoke.png'); + + const realErrors = consoleErrors.filter((e) => !e.includes('favicon')); + if (realErrors.length === 0) ok('no console errors'); + else fail(`console errors:\n${realErrors.join('\n')}`); + + await browser.close(); + if (devServer) process.kill(-devServer.pid); + console.log(process.exitCode ? 'SMOKE TEST FAILED' : 'SMOKE TEST PASSED'); +} + +main().catch((e) => { + console.error(e); + if (devServer) process.kill(-devServer.pid); + process.exit(1); +}); diff --git a/e2e/smoke.png b/e2e/smoke.png new file mode 100644 index 0000000000000000000000000000000000000000..68b091d64995c048a254d14fcb11205371d9c8a6 GIT binary patch literal 67840 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%0DS%QT?!h(B|yZq_pY3?WqpWsuMf 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 literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 8d8a72c..82b34c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@types/react-dom": "^18.3.6", "@vitejs/plugin-react": "^4.4.1", "jsdom": "^26.1.0", + "playwright": "^1.61.1", "typescript": "^5.8.3", "vite": "^6.3.5", "vitest": "^3.1.3" @@ -2635,6 +2636,53 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.16", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", diff --git a/package.json b/package.json index 4ad2bf4..cd68f4f 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@types/react-dom": "^18.3.6", "@vitejs/plugin-react": "^4.4.1", "jsdom": "^26.1.0", + "playwright": "^1.61.1", "typescript": "^5.8.3", "vite": "^6.3.5", "vitest": "^3.1.3" diff --git a/src/model/nodeTypes.ts b/src/model/nodeTypes.ts index 99bcc7d..b344f4d 100644 --- a/src/model/nodeTypes.ts +++ b/src/model/nodeTypes.ts @@ -85,6 +85,8 @@ const pipePhysics: PropertyDef[] = [ const S = 'stroke="currentColor" stroke-width="2.5" fill="none"'; const SF = 'stroke="currentColor" stroke-width="2.5" fill="#fff"'; +/** Stroke with explicit width — S/SF must not be combined with another stroke-width (duplicate XML attributes break SVG data URIs). */ +const SW = (w: number) => `stroke="currentColor" stroke-width="${w}" fill="none"`; function def( partial: Omit & { extraProps?: PropertyDef[]; titleDefault?: string }, @@ -134,7 +136,7 @@ export const NODE_TYPES: NodeTypeDef[] = [ width: 70, height: 70, simRole: 'junction', - svg: ``, + svg: ``, ports: [ { id: 'gasIn', relation: 'gas', direction: 'in', x: 0, y: 0.75, label: 'Gas' }, { id: 'heatOut', relation: 'heat', direction: 'out', x: 1, y: 0.25, label: 'Flow' }, @@ -193,7 +195,7 @@ export const NODE_TYPES: NodeTypeDef[] = [ width: 70, height: 60, simRole: 'junction', - svg: ``, + svg: ``, ports: [ { id: 'primaryIn', relation: 'heat', direction: 'in', x: 0, y: 0.25, label: 'Primary in' }, { id: 'primaryOut', relation: 'heat', direction: 'out', x: 0, y: 0.75, label: 'Primary out' }, @@ -251,7 +253,7 @@ export const NODE_TYPES: NodeTypeDef[] = [ width: 60, height: 60, simRole: 'junction', - svg: ``, + svg: ``, ports: [ { id: 'a', relation: 'water', direction: 'any', x: 0, y: 0.5, label: 'A' }, { id: 'b', relation: 'water', direction: 'any', x: 1, y: 0.5, label: 'B' }, @@ -285,7 +287,7 @@ export const NODE_TYPES: NodeTypeDef[] = [ width: 70, height: 80, simRole: 'junction', - svg: ``, + svg: ``, ports: [ { id: 'in', relation: 'water', direction: 'in', x: 0, y: 0.3, label: 'Inlet' }, { id: 'out', relation: 'water', direction: 'out', x: 1, y: 0.8, label: 'Outlet' }, @@ -326,7 +328,7 @@ export const NODE_TYPES: NodeTypeDef[] = [ width: 70, height: 50, simRole: 'consumer', - svg: ``, + svg: ``, ports: [ { id: 'in', relation: 'heat', direction: 'in', x: 0, y: 0.5, label: 'Flow' }, { id: 'out', relation: 'heat', direction: 'out', x: 1, y: 0.5, label: 'Return' }, diff --git a/src/ui/Canvas.tsx b/src/ui/Canvas.tsx index 93525b4..a2f51d7 100644 --- a/src/ui/Canvas.tsx +++ b/src/ui/Canvas.tsx @@ -19,7 +19,13 @@ export function Canvas({ onReady }: CanvasProps) { useEffect(() => { const host = hostRef.current; if (!host || controllerRef.current) return; - const controller = new EditorController(host, getState().config, { + // The paper gets its own child div: Paper.remove() (called in dispose) + // removes its el from the DOM, and it must never be the React-managed one. + const paperHost = document.createElement('div'); + paperHost.style.width = '100%'; + paperHost.style.height = '100%'; + host.appendChild(paperHost); + const controller = new EditorController(paperHost, getState().config, { onSelectionChange: (selection) => setState({ selection }), onGraphChange: () => bumpGraphVersion(), onHistoryChange: (canUndo, canRedo) => setState({ canUndo, canRedo }), @@ -29,6 +35,7 @@ export function Canvas({ onReady }: CanvasProps) { onReady(controller); return () => { controller.dispose(); + paperHost.remove(); controllerRef.current = null; }; // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/tests/properties.test.ts b/tests/properties.test.ts index f3a66af..558506b 100644 --- a/tests/properties.test.ts +++ b/tests/properties.test.ts @@ -95,12 +95,12 @@ describe('defaults and grouping', () => { it('groups defs in canonical order', () => { const defs: PropertyDef[] = [ - def({ key: 'a', group: 'physics' }), - def({ key: 'b', group: 'general' }), - def({ key: 'c', group: 'presentation' }), - def({ key: 'd', group: 'physics' }), + def({ key: 'a', group: 'physics', type: 'string' }), + def({ key: 'b', group: 'general', type: 'string' }), + def({ key: 'c', group: 'presentation', type: 'string' }), + def({ key: 'd', group: 'physics', type: 'string' }), ]; - const groups = groupDefs(defs.map((d) => ({ ...d, type: 'string' as const }))); + const groups = groupDefs(defs); expect(groups.map((g) => g.group.id)).toEqual(['general', 'presentation', 'physics']); expect(groups[2].defs.map((d) => d.key)).toEqual(['a', 'd']); }); diff --git a/tsconfig.json b/tsconfig.json index eab6ede..b99561b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target": "ES2021", + "target": "ES2022", "useDefineForClassFields": true, - "lib": ["ES2021", "DOM", "DOM.Iterable"], + "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo new file mode 100644 index 0000000..af8156d --- /dev/null +++ b/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/main.tsx","./src/editor/colorSchemes.ts","./src/editor/edgeStyles.ts","./src/editor/paper.ts","./src/editor/shapes.ts","./src/model/catalog.ts","./src/model/graph.ts","./src/model/nodeTypes.ts","./src/model/properties.ts","./src/model/validation.ts","./src/sim/flow.ts","./src/state/history.ts","./src/state/persistence.ts","./src/state/store.ts","./src/ui/App.tsx","./src/ui/Canvas.tsx","./src/ui/Palette.tsx","./src/ui/PropertyPanel.tsx","./src/ui/SettingsDialog.tsx","./src/ui/Toolbar.tsx","./tests/catalog.test.ts","./tests/components.test.tsx","./tests/edgeStyles.test.ts","./tests/editor.test.ts","./tests/flow.test.ts","./tests/graph.test.ts","./tests/history.test.ts","./tests/nodeTypes.test.ts","./tests/properties.test.ts","./tests/setup.ts","./tests/validation.test.ts"],"version":"5.9.3"} \ No newline at end of file