From a2005d007e7f930a9dca06b8902c50f0dc8d3c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gjermund=20H=C3=B8s=C3=B8ien=20Wiggen?= Date: Tue, 9 Jun 2026 13:50:03 +0200 Subject: [PATCH] fix: resize now uses functional setState to avoid stale closure The onUp handler was capturing stale widgets from the render closure, overwriting the resize dimensions. Now uses setWidgets(current => ...) to read latest state and apply overlap resolution correctly. Co-Authored-By: Claude Opus 4.8 --- web/src/app/dashboards/[id]/page.tsx | 67 ++++++++++++---------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/web/src/app/dashboards/[id]/page.tsx b/web/src/app/dashboards/[id]/page.tsx index 134d571..5f7be6e 100644 --- a/web/src/app/dashboards/[id]/page.tsx +++ b/web/src/app/dashboards/[id]/page.tsx @@ -190,54 +190,45 @@ export default function DashboardPage({ params }: { params: Promise<{ id: string ); }; - const onUp = async () => { + const onUp = () => { document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); setResizingId(null); - // Resolve overlaps: push overlapping widgets out of the way - let resolved = widgets.map((w) => (w.id === widgetId ? { ...w, position: { ...w.position } } : w)); - const movedIds = new Set(); + // Resolve overlaps using latest state via functional updater + setWidgets((current) => { + let resolved = current.map((w) => ({ ...w, position: { ...w.position } })); - // Keep pushing until no overlaps - for (let pass = 0; pass < 10; pass++) { - let hasOverlap = false; - for (let i = 0; i < resolved.length; i++) { - for (let j = i + 1; j < resolved.length; j++) { - const a = resolved[i].position; - const b = resolved[j].position; - const ax2 = a.x + a.w; - const ay2 = a.y + a.h; - const bx2 = b.x + b.w; - const by2 = b.y + b.h; - - if (ax2 > b.x && a.x < bx2 && ay2 > b.y && a.y < by2) { - hasOverlap = true; - // Fix the resized widget in place, push the OTHER widget down - const toMove = widgetId === resolved[i].id ? j : i; - const movedW = resolved[toMove]; - const fixedW = resolved[widgetId === resolved[i].id ? i : j]; - const newY = fixedW.position.y + fixedW.position.h; - resolved[toMove] = { - ...movedW, - position: { ...movedW.position, y: newY }, - }; - movedIds.add(movedW.id); + for (let pass = 0; pass < 10; pass++) { + let hasOverlap = false; + for (let i = 0; i < resolved.length; i++) { + for (let j = i + 1; j < resolved.length; j++) { + const a = resolved[i].position; + const b = resolved[j].position; + if (a.x + a.w > b.x && a.x < b.x + b.w && a.y + a.h > b.y && a.y < b.y + b.h) { + hasOverlap = true; + const toMove = widgetId === resolved[i].id ? j : i; + const fixedW = resolved[widgetId === resolved[i].id ? i : j]; + resolved[toMove] = { + ...resolved[toMove], + position: { ...resolved[toMove].position, y: fixedW.position.y + fixedW.position.h }, + }; + } } } + if (!hasOverlap) break; } - if (!hasOverlap) break; - } - setWidgets(resolved); - - // Persist positions - for (const w of resolved) { - const original = widgets.find((o) => o.id === w.id); - if (original && (original.position.y !== w.position.y || original.position.h !== w.position.h)) { - await updateWidget(id, w.id, { position: w.position }); + // Persist changed positions + for (const w of resolved) { + const orig = current.find((o) => o.id === w.id); + if (orig && (orig.position.y !== w.position.y || orig.position.h !== w.position.h)) { + updateWidget(id, w.id, { position: w.position }); + } } - } + + return resolved; + }); }; document.addEventListener("mousemove", onMove);