From f9f88c8fb00ffbd5a5e0812bec1e5f8a75d4bcf1 Mon Sep 17 00:00:00 2001 From: Pouzor Date: Thu, 27 Aug 2026 01:00:14 +0200 Subject: [PATCH] refactor(canvas): drop a dead custom_colors.height write on zone growth Growing a zone during a subnet import wrote the new height twice: to `node.height`, the live field, and to `data.custom_colors.height`, which nothing reads. The blob copy is produced by the serializer at save time (rebuilt from `node.height`, so the value written here was overwritten before it reached the API) and consumed at load time off the API payload. Writing it from the store was invisible, and misleading in a blob that otherwise holds colours and style. The test asserting the dead field is replaced by a save/load round-trip through the real serializer, which is what actually protects the height. ha-relevant: yes --- .../src/stores/__tests__/canvasStore/zones.test.ts | 11 +++++++++-- frontend/src/stores/canvasStore.ts | 14 ++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/frontend/src/stores/__tests__/canvasStore/zones.test.ts b/frontend/src/stores/__tests__/canvasStore/zones.test.ts index 2a0424c..d9c7c90 100644 --- a/frontend/src/stores/__tests__/canvasStore/zones.test.ts +++ b/frontend/src/stores/__tests__/canvasStore/zones.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, beforeEach } from 'vitest' import { useCanvasStore } from '@/stores/canvasStore' import { makeNode } from '@/test/factories' +import { serializeNode, deserializeApiNode, type ApiNode } from '@/utils/canvasSerializer' function resetStore() { useCanvasStore.setState({ @@ -238,8 +239,14 @@ describe('canvasStore — importZoneSubnet', () => { const z = useCanvasStore.getState().nodes.find((n) => n.id === 'z1')! expect(z.height!).toBeGreaterThan(300) - // The renderer reads the persisted size back out of custom_colors. - expect(z.data.custom_colors?.height).toBe(z.height) + + // The grown height has to survive a save/load round-trip. A zone has no + // height column, so the serializer stashes it in the custom_colors blob + // on the way out and hoists it back on the way in. + const wire = serializeNode(z) as { custom_colors: { height: number } } + expect(wire.custom_colors.height).toBe(z.height) + const reloaded = deserializeApiNode(wire as unknown as ApiNode, new Map()) + expect(reloaded.height).toBe(z.height) }) it('keeps the parent ahead of its new children, as React Flow requires', () => { diff --git a/frontend/src/stores/canvasStore.ts b/frontend/src/stores/canvasStore.ts index 0d6246f..d38e530 100644 --- a/frontend/src/stores/canvasStore.ts +++ b/frontend/src/stores/canvasStore.ts @@ -1072,17 +1072,11 @@ export const useCanvasStore = create((rawSet, get) => { set((s) => { const updated = s.nodes.map((n) => { + // `height` is the only live field: the serializer stashes it into the + // custom_colors blob on save, since the nodes table has no height + // column for a zone, and hoists it back out on load. if (n.id === zoneId) { - return grownHeight === zoneHeight - ? n - : { - ...n, - height: grownHeight, - data: { - ...n.data, - custom_colors: { ...(n.data.custom_colors ?? {}), height: grownHeight }, - }, - } + return grownHeight === zoneHeight ? n : { ...n, height: grownHeight } } const at = placements.get(n.id) if (!at) return n