From 2b91f31807f933bf49270f57df56766b1c26f1f3 Mon Sep 17 00:00:00 2001 From: Pouzor Date: Sun, 10 May 2026 19:30:04 +0200 Subject: [PATCH] fix(group): preserve children + size on edit, fix status WS proxy - Skip NodeModal on double-click for 'group'/'groupRect' (use inline rename) - Exempt 'group' from properties-clears-height rule in updateNode (was collapsing group + hiding children with extent: 'parent') - Enable ws: true on Vite /api proxy so /api/v1/status/ws/status upgrades (status bubbles stayed grey because WS handshake failed) - Add regression tests for group/groupRect height preservation --- frontend/src/App.tsx | 5 ++++ .../src/stores/__tests__/canvasStore.test.ts | 30 +++++++++++++++++++ frontend/src/stores/canvasStore.ts | 2 +- frontend/vite.config.ts | 6 +++- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 73be230..62df72c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -465,6 +465,11 @@ export default function App() { }, []) const handleNodeDoubleClick = useCallback((node: Node) => { + // 'group' uses inline rename (pencil button in header). Opening the + // generic NodeModal would clobber the group's height (via the + // properties-clears-height rule in updateNode) and lose its children. + // 'groupRect' has its own onDoubleClick that already routes to GroupRectModal. + if (node.data.type === 'group' || node.data.type === 'groupRect') return handleEditNode(node.id) }, [handleEditNode]) diff --git a/frontend/src/stores/__tests__/canvasStore.test.ts b/frontend/src/stores/__tests__/canvasStore.test.ts index 87c0d60..9918d28 100644 --- a/frontend/src/stores/__tests__/canvasStore.test.ts +++ b/frontend/src/stores/__tests__/canvasStore.test.ts @@ -724,6 +724,36 @@ describe('canvasStore', () => { expect(stored?.height).toBeUndefined() }) + it('updateNode preserves height for group type when properties change (children must not vanish)', () => { + const group: Node = { + id: 'g1', + type: 'group', + position: { x: 0, y: 0 }, + width: 360, + height: 240, + data: { label: 'Zone', type: 'group', status: 'unknown', services: [] }, + } + useCanvasStore.setState({ nodes: [group], edges: [] }) + useCanvasStore.getState().updateNode('g1', { properties: [] }) + const stored = useCanvasStore.getState().nodes.find((n) => n.id === 'g1') + expect(stored?.height).toBe(240) + expect(stored?.width).toBe(360) + }) + + it('updateNode preserves height for groupRect when properties change', () => { + const rect: Node = { + id: 'r1', + type: 'groupRect', + position: { x: 0, y: 0 }, + width: 360, + height: 240, + data: { label: 'Rect', type: 'groupRect', status: 'unknown', services: [] }, + } + useCanvasStore.setState({ nodes: [rect], edges: [] }) + useCanvasStore.getState().updateNode('r1', { properties: [] }) + expect(useCanvasStore.getState().nodes.find((n) => n.id === 'r1')?.height).toBe(240) + }) + // ── bottom_handles edge remapping ────────────────────────────────────────── it('remaps source edges to "bottom" when bottom_handles is reduced', () => { diff --git a/frontend/src/stores/canvasStore.ts b/frontend/src/stores/canvasStore.ts index 339de6e..25898fc 100644 --- a/frontend/src/stores/canvasStore.ts +++ b/frontend/src/stores/canvasStore.ts @@ -209,7 +209,7 @@ export const useCanvasStore = create((set) => ({ if (n.id !== id) return n const updated: Node = { ...n, data: { ...n.data, ...data } } // When properties change, clear stored height so the node auto-sizes to fit new content - if ('properties' in data && n.data.type !== 'proxmox' && n.data.type !== 'groupRect') { + if ('properties' in data && n.data.type !== 'proxmox' && n.data.type !== 'groupRect' && n.data.type !== 'group') { updated.height = undefined } if ('parent_id' in data) { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index fbca3fc..7691592 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -28,7 +28,11 @@ export default defineConfig({ }, server: { proxy: { - '/api': 'http://localhost:8000', + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + ws: true, + }, '/ws': { target: 'ws://localhost:8000', ws: true,