From bfe7ce736778c8f314a7da9eacaea0e58d6111ea Mon Sep 17 00:00:00 2001 From: Developer Date: Mon, 6 Jul 2026 10:29:24 +0000 Subject: [PATCH] Fix: WidgetConfigDialog scope + reorder race condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs in the widget config dialog: 1. Service Overview edit showed main-dashboard widgets. The dialog called useWidgetInstances() with no args, fetching ALL widgets. Now accepts a serviceId prop; OverviewTab passes instance.id so the dialog lists + creates only service-scoped widgets. New built-in widgets added from a service Overview inherit the serviceId. 2. Reorder up/down buttons did nothing. moveInstance fired two saveWidget mutations via Promise.all — the first mutation's onSuccess cache invalidation triggered a refetch before the second completed, reverting the swap. Changed to sequential awaits so both sort_order writes land before the cache refreshes. 127 tests pass; lint/build green. --- frontend/src/components/WidgetConfigDialog.tsx | 16 +++++++++------- frontend/src/pages/service-tabs/OverviewTab.tsx | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/WidgetConfigDialog.tsx b/frontend/src/components/WidgetConfigDialog.tsx index 17b3e4a..8eb644c 100644 --- a/frontend/src/components/WidgetConfigDialog.tsx +++ b/frontend/src/components/WidgetConfigDialog.tsx @@ -38,6 +38,8 @@ import { interface Props { open: boolean; onClose: () => void; + /** When set, scope the dialog to a specific service instance's widgets. */ + serviceId?: string; } interface Draft { @@ -164,8 +166,8 @@ function WidgetConfigEditor({ ); } -export function WidgetConfigDialog({ open, onClose }: Props) { - const { data: instances = [] } = useWidgetInstances(); +export function WidgetConfigDialog({ open, onClose, serviceId }: Props) { + const { data: instances = [] } = useWidgetInstances(serviceId); const { data: services = [] } = useServiceInstances(); const { data: tasks = [] } = useTasks(); const saveWidget = useSaveWidgetInstance(); @@ -184,7 +186,7 @@ export function WidgetConfigDialog({ open, onClose }: Props) { function startAddBuiltIn(kind: string) { const binding = BUILTIN_WIDGETS[kind]; setDraft({ - serviceId: null, + serviceId: serviceId ?? null, widgetKind: kind, title: binding?.name ?? kind, config: { ...(binding?.defaultConfig ?? {}) }, @@ -255,10 +257,10 @@ export function WidgetConfigDialog({ open, onClose }: Props) { if (targetIndex < 0 || targetIndex >= sortedInstances.length) return; const a = sortedInstances[index]; const b = sortedInstances[targetIndex]; - await Promise.all([ - saveWidget.mutateAsync({ ...a, sort_order: b.sort_order }), - saveWidget.mutateAsync({ ...b, sort_order: a.sort_order }), - ]); + // Sequential (not Promise.all) to avoid a race where the first mutation's + // cache invalidation refetches before the second completes, reverting the swap. + await saveWidget.mutateAsync({ ...a, sort_order: b.sort_order }); + await saveWidget.mutateAsync({ ...b, sort_order: a.sort_order }); } async function removeInstance(instance: WidgetInstance) { diff --git a/frontend/src/pages/service-tabs/OverviewTab.tsx b/frontend/src/pages/service-tabs/OverviewTab.tsx index 0c2e9e4..4c54394 100644 --- a/frontend/src/pages/service-tabs/OverviewTab.tsx +++ b/frontend/src/pages/service-tabs/OverviewTab.tsx @@ -72,6 +72,7 @@ export function OverviewTab({ instance }: { instance: ServiceInstance }) { setConfigOpen(false)} + serviceId={instance.id} /> );