From fa799d3cb53bb0b0ed0e64e4123764d4a85ac736 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 16 Feb 2026 23:08:09 -0800 Subject: [PATCH] feat: Implement optimistic updates for feature persistence Add optimistic UI updates with rollback capability for feature creation and deletion operations. Await persistFeatureDelete promise and add Playwright testing dependency. --- .../board-view/hooks/use-board-actions.ts | 2 +- .../board-view/hooks/use-board-persistence.ts | 36 ++++++++++++++++--- package-lock.json | 13 ++----- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts b/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts index 4f3c0517..b0a917fe 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-actions.ts @@ -510,7 +510,7 @@ export function useBoardActions({ } removeFeature(featureId); - persistFeatureDelete(featureId); + await persistFeatureDelete(featureId); }, [features, runningAutoTasks, autoMode, removeFeature, persistFeatureDelete] ); diff --git a/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts b/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts index 6e5d23f5..0d0ec346 100644 --- a/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts +++ b/apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts @@ -86,16 +86,26 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps return; } + // Optimistically add to React Query cache for immediate board refresh + queryClient.setQueryData( + queryKeys.features.all(currentProject.path), + (existing) => (existing ? [...existing, feature] : [feature]) + ); + const result = await api.features.create(currentProject.path, feature as ApiFeature); if (result.success && result.feature) { updateFeature(result.feature.id, result.feature as Partial); - // Invalidate React Query cache to sync UI - queryClient.invalidateQueries({ - queryKey: queryKeys.features.all(currentProject.path), - }); } + // Always invalidate to sync with server state + queryClient.invalidateQueries({ + queryKey: queryKeys.features.all(currentProject.path), + }); } catch (error) { logger.error('Failed to persist feature creation:', error); + // Rollback optimistic update on error + queryClient.invalidateQueries({ + queryKey: queryKeys.features.all(currentProject.path), + }); } }, [currentProject, updateFeature, queryClient] @@ -106,6 +116,15 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps async (featureId: string) => { if (!currentProject) return; + // Optimistically remove from React Query cache for immediate board refresh + const previousFeatures = queryClient.getQueryData( + queryKeys.features.all(currentProject.path) + ); + queryClient.setQueryData( + queryKeys.features.all(currentProject.path), + (existing) => (existing ? existing.filter((f) => f.id !== featureId) : existing) + ); + try { const api = getElectronAPI(); if (!api.features) { @@ -114,12 +133,19 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps } await api.features.delete(currentProject.path, featureId); - // Invalidate React Query cache to sync UI + // Invalidate to sync with server state queryClient.invalidateQueries({ queryKey: queryKeys.features.all(currentProject.path), }); } catch (error) { logger.error('Failed to persist feature deletion:', error); + // Rollback optimistic update on error + if (previousFeatures) { + queryClient.setQueryData(queryKeys.features.all(currentProject.path), previousFeatures); + } + queryClient.invalidateQueries({ + queryKey: queryKeys.features.all(currentProject.path), + }); } }, [currentProject, queryClient] diff --git a/package-lock.json b/package-lock.json index 8804b479..3c60eba6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "automaker", "version": "0.13.0", "hasInstallScript": true, + "license": "MIT", "workspaces": [ "apps/*", "libs/*" @@ -56,6 +57,7 @@ "yaml": "2.7.0" }, "devDependencies": { + "@playwright/test": "1.57.0", "@types/cookie": "0.6.0", "@types/cookie-parser": "1.4.10", "@types/cors": "2.8.19", @@ -11475,7 +11477,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11497,7 +11498,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11519,7 +11519,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11541,7 +11540,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11563,7 +11561,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11585,7 +11582,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11607,7 +11603,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11629,7 +11624,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11651,7 +11645,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11673,7 +11666,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11695,7 +11687,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" },